Symfony AI bundle in a SaaS (2026): what it gives you, and what you still build yourself
symfony/ai-bundle is the official way to call OpenAI, Anthropic, Mistral or Ollama from Symfony. It is still experimental, and it stops where a paid product starts: per-customer quotas and usage tied to your plans. Here is where that line runs, checked on v0.14.1.
You install symfony/ai-bundle, point it at Claude or GPT with ten lines
of YAML, and ship a chat feature the same afternoon. The first time you
learn that one customer on your free plan sent a few thousand messages is
the provider's invoice, because nothing between your customers and the
model was counting.
That gap is what this article is about. The bundle is a good model client,
and in 2026 it is the default I would reach for. It is also experimental,
and it stops at the model: which customer may spend how many tokens this
month is application code. Everything below was checked on Packagist and
GitHub on October 1, 2026, against the v0.14.1 tag.
Disclosure: I sell ShipAnvil, a Symfony SaaS kit with its own AI module, described at the end. It does not use Symfony AI, and I say why.
What you install
Symfony AI lives in the symfony/ai repository, with its own release line,
separate from Symfony itself. For a product that calls a model, three
packages matter:
symfony/ai-platform: one interface in front of the providers, with one bridge package per provider (symfony/ai-anthropic-platform, OpenAI, Azure, Bedrock, Gemini, Mistral, Ollama and about forty others).symfony/ai-agent: system prompts, tool calling and multi-step agents on top of the platform.symfony/ai-bundle: wires both into the container fromconfig/packages/ai.yaml.
The same repository also holds a vector store, a chat history component, an MCP bundle and a command-line assistant. They are a different question from "my product calls a model", and this article leaves them out.
The latest release is v0.14.1, from September 26, 2026. The first, v0.1.0,
came out on December 23, 2025: fourteen minor versions in nine months. It
requires PHP 8.2 and Symfony ^7.3|^8.0, so it installs on the 7.4 LTS and
on Symfony 8.
The READMEs of the bundle and of every component say "experimental": they
are not covered by Symfony's backward compatibility promise. The Platform
changelog alone has more than twenty entries marked [BC BREAK], and v0.14
added a method to TokenUsageInterface. An UPGRADE.md documents each
step. Read it at every minor version.
What it does for you
The configuration for one provider and one agent:
# config/packages/ai.yaml
ai:
platform:
anthropic:
api_key: '%env(ANTHROPIC_API_KEY)%'
agent:
default:
model: 'claude-sonnet-4-6'
With that, you inject AgentInterface and call it. Switching provider is
configuration, and a Failover bridge falls back from one platform to
another. Streaming is one option away ('stream' => true, then
asTextStream() on a platform result), and you wrap the loop in a
StreamedResponse. Structured
output turns the answer into a PHP object on models that support it.
Tools are service methods the agent may call, with a cap on iterations.
Tests run without a network: InMemoryPlatform returns a canned answer,
MockPlatformFactory keeps the real routing and fakes only HTTP, and
AiAssertionsTrait checks what the agent did (assertToolCalled,
assertModelCalled) rather than what the model wrote. A profiler panel
shows each call.
If you need several providers, retrieval over your own documents or real agents, write none of this yourself.
Where it stops
The bundle tells you what a call consumed. After a call, the usage is in the result metadata, when the provider reports it:
$result = $this->agent->call($messages);
$text = $result->getContent();
/** @var TokenUsageInterface|null $usage */
$usage = $result->getMetadata()->get('token_usage');
$usage?->getPromptTokens();
$usage?->getCompletionTokens();
$usage?->getModel(); // the model that consumed the tokens, for pricing
That is where the bundle ends. It does not know your customers, and it never reads your billing. In this article a quota means a monthly number of tokens that a plan allows one customer organization. Everything between "this call used 1,800 tokens" and "this organization has used its quota" is yours:
- A usage row per call. Organization, feature, model and the token counts above, written somewhere you can sum by month.
- The quota check before the call, read from the plan the organization pays for, with an answer the interface can show instead of a 500.
- A ceiling on the worst case: a maximum output per response and a bounded number of past turns replayed into each chat. With the quota, that bounds what one customer can cost you.
- Tenant scoping: usage rows and conversations go through the same tenant filter as the rest of your data.
- A switch to turn the feature off for a plan, without a deploy.
Streaming is where the usage row gets lost. A streamed usage is known only
once the stream has been consumed (the Platform docs say so, and a
StreamListener collects it), so the write belongs at the end of your
StreamedResponse callback. Read right after the call returns, it is not
there yet.
Bundle, SDK, or your own client
The alternatives, checked the same day:
openai-php/symfonyv0.19.0 (February 11, 2026), a bundle aroundopenai-php/clientv0.21.0: OpenAI and compatible APIs only.anthropic-ai/sdkv0.54.0 (September 30, 2026), Anthropic's official PHP SDK, not tied to a framework.theodo-group/llphant1.0.5, a generative AI library past 1.0, not tied to a framework.- Symfony's HttpClient with a scoped client per provider. One provider's API is a few JSON endpoints, and retries, timeouts and streaming are already in HttpClient.
With several providers, retrieval or agents, take symfony/ai-bundle and
budget the upgrade reading until it reaches 1.0. With one provider and a
few product features meant to last years, a small client of your own or
the provider's SDK changes only when you change it. In both cases, put
your own interface between your features and the library, so a breaking
release touches one class.
What ShipAnvil does
ShipAnvil is not a bundle: it is a complete Symfony 7.4 application you start your SaaS from. Its AI module took the second road and ships the part the bundle leaves to you:
- A small
AiClientInterface(complete()andstream()) with an Anthropic implementation on a scoped HttpClient, timeouts and retries on rate limiting and overload set in configuration. It supports one provider, Anthropic. The interface is where another one would plug in, Symfony AI included, but that adapter is not shipped. - An offline fake, the default, so the application and its tests run without an API key or network access.
- Two working features: a chat streamed token by token over Server-Sent Events, and structured extraction through forced tool use (how both are built).
- A usage row per AI call, scoped to the tenant like the rest of the data (multi-tenancy).
- Monthly quotas per plan, read from the same plan configuration as billing and checked before every call, with a message when the quota is used.
- One setting to turn the AI features off for the product, and a plan setting to turn them off per plan.
It does not include retrieval, vector stores, agents or MCP. If you need
them, add symfony/ai-bundle next to it in the same application.
The live demo lets you sign in and look around. Price and license terms are on the pricing page. If you are comparing whole kits, the Symfony SaaS boilerplate comparison checks every living option the same way.