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

laravel/ai

Unified Laravel API for AI providers like OpenAI, Anthropic, and Gemini. Build intelligent agents with tools and structured output, generate images, synthesize and transcribe audio, and create vector embeddings, all through one consistent, expressive interface.

View on GitHub
Deep Wiki
Context7

Getting Started

Install and configure in three steps

composer require laravel/ai
php artisan vendor:publish --tag=ai-config   # lands as config/ai.php

Put your provider credentials in .env (OPENAI_API_KEY, ANTHROPIC_API_KEY, etc.) and let config/ai.php map them to named connections. Because the package is still 0.x, pin the version in composer.json rather than floating on ^0.11 — default models change between minors (Gemini's default text model has already been bumped twice, DeepSeek's model name was replaced).

Where to look first

  1. The official docs at laravel.com/docs/ai-sdk — the README is a stub and deliberately punts to the site. Treat the docs as the source of truth; the repo's own skills/ai-sdk-development/SKILL.md says it explicitly: never guess at APIs.
  2. That same SKILL.md is the best single file in the repo for orientation — it contains the decision workflow (which entry point for which task) and copy-pasteable examples.
  3. changelog/v0.x.md before each upgrade. This package ships frequently and behaviour changes (e.g. stream errors began throwing in v0.11 instead of silently ending).

Your first use case: a support-reply agent

Don't start with images or embeddings. Start with one agent class, because the Agent + Promptable pair is the primitive everything else composes around.

namespace App\Ai\Agents;

use Laravel\Ai\Contracts\Agent;
use Laravel\Ai\Promptable;

class SupportResponder implements Agent
{
    use Promptable;

    public function instructions(): string
    {
        return 'You are a concise support agent for a Laravel SaaS. Never invent pricing.';
    }
}
// app/Http/Controllers/SupportReplyController.php
$response = (new SupportResponder)->prompt($ticket->body);

return $response->text;

That's the whole loop. The next hour should be spent on two things: (a) a test using the fake helpers so your suite never hits a real API, and (b) moving the provider/model out of prompt() calls and into config/ai.php. Everything else in the package follows the same Something::of(...) → ->generate() shape, so the mental model transfers immediately.

Implementation Patterns

Agents as domain objects, not prompt strings. Resist building a PromptService with a giant switch. One class per job (SupportResponder, TranscriptSummarizer, InvoiceExtractor) gives you a natural place for instructions(), tool registration, and middleware. Resolve through the container so constructor dependencies work:

$agent = SalesCoach::make(user: $user);   // container-resolved

Keep one-off prompts out of the class tree entirely with the anonymous helper:

use function Laravel\Ai\{agent};

$response = agent(instructions: 'You are a helpful assistant.')->prompt('Hello');

Override provider/model/timeout per call, not per class. The same agent should be able to run cheap in a queued job and expensive on a user-facing request:

$response = (new SalesCoach)->prompt(
    $transcript,
    provider: Lab::Anthropic,
    model: 'claude-haiku-4-5-20251001',
    timeout: 120,
);

This is also your escape hatch when a provider has an outage or you need to A/B cost against quality.

Two conversation strategies — pick deliberately. For history you control (already stored in your own tables, or you need to filter/redact), implement Conversational and return Message objects from messages(). For history the package should own, add RemembersConversations:

$response = (new SalesCoach)->forUser($user)->prompt('Hello!');
$conversationId = $response->conversationId;

$response = (new SalesCoach)->continue($conversationId, as: $user)->prompt('Tell me more.');

The trait route ships with its own tables and now stores participants polymorphically — good enough for most "chat with our docs" features. Use the manual interface when conversations are a first-class part of your schema and you need to join against them.

Structured output beats regex. Anything you intend to persist or branch on should come back as a schema, not prose:

class InvoiceExtractor implements Agent, HasStructuredOutput
{
    use Promptable;

    public function instructions(): string { return 'Extract invoice fields exactly.'; }

    public function schema(JsonSchema $schema): array
    {
        return [
            'total'   => $schema->number()->required(),
            'due_date'=> $schema->string()->required(),
        ];
    }
}

Then bind the array straight into a DTO or Model::create(). Note the provider asymmetries: Anthropic historically rejected certain JSON Schema constraints, and OpenAI-compatible gateways sometimes wrap output in markdown fences (now stripped for you). Validate the result with a Laravel validator anyway — a schema is a strong hint to the model, not a guarantee from it.

Streaming is a route concern, not a controller concern. Return the stream response directly from a route and let the framework do SSE:

Route::post('/coach', fn (Request $request) =>
    (new SalesCoach)->stream($request->input('message'))
);

If your front end is Livewire/Inertia rather than raw fetch, use the broadcasting support instead of hand-rolling SSE parsing. Either way, wrap the stream in a try/catch: since v0.11 stream errors throw instead of quietly ending the run, so a provider hiccup mid-response will now surface as an exception on your side — which is what you want, but only if you handle it.

Queue anything that costs money or takes more than a second. Image generation, TTS, transcription, embeddings over a large corpus — all of it:

(new SalesCoach)->queue($transcript)
    ->then(fn ($response) => $user->notify(new CoachReady($response)));

The then() callback is the important part for testing: since v0.11, faking a queued generation does execute then(), so your post-processing code is covered without a real queue worker.

Tools are where agents stop being chatbots. Local tools let the agent call your code; provider tools (web_search, web_fetch, file_search, hosted ToolSearch) let it call the provider's infrastructure. Tool choice is supported across Gemini, OpenAI, and Anthropic, so you can force a specific tool for deterministic flows. For anything that writes to your database or charges a card, use the human-in-the-loop support (v0.10+): the run pauses, provider content blocks are persisted, and you resume after approval rather than letting the model commit.

Media and retrieval follow one consistent shape. Learn it once:

Image::of('a lighthouse at dusk')->generate();
Audio::of($script)->generate();
Transcription::fromPath($path)->generate();
$vectors = Embeddings::for([$a, $b])->generate();
$ranked  = Reranking::of($query)->documents($docs)->rerank();
Document::fromPath($pdf)->put();
Stores::create('docs');

Embedding caching is built in and keyed per batch — if you were maintaining your own cache layer, check for the v0.10.2 key-collision fix before assuming your version was correct. Generated images and audio can be written straight to a filesystem disk.

Failover as configuration, not code. Provider connections fail over on transient gateway/Cloudflare errors, 503s, and (for Anthropic) usage-limit rejections. Configure the fallback chain in config/ai.php and let it work. Do not build retry loops around prompt() — you'll double up.

Testing: fake first, assert second. The package ships fakes for every entry point, and they're the reason you can put AI in a CI pipeline:

SupportResponder::fake(['Sorry for the trouble!']);

$this->post('/support', [...])->assertOk();

SupportResponder::assertPrompted('...');
SupportResponder::assertPromptedTimes(1);

Fakes exist for agents, images, audio, transcriptions, embeddings, reranking, files, and vector stores. Fake the entry point you use, not the one you assume — a faked Image fake won't cover a queued Transcription.

Observability via the run context. Since v0.11 a single invocation ID threads through an entire agent run, and each generation step, tool invocation, tool failure, and terminal failure is reported. Hook the events into your existing logging/tracing rather than wrapping agents in custom instrumentation — and use the invocation ID as the correlation key when a run spawns sub-agents (an agent invoked as a tool is linked back to its parent).

Gotchas and Tips

  • Assume the API moves. This is 0.x with a changelog entry nearly every week. Pin the exact version, read the changelog before bumping, and keep model names in config rather than literals in classes. Default-model bumps have silently changed output quality and cost for anyone who hardcoded nothing but relied on defaults.
  • Search the docs before writing code — literally. The package's own skill file instructs agents (and humans) to query the docs rather than infer APIs. The surface is large enough that guessing at method names will burn more time than a lookup. Query broadly (test agent fake, not laravel ai test agent fake).
  • Promptable is a trait; Agent is the contract. Implement the interface and use the trait. A class missing the interface resolves fine in isolation and fails confusingly at the call site.
  • Streaming text can fragment mid-sentence across multi-step runs. TextDelta::combine was gluing chunks incorrectly pre-v0.9.1. If you render deltas client-side and see mangled words on tool-using agents, upgrade before debugging your own buffer logic.
  • Token accounting is subtler than it looks. Usage must be summed across tool-call steps, and cache-write tokens were being dropped on the OpenAI gateway. If you bill customers on tokens, verify against the newly exposed raw HTTP response rather than trusting a single usage object — especially on multi-step agent runs.
  • Embeddings are model-specific. Switching provider or model invalidates every stored vector; there is no migration for that. Store the model name alongside the vectors and treat a dimension change as a re-index event. Also re-check any custom embedding cache you built — the batch key collision bug meant distinct string batches could collide.
  • Conversation replay can orphan tool calls. If you prune or edit history, partially-orphaned tool calls are now filtered when replaying, but a half-deleted tool exchange can still confuse strict providers. Prefer appending over editing.
  • Failover has boundaries. Transient gateway errors, 503s, connection failures, and Anthropic usage limits trigger it. A 400 from a malformed schema does not (and shouldn't). Don't expect failover to paper over a bad prompt.
  • Per-connection headers are a real debugging tool. Custom HTTP headers via connection config (v0.10.3+) let you route through a proxy, tag requests for provider dashboards, or satisfy an enterprise gateway — without forking the provider. Header casing is preserved when merging.
  • Timeouts default to something, and it's usually too short for reasoning models. Set timeout explicitly on any prompt that involves tools or long context; a truncated stream looks like a provider bug and isn't.
  • Provider capability parity is uneven and moves. Web search, web fetch, file search, transcription, embeddings, and multimodal input each landed on different providers at different times (xAI web fetch, Groq transcription, Azure web search, VoyageAI multimodal). Check the provider support matrix before designing a feature around one provider — and prefer Lab-agnostic code so you can swap.
  • Empty-base64 and blank-file inputs now throw consistently. If you were relying on a silent no-op, that's a breaking change hiding in a patch release.
  • MCP generators need the request bound during conversion. If you wrap MCP tool results yourself, don't convert outside the request lifecycle.
  • Extension points, in order of preference: config (models, failover, headers) → agent middleware (cross-cutting prompt/response concerns) → events/run context (observability) → macros like the built-in summarize → a custom OpenAI-compatible provider → forking a gateway. Most teams stop at the second step and never need the last.
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.
vento/laravel-airbrake
wfeller/laravel-batch
maco-studios/mage-os-installer
busarm/armie
siberfx/laravel-logger
kfteam1991/dcat-admin
dashed/dashed-laposta
dashed/dashed-ecommerce-montaportal
dashed/dashed-ecommerce-etsy
neohq/string-blade-compiler
log1x/acf-composer
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core
nodisrupt/laravel-agent
sierratecnologia/media-manager