با stream: true پاسخ بهصورت Server-Sent Events و توکنبهتوکن ارسال میشود — دقیقاً با همان قالب OpenAI (chat.completion.chunk). این برای همهٔ ارائهدهندگان کار میکند: رویدادهای Anthropic، Gemini و OpenRouter در سمت نِت اَرز به قالب OpenAI ترجمه میشوند.
نمونه
stream = client.chat.completions.create(
model="gpt-4o-mini",
messages=[{"role": "user", "content": "یک شعر کوتاه دربارهٔ پاییز بگویید."}],
stream=True,
max_tokens=200,
)
for chunk in stream:
if chunk.choices and chunk.choices[0].delta.content:
print(chunk.choices[0].delta.content, end="", flush=True)
if chunk.usage: # آخرین رویداد
print("\n\ntokens:", chunk.usage.total_tokens)
const stream = await client.chat.completions.create({
model: "gpt-4o-mini",
messages: [{ role: "user", content: "یک شعر کوتاه دربارهٔ پاییز بگویید." }],
stream: true,
max_tokens: 200,
});
for await (const chunk of stream) {
process.stdout.write(chunk.choices[0]?.delta?.content ?? "");
if (chunk.usage) console.log("\n\ntokens:", chunk.usage.total_tokens); // آخرین رویداد
}
curl -N https://netarz.ir/api/ai/v1/chat/completions \
-H "Authorization: Bearer $NETARZ_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "gpt-4o-mini",
"messages": [{"role": "user", "content": "یک شعر کوتاه دربارهٔ پاییز بگویید."}],
"stream": true,
"max_tokens": 200
}'
قالب رویدادها
هر رویداد یک خط data: {...} است که با یک خط خالی جدا میشود. آخرین رویداد پیش از [DONE] شامل usage است (نیازی به فرستادن stream_options.include_usage نیست؛ خودکار اضافه میشود):
HTTP/1.1 200 OK
Content-Type: text/event-stream; charset=utf-8
Cache-Control: no-cache, no-transform
X-Request-Id: req_9f2c1e0b7a6d4c3f8e1a2b3c
data: {"id":"req_9f2c1e0b7a6d4c3f8e1a2b3c","object":"chat.completion.chunk","created":1756540800,"model":"gpt-4o-mini","choices":[{"index":0,"delta":{"role":"assistant","content":""},"finish_reason":null}]}
data: {"id":"req_9f2c1e0b7a6d4c3f8e1a2b3c","object":"chat.completion.chunk","created":1756540800,"model":"gpt-4o-mini","choices":[{"index":0,"delta":{"content":"برگها"},"finish_reason":null}]}
data: {"id":"req_9f2c1e0b7a6d4c3f8e1a2b3c","object":"chat.completion.chunk","created":1756540800,"model":"gpt-4o-mini","choices":[{"index":0,"delta":{"content":" میرقصند"},"finish_reason":null}]}
data: {"id":"req_9f2c1e0b7a6d4c3f8e1a2b3c","object":"chat.completion.chunk","created":1756540800,"model":"gpt-4o-mini","choices":[{"index":0,"delta":{},"finish_reason":"stop"}]}
data: {"id":"req_9f2c1e0b7a6d4c3f8e1a2b3c","object":"chat.completion.chunk","created":1756540800,"model":"gpt-4o-mini","choices":[],"usage":{"prompt_tokens":19,"completion_tokens":32,"total_tokens":51}}
data: [DONE]
خطا در میانهٔ استریم
اگر ارائهدهنده پیش از تولید هر توکنی خطا بدهد، پاسخ HTTP با کد خطای معمولی (مثلاً ۵۰۲) برمیگردد و هزینهای ندارد. اگر ارتباط در میانهٔ استریم قطع شود، یک رویداد خطا ارسال و استریم بسته میشود:
data: {"error":{"message":"ارتباط با ارائهدهنده قطع شد.","type":"upstream_error","code":"stream_interrupted"}}
وقتی ارتباط از سمت ارائهدهنده قطع شود (کد stream_interrupted)، هزینهای کسر نمیشود، حتی برای بخشی از پاسخ که به دستتان رسیده است. اگر خود ارائهدهنده پس از تولید بخشی از پاسخ رویداد خطا بفرستد (با type برابر upstream_error و کدی جز stream_interrupted)، فقط همان بخش تولیدشده محاسبه میشود. مبلغ نهایی هر درخواست را از GET /requests/{id} بخوانید؛ شناسه در هدر X-Request-Id پیش از اولین رویداد میرسد. SDK رسمی OpenAI این رویداد را به استثنا تبدیل میکند؛ اگر SSE را خودتان میخوانید، وجود کلید error را بررسی کنید.
قطع استریم توسط شما
اگر اتصال را ببندید (مثلاً کاربر دکمهٔ توقف را زد)، تولید متوقف میشود و هزینهٔ توکنهای تولیدشده تا آن لحظه (طبق گزارش ارائهدهنده یا برآورد از طول خروجی) کسر میشود. این درخواستها در گزارش با client_aborted مشخص میشوند.
خواندن SSE بدون SDK
// مرورگر/Edge runtime: خواندن SSE بدون SDK — فقط از سرور خودتان (کلید را در مرورگر قرار ندهید)
const res = await fetch("https://netarz.ir/api/ai/v1/chat/completions", {
method: "POST",
headers: { Authorization: `Bearer ${process.env.NETARZ_API_KEY}`, "Content-Type": "application/json" },
body: JSON.stringify({ model: "gpt-4o-mini", stream: true, messages: [{ role: "user", content: "سلام" }] }),
});
const reader = res.body.getReader();
const decoder = new TextDecoder();
let buffer = "";
while (true) {
const { value, done } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
const lines = buffer.split("\n\n");
buffer = lines.pop();
for (const line of lines) {
const data = line.replace(/^data: /, "").trim();
if (!data || data === "[DONE]") continue;
const json = JSON.parse(data);
if (json.error) throw new Error(json.error.message);
process.stdout.write(json.choices?.[0]?.delta?.content ?? "");
}
}
نکات فنی
- پاسخ با هدرهای
Content-Type: text/event-stream،Cache-Control: no-cache, no-transformوX-Accel-Buffering: noمیآید؛ اگر پشت پراکسی خودتان هستید، بافر کردن پاسخ را غیرفعال کنید. - Responses API و Completions هم استریم را پشتیبانی میکنند (رویدادهای Responses بههمان شکل OpenAI عبور میکنند).
- در استریم، هر درخواست تا پایان پاسخ یکی از اسلاتهای همزمانی حساب شما را اشغال میکند (محدودیتها).
- اگر پشتیبانی موقتاً استریم را غیرفعال کند، خطای ۴۰۰ با کد
streaming_disabledمیگیرید؛ همان درخواست را باstream: falseبفرستید. - برای اندازهگیری، زمان تا اولین توکن (TTFB) و کل تأخیر هر درخواست در پنل ثبت میشود.