symfony/models-dev
Symfony integration for models.dev, giving your app structured metadata on AI models—context windows, pricing, and capabilities—via a simple, cacheable service, so you can query and compare providers without hardcoding model specs.
composer require symfony/models-dev
The Flex recipe (if enabled) registers a config file at config/packages/models_dev.yaml and wires the package's HTTP client into the container. The first place to look is the README's usage block, then the package's src/ directory — the surface is small, typically one catalog/client service returning DTOs for providers and models. Run bin/console debug:container models to find the exact service ID and autowiring alias before writing code against it.
The canonical first use case is resolving model metadata at runtime instead of hardcoding it:
final class ModelPicker
{
public function __construct(private ModelsDevClient $catalog) {}
/** @return list<Model> */
public function visionCapable(): array
{
return array_values(array_filter(
$this->catalog->models(),
static fn (Model $m) => $m->supports('vision') && $m->contextWindow() >= 128_000,
));
}
}
Start with a console command that dumps the catalog (models-dev:list --provider=openai) before touching application code — it validates connectivity and shows you the real shape of the data.
Catalog as a lookup, not a hot path. models.dev is a remote dataset. Treat it as reference data: pull it once, cache it, and let application code read from cache. A small warmup command run at deploy (or a cache warmer) keeps request-time calls out of the critical path.
# config/packages/models_dev.yaml
models_dev:
http_client: http_client.models_dev
cache_pool: cache.app
cache_ttl: 86400
Validate configured model IDs in CI. Most apps store model names in services.yaml or env vars. Add a test that asserts every configured ID exists in the catalog — this catches typos and deprecated models before they hit production, which is where this package earns its keep.
#[Test]
public function configured_models_exist(): void
{
$ids = $this->catalog->modelIds();
foreach ($this->parameterBag->get('ai.models') as $id) {
self::assertContains($id, $ids, "Unknown model: {$id}");
}
}
Drive cost/capability decisions from metadata. Build a thin selector service over the catalog (cheapest model meeting a context requirement, provider fallback order, capability gating) and inject that — not the raw client — into your AI code. Swapping or pinning a model then becomes a one-line config change.
Sync to a local table for reporting. If you need pricing history, per-model usage dashboards, or admin filters, import the catalog into Doctrine via a scheduled command rather than joining on remote data. Keep the sync idempotent (upsert on provider + model ID).
Render pricing and capabilities. A Twig extension or EasyAdmin field reading the cached catalog is a quick, high-value win for admin UIs and model-selection dropdowns.
Test with fixtures. Don't hit the network in tests — alias the underlying HTTP client to MockHttpClient with a trimmed JSON fixture, or decorate the catalog service in the test container. This is also the clean extension point for adding custom metadata (internal aliases, allowlists) without forking.
symfony/ai-* platform) still enforces the real contract. Validate both layers, especially around tool calling and structured output.composer.json, keep the client behind your own selector service, and re-read the changelog on upgrades. The wrapper is also your insurance if the package stalls.cache.app with a long TTL, remember bin/console cache:pool:clear cache.app or tag the pool so releases don't serve stale model lists.debug:autowiring may show several candidates. Use explicit named arguments or a service alias in services.yaml rather than relying on type-hint resolution.-vvv on the console command) for the actual request/response — most "empty catalog" reports are a timeout, proxy issue, or unexpected schema change upstream.How can I help you explore Laravel packages today?