symfony/ai-mistral-platform
Symfony AI bridge for the Mistral platform, providing chat completions and OCR integration through Mistral's API. Enables Symfony applications to use Mistral models for AI-powered text and document processing.
ClientInterface and Provider abstractions without major refactoring.#2622 renames the shared canonical max_output_tokens option to each bridge's own wire field. That is an architectural regression in the abstraction's symmetry: a single provider-agnostic options object no longer covers output-length control across bridges. A translation layer (our code, not the package's) is now required to keep multi-provider configs uniform.AiEventDispatcher can be bridged to Laravel's Illuminate\Events\Dispatcher for centralized logging, monitoring, and analytics. Unchanged in this release.DeltaInterface for semantic streaming is compatible with Laravel's queue system, though queue middleware may be required for real-time chunk processing. Unchanged.TokenUsage carries a model property, enabling per-model, per-request cost accounting inside the application layer. For Laravel this maps onto a cost-calculation service bound to the container and onto existing logging/metrics channels.#2666): Catalogs continue to be generated by github-actions, so the package tracks Mistral's model lineup upstream. This remains the machine-maintained source of truth for "which models exist and what they cost."EmbeddingClient and ChatClient inject directly into Laravel services.App\Exceptions\Handler can extend Symfony's error traits.// config/ai.php
'providers' => [
'mistral' => [
'client' => \Symfony\Ai\Mistral\MistralClient::class,
'api_key' => env('MISTRAL_API_KEY'),
'endpoint' => env('MISTRAL_ENDPOINT', 'https://api.mistral.ai'),
// v0.15.0: use Mistral's wire field, not the old canonical key
'max_tokens' => env('AI_MAX_OUTPUT_TOKENS', 1024),
],
],
If we intend to keep a provider-agnostic application config, add a small mapper so call sites never reference a wire field directly:
// app/Services/OutputLimitTranslator.php
public function toWireOptions(string $provider, int $maxOutputTokens): array
{
return match ($provider) {
'mistral' => ['max_tokens' => $maxOutputTokens],
default => ['max_output_tokens' => $maxOutputTokens],
};
}
TokenUsage exposes the serving model, a cost layer remains a thin, testable service rather than a cross-cutting concern. The ai-pricing.php map, the AiCostCalculator, and the AiEventFired listener pattern from the prior assessment still apply verbatim — this release does not touch TokenUsage.#2666) is a direct, mechanical update to our ModelRegistry; it requires no code change, only cache invalidation of the boot-time catalog singleton.#2622): max_output_tokens → per-bridge wire field is a public-surface change. Any config array, DTO, or call site passing the canonical key will not necessarily error; depending on validation strictness it may be silently ignored, falling back to a provider default. That produces truncated outputs or unexpected token spend with no exception — the worst failure class for a silent-degradation path.#2753): symfony/ai-* constraints move to 0.15. This makes the upgrade all-or-nothing across the AI package family — a partial bump will fail dependency resolution, and any other Symfony AI bridge we adopt must move in lockstep.AppServiceProvider.DeltaInterface) may require custom queue handlers or event listeners.ai-pricing.php produces silently wrong cost attribution.config:cache and mask the migration.max_output_tokens Blast Radius: Where is the canonical key currently set — config files, env vars, DTOs, tests, fixtures, documentation? Does the bridge throw on an unknown option or ignore it? The answer determines whether this is a 30-minute or a 3-day migration.ai-pricing.php, and what is the review cadence against Mistral's published pricing? Is there a monthly reconciliation against the Mistral invoice?TokenUsage.model supports all four; the decision determines cache key design and reporting schema.AiEventDispatcher integrate with Monolog, Sentry/Datadog, and Telescope? Do token/cost telemetry and the new option-rename errors land in the same pipeline?0.15 couples all Symfony AI packages, do we pin exactly and schedule reviews, or float and accept unreviewed model/option changes?HttpClientInterface injects directly:
// app/Providers/AppServiceProvider.php
public function register()
{
$this->app->bind(\Symfony\Contracts\HttpClient\HttpClientInterface::class, function () {
return \Symfony\Contracts\HttpClient\HttpClient::create([
'base_uri' => env('MISTRAL_ENDPOINT'),
'auth_bearer' => env('MISTRAL_API_KEY'),
]);
});
}
MistralClient to the Symfony HTTP client; bind ProviderInterface to the Mistral implementation with room for fallbacks.$this->app->bind(\Symfony\Ai\Mistral\MistralClient::class, function ($app) {
return new \Symfony\Ai\Mistral\MistralClient(
$app->make(\Symfony\Contracts\HttpClient\HttpClientInterface::class),
// v0.15.0 wire field
maxTokens: (int) config('ai.providers.mistral.max_tokens'),
);
});
AiEventDispatcher to Laravel events and attach the cost calculator as a listener (unchanged from v0.14.0):
// app/Listeners/RecordAiSpend.php
public function handle(AiEventFired $event): void
{
if ($usage = $event->getTokenUsage()) {
$this->costs->calculate($usage, tenant()->id, feature: $event->getContext()['feature'] ?? 'unknown');
}
}
$this->app->singleton(ModelRegistry::class, fn () =>
ModelRegistry::fromCatalog(cache()->rememberForever('ai.models', fn () =>
json_decode(file_get_contents(base_path('vendor/.../models.json')), true)
))
);
max_output_tokens across config, env, DTOs, tests, and docs. Treat this inventory as the migration checklist before touching composer.json.0.15.0 (not a range) in a single non-critical service; validate chat, embeddings, streaming, and output-token behavior (assert the requested limit is honored, not defaulted).CHANGELOG.md from v0.8.0 through v0.15.0. Prior releases covered only the last hop; that debt has now grown by one more minor.php artisan config:clear and a cache-key bump for the model registry. A stale config:cache can preserve the old key and mask both the rename and the catalog refresh.TokenUsage.model is unchanged in this release; the shadow-mode rollout from the prior assessment proceeds as planned.0.15 constraint bump (#2753) couples all Symfony AI packages — partial upgrades will not resolve. Coordinate as a single dependency unit.max_output_tokens is replaced by per-bridge wire fields. This is the only behavior-affecting change in the release and the primary compatibility risk.TokenUsage and catalog shapes are untouched; the cost layer and registry see no disruption. Code performing strict option-array equality assertions in tests will need updating.config:cache is the highest-risk combination in this release — stale cached config can silently suppress the new key.#2666 catalog refresh may introduce or rename models; hard-coded model strings must be validated against the registry at boot.max_output_tokens usage and confirm the bridge's behavior on unknown options (throw vs. ignore).0.15.0 across the whole Symfony AI family; clear and rebuild config and registry caches.AiEventDispatcher to Laravel events and add observability listeners.#2622 — cheap individually, but they demand a named owner and a scheduled review slot.^0.15.0.15 constraint bump means Symfony AI packages must move together — treat them as one dependency with one upgrade ticket.TokenUsage.model continues to make cost tickets traceable to model and provider. But a silently ignored output-limit key presents as "the model gives short answers," which is a product bug report, not an AI one. Add "was an output limit applied?" to the triage script.Cache::increment into aggregates or push to a time-series store at high volume.| Failure | Blast Radius | Mitigation |
|---|---|---|
| Mistral API outage / rate limit | Requests fail or stall | Multi-provider fallback via Provider abstraction; retry with jittered backoff |
Renamed max_output_tokens silently ignored |
Truncated outputs or inflated spend, no exception | Grep inventory pre-upgrade; assertion test that the limit is honored; log effective options per request |
Stale config:cache after upgrade |
Old key persists, masking the rename | config:clear in deploy pipeline; cache-key version bump |
| Partial Symfony AI package upgrade | Dependency resolution failure at deploy | Bump the whole family in one commit; CI install check |
| Stale pricing config | Silent financial misreporting | Monthly invoice reconciliation; alert on "unknown model" lookups |
Unknown model in TokenUsage |
Null/zero cost attributed | Pre-dispatch validation against refreshed catalog; log-and-alert on unmapped models |
| Catalog update renames a model | Dispatch failures on hard-coded identifiers | Registry-based validation at boot; fail fast in CI |
| Streaming chunk loss | Truncated responses, orphaned buffers | Idempotent buffer keys, TTLs, dead-letter queues |
| Upgrade regression across seven minors | Unexpected breakage in production | Exact pinning, staged rollout, contract tests on chat/embeddings/streaming |
| API key exposure | Credential compromise | Env/Vault storage, no keys in config cache, rotation runbook |
| Cost runaway from agentic loops | Budget overrun | Per-tenant token budgets enforced pre-dispatch |
max_output_tokens migration plus a per-bridge translation helper if multi-provider is in scope.TokenUsage model and Laravel's container/event binding. New since v0.14.0: the option surface is no longer uniform across bridges, so "which key does this provider expect?" becomes a recurring question the internal docs must answer.Assessment delta from v0.14.0 → v0.15.0: Not additive — one breaking change. #2622 renames the canonical max_output_tokens option to per-bridge wire fields, which (a) requires an inventory-and-migrate pass on our config surface, (b) can fail silently if an old key is retained, and (c) weakens the provider-portability claim that underpinned the multi-provider fallback plan by introducing a per-bridge translation requirement. #2753 couples all Symfony AI packages to 0.15, making upgrades all-or-nothing. #2666 is a routine catalog refresh requiring only cache invalidation. The v0.14.0 cost-attribution thesis is unchanged and still valid — TokenUsage was not touched. Net: the strategic case for the package holds, but the upgrade is no longer a drop-in, and the unverified v0.8.0 → v0.14.0 path now extends to v0.15.0 and must be diffed before adoption.
How can I help you explore Laravel packages today?