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

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

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

  [ home ](https://www.msaied.com)    [ articles ](https://www.msaied.com/articles)    Streaming AI Responses in Laravel: Token Budgets, Structured Output, and Agent Contracts        On this page       1. [  The Problem With Naive LLM Integration ](#the-problem-with-naive-llm-integration)
2. [  Streaming Responses to the Browser ](#streaming-responses-to-the-browser)
3. [  Enforcing Token Budgets ](#enforcing-token-budgets)
4. [  1. Hard limit via max\_tokens ](#1-hard-limit-via-codemax-tokenscode)
5. [  2. Prompt token pre-check ](#2-prompt-token-pre-check)
6. [  Structured Output Contracts ](#structured-output-contracts)
7. [  Wiring It Together in a Job ](#wiring-it-together-in-a-job)
8. [  Key Takeaways ](#key-takeaways)

  ![Streaming AI Responses in Laravel: Token Budgets, Structured Output, and Agent Contracts](https://cdn.msaied.com/584/3ffe135721c65ab3f9b40401dc3c41de.png)

  #laravel   #ai   #llm   #streaming   #agents  

 Streaming AI Responses in Laravel: Token Budgets, Structured Output, and Agent Contracts 
==========================================================================================

     23 Aug 2026      4 min read    ![Mohamed Said](https://cdn.msaied.com/01KT78WE565VEMM3PSNQAAB0MJ.jpg)  Mohamed Said  

       Table of contents

1. [  01   The Problem With Naive LLM Integration  ](#the-problem-with-naive-llm-integration)
2. [  02   Streaming Responses to the Browser  ](#streaming-responses-to-the-browser)
3. [  03   Enforcing Token Budgets  ](#enforcing-token-budgets)
4. [  04   1. Hard limit via max\_tokens  ](#1-hard-limit-via-codemax-tokenscode)
5. [  05   2. Prompt token pre-check  ](#2-prompt-token-pre-check)
6. [  06   Structured Output Contracts  ](#structured-output-contracts)
7. [  07   Wiring It Together in a Job  ](#wiring-it-together-in-a-job)
8. [  08   Key Takeaways  ](#key-takeaways)

 The Problem With Naive LLM Integration
--------------------------------------

Most Laravel + LLM tutorials show a single `chat()` call and a `dd($response->content)`. That works in a demo. In production you face three hard problems: responses block the HTTP worker until the model finishes, uncapped prompts silently drain your budget, and free-form JSON from the model breaks your downstream code without warning.

This article tackles all three with concrete patterns.

---

Streaming Responses to the Browser
----------------------------------

OpenAI's streaming API sends server-sent events (SSE). Laravel's `StreamedResponse` lets you forward them without buffering the entire completion.

```php
use Illuminate\Http\Response;
use OpenAI\Laravel\Facades\OpenAI;

Route::get('/chat', function () {
    return response()->stream(function () {
        $stream = OpenAI::chat()->createStreamed([
            'model' => 'gpt-4o',
            'messages' => [['role' => 'user', 'content' => request('prompt')]],
        ]);

        foreach ($stream as $response) {
            $delta = $response->choices[0]->delta->content ?? '';
            if ($delta !== '') {
                echo "data: " . json_encode(['token' => $delta]) . "\n\n";
                ob_flush();
                flush();
            }
        }

        echo "data: [DONE]\n\n";
    }, 200, [
        'Content-Type' => 'text/event-stream',
        'X-Accel-Buffering' => 'no', // critical for nginx
        'Cache-Control' => 'no-cache',
    ]);
});

```

The `X-Accel-Buffering: no` header is the most commonly forgotten detail when running behind nginx — without it, nginx buffers the entire stream before forwarding.

---

Enforcing Token Budgets
-----------------------

Token costs compound fast when users craft adversarial prompts. Enforce budgets at two layers.

### 1. Hard limit via `max_tokens`

```php
$payload = [
    'model' => 'gpt-4o',
    'max_tokens' => config('ai.max_completion_tokens', 512),
    'messages' => $messages,
];

```

### 2. Prompt token pre-check

Count tokens before sending using `tiktoken-php` or a simple heuristic, and reject early:

```php
use Yethee\Tiktoken\EncoderProvider;

final class TokenBudgetGuard
{
    private const MODEL_LIMIT = 8_192;
    private const RESERVED_FOR_COMPLETION = 512;

    public function __construct(private EncoderProvider $provider) {}

    public function assertFits(array $messages, string $model = 'gpt-4o'): void
    {
        $encoder = $this->provider->getForModel($model);
        $tokens = array_sum(
            array_map(fn ($m) => count($encoder->encode($m['content'])), $messages)
        );

        $budget = self::MODEL_LIMIT - self::RESERVED_FOR_COMPLETION;

        if ($tokens > $budget) {
            throw new TokenBudgetExceededException($tokens, $budget);
        }
    }
}

```

Bind this as a singleton and inject it into your agent service. Throw early — never let an oversized prompt reach the API.

---

Structured Output Contracts
---------------------------

OpenAI's `response_format` with `json_schema` mode guarantees the model returns JSON matching your schema. Pair that with a typed PHP DTO and you get end-to-end type safety.

```php
readonly class ProductSuggestion
{
    public function __construct(
        public string $name,
        public string $reason,
        public int $confidencePercent,
    ) {}

    public static function fromArray(array $data): self
    {
        return new self(
            name: $data['name'],
            reason: $data['reason'],
            confidencePercent: $data['confidence_percent'],
        );
    }
}

```

```php
$response = OpenAI::chat()->create([
    'model' => 'gpt-4o-2024-08-06', // structured output requires this or later
    'messages' => $messages,
    'response_format' => [
        'type' => 'json_schema',
        'json_schema' => [
            'name' => 'product_suggestion',
            'strict' => true,
            'schema' => [
                'type' => 'object',
                'properties' => [
                    'name' => ['type' => 'string'],
                    'reason' => ['type' => 'string'],
                    'confidence_percent' => ['type' => 'integer'],
                ],
                'required' => ['name', 'reason', 'confidence_percent'],
                'additionalProperties' => false,
            ],
        ],
    ],
]);

$suggestion = ProductSuggestion::fromArray(
    json_decode($response->choices[0]->message->content, true, flags: JSON_THROW_ON_ERROR)
);

```

With `strict: true` the model will refuse to emit keys not in your schema. Validation failures become model refusals, not silent bad data.

---

Wiring It Together in a Job
---------------------------

For non-interactive workloads, push the agent call to a queued job and store the result:

```php
class RunProductSuggestionAgent implements ShouldQueue
{
    use Dispatchable, Queueable;

    public int $tries = 2;
    public int $timeout = 60;

    public function __construct(private int $productId) {}

    public function handle(TokenBudgetGuard $guard, ProductRepository $repo): void
    {
        $product = $repo->findOrFail($this->productId);
        $messages = MessageBuilder::forProduct($product);

        $guard->assertFits($messages);

        // ... call OpenAI, hydrate DTO, persist
    }
}

```

Set `$timeout` explicitly — the default 60 s is often too short for large completions and too long to leave zombie workers hanging.

---

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

- Stream via `response()->stream()` and set `X-Accel-Buffering: no` for nginx.
- Pre-check prompt token counts before hitting the API; throw early.
- Use `max_tokens` as a hard ceiling on every request.
- Lock structured output with `json_schema` + `strict: true` and hydrate into readonly DTOs.
- Push long-running completions to queued jobs with explicit `$timeout` and `$tries`.

 Found this useful?

          [  ](https://twitter.com/intent/tweet?url=https%3A%2F%2Fwww.msaied.com%2Farticles%2Fstreaming-ai-responses-in-laravel-token-budgets-structured-output-and-agent-contracts&text=Streaming+AI+Responses+in+Laravel%3A+Token+Budgets%2C+Structured+Output%2C+and+Agent+Contracts) [  ](https://www.linkedin.com/sharing/share-offsite/?url=https%3A%2F%2Fwww.msaied.com%2Farticles%2Fstreaming-ai-responses-in-laravel-token-budgets-structured-output-and-agent-contracts) 

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

  3 questions  

     Q01  Does streaming work with Laravel Octane?        Yes, but you must use Swoole's chunked response or FrankenPHP's early-flush support rather than PHP's `ob_flush`. Octane workers keep the connection open, so the SSE loop works — just replace `ob_flush()/flush()` with the server-appropriate API and avoid storing streamed state on the worker. 

      Q02  What happens if the model returns malformed JSON even with strict mode?        With `strict: true` and a well-formed JSON Schema, the model is constrained by the API to match the schema. If the API itself returns an error or a refusal, the OpenAI PHP client throws an exception you can catch and retry. Always wrap the `json_decode` call with `JSON_THROW_ON_ERROR` as a final safety net. 

      Q03  How do I track token usage per user for billing?        The non-streaming response includes a `usage` object with `prompt_tokens` and `completion_tokens`. For streaming, request `stream_options: ['include_usage' =&gt; true]` — the final SSE chunk will carry the usage data. Persist it in a `ai_usage_logs` table keyed by user and model for cost attribution. 

  Continue reading

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

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

 [ ![Typed PHP 8.3 Enums as Eloquent Casts, Route Parameters, and Validation Rules](https://cdn.msaied.com/583/7301900aac0f2ec5d1347df11ce92188.png) laravel php8.3 enums 

### Typed PHP 8.3 Enums as Eloquent Casts, Route Parameters, and Validation Rules

Go beyond basic enum casting. Learn how to wire PHP 8.3 backed enums into Eloquent, route model binding, and c...

  ![Mohamed Said](https://cdn.msaied.com/01KT78WE565VEMM3PSNQAAB0MJ.jpg)  Mohamed Said 

 23 Aug 2026     1 min read  

  Read    

 ](https://www.msaied.com/articles/typed-php-83-enums-as-eloquent-casts-route-parameters-and-validation-rules) [ ![Livewire v3 Lazy Components, Islands, and Deferred Loading in Practice](https://cdn.msaied.com/582/564b2d098d2b94b53d4f5319ac77d58b.png) livewire laravel performance 

### Livewire v3 Lazy Components, Islands, and Deferred Loading in Practice

Lazy components, islands, and deferred loading in Livewire v3 let you ship fast initial pages without sacrific...

  ![Mohamed Said](https://cdn.msaied.com/01KT78WE565VEMM3PSNQAAB0MJ.jpg)  Mohamed Said 

 23 Aug 2026     1 min read  

  Read    

 ](https://www.msaied.com/articles/livewire-v3-lazy-components-islands-and-deferred-loading-in-practice-1) [ ![Building a Laravel Package: Service Providers, Auto-Discovery, and Config Merging](https://cdn.msaied.com/581/f2ebb3b6b30fffad55642b4f8e8d6ee1.png) laravel packages service-providers 

### Building a Laravel Package: Service Providers, Auto-Discovery, and Config Merging

A practical deep-dive into authoring a production-ready Laravel package — covering service provider design, au...

  ![Mohamed Said](https://cdn.msaied.com/01KT78WE565VEMM3PSNQAAB0MJ.jpg)  Mohamed Said 

 22 Aug 2026     3 min read  

  Read    

 ](https://www.msaied.com/articles/building-a-laravel-package-service-providers-auto-discovery-and-config-merging-3) 

   [  ![Mohamed Said](https://cdn.msaied.com/01KT78WE565VEMM3PSNQAAB0MH.png)   Mohamed Said Laravel Backend Engineer  ](https://www.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://www.msaied.com)
- [Projects](https://www.msaied.com/projects)
- [Articles](https://www.msaied.com/articles)
- [Certificates](https://www.msaied.com/certificates)
- [Contact](https://www.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)
