Shipping Laravel 13 AI Features That Survive Production
A production-minded guide to Laravel's AI SDK: structured output, queues, tool approval, evaluation, failover, cost, and observability.
Laravel's AI SDK is the first PHP AI integration I have seen that feels like part of the framework instead of a collection of provider wrappers. That is useful, but also slightly dangerous: a clean API can make a probabilistic dependency look more predictable than it is. This is how I would build an AI feature that I still want to own six months after launch.
Start with a product decision, not an agent
Before choosing a provider or generating an agent class, I write down the job in one sentence. ‘Help support agents draft a reply from an existing case’ is a job. ‘Add AI to support’ is not. The sentence tells me what context is allowed, what a good output looks like, who remains responsible, and whether the feature can safely fail.
I also decide whether the model is advising or acting. Drafting text that a person reviews is very different from refunding an order or changing a medical result. Laravel supports tools and human tool approval, but approval is not decoration. Any tool with financial, destructive, external-communication, or permission-changing consequences should have an explicit approval step or stay outside the model loop entirely.
- Name one user and one measurable job
- Define the maximum consequence of a wrong answer
- Choose the non-AI fallback before launch
Use structured output as a boundary
Free-form text is fine when the final destination is a text box. It is a poor contract for application logic. Laravel agents can implement HasStructuredOutput and define a JSON schema. I use that whenever code needs to make a decision from the response. The schema catches malformed shape; normal domain validation still decides whether the values make sense.
The example below is intentionally small. A confidence field is not truth, so I would never use it as the only release gate. It can help route low-confidence suggestions to a different user experience, but the useful tests are based on known examples, not on whether the model says it feels confident.
final class SupportDraft implements Agent, HasStructuredOutput
{
use Promptable;
public function instructions(): string
{
return 'Draft a factual reply using only the supplied case history.';
}
public function schema(JsonSchema $schema): array
{
return [
'reply' => $schema->string()->required(),
'missing_information' => $schema->array()->items(
$schema->string()
)->required(),
'needs_human_review' => $schema->boolean()->required(),
];
}
}Queue work, but make retries safe
Provider calls are slow and occasionally fail. Queueing keeps the request responsive, but it adds the possibility that the provider completed while our worker timed out. I create an operation record before dispatch with a unique key, input hash, prompt version, requested model, status, and eventual result. A retry first checks that record instead of blindly generating again.
I keep model work separate from business side effects. The AI job may produce a proposed action; another application service validates permissions and current state before applying it. This matters because queued context becomes stale. A refund that was valid when the prompt was created may no longer be valid when a worker finishes thirty seconds later.
Evaluation is a test suite for behaviour
I build an evaluation set before tuning prompts. Twenty to fifty carefully reviewed cases are enough to start. I include ordinary cases, ambiguous requests, missing context, prompt-injection attempts, content from another tenant, and examples where the correct answer is to refuse. Each case has criteria a human can apply consistently: required facts, forbidden claims, acceptable sources, and whether escalation is required.
Run that set whenever the instructions, tool list, retrieval logic, provider, or model changes. Store the output and reviewer result. Averages can hide serious failures, so I also track the worst safety and authorization cases individually. Laravel’s Agent::fake() and prompt assertions are useful for deterministic application tests; they do not replace evaluation against a real model.
- Application tests: was the correct agent invoked with allowed context?
- Contract tests: did structured output validate?
- Evaluation: was the answer useful, grounded, and safe?
- Production: how often did a human edit, reject, or escalate it?
Observe cost and failure per user action
A monthly provider bill is not actionable. I record latency, provider, model, token usage, estimated cost, retry count, tool calls, and outcome against a stable product action. Laravel emits events around prompts, tools, embeddings, files, and generated media; those are good instrumentation points. Payloads and prompts may contain private data, so logs should contain identifiers and safe metadata rather than raw content.
Failover deserves the same evaluation as the primary model. Providers do not interpret prompts or schemas identically. I only enable a fallback after it passes the same cases, and I expose fallback use in telemetry. Sometimes the honest failure mode is a clear ‘try again later’ message. Reliability is not improved by returning a confident but materially worse answer.
Use this in practice
- Write the job and maximum consequence first
- Use structured output for application decisions
- Persist an idempotent operation before queueing
- Evaluate every prompt, model, tool, and retrieval change
- Measure cost, edits, rejection, latency, and fallback use
