Laravel AI Agents: Streaming, Token Budgets &amp; Typed Output | Mohamed Said        [  ![Mohamed Said](https://cdn.msaied.com/01KT78WE565VEMM3PSNQAAB0MH.png)   Mohamed Said Laravel Backend Engineer  ](https://msaied.com) [ Home ](https://msaied.com) [ Projects ](https://msaied.com/projects) [ Articles  ](https://msaied.com/articles) [ Certificates ](https://msaied.com/certificates) [ Contact ](https://msaied.com#contact-section) 

       [  ](https://github.com/EG-Mohamed)       

 [ Home ](https://msaied.com) [ Projects ](https://msaied.com/projects) [ Articles ](https://msaied.com/articles) [ Certificates ](https://msaied.com/certificates) [ Contact ](https://msaied.com#contact-section) 

  [ home ](https://msaied.com)    [ articles ](https://msaied.com/articles)    Production AI Agents in Laravel: Streaming, Token Budgets, and Structured Output Contracts        On this page       1. [  The Gap Between Demo and Production AI Agents ](#the-gap-between-demo-and-production-ai-agents)
2. [  Streaming with Server-Sent Events ](#streaming-with-server-sent-events)
3. [  Enforcing Token Budgets at the Application Layer ](#enforcing-token-budgets-at-the-application-layer)
4. [  Structured Output Contracts with Readonly DTOs ](#structured-output-contracts-with-readonly-dtos)
5. [  Prompt-Side Contract Enforcement ](#prompt-side-contract-enforcement)
6. [  Key Takeaways ](#key-takeaways)

  ![Production AI Agents in Laravel: Streaming, Token Budgets, and Structured Output Contracts](https://cdn.msaied.com/466/15db89e4226fa2eecdd82ea72f2d2c01.png)

  #laravel   #ai   #agents   #streaming   #structured-output  

 Production AI Agents in Laravel: Streaming, Token Budgets, and Structured Output Contracts 
============================================================================================

     25 Jul 2026      3 min read    ![Mohamed Said](https://cdn.msaied.com/01M22N44A70A5MC2S599JP0MPH.webp)  Mohamed Said  

       Table of contents

1. [  01   The Gap Between Demo and Production AI Agents  ](#the-gap-between-demo-and-production-ai-agents)
2. [  02   Streaming with Server-Sent Events  ](#streaming-with-server-sent-events)
3. [  03   Enforcing Token Budgets at the Application Layer  ](#enforcing-token-budgets-at-the-application-layer)
4. [  04   Structured Output Contracts with Readonly DTOs  ](#structured-output-contracts-with-readonly-dtos)
5. [  05   Prompt-Side Contract Enforcement  ](#prompt-side-contract-enforcement)
6. [  06   Key Takeaways  ](#key-takeaways)

 The Gap Between Demo and Production AI Agents
---------------------------------------------

Most Laravel AI tutorials stop at `$client->chat()`. Production agents need three things demos skip: streaming responses that don't time out, hard token budgets that protect your bill, and structured output contracts that fail loudly when a model returns garbage.

This article tackles all three with concrete, opinionated patterns.

---

Streaming with Server-Sent Events
---------------------------------

Laravel's `StreamedResponse` is the right primitive. Pair it with a generator-based OpenAI client call and you get true SSE without a WebSocket server.

```php
// routes/api.php
Route::post('/agent/stream', AgentStreamController::class);

// app/Http/Controllers/AgentStreamController.php
final class AgentStreamController
{
    public function __invoke(AgentRequest $request, AgentService $agent): StreamedResponse
    {
        return response()->stream(
            function () use ($request, $agent): void {
                foreach ($agent->stream($request->validated('prompt')) as $chunk) {
                    echo "data: " . json_encode(['text' => $chunk]) . "\n\n";
                    ob_flush();
                    flush();
                }
                echo "data: [DONE]\n\n";
            },
            headers: ['Content-Type' => 'text/event-stream', 'X-Accel-Buffering' => 'no']
        );
    }
}

```

The `X-Accel-Buffering: no` header is critical when Nginx sits in front — without it, Nginx buffers the entire response.

---

Enforcing Token Budgets at the Application Layer
------------------------------------------------

Don't rely solely on `max_tokens` in the API call. A multi-turn agent accumulates context silently. Track token usage yourself and abort before you hit a cost cliff.

```php
final class TokenBudget
{
    private int $used = 0;

    public function __construct(private readonly int $limit) {}

    public function consume(int $tokens): void
    {
        $this->used += $tokens;
        if ($this->used > $this->limit) {
            throw new TokenBudgetExceededException(
                "Budget of {$this->limit} tokens exceeded (used: {$this->used})"
            );
        }
    }

    public function remaining(): int
    {
        return max(0, $this->limit - $this->used);
    }
}

```

Inject a `TokenBudget` into your agent loop and call `consume()` after each API response using the usage object the API returns. This gives you per-request, per-user, or per-tenant budget enforcement — whichever granularity your SaaS needs.

```php
$budget = new TokenBudget(limit: 8_000);

foreach ($turns as $turn) {
    $response = $this->client->chat($turn->toMessages(), maxTokens: $budget->remaining());
    $budget->consume($response->usage->totalTokens);
    // ...
}

```

---

Structured Output Contracts with Readonly DTOs
----------------------------------------------

JSON mode is not a contract. Models hallucinate keys, change nesting, or return `null` where you expect a string. Validate every structured response against a typed DTO immediately after deserialization.

```php
readonly class SentimentResult
{
    public function __construct(
        public readonly string $sentiment,  // 'positive'|'negative'|'neutral'
        public readonly float  $confidence, // 0.0–1.0
        public readonly string $summary,
    ) {}

    public static function fromArray(array $data): self
    {
        $validated = validator($data, [
            'sentiment'  => ['required', 'string', Rule::in(['positive','negative','neutral'])],
            'confidence' => ['required', 'numeric', 'min:0', 'max:1'],
            'summary'    => ['required', 'string', 'max:500'],
        ])->validate();

        return new self(...$validated);
    }
}

```

Call `SentimentResult::fromArray(json_decode($response->content, true))` and let Laravel's validator throw a `ValidationException` on malformed output. This surfaces model regressions immediately rather than letting bad data propagate into your database.

### Prompt-Side Contract Enforcement

Include the schema in your system prompt as a JSON Schema snippet. Models with JSON mode enabled respect it more reliably than natural-language instructions alone. Store the schema alongside the DTO so they stay in sync:

```php
public static function jsonSchema(): array
{
    return [
        'type' => 'object',
        'required' => ['sentiment', 'confidence', 'summary'],
        'properties' => [
            'sentiment'  => ['type' => 'string', 'enum' => ['positive','negative','neutral']],
            'confidence' => ['type' => 'number', 'minimum' => 0, 'maximum' => 1],
            'summary'    => ['type' => 'string', 'maxLength' => 500],
        ],
    ];
}

```

---

Key Takeaways
-------------

- Use `StreamedResponse` with `X-Accel-Buffering: no` for true SSE through Nginx.
- Track token usage from API responses; enforce budgets in application code, not just `max_tokens`.
- Validate every structured model response against a typed readonly DTO using Laravel's validator.
- Co-locate the JSON Schema with the DTO so prompt and validation contracts never drift.
- Throw typed exceptions (`TokenBudgetExceededException`, `ValidationException`) so callers can handle failures explicitly.

 Found this useful?

          [  ](https://twitter.com/intent/tweet?url=https%3A%2F%2Fmsaied.com%2Farticles%2Fproduction-ai-agents-in-laravel-streaming-token-budgets-and-structured-output-contracts-3&text=Production+AI+Agents+in+Laravel%3A+Streaming%2C+Token+Budgets%2C+and+Structured+Output+Contracts) [  ](https://www.linkedin.com/sharing/share-offsite/?url=https%3A%2F%2Fmsaied.com%2Farticles%2Fproduction-ai-agents-in-laravel-streaming-token-budgets-and-structured-output-contracts-3) 

 Frequently Asked Questions 
----------------------------

  3 questions  

     Q01  Why not just use `max\_tokens` to control costs instead of a custom TokenBudget?        `max_tokens` caps a single API call but doesn't track cumulative usage across a multi-turn conversation. A TokenBudget class aggregates usage from every turn's response object, giving you per-request or per-user cost control that `max_tokens` alone cannot provide. 

      Q02  Does JSON mode from OpenAI guarantee the response matches my DTO?        No. JSON mode guarantees valid JSON syntax, not that the keys, types, or values match your schema. Always validate the decoded array against your DTO's rules immediately after deserialization and throw on failure. 

      Q03  How do I prevent Nginx from buffering my SSE stream?        Set the `X-Accel-Buffering: no` response header. Nginx respects this header and disables proxy buffering for that response, allowing chunks to reach the client as they are flushed. 

  Continue reading

 More Articles 
---------------

 [ View all    ](https://msaied.com/articles) 

 [ ![Event Sourcing in Laravel: Aggregates, Projectors, and Rebuilding Read Models](https://cdn.msaied.com/647/586a0f822614fed8091917a895ebc502.png) laravel event-sourcing ddd 

### Event Sourcing in Laravel: Aggregates, Projectors, and Rebuilding Read Models

A practical walkthrough of event sourcing in Laravel — defining aggregates, persisting domain events, building...

  ![Mohamed Said](https://cdn.msaied.com/01M22N44A70A5MC2S599JP0MPH.webp)  Mohamed Said 

 9 Sep 2026     3 min read  

  Read    

 ](https://msaied.com/articles/event-sourcing-in-laravel-aggregates-projectors-and-rebuilding-read-models) [ ![Queue totalSize() and JobInterrupted Event in Laravel 13.31](https://cdn.msaied.com/648/d0e925e5b65d5b1d925fdaf612af5db2.png) Laravel 13 Queue Eloquent 

### Queue totalSize() and JobInterrupted Event in Laravel 13.31

Laravel 13.31 ships Queue::totalSize(), a new JobInterrupted event, chaperone support for BelongsToMany pivot...

  ![Mohamed Said](https://cdn.msaied.com/01M22N44A70A5MC2S599JP0MPH.webp)  Mohamed Said 

 9 Sep 2026     3 min read  

  Read    

 ](https://msaied.com/articles/queue-totalsize-and-jobinterrupted-event-in-laravel-1331) [ ![Read/Write Splitting and Sticky Reads in Laravel: A Production Guide](https://cdn.msaied.com/646/4155b1eed8a491a99c3dda6f7dddd80e.png) laravel database performance 

### Read/Write Splitting and Sticky Reads in Laravel: A Production Guide

Learn how Laravel's read/write connection splitting works under the hood, when sticky reads save you from repl...

  ![Mohamed Said](https://cdn.msaied.com/01M22N44A70A5MC2S599JP0MPH.webp)  Mohamed Said 

 9 Sep 2026     3 min read  

  Read    

 ](https://msaied.com/articles/readwrite-splitting-and-sticky-reads-in-laravel-a-production-guide) 

   [  ![Mohamed Said](https://cdn.msaied.com/01KT78WE565VEMM3PSNQAAB0MH.png)   Mohamed Said Laravel Backend Engineer  ](https://msaied.com)Senior Backend Engineer specializing in Laravel, scalable SaaS platforms, APIs, and cloud infrastructure. I build secure, high-performance web applications that help businesses grow.

Explore

- [Home](https://msaied.com)
- [Projects](https://msaied.com/projects)
- [Articles](https://msaied.com/articles)
- [Certificates](https://msaied.com/certificates)
- [Contact](https://msaied.com#contact-section)

Connect

- [   hello@msaied.com ](mailto:hello@msaied.com)
- [   +20 109 461 9204 ](tel:+201094619204)

© 2026 Mohamed Said. All rights reserved.

 [  ](https://github.com/EG-Mohamed) [  ](https://www.linkedin.com/in/msaiedm/) [  ](https://wa.me/201094619204) [  ](mailto:hello@msaied.com) [  ](https://drive.google.com/file/u/0/d/1MF20IPRJyzfy32mhEutjL5EpSls0w2Q8/view)
