A unified, expressive API for interacting with AI providers — build agents with tools and structured output, generate images, synthesise and transcribe audio, create vector embeddings, and more, all with a consistent Laravel-friendly interface.
Laravel AI SDK provides a unified, expressive API for interacting with AI providers such as OpenAI, Anthropic, Gemini, and more. With the AI SDK you can build intelligent agents with tools and structured output, generate images, synthesize and transcribe audio, create vector embeddings, and much more — all using a consistent, Laravel-friendly interface.
Laravel AI SDK is an official package (laravel/ai) available in Laravel 13.
The default models for each capability (text, images, audio, transcription, embeddings) may also be configured in config/ai.php. For example, to default to GPT-4o for text generation and dall-e-3 for image generation:
For APIs compatible with OpenAI, such as LM Studio, vLLM, Together, Fireworks, or a local gateway, configure a provider with the openai-compatible driver. The url is required. If you specify a key, it is sent as a bearer token.
OpenAI-compatible providers support text generation, streaming, tools, structured output, and image attachments. If the endpoint requires additional request body fields, use provider options.
Agents are the fundamental building block of the Laravel AI SDK. Each agent is a PHP class that encapsulates a system prompt, conversation context, tools, and an optional structured output schema.
$response = (new SalesCoach)->prompt('Analyze this sales transcript...');return (string) $response;
Use make to resolve the agent from the service container:
$agent = SalesCoach::make(user: $user);
Override the provider, model, or timeout per request:
use Laravel\Ai\Enums\Lab;$response = (new SalesCoach)->prompt( 'Analyze this sales transcript...', provider: Lab::Anthropic, model: 'claude-haiku-4-5-20251001', timeout: 120,);
The RemembersConversations trait stores and retrieves history automatically using the published migrations:
use Laravel\Ai\Concerns\RemembersConversations;class SalesCoach implements Agent, Conversational{ use Promptable, RemembersConversations; public function instructions(): string { return 'You are a sales coach...'; }}// Start a new conversation$response = (new SalesCoach)->forUser($user)->prompt('Hello!');$conversationId = $response->conversationId;// Continue an existing conversation$response = (new SalesCoach) ->continue($conversationId, as: $user) ->prompt('Tell me more about that.');
use Laravel\Ai\Files;$response = (new SalesCoach)->prompt( 'Analyze the attached sales transcript...', attachments: [ Files\Document::fromStorage('transcript.pdf'), Files\Document::fromPath('/home/laravel/transcript.md'), $request->file('transcript'), ]);
For image attachments use Files\Image:
$response = (new ImageAnalyzer)->prompt('What is in this image?', attachments: [ Files\Image::fromStorage('photo.jpg'), Files\Image::fromPath('/home/laravel/photo.jpg'), $request->file('photo'),]);
Broadcast each streamed event over a Laravel channel:
use Illuminate\Broadcasting\Channel;$stream = (new SalesCoach)->stream('Analyze this sales transcript...');foreach ($stream as $event) { $event->broadcast(new Channel('channel-name'));}
Or use broadcastOnQueue to broadcast asynchronously:
(new SalesCoach)->broadcastOnQueue( 'Analyze this sales transcript...', new Channel('channel-name'),);
Some broadcasting platforms limit WebSocket messages to approximately 10 KB. Data-heavy stream events, such as large tool results, can exceed that limit and fail to broadcast. Use the WithoutBroadcasting attribute to exclude specific event types.
Excluded events are not broadcast, but they are still stored in the agent_conversation_messages table. The frontend can retrieve the complete tool data after streaming finishes. This works for queued broadcasting with broadcastOnQueue and synchronous broadcasting with broadcast or broadcastNow.
Tools extend what an agent can do — query databases, call external APIs, perform calculations, etc.
php artisan make:tool RandomNumberGenerator
<?phpnamespace App\Ai\Tools;use Illuminate\Contracts\JsonSchema\JsonSchema;use Laravel\Ai\Contracts\Tool;use Laravel\Ai\Tools\Request;use Stringable;class RandomNumberGenerator implements Tool{ public function description(): Stringable|string { return 'This tool may be used to generate cryptographically secure random numbers.'; } public function handle(Request $request): Stringable|string { return (string) random_int($request['min'], $request['max']); } public function schema(JsonSchema $schema): array { return [ 'min' => $schema->integer()->min(0)->required(), 'max' => $schema->integer()->required(), ]; }}
Register tools in the agent’s tools method:
use Laravel\Ai\Contracts\HasTools;class SalesCoach implements Agent, HasTools{ use Promptable; public function tools(): iterable { return [new RandomNumberGenerator]; }}
The FileStorage tool factory gives an agent access to a Laravel filesystem disk. The all method returns tools for listing, reading, generating URLs for, writing, deleting, and copying files on the selected disk.
use Laravel\Ai\Tools\FileStorage;public function tools(): iterable{ return FileStorage::all('local');}
Use readOnly to provide read-only access:
return FileStorage::readOnly('local');
These methods return an Illuminate\Support\Collection, so you can further restrict the tools you expose:
use Laravel\Ai\Tools\Filesystem\DeleteFile;return FileStorage::all('s3') ->reject(fn ($tool) => $tool instanceof DeleteFile);
If your application uses Laravel MCP, you may give your agents tools exposed by Model Context Protocol servers. Using the Laravel MCP client, you may connect to a remote or local MCP server and pass its tools directly to your agent.
MCP tools require the Laravel MCP package to be installed in your application.
Because an MCP client’s tools method returns a collection, spread it into your agent’s tools array using the ... operator:
use App\Ai\Tools\RandomNumberGenerator;use Laravel\Mcp\Client;/** * Get the tools available to the agent. * * @return Tool[] */public function tools(): iterable{ return [ ...Client::web('https://mcp.example.com') ->withToken($token) ->tools(), new RandomNumberGenerator, ];}
The AI SDK automatically wraps each MCP tool so the agent can call it like any other tool. You may also use a named MCP client:
use Laravel\Mcp\Facades\Mcp;public function tools(): iterable{ return [ ...Mcp::client('github')->tools(), ];}
You can return another agent from an agent’s tools() method. Registering an agent as a tool lets the parent delegate a specific task to the subagent and incorporate the result into its original response. This is useful when a general-purpose agent needs access to specialists with their own instructions, tools, model, and provider settings.For example, a customer support agent can delegate refund policy questions to a refund specialist:
<?phpnamespace App\Ai\Agents;use Laravel\Ai\Contracts\Agent;use Laravel\Ai\Contracts\HasTools;use Laravel\Ai\Promptable;class CustomerSupportAgent implements Agent, HasTools{ use Promptable; public function instructions(): string { return 'You help customers with account, order, and billing questions. Delegate refund policy questions to the refunds specialist.'; } public function tools(): iterable { return [new RefundsAgent]; }}
To customize how the subagent appears to its parent, implement CanActAsTool and define its tool name and description:
<?phpnamespace App\Ai\Agents;use App\Ai\Tools\LookupOrder;use Laravel\Ai\Attributes\Provider;use Laravel\Ai\Contracts\Agent;use Laravel\Ai\Contracts\CanActAsTool;use Laravel\Ai\Contracts\HasTools;use Laravel\Ai\Enums\Lab;use Laravel\Ai\Promptable;#[Provider(Lab::Anthropic)]class RefundsAgent implements Agent, CanActAsTool, HasTools{ use Promptable; public function instructions(): string { return 'You are a refunds specialist. Use order details and the refund policy to give concise eligibility guidance.'; } public function name(): string { return 'refunds_specialist'; } public function description(): string { return 'Determine whether an order is eligible for a refund and explain the next step.'; } public function tools(): iterable { return [new LookupOrder]; }}
If a subagent does not implement CanActAsTool, Laravel derives the tool name from its class and generates a generic description. Each subagent invocation is isolated and does not inherit the parent’s conversation history.
Agent middleware lets you inspect or modify prompts and responses before and after they are sent.
php artisan make:agent-middleware LogPrompts
Implement the middleware on the agent:
use App\Ai\Middleware\LogPrompts;use Laravel\Ai\Contracts\HasMiddleware;class SalesCoach implements Agent, HasMiddleware{ use Promptable; public function middleware(): array { return [new LogPrompts]; }}
A middleware class:
<?phpnamespace App\Ai\Middleware;use Closure;use Laravel\Ai\Prompts\AgentPrompt;class LogPrompts{ public function handle(AgentPrompt $prompt, Closure $next) { Log::info('Prompting agent', ['prompt' => $prompt->prompt]); return $next($prompt); }}
Inspect the response after it is generated using then:
public function handle(AgentPrompt $prompt, Closure $next){ return $next($prompt)->then(function (AgentResponse $response) { Log::info('Agent responded', ['text' => $response->text]); });}
Create a one-off agent inline with the agent() helper:
use function Laravel\Ai\{agent};$response = agent( instructions: 'You are an expert at software development.', messages: [], tools: [],)->prompt('Tell me about Laravel');
With structured output:
use Illuminate\Contracts\JsonSchema\JsonSchema;$response = agent( schema: fn (JsonSchema $schema) => ['number' => $schema->integer()->required()],)->prompt('Generate a random number less than 100');
Generate images with the Image class. Supported providers: OpenAI, Gemini, xAI, Azure, Bedrock, OpenRouter.
use Laravel\Ai\Image;$image = Image::of('A donut sitting on the kitchen counter')->generate();$rawContent = (string) $image;
Specify quality and aspect ratio:
$image = Image::of('A donut sitting on the kitchen counter') ->quality('high') ->landscape() ->timeout(120) ->generate();
Generate from a reference image:
use Laravel\Ai\Files;$image = Image::of('Update this photo to be in the style of an impressionist painting.') ->attachments([Files\Image::fromStorage('photo.jpg')]) ->landscape() ->generate();
Generate speech from text with the Audio class. Supported providers: OpenAI, ElevenLabs, Gemini.
use Laravel\Ai\Audio;$audio = Audio::of('I love coding with Laravel.')->generate();$rawContent = (string) $audio;
Select a voice:
$audio = Audio::of('I love coding with Laravel.')->female()->generate();$audio = Audio::of('I love coding with Laravel.')->voice('voice-id-or-name')->generate();$audio = Audio::of('I love coding with Laravel.')->female()->instructions('Said like a pirate')->generate();
use Laravel\Ai\Responses\AudioResponse;Audio::of('I love coding with Laravel.') ->queue() ->then(function (AudioResponse $audio) { $path = $audio->store(); });
Generate embeddings using the Embeddings class or the Str macro.
use Illuminate\Support\Str;$embeddings = Str::of('Napa Valley has great wine.')->toEmbeddings();
use Laravel\Ai\Embeddings;$response = Embeddings::for([ 'Napa Valley has great wine.', 'Laravel is a PHP framework.',])->generate();$response->embeddings; // [[0.123, 0.456, ...], [0.789, 0.012, ...]]
Specify dimensions and model:
$response = Embeddings::for(['Napa Valley has great wine.']) ->dimensions(1536) ->generate(Lab::OpenAI, 'text-embedding-3-small');
$response = Embeddings::for(['Napa Valley has great wine.'])->cache()->generate();$response = Embeddings::for(['Napa Valley has great wine.'])->cache(seconds: 3600)->generate();// Via the Str macro$embeddings = Str::of('Napa Valley has great wine.')->toEmbeddings(cache: true);$embeddings = Str::of('Napa Valley has great wine.')->toEmbeddings(cache: 3600);
Rerank a list of documents by relevance to a query. Supported providers: Cohere, Jina, VoyageAI.
use Laravel\Ai\Reranking;$response = Reranking::of([ 'Django is a Python web framework.', 'Laravel is a PHP web application framework.', 'React is a JavaScript library for building user interfaces.',])->rerank('PHP frameworks');$response->first()->document; // "Laravel is a PHP web application framework."$response->first()->score; // 0.95$response->first()->index; // 1$response = Reranking::of($documents)->limit(5)->rerank('search query');
Pass an array of providers to automatically fall back when one is unavailable:
use App\Ai\Agents\SalesCoach;use Laravel\Ai\Image;$response = (new SalesCoach)->prompt( 'Analyze this sales transcript...', provider: [Lab::OpenAI, Lab::Anthropic],);$image = Image::of('A donut sitting on the kitchen counter') ->generate(provider: [Lab::Gemini, Lab::xAI]);
use Laravel\Ai\QueuedAgentPrompt;SalesCoach::assertQueued('Analyze this...');SalesCoach::assertQueued(function (QueuedAgentPrompt $prompt) { return $prompt->contains('Analyze');});SalesCoach::assertNotQueued('Missing prompt');SalesCoach::assertNeverQueued();
Prevent real prompts from being sent during tests:
SalesCoach::fake()->preventStrayPrompts();
For agents that return structured output, pass an array as a fake response:
SalesCoach::fake([ ['score' => 87],]);
If you call fake() for a structured-output agent without explicitly providing fake data, Laravel automatically generates data that matches the agent’s schema.
For anonymous agents:
use Laravel\Ai\AnonymousAgent;AnonymousAgent::fake(['Test response']);