# Streaming

> Tokens gateway দিয়ে chat completions আর messages-এ Server-Sent Events streaming কীভাবে কাজ করে: event format, usage chunk, timeout আর disconnect সামলানো।

chat completions, messages, responses বা legacy completions-এ `"stream": true` দিলে gateway upstream provider-এর Server-Sent Events (SSE) আসার সাথে সাথেই আপনার কাছে পাঠিয়ে দেয়। gateway content buffer করে না, বদলায়ও না। billing-এর জন্য শুধু stream থেকে token count পড়ে নেয়, বাকি bytes যেমন আছে তেমনই পাঠায়।

## Chat completions-এর SSE format

প্রতিটা event হলো একটা `data:` লাইন, যার ভেতরে একটা JSON chunk থাকে। দুটো event-এর মাঝে একটা ফাঁকা লাইন থাকে। stream শেষ হয় `data: [DONE]` দিয়ে।

```text
data: {"id":"chatcmpl-a1b2","object":"chat.completion.chunk","created":1790000000,"model":"deepseek/deepseek-v4.1-flash","choices":[{"index":0,"delta":{"role":"assistant","content":""},"finish_reason":null}]}

data: {"id":"chatcmpl-a1b2","object":"chat.completion.chunk","created":1790000000,"model":"deepseek/deepseek-v4.1-flash","choices":[{"index":0,"delta":{"content":"Retry"},"finish_reason":null}]}

data: {"id":"chatcmpl-a1b2","object":"chat.completion.chunk","created":1790000000,"model":"deepseek/deepseek-v4.1-flash","choices":[{"index":0,"delta":{},"finish_reason":"stop"}]}

data: {"id":"chatcmpl-a1b2","object":"chat.completion.chunk","created":1790000000,"model":"deepseek/deepseek-v4.1-flash","choices":[],"usage":{"prompt_tokens":18,"completion_tokens":42,"total_tokens":60}}

data: [DONE]
```

Reasoning model-এ `delta.reasoning_content`-ও আসতে পারে। tool call আসে `delta.tool_calls`-এর ছোট ছোট অংশ হিসেবে ([tool calling](/docs/tool-calling) দেখুন)।

## include_usage দিয়ে streaming-এর সময় token usage পাওয়া

`"choices": []` আর `usage` object-ওয়ালা chunk-টা আপনি না চাইলে আসে না। চাইতে হয় এভাবে:

```json
{
  "stream": true,
  "stream_options": { "include_usage": true }
}
```

এটা না দিলে usage chunk আসবে না, OpenAI-ও ঠিক এভাবেই কাজ করে। তবে billing এই flag-এর ওপর নির্ভর করে না, gateway সব সময়ই stream মেপে রাখে। flag শুধু ঠিক করে, সংখ্যাগুলো আপনি দেখতে পাবেন কি না।

এটা দিলে আপনার loop-এ ফাঁকা `choices` array-র জন্য check রাখুন। যে code কোনো শর্ত ছাড়াই `chunk.choices[0]` পড়ে, সেটা শেষ chunk-এ crash করবে।

## Anthropic messages-এর SSE format

`/v1/messages` নামওয়ালা event stream করে: `message_start`, `content_block_start`, `content_block_delta`, `content_block_stop`, `message_delta` আর `message_stop`। input token-এর usage থাকে `message_start`-এ, output-এর usage `message_delta`-তে, তাই আলাদা কোনো flag দিতে হয় না। পুরো sequence [messages](/docs/messages) পেজে আছে। Responses API-র নিজস্ব event-এর নাম আছে, সেগুলো [responses](/docs/responses) পেজে পাবেন।

## Python আর Node.js-এ stream করা

:::code-tabs

```python title="Python"
import os
from openai import OpenAI

client = OpenAI(
    base_url="https://tokens.bd/v1",
    api_key=os.environ["TOKENS_API_KEY"],
    timeout=600,  # seconds; long reasoning answers can take minutes
)

stream = client.chat.completions.create(
    model="deepseek/deepseek-v4.1-flash",
    messages=[{"role": "user", "content": "Explain exponential backoff with jitter."}],
    stream=True,
    stream_options={"include_usage": True},
)

finished = False
for chunk in stream:
    if chunk.choices:
        choice = chunk.choices[0]
        if choice.delta.content:
            print(choice.delta.content, end="", flush=True)
        if choice.finish_reason:
            finished = True
    if chunk.usage:
        print("\n", chunk.usage)

if not finished:
    print("\n[stream ended without a finish_reason: treat the answer as incomplete]")
```

```typescript title="Node.js"
import OpenAI from "openai";

const client = new OpenAI({
  baseURL: "https://tokens.bd/v1",
  apiKey: process.env.TOKENS_API_KEY,
  timeout: 600_000, // ms
});

const controller = new AbortController();
// Cancel after 2 minutes, or wire this to a user's "stop" button.
const timer = setTimeout(() => controller.abort(), 120_000);

const stream = await client.chat.completions.create(
  {
    model: "deepseek/deepseek-v4.1-flash",
    messages: [{ role: "user", content: "Explain exponential backoff with jitter." }],
    stream: true,
    stream_options: { include_usage: true },
  },
  { signal: controller.signal }
);

try {
  for await (const chunk of stream) {
    const delta = chunk.choices[0]?.delta?.content;
    if (delta) process.stdout.write(delta);
    if (chunk.usage) console.log("\n", chunk.usage);
  }
} finally {
  clearTimeout(timer);
}
```

:::

সরাসরি HTTP-তে দেখতে চাইলে `curl -N` দিন। এতে output buffer হয় না, তাই event আসার সাথে সাথে চোখের সামনে দেখতে পাবেন:

```bash
curl -N https://tokens.bd/v1/chat/completions \
  -H "Authorization: Bearer $TOKENS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"model":"deepseek/deepseek-v4.1-flash","stream":true,"messages":[{"role":"user","content":"Count to five."}]}'
```

## Stream শুরুর আগে ও চলার সময়ের error

প্রথম byte আসার আগে যে error হয়, যেমন ভুল key, ব্যালান্স নেই, rate limit, বা upstream request ফিরিয়ে দিয়েছে, সেগুলো SSE event হিসেবে আসে না। আসে সাধারণ JSON error হিসেবে, সঠিক HTTP status সহ। তাই event parse করা শুরুর আগে status code দেখে নিন। সব code-এর তালিকা [errors](/docs/errors) পেজে আছে।

stream একবার শুরু হয়ে গেলে status আগেই 200 হয়ে গেছে। মাঝপথে upstream fail করলে `[DONE]` ছাড়াই connection বন্ধ হয়ে যায় (messages-এ `message_stop` ছাড়া)। যে stream `finish_reason` বা stop event ছাড়া শেষ হয়েছে, সেটাকে অসম্পূর্ণ ধরে নিন।

`x-tokens-request-id` response header আসে header-এর সাথেই, কোনো content আসার আগে। প্রতিটা request-এর শুরুতেই এটা log করে রাখুন। stream পরে মাঝপথে মরে গেলেও id আপনার হাতে থাকবে।

## লম্বা request-এর timeout

লম্বা request নিয়ে চিন্তা নেই। upstream সাড়া দেওয়া শুরু করার জন্য gateway 600 সেকেন্ড পর্যন্ত অপেক্ষা করে। যেসব reasoning model প্রথম token দেওয়ার আগে কয়েক মিনিট ভাবে, তাদের জন্য এটা যথেষ্ট। stream চালু হওয়ার পর দুটো chunk-এর মাঝে অপেক্ষা করে 300 সেকেন্ড পর্যন্ত। upstream সময়মতো শুরু না করলে আপনি পান 504 `upstream_timeout`। কোনো model-এর পেছনে backup provider থাকলে প্রথম token ছাড়া প্রায় 30 সেকেন্ড পার হলে gateway backup-এ চলে যায়। তাই পুরো অপেক্ষা তখনই লাগে, যখন শেষ provider-টাই ধীর। এই বদলটা আপনি টেরও পান না।

আপনার client-এর timeout-ও অন্তত এতটাই বড় রাখতে হবে। উপরের উদাহরণের মতো নিজে ঠিক করে দিন। library-র default ধরে বসে থাকবেন না, আর আপনার app-এর সামনে এমন কোনো proxy থাকলে সতর্ক থাকুন যেটা 30 বা 60 সেকেন্ড idle থাকলেই connection কেটে দেয়।

## Client disconnect সামলানো

stream-এর মাঝখানে আপনার client disconnect বা abort করলে gateway upstream request বাতিল করে দেয়। ততক্ষণ পর্যন্ত যা তৈরি হয়েছে তার জন্য বিল হয়: input আর এর মধ্যে stream হয়ে যাওয়া output। এর পরে আর কিছু কাটা হয় না।

ভাঙা stream retry করার আগে মনে রাখবেন, retry-তে পুরো prompt আবার পাঠানো হয় আর আবার বিল হয়। agent-এর লম্বা prompt হলে খরচটা জমে যায়। কোনো content আসার আগেই retryable error-এ fail হওয়া stream retry করুন। মাঝপথে ভাঙা stream-এর ক্ষেত্রে আগে ভেবে দেখুন আংশিক output কাজে লাগবে কি না। error code অনুযায়ী retry-র নিয়ম [errors](/docs/errors) পেজে আছে।

প্রতিটা খোলা stream শেষ না হওয়া পর্যন্ত আপনার অ্যাকাউন্টের concurrency limit-এর একটা slot দখল করে রাখে। যে stream ফেলে রেখেছেন কিন্তু বন্ধ করেননি, সেটাও slot ধরে রাখে। তাই নিজে close বা abort করুন। বিস্তারিত [rate limits](/docs/rate-limits) পেজে।

---
Page: https://tokens.bd/bn/docs/streaming
