Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Ai Mistral Platform Laravel Package

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.

View on GitHub
Deep Wiki
Context7

Getting Started

Minimal Steps

  1. Install the Package Add the package via Composer in your Laravel project:

    composer require symfony/ai-mistral-platform
    

    v0.15.0 bumps the internal version constraints to 0.15 (see #2753). Every symfony/ai-* component in your composer.lock must move together — if another package pins ^0.14 you will get a resolver conflict, not a runtime error.

  2. Set Up HTTP Client Bind the Symfony HTTP client to Laravel's container in a real service provider (not config/app.php):

    // app/Providers/AppServiceProvider.php
    public function register(): void
    {
        $this->app->singleton(
            \Symfony\Contracts\HttpClient\HttpClientInterface::class,
            fn () => \Symfony\Component\HttpClient\HttpClient::create()
        );
    }
    
  3. Configure Mistral API Key Store your Mistral API key in Laravel's .env:

    MISTRAL_API_KEY=your_api_key_here
    
  4. First Use Case: Chat Completion Create a simple service to interact with Mistral's chat endpoint:

    use Symfony\Ai\Mistral\MistralClient;
    use Symfony\Ai\Mistral\ChatCompletion;
    
    class MistralChatService
    {
        public function __construct(private MistralClient $client)
        {
        }
    
        public function ask(string $question): string
        {
            $response = $this->client->chatCompletion(
                new ChatCompletion('mistral-tiny', $question)
            );
            return $response->getContent();
        }
    }
    
  5. Register the Service Bind the MistralClient and MistralChatService in a provider:

    use Symfony\Ai\Mistral\MistralClient;
    
    public function register(): void
    {
        $this->app->bind(MistralClient::class, function ($app) {
            return new MistralClient(
                $app->make(\Symfony\Contracts\HttpClient\HttpClientInterface::class),
                $app['config']['services.mistral.api_key']
            );
        });
    
        $this->app->bind(MistralChatService::class);
    }
    
  6. Use in a Controller Inject the service into a controller and call it:

    public function askQuestion(Request $request, MistralChatService $chatService)
    {
        $response = $chatService->ask($request->input('question'));
        return response()->json(['answer' => $response]);
    }
    
  7. Read Token Usage (v0.14+) Every result exposes a TokenUsage value object. Since v0.14.0 it also carries the model that produced the tokens, which is what you need for per-model cost accounting:

    $result = $client->chatCompletion(new ChatCompletion('mistral-small-latest', $question));
    
    $usage = $result->getTokenUsage();
    // $usage->getModel(), $usage->getPromptTokens(),
    // $usage->getCompletionTokens(), $usage->getTotalTokens()
    
  8. Cap Output Length (changed in v0.15.0) The canonical max_output_tokens option was removed in favour of each bridge's own wire field (#2622). For Mistral that is max_tokens:

    $client->chatCompletion(
        new ChatCompletion('mistral-small-latest', $question, ['max_tokens' => 512])
    );
    

    If you were passing max_output_tokens, this is the one thing to fix before upgrading — see Gotchas.


Implementation Patterns

Usage Patterns

  1. Provider Abstraction for Multi-Provider Support Leverage the Provider abstraction to route requests dynamically between Mistral and other AI providers (e.g., OpenAI):

    use Symfony\Ai\Provider\ProviderInterface;
    use Symfony\Ai\Provider\MistralProvider;
    
    class AiProviderRouter
    {
        public function __construct(
            private ProviderInterface $mistralProvider,
            private ProviderInterface $openAiProvider
        ) {}
    
        public function getProviderForModel(string $model): ProviderInterface
        {
            return str_starts_with($model, 'mistral-')
                ? $this->mistralProvider
                : $this->openAiProvider;
        }
    }
    
  2. Streaming Responses with Laravel Queues Process streaming responses (DeltaInterface) asynchronously using Laravel Queues:

    use Symfony\Ai\Mistral\MistralStreamingClient;
    
    public function streamChatResponse(MistralClient $client, string $question)
    {
        $response = $client->chatCompletion(
            new ChatCompletion('mistral-tiny', $question)
        );
    
        foreach ($response->getStream() as $delta) {
            ProcessStreamDelta::dispatch($delta->getContent());
        }
    }
    
  3. Embeddings for Semantic Search Use Mistral's embeddings for document similarity or search:

    use Symfony\Ai\Mistral\EmbeddingClient;
    
    public function generateEmbedding(EmbeddingClient $client, string $text): array
    {
        $embedding = $client->embed($text);
        return $embedding->getEmbedding();
    }
    
  4. Cost Accounting per Model (v0.14+) TokenUsage reports the model it belongs to, so a single choke point can persist spend without re-reading the request:

    use Symfony\Ai\Mistral\MistralClient;
    use Symfony\Ai\Mistral\ChatCompletion;
    
    final class RecordAiSpend
    {
        public function __construct(private MistralClient $client) {}
    
        public function complete(string $model, string $prompt, array $options = []): string
        {
            $result = $this->client->chatCompletion(new ChatCompletion($model, $prompt, $options));
            $usage  = $result->getTokenUsage();
    
            \DB::table('ai_usage')->insert([
                'model'             => $usage->getModel(),   // the model actually billed
                'prompt_tokens'     => $usage->getPromptTokens(),
                'completion_tokens' => $usage->getCompletionTokens(),
                'total_tokens'      => $usage->getTotalTokens(),
                'cost_usd'          => $this->pricing->cost(
                    $usage->getModel(),
                    $usage->getPromptTokens(),
                    $usage->getCompletionTokens()
                ),
                'created_at'        => now(),
            ]);
    
            return $result->getContent();
        }
    }
    
  5. Pricing Table Driven by the Model Catalog Because the package ships an updated model catalog each release (again in v0.15.0, #2666), pair the model property with a price list keyed by model identifier:

    // config/mistral-pricing.php
    return [
        'mistral-small-latest' => ['input' => 0.20 / 1_000_000, 'output' => 0.60 / 1_000_000],
        'mistral-large-latest' => ['input' => 2.00 / 1_000_000, 'output' => 6.00 / 1_000_000],
    ];
    
    public function cost(string $model, int $in, int $out): float
    {
        $rate = config("mistral-pricing.{$model}");
        return $rate ? ($in * $rate['input']) + ($out * $rate['output']) : 0.0;
    }
    

    If $rate is null you are silently free-of-charge — log a warning rather than returning 0.0 in production.

  6. Output-Length Limits via a Single Option Resolver (new in v0.15.0) Now that each bridge names its own wire field, centralise the mapping so a future rename touches one file:

    // config/mistral.php
    'options' => [
        'max_tokens' => 512,   // Mistral wire field; was the canonical `max_output_tokens`
    ];
    
    public function complete(string $prompt)
    {
        return $this->client->chatCompletion(
            new ChatCompletion($this->model(), $prompt, config('services.mistral.options'))
        );
    }
    

    Never scatter literal option keys through jobs — that is exactly how the v0.15 rename becomes a multi-file migration.

  7. Error Handling with Uniform Exceptions Catch Mistral-specific errors uniformly:

    use Symfony\Component\Ai\Exception\AiException;
    
    try {
        $response = $client->chatCompletion($completion);
    } catch (AiException $e) {
        report($e);
        return response()->json(['error' => $e->getMessage()], 500);
    }
    
  8. Configuration Management Centralize Mistral configuration in config/services.php:

    'mistral' => [
        'api_key' => env('MISTRAL_API_KEY'),
        'base_uri' => env('MISTRAL_API_BASE_URI', 'https://api.mistral.ai/v1'),
        'timeout' => 30.0,
        'models' => [
            'chat' => 'mistral-tiny',
            'embedding' => 'mistral-embed',
        ],
        'options' => [
            'max_tokens' => 512,
        ],
    ],
    

Workflows

  1. Real-Time Chatbot Integration

    • Use ChatCompletion for interactive chat interfaces.
    • Stream responses to update UI dynamically (e.g., with Laravel Echo or Livewire).
      public function chat(Request $request)
      {
          $response = $this->client->chatCompletion(
              new ChatCompletion('mistral-tiny', $request->input('prompt'))
          );
      
          return response()->stream(function () use ($response) {
              foreach ($response->getStream() as $delta) {
                  echo $delta->getContent();
                  flush();
              }
          });
      }
      
  2. Batch Embedding Generation

    • Generate embeddings for large datasets using Laravel's queue system:
      public function generateBatchEmbeddings(array $texts)
      {
          foreach ($texts as $text) {
              GenerateEmbeddingJob::dispatch($text);
          }
      }
      
  3. Hybrid Search with Embeddings

    • Combine Mistral embeddings with Laravel Scout or a custom search engine:
      public function searchDocuments(string $query)
      {
          $queryEmbedding = $this->generateEmbedding($query);
          return $this->searchEngine->search($queryEmbedding, limit: 5);
      }
      
  4. Fallback Logic for Multi-Provider

    • Implement a fallback mechanism when Mistral is unavailable:
      public function getCompletionWithFallback(string $model, string $prompt)
      {
          try {
              return $this->mistralProvider->complete($model, $prompt);
          } catch (AiException) {
              return $this->openAiProvider->complete('gpt-3.5-turbo', $prompt);
          }
      }
      
  5. Per-User / Per-Tenant AI Budgets

    • With TokenUsage::getModel() available, aggregate spend by tenant and enforce a soft cap before dispatching the next request:
      public function guard(string $tenantId): void
      {
          $spent = AiUsage::where('tenant_id', $tenantId)
              ->whereMonth('created_at', now()->month)
              ->sum('cost_usd');
      
          abort_if($spent > config('ai.monthly_budget'), 429, 'AI budget exceeded');
      }
      
  6. Upgrade Routine for Option Renames and Catalog Bumps (v0.15.0)

    • max_output_tokens → per-bridge wire field (Mistral: max_tokens).
    • Model catalog refreshed again (#2666).
    • Practical loop:
      1. composer update "symfony/ai-*" --with-dependencies and expect all components at 0.15.
      2. grep -rn "max_output_tokens" app/ config/ — every hit must become max_tokens.
      3. Diff vendor/symfony/ai-mistral-platform/src/Model/ against the previous tag; reconcile your pricing table and allow-list.
      4. Run the suite; assert on persisted model values and on token-limit behaviour.

Integration Tips

  1. Laravel Facade for Cleaner Syntax

    // app/Facades/Mistral.php
    namespace App\Facades;
    
    use Illuminate\Support\Facades\Facade;
    
    class Mistral extends Facade
    {
        protected static function getFacadeAccessor()
        {
            return 'mistral.client';
        }
    }
    

    Usage:

    use App\Facades\Mistral;
    
    $response = Mistral::chatCompletion(new ChatCompletion('mistral-tiny', 'Hello, world!'));
    
  2. Laravel Events for Observability

    use Symfony\Component\Ai\Event\AiEventDispatcher;
    use App\Events\MistralApiCalled;
    
    public function __construct(private AiEventDispatcher $dispatcher)
    {}
    
    public function chatCompletion(string $model, string $prompt)
    {
        $response = $this->client->chatCompletion(new ChatCompletion($model, $prompt));
        $this->dispatcher->dispatch(new MistralApiCalled($response));
        return $response;
    }
    
  3. Caching Embeddings

    use Illuminate\Support\Facades\Cache;
    
    public function getEmbedding(string $text): array
    {
        return Cache::remember("embedding_" . sha1($text), now()->addHours(1), function () use ($text) {
            return $this->client->embed($text)->getEmbedding();
        });
    }
    
  4. Rate Limiting and Retries

    use Illuminate\Cache\RateLimiter;
    
    public function __construct(private RateLimiter $limiter)
    {}
    
    public function safeChatCompletion(string $model, string $prompt)
    {
        $this->limiter->hit('mistral-chat', now()->addMinutes(1));
        return $this->client->chatCompletion(new ChatCompletion($model, $prompt));
    }
    
  5. Testing with Mocks

    use Symfony\Ai\Mistral\MistralClient;
    
    public function test_controller_returns_answer()
    {
        $this->mock(MistralClient::class, function ($mock) {
            $mock->shouldReceive('chatCompletion')
                ->once()
                ->andReturn(new FakeResult('42', new TokenUsage(
                    model: 'mistral-small-latest',
                    promptTokens: 12,
                    completionTokens: 1,
                    totalTokens: 13,
                )));
        });
    
        $this->postJson('/ask', ['question' => 'Meaning of life?'])
            ->assertOk()
            ->assertJson(['answer' => '42']);
    }
    

    Also assert the options array your service builds — a unit test on the ChatCompletion arguments is the cheapest guard against another option rename slipping through.


Gotchas and Tips

  • v0.15.0 renames the canonical max_output_tokens option (#2622). Each bridge now uses its own wire field; for Mistral the correct key is max_tokens. Anything still passing max_output_tokens will not be forwarded — the request succeeds, the limit is silently ignored, and your bills (and latency) change without an error. Grep for the old key before you deploy.
  • Version constraints are locked to 0.15 (#2753). Mixing a 0.15 Mistral bridge with 0.14 symfony/ai-platform/ai-store packages can produce a composer conflict, and hand-pinned constraints in unrelated packages can block the upgrade entirely. Bump the whole symfony/ai-* family in one commit.
  • Model catalogs are auto-regenerated each release (again in v0.15.0, #2666). Model identifiers are not a stable API surface: mistral-tiny and friends get deprecated upstream. Never hardcode model strings in application code — resolve them from config('services.mistral.models.*').
  • Catalog updates are not semver-major, so a composer update inside your constraint can quietly change which models resolve and therefore your per-token pricing. Log the model returned in TokenUsage, not the model you requested — aliases like -latest can resolve to different weights.
  • TokenUsage::getModel() is v0.14.0+. If the package is a transitive dependency and you still have 0.13 in the lock file, the model is absent/null and your cost rows are silently wrong. Make the column nullable and backfill.
  • TokenUsage is not a bill. It reports tokens, not currency. Every cost calculation lives in your code and must be updated when Mistral changes prices; the model property is a join key, nothing more.
  • Streaming responses typically have no final usage block unless you request one — do not assume getTokenUsage() is populated on deltas. For accurate cost on streamed chats, either estimate from prompt + accumulated deltas or do a non-streamed echo pass.
  • Watch the namespace. The Symfony AI components moved around (Symfony\Ai\... vs Symfony\AI\Platform\... vs the older Symfony\Component\Ai\...). Confirm imports against vendor/symfony/ai-mistral-platform/src — the 0.15.0 constraints in this release do not guarantee every class name stayed put.
  • Laravel container binding of the Symfony HTTP client must live in a service provider: $this->app->singleton(HttpClientInterface::class, fn () => HttpClient::create()). HttpClient::create() is static; the array entry in older Laravel snippets is not valid config/app.php syntax.
  • No retry/backoff is built in. Wrap calls in your own retry logic or Laravel's RateLimiter; a 429 from Mistral surfaces as an AiException and, on a queue worker, will burn the job unless you set $tries and $backoff.
  • Log token usage from a single choke point (middleware or a decorator around MistralClient) rather than at each call site — the model-aware TokenUsage makes this cheap, and it is the only reliable way to catch spend drift after a catalog bump.
  • Extension point: the ProviderInterface abstraction is the stable seam. The v0.15 option rename is a reminder that convenience options are not stable — depend on ProviderInterface in application code so a provider or option change does not ripple through your services.
  • Release hygiene: v0.15.0 is a minor release with one behavioural change (the option rename) and one data change (the catalog). Both affect production behaviour without failing a type check — always diff src/Model and grep for renamed option keys after upgrading.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
steadrun/laravel-monitor
labyrinthe/connect-laravel
elmogy/layan
drewdan/sentinel-client
dskripchenko/laravel-llm
fatryst/nuonuo
stevenay/myanfont
gmostafa/php-graphql-client
xeviant/paystack
snowsoft/faker
meditationsteps/blogetc
animalyoung/laravel-influxdb
onix-solutions/sms-telera
lonely-lights/prosetta
oxygen/data
oxygen/core
jengo/base
xiloryx/log-collector
a21ns1g4ts/filament-stripe
la-php/laravel-admin