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 Cohere Platform Laravel Package

symfony/ai-cohere-platform

Cohere platform bridge for Symfony AI, adding chat, embed, rerank, and audio transcription support through the official Cohere API. Links to Cohere reference docs and the main Symfony AI repo for contributing and issue reports.

View on GitHub
Deep Wiki
Context7

Getting Started

Prerequisites. PHP 8.1+ (match whatever symfony/ai requires — check the constraint the bridge declares, it now moves in lockstep with the 0.15 line), Composer, and an explicit decision that you're willing to pull Symfony components into a Laravel app. This bridge is not a Laravel package — it's a Symfony AI platform bridge, so symfony/ai comes along for the ride.

composer require symfony/ai symfony/ai-cohere-platform symfony/http-client

Pin it. It's a 0.x split repo — treat the version like "0.15.*" and read the changelog on every bump. Recent releases have been low-noise: v0.15.0 is a version-constraint bump plus an automated model-catalog refresh, nothing structural. But "nothing structural" is not "nothing" — the catalog refresh changes which model strings resolve.

Where to look first — not the README. The README is a link hub: four Cohere endpoint links and a pointer to the symfony/ai monorepo for issues/PRs. There are no usage samples. Your first stop is the source, which is tiny:

vendor/symfony/ai-cohere-platform/src/       # Platform, factory, model catalog, result mapping
vendor/symfony/ai/src/                       # PlatformInterface, MessageBag, Result, exceptions

Look specifically for the platform entry point (a Platform factory taking your API key and an optional Symfony HTTP client), the message classes you'll construct, the model catalog class (it changes on automated PRs — that's where deprecated model IDs disappear), and the exception type thrown on API failures. Also read the symfony/ai monorepo tests — that's where real usage examples live.

Minimal wiring. Register the platform once as a singleton and never instantiate it inline:

// config/cohere.php
return [
    'api_key' => env('COHERE_API_KEY'),
    'chat_model' => env('COHERE_CHAT_MODEL', 'command-r-plus'),
    'embed_model' => env('COHERE_EMBED_MODEL', 'embed-english-v3.0'),
    'rerank_model' => env('COHERE_RERANK_MODEL', 'rerank-v3.5'),
];
// app/Providers/CohereServiceProvider.php
public function register(): void
{
    $this->app->singleton(PlatformInterface::class, function () {
        // Verify the exact factory signature in vendor/ — 0.8 added provider/model routing
        return PlatformFactory::create(
            apiKey: config('cohere.api_key'),
            httpClient: HttpClient::create(['timeout' => 30, 'max_duration' => 60]),
        );
    });
}

Add a facade (Cohere::invoke(...)) if your team prefers that ergonomics, but keep it accessor-only — no logic in the facade.

First use case. Spend half a day proving plumbing in php artisan tinker: one chat call, one embed call, one forced 401 to see the exception type. Chat is the right first feature because the input/output is obvious and failures are visible. Don't start with audio transcription — it's the one capability with file handling, chunking, and async requirements all at once.

Implementation Patterns

Wrap per capability, not one god-service. Model names and option shapes differ sharply between chat, embed, rerank, and transcribe. A single AiService::ask() wrapper rots immediately. Use small collaborators — ChatClient, Embedder, Reranker, Transcriber — each injecting the shared PlatformInterface. This also makes them trivially mockable.

Chat with persisted conversation state. Keep the message history in your DB (or session), not in the platform object. Map Eloquent rows into the bridge's message bag each turn, and store usage metadata returned in the result so you can attribute cost per user/tenant.

final class ChatClient
{
    public function __construct(private PlatformInterface $platform) {}

    public function reply(Collection $history, string $prompt): string
    {
        $messages = MessageBag::fromArray(
            $history->map(fn ($m) => $m->role === 'user'
                ? new UserMessage($m->content)
                : new AssistantMessage($m->content))->all()
        )->with(new SystemMessage(config('cohere.system_preamble')));

        return $this->platform
            ->invoke(config('cohere.chat_model'), $messages->with(new UserMessage($prompt)))
            ->asText();
    }
}

Structured output, then Laravel validation. Cohere supports structured/JSON responses; ask for JSON, but always run the result through Validator::make() or a DTO hydrator. Models confidently return invalid JSON roughly the rate you'd expect, and a retry-with-error-feedback loop is cheaper than a 500.

Embeddings for semantic search. Three non-negotiables: (1) store model and dimensions alongside every vector — changing models silently breaks cosine similarity, and the catalog refresh in v0.15.0 is exactly the kind of release that changes available model IDs; (2) cache by sha256(text|model|input_type) so re-indexing doesn't re-bill; (3) get input_type right (search_document for indexing, search_query for querying) — mismatch degrades recall and is invisible in tests.

Rerank is your highest-ROI integration. It slots into an existing Laravel/Scout search without reindexing anything:

$candidates = Post::search($query)->take(50)->get();          // cheap first pass
$reranked   = $this->reranker->rerank($query, $candidates, topN: 10);

Map results back by array index, not by any returned ID (the API returns indices into your input array), and handle count($reranked) < topN.

Transcription belongs in a queue. Dispatch a job from the controller, store the audio on an S3/local disk, poll or webhook the job status, then broadcast() on completion. Use WithoutOverlapping keyed on the file hash, delete temp files in finally, and set a generous retryUntil — long files fail on time, not on logic.

Multi-provider via named singletons. If you're genuinely hedging against vendor lock-in, bind per-provider platforms as cohere.platform / openai.platform and resolve from config. The 0.8.0 provider-and-routing abstraction helps here, but note the naming collision: Symfony's Provider is not Laravel's ServiceProvider.

Observability is your job. The bridge has no Telescope integration and no token accounting. Decorate PlatformInterface with a logging implementation that writes {provider, model, capability, latency_ms, input_tokens, output_tokens, status} to an ai_calls table. Build this on day one — retrofitting it after three features ship is miserable.

Testing. Never hit the API in CI. Bind a stub PlatformInterface that returns a canned Result, and unit-test your prompt construction and response parsing separately from transport. For the rare end-to-end check, use HttpClient's mock response mechanism against a recorded fixture. Add one cheap guard test that asserts every model string in config/cohere.php still resolves in the bridge's catalog — the automated catalog-update PRs land on someone else's schedule, and that test is what turns a silent runtime 404 into a red build.

Gotchas and Tips

  • It's pre-1.0, but the churn has moved. Zero stars, zero dependents, and the split repo takes no issues (all PRs go to symfony/ai). The wild west was 0.7.0 (native bridge introduced) and 0.8.0 (Provider abstraction + model routing + catalog shuffle). Since then the cadence has settled: v0.15.0 is a constraint bump plus an automated Update model catalogs PR. The remaining risk is no longer your code breaking — it's model IDs aging out under you with no deprecation notice in your own diff.
  • Model catalog updates arrive as automated PRs. They bump the bridge's supported model list and constrain symfony/ai to the matching minor. That means a composer update can change the set of valid model strings without a single line of your code changing. Config-driven model names plus the guard test above is the whole mitigation.
  • Provider means two different things. Don't try to register a Symfony AI provider through Illuminate\Support\ServiceProvider semantics and expect the container to sort it out.
  • Errors were unified in 0.8.0 via a shared trait. Catch the Symfony AI exception, not GuzzleHttp\Exception or Http_Exception. Then split retryable (429, 5xx, timeouts) from non-retryable (400, 401, 403, 422) before handing to retry() — queues cheerfully retry a bad API key four times otherwise.
  • The HTTP transport is symfony/http-client, not Laravel's Http facade. Default timeouts are long enough to hang a web request. Bind a configured client with explicit timeout/max_duration and pass it to the platform factory. Laravel's HTTP client events, middleware, and Telescope records will not see these calls.
  • Config caching. Read env() only inside config/ files. config('cohere.api_key') from a cached config is fine; env('COHERE_API_KEY') at runtime in a service is not.
  • Model catalog drift. Cohere deprecates and renames models regularly (the command-r family, embed-*-v3.0), and the bridge mirrors that on an automated schedule. Keep model names in config, assert them in a test against the bridge's catalog, and never let a model string appear in more than one place.
  • Silent truncation. Context windows and max_tokens are per-model. Long prompts get cut with no error. Chunk and assert on input length before dispatch; log a warning when you near the limit. A catalog bump can change the model you're actually hitting if your config default is a floating name rather than a pinned one.
  • Rerank returns indices and scores only. Fewer results than requested is normal, and the ordering is what matters — don't re-sort by score yourself.
  • Streaming is friction. Symfony HttpClient's streams don't map cleanly onto Laravel's response()->eventStream(), and Octane/nginx buffering will bite you. Ship non-streamed chat first; revisit streaming with a StreamedResponse once the non-streaming path is proven.
  • Octane. A singleton platform holding keep-alive connections is generally fine, but reset on WorkerStopping/RequestTerminated if you see stale connections. Also make sure any per-request message state lives in request-scoped objects, not the singleton.
  • Vendor patching is easy. MIT license, small source tree. If a Cohere feature you need isn't exposed, a local fork is a legitimate two-hour move — but submit the PR upstream at symfony/ai, since this repo won't accept it. Same applies to the model catalog: if a model you need is missing, patching the catalog is a small, low-conflict change.
  • Question the abstraction if you only need one endpoint. If all you want is chat and you'll never swap providers, a thin direct HTTP wrapper with Laravel's Http facade gives you Telescope, fakes, retries, and Http::pool() for free. The bridge pays off when you're using three or more capabilities, or genuinely planning multi-provider routing.
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.
diephp/sequences
users-au/socialite-provider
twidpay/twid-logger
mrnewport/laravel-plaid
mrnewport/laravel-repo
mrnewport/laravel-docsign
mrnewport/laravel-stow
mrnewport/laravel-flow
mrnewport/laravel-priceable
mrnewport/laravel-nestedterms
fredmeng/laravel-apidoc-generator
itstructure/laravel-multilingual-tools
pdustdar/elasticsearch
markupai/php-sdk
rlacerda83/lumem-microservice-shipment
uni-method/symfony-lib
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle