Cheat sheet · laravel/ai 1.2 · Laravel 13
Laravel AI SDK cheat sheet (laravel/ai 1.2)
Agents, prompt/stream/queue, attachments, structured output, tools with approval, Stop middleware, conversations, streaming to Vue, embeddings and vector search, images, audio, transcription, reranking, classification, events and the testing fakes, checked against laravel/ai 1.2.3.
Checked against the official docs on 2026-10-17
Install and scaffold
Package, config, migrations
bash
composer require laravel/ai
php artisan vendor:publish --provider="Laravel\Ai\AiServiceProvider"
php artisan migrate # conversations, messages, approvals
# .env: OPENAI_API_KEY=... ANTHROPIC_API_KEY=... (defaults in config/ai.php)
php artisan make:agent SalesCoach # --structured for a schema() stub
php artisan make:tool RandomNumberGenerator
php artisan make:agent-middleware EnforceBudgetAn agent
php
use Laravel\Ai\Attributes\{MaxSteps, Model, Provider, Temperature, Timeout};
use Laravel\Ai\Contracts\{Agent, HasTools};
use Laravel\Ai\Enums\Lab;
use Laravel\Ai\Promptable;
#[Provider([Lab::Anthropic, Lab::OpenAI])] // first wins, next is the failover
#[Model('claude-sonnet-5'), MaxSteps(8), Temperature(0.2), Timeout(120)]
class SalesCoach implements Agent, HasTools
{
use Promptable;
public function instructions(): string { return 'You are a sales coach...'; }
public function tools(): iterable { return [new RandomNumberGenerator]; }
}Prompt, stream, queue
Three ways to run it
php
$response = (new SalesCoach)->prompt('Analyze this transcript...');
$response->text; $response->usage->inputTokens; $response->steps; $response->meta->provider;
(new SalesCoach)->prompt($text, provider: Lab::OpenAI, model: 'gpt-5', timeout: 60); // one-off override
return (new SalesCoach)->stream($text); // SSE response for a route
(new SalesCoach)->queue($text); // on the queue; ->broadcast($text) streams to a channel
Str::of($longText)->summarize(); // one-liner helpers: summarize(), decide(), toEmbeddings(), toAudio()Attachments
php
use Laravel\Ai\Files\{Document, Image};
(new ImageAnalyzer)->prompt('What is in this image?', attachments: [
Image::fromPath('/tmp/photo.jpg'), // fromUrl, fromUpload($request->file('x')), fromStorage('disk', 'path'), fromBase64
Document::fromPath('/tmp/brief.md'), // also fromString('...')
]);Structured output and tools
Structured output
php
use Illuminate\Contracts\JsonSchema\JsonSchema;
use Laravel\Ai\Contracts\HasStructuredOutput;
class SalesCoach implements Agent, HasStructuredOutput
{
public function schema(JsonSchema $schema): array
{
return ['score' => $schema->integer()->min(0)->max(100)->required(),
'summary' => $schema->string()->required()];
}
}
$response['score']; // array access on the responseA tool, with approval
php
use Laravel\Ai\Concerns\InteractsWithApprovals;
use Laravel\Ai\Contracts\{Approvable, Tool};
use Laravel\Ai\Tools\Request;
class DeleteFile implements Tool, Approvable
{
use InteractsWithApprovals; // the model must get a Decision first
public function description(): string { return 'Delete a file by path.'; }
public function schema(JsonSchema $schema): array { return ['path' => $schema->string()->required()]; }
public function handle(Request $request): string { unlink($request['path']); return 'Deleted.'; }
}
// Resume with the user's answer
(new FileAssistant)->continue($conversationId, as: $user)
->prompt(Decisions::from(['call_abc' => Decision::approve(), 'call_def' => Decision::reject('Keep it.')]));Middleware and conversations
Stop a run early (1.2.1+)
php
use Laravel\Ai\Contracts\HasMiddleware;
use Laravel\Ai\Middleware\Stop;
use Laravel\Ai\PendingStep;
public function middleware(): array // on an Agent implementing HasMiddleware
{
return [
Stop::when(fn (PendingStep $step) => Cache::has("chat:{$this->chat->id}:stopped")),
Stop::unless(fn (PendingStep $step) => $step->usage->inputTokens < 50_000),
];
}
// Custom: handle(PendingStep $step, Closure $next) { return $next($step); } or StepResult::stop()Remember the conversation
php
use Laravel\Ai\Concerns\RemembersConversations;
use Laravel\Ai\Contracts\Conversational;
class SupportAgent implements Agent, Conversational { use Promptable, RemembersConversations; }
$response = (new SupportAgent)->forUser($user)->prompt('Hello!');
$id = $response->conversationId;
(new SupportAgent)->continue($id, as: $user)->prompt('Tell me more.');
(new SupportAgent)->forUser($user)->continueLastConversation()->prompt('And then?');Streaming to Vue
Routes
php
Route::post('/chats/{chat}/stream', fn (Chat $chat, Request $r) => (new SupportAgent($chat))->stream($r->string('prompt')));
// Vercel AI SDK protocol (useChat):
Route::post('/chat', function (Request $request) {
$chat = Vercel::chat($request);
return (new SupportAgent)->withMessages($chat->history())->stream($chat)->usingProtocol($chat->protocol());
});Read the SSE in a component
typescript
const res = await fetch(`/chats/${chatId}/stream`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-XSRF-TOKEN': xsrf() }, body: JSON.stringify({ prompt }) })
const reader = res.body!.pipeThrough(new TextDecoderStream()).getReader()
let buffer = ''
while (true) {
const { value, done } = await reader.read(); if (done) break
buffer += value; const chunks = buffer.split('\n\n'); buffer = chunks.pop() ?? ''
for (const chunk of chunks) {
const data = chunk.replace(/^data: /, ''); if (data === '[DONE]') continue
const event = JSON.parse(data) // text_delta, tool_call, tool_result, tool_approval_request, stream_end
if (event.type === 'text_delta') answer.value += event.delta
if (event.type === 'stream_end') status.value = event.reason === 'cancelled' ? 'stopped' : 'done'
}
}Embeddings, images, audio, reranking
Embeddings and similarity search
php
$response = Embeddings::for(['Napa Valley has great wine.', 'Laravel is a PHP framework.'])->generate();
$vector = Str::of('Napa Valley has great wine.')->toEmbeddings();
Document::query()->whereVectorSimilarTo('embedding', 'best wineries in Napa')->limit(10)->get(); // Laravel 13 vector columns
$store = Stores::create('Knowledge Base'); $store->add(Document::fromPath('/tmp/handbook.pdf')); // provider file storesImages, audio, transcription, reranking
php
$image = Image::of('A donut on a kitchen counter')->landscape()->quality('high')->generate();
$image->storeAs('donut.jpg'); // quality(), square(), portrait(), size(), attachments(), queue()
$audio = Audio::of('I love coding with Laravel.')->voice('alloy')->generate(); $audio->storeAs('hello.mp3');
$text = Transcription::fromPath('/tmp/call.mp3')->language('en')->diarize()->generate();
$ranked = Reranking::of($documents)->limit(3)->rerank('PHP frameworks');Classification
Probabilities instead of generated text
php
use Laravel\Ai\Classification;
use Laravel\Ai\Classification\{Boolean, Choice, Score};
$result = Classification::of($ticket->body, ['front' => Image::fromPath($photo)]) // attachments optional
->questions([
'urgent' => new Boolean('Does this need an immediate response?'),
'team' => new Choice('Which team?', ['billing' => 'Invoices and refunds', 'tech' => 'Bugs and outages']),
'anger' => new Score('How frustrated is the customer?', ['Calm', 'Frustrated', 'Very angry']),
])
->classify(provider: 'openai'); // default provider: config 'default_for_classification' (typesafe)
$result['urgent']->isTrue(threshold: 0.8); $result['urgent']->probability;
$result['team']->choice; $result['team']->probabilityOf('tech'); $result['anger']->level(); $result['anger']->label();Events and testing
Listen
php
use Laravel\Ai\Events\{AgentPrompted, AgentFailed, ClassificationFailed, EmbeddingsFailed, ToolInvoked};
Event::listen(AgentPrompted::class, fn (AgentPrompted $e) => /* usage, provider, model */ null);
Event::listen([EmbeddingsFailed::class, ClassificationFailed::class], RecordAiFailure::class); // 1.2.2+: *Failed per attemptFakes and assertions
php
SalesCoach::fake(['Scored 82/100.']); // canned answers in order, or a closure
(new SalesCoach)->prompt('Analyze...');
SalesCoach::assertPrompted(fn (AgentPrompt $p) => $p->contains('Analyze')); // assertPromptedTimes, assertNotPrompted, assertQueued
Embeddings::fake(); Image::fake(); Audio::fake(); Transcription::fake(); Reranking::fake(); Classification::fake();
Embeddings::assertGenerated(fn (EmbeddingsPrompt $p) => $p->contains('Laravel'));
Image::assertNothingGenerated(); Classification::assertClassified(fn (ClassificationPrompt $p) => $p->asks('urgent'));
Http::fake(); // provider HTTP is plain Laravel HTTP client: fake it to test failover and *Failed eventsSources: Laravel docs: AI SDK · laravel/ai releases · laravel/ai source (1.x)
Free PDF
Get the Laravel AI SDK cheat sheet as a printable PDF
A4, two pages, same content as this page. Join the monthly Laravel & Vue newsletter and the download link appears right here.
You're on the list. Here is your cheat sheet:
Download the PDF