cd /news/large-language-models/03-streaming-llm-tokens-in-php-with-… · home › topics › large-language-models › article
[ARTICLE · art-140585] src=dev.to ↗ pub= topic=large-language-models verified=true sentiment=↑ positive

03 - Streaming LLM Tokens in PHP with Server-Sent Events

A developer demonstrated how to stream LLM tokens in PHP using Server-Sent Events, wrapping NanoAgent's Agent::stream() callback to emit each generated token as an SSE data line. The approach requires flushing output after every token and setting the X-Accel-Buffering: no header to prevent Nginx buffering, with a [DONE] sentinel closing the client's EventSource connection. The writeup argues SSE is simpler than WebSockets for one-way token flow since it uses plain HTTP, auto-reconnects, and passes through proxies that block WebSockets.

by read2 min views1 publishedSep 27, 2026

The worst part of a chat UI is the blank screen while the model thinks. Fix: stream the tokens as they're generated, not the whole reply at the end. In PHP that's Server-Sent Events (SSE) - one persistent response that keeps pushing data.

NanoAgent's Agent::stream() hands you each token through a callback. Your job is just to format it as SSE and flush.

Everything below only runs when the request is asking for a stream (?stream=1&message=...). The four headers are what turn a normal PHP response into a Server-Sent Events stream the browser will keep open and read incrementally.

require_once __DIR__ . '/../NanoAgent/auto.php';
use NanoAgent\Agent;

if (isset($_GET['stream']) && !empty($_GET['message'])) {
    header('Content-Type: text/event-stream');
    header('Cache-Control: no-cache');
    header('Connection: keep-alive');
    header('X-Accel-Buffering: no');   // critical under Nginx

    $userMessage = trim($_GET['message']);

    $configFile = __DIR__ . '/../NanoAgent/config.php';
    $config = file_exists($configFile) ? require $configFile : [];

    $agent = new Agent(
        llm: [
            'provider' => $config['provider'] ?? 'groq',
            'model'    => $config['model']    ?? 'llama-3.3-70b-versatile',
            'api_key'  => $config['api_key']  ?? ''
        ],
        systemPrompt: "You are a helpful and concise streaming assistant."
    );

stream() calls your closure once for every token the model generates. Each call formats the token as an SSE data: line and forces PHP to send it immediately instead of buffering it.

    $agent->stream($userMessage, function ($token) {
        echo "data: " . json_encode(['token' => $token]) . "\n\n";
        ob_flush();
        flush();
    });

Once the model is done, send a sentinel value the client can watch for, then close out the request.

    echo "data: [DONE]\n\n";
    ob_flush();
    flush();
    exit;
}

Two things are non-negotiable here:

ob_flush() + flush() after every token.X-Accel-Buffering: no. A plain EventSource - no library, no WebSocket:

const es = new EventSource(`/streaming.php?stream=1&message=${encodeURIComponent(msg)}`);

es.onmessage = (e) => {
  if (e.data === '[DONE]') { es.close(); return; }
  const { token } = JSON.parse(e.data);
  answerEl.textContent += token;
};

For one-way token flow (model → browser), SSE is simpler: it's plain HTTP, auto-reconnects, and works through proxies that choke on WebSockets. You only need a real bidirectional socket if the client has to interrupt mid-stream - which is a separate feature.

stream() turns "wait 8 seconds for a wall of text" into "watch it type." The entire change from a blocking chat() is one callback + two flush() calls + the SSE headers. Everything else in your app stays the same.

Part of the NanoAgent examples series. Landing + demos.

── more in #large-language-models 4 stories · sorted by recency
── more on @nanoagent 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain — perfect for shipping the agent you just read about.

$git push zahid main
→ Live at https://your-agent.zahid.host ✓
Get free account → Pricing
from €0/mo · no card required
LIVE [news/03-streaming-llm-tok…] indexed:0 read:2min 2026-09-27 · —