Laravel released v1.0 of its AI SDK on 23 September 2026. The package, laravel/ai, gives a Laravel application one API for text, images, audio, embeddings, reranking and now classification, across OpenAI, Anthropic, Gemini, Azure, Bedrock, Groq, xAI, DeepSeek, Mistral, Ollama, OpenRouter and more. It has been in beta since earlier this year. The 1.0 tag means the API and, more importantly, the database schema for stored conversations are now meant to stay put.
I build Laravel backends and admin panels for client websites and for the mobile apps I ship, and most of those projects now include at least one AI feature. This post covers what the SDK does, what is new in 1.0, and what to check before upgrading from a 0.x beta.
What the Laravel AI SDK is
Before this package, adding AI to a Laravel app meant choosing a vendor SDK or a community wrapper, and switching providers later meant rewriting calls. The Laravel AI SDK sits in front of all of them. You describe an agent as a PHP class, with its instructions, tools and an optional structured-output schema, and prompt it the same way whichever provider answers. Conversations can be stored in your database, responses can be streamed or queued, and agents can be faked in tests like any other Laravel service.
Version 1.0 requires PHP 8.3 or later and supports Laravel 12 and 13.
Installing it
composer require laravel/ai
php artisan vendor:publish --provider="Laravel\Ai\AiServiceProvider"
php artisan migrate
The migration creates the conversation tables. Provider keys go in your .env file, and the published config/ai.php decides which provider and model each capability uses by default. In 1.0 the default OpenAI model moved to GPT-6 and Anthropic's "smartest" default moved to Opus 5.5, so check that config if you care which model you are billed for.
Agents in one class
An agent is generated with php artisan make:agent and implements the Agent contract through the Promptable trait. Instructions, message history, tools and output schema are each a method:
<?php
namespace App\Ai\Agents;
use Illuminate\Contracts\JsonSchema\JsonSchema;
use Laravel\Ai\Contracts\Agent;
use Laravel\Ai\Contracts\HasStructuredOutput;
use Laravel\Ai\Promptable;
class SalesCoach implements Agent, HasStructuredOutput
{
use Promptable;
public function instructions(): string
{
return 'You are a sales coach, analyzing transcripts and providing feedback and an overall sales strength score.';
}
public function schema(JsonSchema $schema): array
{
return [
'feedback' => $schema->string()->required(),
'score' => $schema->integer()->min(1)->max(10)->required(),
];
}
}
$response = (new SalesCoach)->prompt('Analyze this sales transcript...');
return $response['score'];
Because the schema is declared once, the SDK asks each provider for structured output in whatever form that provider supports, and you read the result as an array. Tools are classes too, made with php artisan make:tool, each with a description, a JSON schema for its arguments and a handle() method.
Add the RemembersConversations trait and the SDK stores every turn for you. forUser($user) starts a conversation, continue($conversationId, as: $user) picks one up, and the new continueOrStart() does whichever applies, which removes an if/else from almost every chat controller.
New in 1.0: classification
Plenty of AI work in a real app is a decision rather than a piece of writing: is this message urgent, which team should get this ticket, is this contact-form entry spam? Running each of those through a general-purpose LLM is slow and costs more than it needs to. Version 1.0 adds classification as a capability of its own, backed today by TypeSafe's Jev models and by OpenRouter.
use Laravel\Ai\Classification;
use Laravel\Ai\Classification\Boolean;
use Laravel\Ai\Classification\Choice;
use Laravel\Ai\Classification\Score;
$response = Classification::of($ticket->body)->questions([
'is_urgent' => new Boolean('Does this message convey urgency?'),
'department' => new Choice('Which team should handle this?', [
'billing' => 'Payments, invoicing, refunds',
'technical' => 'Bugs, outages, integrations',
'sales' => 'Pricing, plans, upgrades',
]),
'frustration' => new Score('How frustrated is the customer?', [
'Calm, stating facts', 'Frustrated but civil', 'Very angry',
]),
])->classify();
$response['is_urgent']->isTrue();
$response['department']->choice; // 'technical'
$response['department']->probabilities; // every option, scored
$response['frustration']->score; // 0.0 to 1.0
For a single yes-or-no question there is now a decide macro on Str, with a threshold for how certain the model must be:
Str::of($message)->decide('Is this spam?');
Str::decide($message, 'Is this spam?', criteria: ['true' => 'Unsolicited bulk mail.'], threshold: 0.9);
This is the feature I expect to use most. Every business website with a contact form gets spam that slips past a honeypot and a CAPTCHA. The restaurant site I shipped this month already receives it. One Str::decide() call before a message is saved or emailed is a small change with a very visible payoff for the client.
New in 1.0: tools that wait for a human
Some tools should never run on the model's word alone: deleting a file, issuing a refund, sending an email to a customer. A tool can now implement the Approvable contract and use the InteractsWithApprovals trait. When the agent reaches for it, the run pauses instead of executing:
use Laravel\Ai\Concerns\InteractsWithApprovals;
use Laravel\Ai\Contracts\Approvable;
use Laravel\Ai\Contracts\Tool;
class DeleteFile implements Approvable, Tool
{
use InteractsWithApprovals;
// ...
}
$response = (new FileAssistant)
->forUser($user)
->prompt('Delete the old invoice.');
if ($response->hasPendingApprovals()) {
foreach ($response->pendingApprovals as $approval) {
// $approval->id, $approval->tool, $approval->arguments, $approval->reason
}
}
You resume the conversation with a decision for each pending call. A decision can approve the call, reject it with a reason the model gets to read, or edit the arguments before the tool runs:
use Laravel\Ai\Approvals\Decision;
use Laravel\Ai\Approvals\Decisions;
$response = (new FileAssistant)
->continue($conversationId, as: $user)
->prompt(Decisions::from([
'call_abc' => Decision::approve(),
'call_ghi' => Decision::reject('The invoice must be retained.'),
]));
Approvals work with prompt, stream, queue and the broadcast methods. For an admin-panel assistant, this is the difference between a demo and something a client can safely be handed.
New in 1.0: middleware on every step
Agent middleware used to run once per prompt. It now wraps each generation step, so an agent that calls three tools before answering runs your middleware three times. Each step arrives as a PendingStep, which you can copy with changes. The official example takes an expensive tool away once the agent has already used it:
use Closure;
use Laravel\Ai\PendingStep;
public function handle(PendingStep $step, Closure $next)
{
if (! $step->isFirstStep()) {
$step = $step->withoutTools('SearchDocumentation');
}
return $next($step);
}
PendingStep also has withModel(), withInstructions(), withMessages(), withTools(), onlyTools(), withToolChoice(), withMaxTokens() and withProviderOptions(). That makes middleware the natural place for cost control: switch to a cheaper model after the first step, trim a long history, or return a cached answer without calling the provider at all.
New in 1.0: fewer tools per request, and code execution
An agent with 30 tools describes all 30 on every request. That costs tokens and makes the model worse at choosing. Wrap the rarely used ones in ToolSearch and the provider loads them only when a prompt needs them. It works with OpenAI and Anthropic:
use Laravel\Ai\Providers\Tools\ToolSearch;
public function tools(): iterable
{
return [
new Weather,
new ToolSearch(tools: [
new SearchInvoices,
new RefundOrder,
]),
];
}
The new CodeExecution provider tool lets the model run code in the provider's own sandbox, which gives far more reliable answers for calculations and data analysis than asking a model to do arithmetic in its head. It is supported on Anthropic, OpenAI, Azure, Gemini and xAI.
use Laravel\Ai\Providers\Tools\CodeExecution;
public function tools(): iterable
{
return [new CodeExecution];
}
New in 1.0: chat front-ends without glue code
The SDK now reads requests and streams responses in the Vercel AI SDK chat protocol and in AG-UI, the Agent User Interaction protocol used by clients such as CopilotKit. One route handles the new message, updates the stored conversation and processes any approval decisions the user submitted:
use Illuminate\Http\Request;
use Laravel\Ai\Vercel\Vercel;
Route::post('/chat', function (Request $request) {
$chat = Vercel::chat($request);
return (new SupportAgent)
->withMessages($chat->history())
->stream($chat)
->usingProtocol($chat->protocol());
});
Vercel::toUiMessages() turns stored messages back into the shape the client expects, so a chat screen can be rebuilt after a page reload. For AG-UI clients, call usingAgentUserInteractionProtocol() on the stream.
Breaking changes to check before you upgrade
If you built on a 0.x beta, the upgrade guide rates each change by likelihood of impact. These are the ones rated high:
Conversation storage. The tool_calls and tool_results columns on agent_conversation_messages are replaced by a single steps JSON column with one entry per model round trip, and approval_state is replaced by a status column (completed, paused or failed). The package's own migration will not run again, so you create a new migration from the backfill code in the upgrade guide. Resolve or abandon any turns waiting for tool approval first: pending turns cannot be resumed after their approval_state data is removed. Any raw SQL against the old columns has to move to steps.
Middleware. handle() now receives a PendingStep and must return the StepResponse produced by $next($step), and it runs once per step rather than once per prompt. Middleware that counted prompts or logged whole runs will now fire several times.
Token usage. promptTokens and completionTokens are now inputTokens and outputTokens, and the totals now include cached, cache-written and reasoning tokens. If you bill clients or set budgets from these numbers, price the categories separately. The upgrade guide gives this pattern:
$usage = $response->usage;
$cost = $usage->uncachedInputTokens() * $baseRate
+ ($usage->cacheReadInputTokens ?? 0) * $cacheReadRate
+ ($usage->cacheWriteInputTokens ?? 0) * $cacheWriteRate;
Existing rows in the usage column keep the old key names, so reporting code that reads history must handle both formats.
Bedrock and Gemini. The AWS SDK is no longer installed for you, so Bedrock users need composer require aws/aws-sdk-php. Gemini moved to its Interactions API, and Gemini vector-store addFile() now waits for the import to finish and returns a different ID than before.
Medium-impact changes include stream protocols becoming objects (usingVercelDataProtocol() no longer takes a boolean), resumed turns folding into the message they paused on, and sub-agent activity being streamed by default.
Letting an AI assistant do the upgrade
Laravel's own recommendation is to hand the upgrade to a coding assistant through Laravel Boost, its MCP server:
composer require laravel/boost --dev
php artisan boost:install
Then run the /upgrade-ai-sdk-v1 slash command in Claude Code, Cursor, OpenCode, Gemini or VS Code, and Boost walks the assistant through the guide one change at a time against your codebase. Whether or not you use it, run the backfill migration on a copy of production data first, and deploy the migration before the code that reads the steps column.
Should you adopt it now?
For a new Laravel project that needs AI, yes. The 1.0 label is mostly a promise about the conversations table, which was the hardest part to change later, and that shape is now settled. You get provider independence, database-backed conversations, streaming, queues and test fakes with little code of your own.
For an existing beta integration, the upgrade is worthwhile but not a composer update and done. Budget time for the conversation migration and for reviewing any middleware and cost reporting. Classification alone may justify the work, if your app makes lots of small yes-or-no decisions that currently go through a full LLM call.
If you have a Laravel app, a website or a mobile app backend that needs an AI feature, or a beta integration to bring up to 1.0, get in touch.