AI agents are useful when they return data your application can trust.
Free-form text is difficult to consume. Field names drift. Values arrive with extra commentary. A response that looks like JSON may still contain invalid syntax or missing properties.
The Laravel AI SDK solves this with structured output. You define the response schema once, and the agent returns data that matches it.
This tutorial uses Laravel AI SDK v0.11.x to build a review-analysis agent. We will expose it through a JSON endpoint and test it without making real provider requests.
Laravel is a PHP web framework, so the complete flow stays inside your application:
- Define an agent.
- Describe its output schema.
- Prompt the agent.
- Validate the incoming request.
- Return clean JSON from a controller.
- Test the endpoint with an agent fake.
Why structured output matters
An ordinary AI response might look like this:
The review is mostly positive. I would give it a score of 8 out of 10.
The customer liked the quality but mentioned slow delivery.
That response may be useful to a person. It is less useful to an API client.
Your frontend or downstream service needs predictable fields:
{
"summary": "The customer liked the quality but mentioned slow delivery.",
"sentiment": "positive",
"score": 8,
"tags": ["quality", "shipping"]
}
Structured output gives the model a schema instead of a blank text area. This helps prevent hallucinated field names, inconsistent types, and messy free-text responses.
The schema does not make the model’s conclusions factual. It makes the shape of those conclusions predictable.
The Laravel AI SDK supports structured agents across OpenAI, Anthropic, and Gemini. Provider-specific details are handled by the SDK, while your agent keeps the same PHP interface.

Install Laravel AI SDK v0.11.x
Install the SDK with Composer:
composer require laravel/ai:^0.11
Publish its configuration and migrations:
php artisan vendor:publish --provider="Laravel\Ai\AiServiceProvider"
php artisan migrate
The SDK can use several providers. Configure the credentials for the provider you want in your .env file:
OPENAI_API_KEY=
ANTHROPIC_API_KEY=
GEMINI_API_KEY=
You can review the available provider configuration in the Laravel AI SDK documentation.
Generate a structured agent:
php artisan make:agent ReviewAnalyzer --structured
The --structured option creates the contract and method needed for an output schema.
Define the agent and its output schema
Create or update app/Ai/Agents/ReviewAnalyzer.php:
<?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 ReviewAnalyzer implements Agent, HasStructuredOutput
{
use Promptable;
public function instructions(): string
{
return <<<'INSTRUCTIONS'
Analyze customer reviews.
Return a concise summary.
Classify the sentiment.
Assign a score from 1 to 10.
Extract short, useful tags.
Do not invent details that are not present in the review.
INSTRUCTIONS;
}
public function schema(JsonSchema $schema): array
{
return [
'summary' => $schema->string()
->description('A concise summary of the review.')
->required(),
'sentiment' => $schema->string()
->enum(['positive', 'neutral', 'negative'])
->required(),
'score' => $schema->integer()
->min(1)
->max(10)
->required(),
'tags' => $schema->array()
->items($schema->string())
->required(),
'metadata' => $schema->object(fn ($schema) => [
'language' => $schema->string()->required(),
'confidence' => $schema->string()
->enum(['low', 'medium', 'high'])
->required(),
])->required(),
];
}
}
The important part is the HasStructuredOutput contract. It requires the schema method.
Each schema field describes the type and constraints the agent must follow:
-
string()creates a string field. -
integer()creates an integer field. -
array()->items(...)describes a list. -
object(...)describes a nested object. -
enum(...)limits a value to known options. -
min()andmax()constrain numeric values. -
required()marks a field as mandatory.
The SDK converts this PHP schema into the provider’s structured-output format. Your application does not need separate payload builders for OpenAI, Anthropic, or Gemini.
Prompt the agent
You can prompt the agent from an application service, controller, job, or command:
use App\Ai\Agents\ReviewAnalyzer;
$review = <<<'REVIEW'
The product quality is excellent and the packaging was thoughtful.
Delivery took longer than expected, but support resolved the issue quickly.
REVIEW;
$result = (new ReviewAnalyzer)->prompt($review);
$result['summary'];
$result['sentiment'];
$result['score'];
$result['tags'];
$result['metadata']['confidence'];
A structured response is array-accessible. You do not need to extract JSON from a text response or remove Markdown code fences.
You can also select a provider explicitly:
use Laravel\Ai\Enums\Lab;
$result = (new ReviewAnalyzer)->prompt(
$review,
provider: Lab::OpenAI,
);
The same agent can use Lab::Anthropic or Lab::Gemini when the selected model supports structured output.
Build a REST API with PHP and Laravel
To build a REST API with PHP, start by validating the request before sending user content to the model.
Create app/Http/Controllers/ReviewAnalysisController.php:
<?php
namespace App\Http\Controllers;
use App\Ai\Agents\ReviewAnalyzer;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
class ReviewAnalysisController extends Controller
{
public function __invoke(Request $request): JsonResponse
{
$validated = $request->validate([
'review' => ['required', 'string', 'max:10000'],
]);
$result = (new ReviewAnalyzer)->prompt($validated['review']);
return response()->json([
'summary' => $result['summary'],
'sentiment' => $result['sentiment'],
'score' => $result['score'],
'tags' => $result['tags'],
'metadata' => [
'language' => $result['metadata']['language'],
'confidence' => $result['metadata']['confidence'],
],
]);
}
}
Add the route in routes/api.php:
<?php
use App\Http\Controllers\ReviewAnalysisController;
use Illuminate\Support\Facades\Route;
Route::post('/reviews/analyze', ReviewAnalysisController::class);
Now send a request:
curl -X POST http://localhost/api/reviews/analyze \
-H "Content-Type: application/json" \
-d '{"review":"Great quality and fast support, but shipping was slow."}'
The endpoint returns a stable JSON shape:
{
"summary": "The customer praised quality and support but disliked slow shipping.",
"sentiment": "positive",
"score": 8,
"tags": ["quality", "support", "shipping"],
"metadata": {
"language": "en",
"confidence": "high"
}
}
Explicitly selecting the response fields is useful at an API boundary. It prevents internal response properties from leaking into your public contract.

Validate the response at the application boundary
The SDK validates generated data against the output schema. You should still apply business-level validation when your API has stricter requirements.
For example, you may want to guarantee that tags are never empty:
use Illuminate\Support\Facades\Validator;
$validatedResult = Validator::make($result, [
'summary' => ['required', 'string'],
'sentiment' => ['required', 'in:positive,neutral,negative'],
'score' => ['required', 'integer', 'between:1,10'],
'tags' => ['required', 'array', 'min:1'],
'tags.*' => ['string', 'max:50'],
'metadata.language' => ['required', 'string'],
'metadata.confidence' => ['required', 'in:low,medium,high'],
])->validate();
This is a second line of defense for your application contract. The agent schema controls model output. Laravel validation protects the endpoint’s business rules.
Keep the two responsibilities separate:
- The AI schema defines what the model should return.
- Laravel validation defines what your application accepts and stores.
Handle provider differences deliberately
Structured output is not identical across every model.
The Laravel AI SDK provides a consistent API, but providers may differ in supported schema features, model behavior, limits, and error messages. Keep schemas portable when you need provider flexibility.
Prefer:
- Required scalar fields.
- Simple arrays.
- Nested objects with clear properties.
- Small, explicit enums.
- Descriptions that explain ambiguous fields.
Be cautious with complex unions, deeply nested objects, and provider-specific JSON Schema features. If your application depends on one provider, test against the exact model used in production.
The v0.11.x line also includes provider improvements around strict structured output handling. That makes it easier to move between supported providers without rewriting your agent.
You can read the v0.11 release history for provider and SDK changes.
Use enums for controlled values
Enums are useful when the response feeds a workflow.
For example, define a PHP enum:
enum ReviewSentiment: string
{
case Positive = 'positive';
case Neutral = 'neutral';
case Negative = 'negative';
}
Then reuse its values in the schema:
'sentiment' => $schema->string()
->enum(array_column(ReviewSentiment::cases(), 'value'))
->required(),
This keeps your PHP domain model and AI output schema aligned. It also gives your IDE and static analysis tools a known set of values.
Test structured agents with fakes
AI-powered features should not make network requests during your test suite. The Laravel AI SDK includes fakes for agents.
Create a feature test:
<?php
use App\Ai\Agents\ReviewAnalyzer;
it('returns structured review analysis', function () {
ReviewAnalyzer::fake([
[
'summary' => 'The customer liked the quality.',
'sentiment' => 'positive',
'score' => 9,
'tags' => ['quality'],
'metadata' => [
'language' => 'en',
'confidence' => 'high',
],
],
])->preventStrayPrompts();
$response = $this->postJson('/api/reviews/analyze', [
'review' => 'Excellent quality and helpful support.',
]);
$response
->assertOk()
->assertJsonPath('sentiment', 'positive')
->assertJsonPath('score', 9)
->assertJsonPath('metadata.confidence', 'high');
ReviewAnalyzer::assertPrompted('Excellent quality and helpful support.');
});
You can also call ReviewAnalyzer::fake() without a response. For structured agents, the SDK can generate fake data that matches the defined schema.
Use explicit fake responses when testing business behavior. Use automatic schema-aware fakes when you only need to verify the integration path.

Keep the schema close to the agent
The output schema is part of the agent’s contract. Keep it beside the agent instructions instead of hiding it in a controller.
This makes the agent easier to:
- Reuse from jobs and commands.
- Test without HTTP.
- Switch between providers.
- Extend with nested fields.
- Review during code changes.
Laravel gives PHP developers a coherent set of developer tools for this workflow. The agent, schema, validation, controller, route, and tests all use familiar application patterns.
Structured output turns an AI response into application data. With the Laravel AI SDK v0.11.x, you can define that data in PHP, validate it at the API boundary, and test it without a provider call.
For the full feature set, explore the Laravel AI SDK and its structured output documentation.