Most of the SaaS work that lands on our desk is Laravel. Has been for years. What changed in 2026 is that "add an AI feature" stopped being a research project. Laravel 13 arrived on 17 March 2026, and alongside it the framework's own AI SDK — laravel/ai, released 5 February 2026 — landed as a first-party way to talk to LLM providers. The demo is a five-line controller. The production build is everything around it.
None of what follows is exotic. It's the boring scaffolding an agency reuses so that the third AI feature costs a fraction of the first, and so the client's ops team can sleep. If you sell Laravel software, this is the checklist we run.
First: pick the layer, not the provider
Before any of the patterns, one decision. How does your code actually reach a model?
- Laravel AI SDK (
laravel/ai) — the official package. Its core building block is the Agent: a dedicated PHP class holding the instructions, conversation context, tools and output schema for one job. It does streaming, structured output, embeddings, image and audio, and ships test fakes. For a new Laravel 13 build, this is our default. - Prism (
prism-php/prism) — the community package that got there first and is still excellent. Same idea: one fluent interface across OpenAI, Anthropic, Mistral, Gemini and local Ollama models, with schema-validated structured output and tool calling. If an app already uses it, we keep it. - Raw HTTP to a provider — only when we need something neither package exposes yet. It's a fine escape hatch, never the starting point.
The two packages are close enough that the choice isn't load-bearing: both give you one interface across providers, so swapping the model — or the whole package — later is cheap. What matters is that something sits between your app and the vendor. Which is the first pattern.
Pattern 1 — the model call lives behind an interface
No controller calls a provider directly. Ever. Every model interaction sits behind an application service — an Agent class in the SDK, or your own interface wrapping Prism — with a method that speaks your domain: summariseTicket(), draftReply(), classifyDocument(). The controller calls that. It has no idea which model answered.
This buys three things you will need. You can swap Claude for Mistral because the client's data can't leave the EU, and touch one class. You can mock the whole AI layer in a test. And you get one place to put the logging, retries and cost tracking that patterns 5 and 6 depend on. A provider SDK call sprayed across twelve controllers is a refactor you'll pay for twice.
The model is a dependency, not a feature. Treat it like your database driver: swappable, wrapped, and never called straight from a controller.
Pattern 2 — anything that calls a model runs on its own queue
Model calls are slow, memory-hungry, bursty and failure-prone in ways your ordinary jobs are not. A single completion can sit for tens of seconds before the first token. Put that on the default queue and one sluggish provider response backs up your welcome emails and your billing webhooks.
So AI work gets a dedicated queue and dedicated workers. Concretely: a separate ai connection, its own Horizon supervisor, and a timeout raised well above Horizon's 60-second default, because that default will kill a large-context call mid-flight. Redis as the driver. Retries with exponential backoff for the provider's inevitable 429s and 503s. And jobs written to be idempotent, because a retried job that double-charges a customer or posts two replies is worse than a failure. The point is simple: AI load must be able to spike, stall and fail without touching the rest of the app.
Pattern 3 — stream to the user, persist in the job
Users expect the token-by-token typing effect now; a spinner for fifteen seconds reads as broken. But the moment you stream, you have a durability problem — a stream that a page refresh throws away is not a saved conversation.
We split it into two layers, and this is the pattern most first attempts get wrong. The live stream is a separate HTTP request over SSE — the SDK's stream() returns a response with the right text/event-stream headers and pushes tokens as they arrive. The durable copy is written by the queued job from pattern 2, which collects the full completion and saves it to the database. Stream for the feel; persist for the truth. If the connection drops, you reload from the database, not from a stream that's already gone. No WebSocket server required.
Pattern 4 — structured output, validated, or it fails loud
The fastest way to a fragile AI feature is json_decode() on a raw model string and a hope. Models drift, wrap JSON in prose, and hallucinate fields. Parse that by hand and you'll ship a bug that only appears on the outputs you didn't test.
So we never ask for "some JSON." We define a schema and use structured output. Both the AI SDK and Prism negotiate that schema with the provider and validate the response against it — and, crucially, they throw when the model returns something that doesn't fit, instead of quietly handing you malformed data. A validation error you can catch, log and retry beats a null that surfaces three layers down as a broken invoice. Combine it with the backoff from pattern 2 and the failure mode becomes "retry, then alert," not "corrupt a record."
Pattern 5 — log every prompt, token and euro
The token bill is rarely the biggest cost of an AI feature, but it's the one that surprises clients — and the one you can't explain without data. So every call through the service layer writes a row: which agent, which model, prompt and completion tokens, latency, cost, and the user or tenant it belonged to.
That single table earns its keep four ways. You can answer "why did last month cost double" in one query. You can bill per-tenant usage honestly on a SaaS. You can spot the runaway prompt that quietly tripled in length after a "small" change. And when an EU client's reviewer asks how the system makes decisions, you already have the audit trail — the same logging the AI Act transparency duties expect, built in from day one rather than retrofitted the week before launch. Because the service layer is the one choke point (pattern 1), this is a few lines in a decorator, not a change in twelve places.
Pattern 6 — a fake provider in every test
A test suite that hits a real model on every run is slow, flaky, non-deterministic and — since 2026 model pricing is per-token — quietly expensive. So the default in every test is a faked provider. The AI SDK ships fakes: you assert an agent was invoked with the prompt you expect and return a canned response, exactly the way Http::fake() works for HTTP calls. The suite stays fast, deterministic and free.
That doesn't mean never touching a real model. We keep a small, separate set of live-provider tests behind a flag, for the handful of paths where the actual model output is the thing under test — a classifier's accuracy, a prompt's tendency to refuse. They run in CI on a schedule, not on every push. The rule of thumb: fake the plumbing, spot-check the intelligence.
A word on the EU angle
Two of these patterns aren't just hygiene in Europe — they're leverage. The interface (pattern 1) is what lets you move a client from a US provider to an EU-hosted one without a rewrite when data residency comes up. The logging (pattern 5) is a running start on the transparency and PII questions a data protection officer will put to you. We walk through those questions in detail in the GDPR checklist for AI features; the takeaway here is that the architecture that keeps your SaaS maintainable is the same one that keeps it compliant. That's not a coincidence you should waste.
The build-time bonus: Boost
One last piece, and it's about how you write the code, not what runs in production. Laravel Boost is the framework team's official MCP server for AI-assisted development. It gives your coding assistant real context about the app it's editing — your Laravel and PHP versions, installed packages, database schema, routes, Artisan commands — plus a documentation API with thousands of Laravel-specific answers. It's a development-time accelerator, not a runtime dependency, and it makes AI-written Laravel code markedly less wrong. Worth ten minutes to install on any serious build.
FAQ
Laravel AI SDK or Prism — which should we use?
For a new build on Laravel 13, start with the official Laravel AI SDK: it's first-party, testable, and its Agent classes map cleanly onto our service-layer pattern. Prism is the mature community package that got there first and is still excellent — reach for it if you're on an older app already using it, or want a feature the SDK hasn't shipped yet. Both give you one interface across OpenAI, Anthropic and others, so the migration cost between them is low.
Why put AI calls on their own queue instead of the default one?
Model calls are slow, memory-hungry, bursty and failure-prone in ways your ordinary jobs aren't. Mixing them into the default queue means one slow provider response backs up your password-reset emails. Give AI work a dedicated queue and dedicated workers, raise the timeout above the default 60 seconds, and you can scale and fail it independently of the rest of the app.
How do we stream a model response to the user but still keep it if they refresh?
Treat them as two layers. The live token stream is a separate HTTP request over Server-Sent Events for the instant feel; the durable copy is written by a queued job that persists the full completion to the database. If the connection drops or the user refreshes, you reload from the database, not from a stream that's already gone.
How do you stop a model returning malformed JSON?
Use structured output with a schema. Both the Laravel AI SDK and Prism negotiate a schema with the provider and validate the response against it, throwing when the model returns something that doesn't fit instead of handing you garbage. You still wrap the call in a retry with backoff, but you never parse a raw string and hope.
How do we test AI features without calling a paid API on every run?
Fake the provider. The Laravel AI SDK ships fakes so you can assert an agent was called with the right prompt and return canned responses, the same way Http::fake() works for HTTP. Your test suite stays fast, deterministic and free, and you keep a small, separate set of real-provider tests behind a flag for the paths where the actual model output matters.
Sources: Laravel, Laravel AI SDK and Laravel Boost · Laravel News, Laravel announces official AI SDK and what we know about Laravel 13 · Prism, prismphp.com and structured output docs. Package APIs move fast — check the current docs before you commit a version.
Scoping an AI feature onto an existing Laravel app and want a second pair of eyes on the architecture? Plan a call — we'll help you wire it, white-label.