I will confess something first: last year I pointed a frontier-class model at the question "is this comment spam?" and paid full price for every answer, in money and in the 800 milliseconds my users spent staring at a spinner. It felt absurd then and it looks worse now, because Laravel AI SDK just shipped v1.0.0 and the piece of it I keep coming back to is the smallest one. Classification is now a first-class capability, running on Jev, a model from TypeSafe built for exactly one job: answering cheap yes/no and pick-one questions fast. Here is my claim, and I will defend it: the majority of what product managers call "AI features" in a typical web app are classification problems, and a framework finally saying so out loud matters more than any chat widget.
<?php
use Laravel\Ai\Classification;
use Laravel\Ai\Classification\Boolean;
use Laravel\Ai\Classification\Choice;
$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',
]),
])->classify();The API reflects that honesty. You hand Classification::of() a ticket body, attach a Boolean question about urgency and a Choice question about which team owns it, call classify(), and you get structured answers you can branch on. For the one-liner case there is even a Str::decide() macro, which means the spam check I overpaid for is now a string method. Support ticket triage, comment moderation, routing, flagging: none of this needs a model that can write sonnets. It needs a fast, inspectable verdict, and it needs to cost so little that you never think about the bill.
Why does naming this in the framework matter? Because defaults shape teams. When the only tool in laravel/ai was "send a prompt to a big model," every trivial decision got routed through the most expensive path available, the way every data problem once got a Redis instance whether it needed one or not. Once the framework offers a dedicated lane for small decisions, your code review conversation changes. Someone reaches for a full generation call to check whether a message conveys urgency, and a colleague can now point at Classification and ask why. That social effect is worth more than the latency win, and the latency win is real.
The honest counter-argument deserves airtime. Classification currently speaks to two providers, TypeSafe and OpenRouter, and it makes me itch a little that a first-party SDK ships with one vendor's purpose-built model as the star. Small models also miss nuance, and a mislabelled "not urgent" is a real customer waiting overnight. I still land where I land, for two reasons. First, the Laravel team says more providers will plug into the same API, and the abstraction is clearly built for that. Second, big models get triage wrong too, at thirty times the cost, and a boolean output is trivially easy to log, sample and audit. If you can measure the error rate, you can manage it. You could never say that about a paragraph of freeform reasoning.
The rest of the 1.0 release backs up the maturity story. The Approvable contract lets an agent stop and wait for a human before executing a risky tool, and you can reject the call with a reason the model actually sees, or edit the arguments before it runs. That is the difference between an agent demo and an agent you would connect to anything that deletes data. Streaming now speaks the Vercel Chat and AG-UI protocols natively, ToolSearch lazy-loads rarely used tools on OpenAI and Anthropic, and middleware runs per generation step instead of once per prompt. The breaking change to know about: conversation history moved into a single steps column, so if you queried the old columns directly, budget migration time. The team suggests letting Laravel Boost's /upgrade-ai-sdk-v1 command drive your editor's assistant through it, which is a pleasingly circular use of the technology.
Meanwhile Laravel 13.33 landed with the kind of fixes you only appreciate if you have been bitten. The #[Refreshes] attribute finally solves the stale-model problem with database-computed columns: create a row whose net_salary comes from a storedAs expression and, until 13.32, your Eloquent object simply did not have that value unless you called refresh() and reloaded everything. Now the framework fetches just the named columns with one tiny scoped query after each save. One caveat worth respecting: that query fires on every save for that model, so if you need the value in one place only, manual refresh() remains the better trade. Cache::memo() gaining tag support is the other keeper, sparing you repeated Redis round trips for the same permissions check inside a single request. Add AsCollection::nullable() finally storing a real NULL instead of the string "null", and this is a minor release that pays rent.
Step back and both releases rhyme. Use the smallest tool that answers the question, whether that is a purpose-built decision model or a one-column follow-up query instead of a full row reload. The Go crowd has preached right-sizing for years while we happily bolted another layer on, so it is satisfying to watch Laravel make frugality a first-party feature. Now tell me where you draw the line: which decision in your codebase would you hand to a classifier tomorrow, and which one would you never let a model make without a human in the loop, no matter how cheap the call gets?




Comments
No comments yet — be the first.
Open the discussion
No account or password needed — just enter your e-mail and we’ll send you a one-time sign-in link. First time here? You’re set up automatically.
Your rating will be applied automatically after you sign in.
Check your inbox
We’ve sent a sign-in link to …. Open it on this device — this tab will sign you in automatically.
Nothing arrived? Check your spam folder — and mark the mail as "Not spam" so it lands in your inbox next time.