راهنمای استریم SSE در API پیکو هوش؛ از curl تا مرورگر
استریم پاسخ مدل با stream true و قالب SSE را در curl، Python و JavaScript پیاده کنید؛ پارس رویدادها، usage آخر استریم، قطع اتصال و رفع بافر پروکسی.
پیکو هوش·انتشار: ·بهروزرسانی: ·۵ دقیقه مطالعه
خلاصه: با ارسال "stream": true در درخواست /v1/chat/completions، پیکو هوش پاسخ را بهصورت Server-Sent Events (SSE) تکهتکه میفرستد: هر خط data: {...} یک تکه از پاسخ است و پایان با data: [DONE] اعلام میشود. استریم تجربهی کاربری را بسیار بهتر میکند چون کاربر لازم نیست منتظر پایان کل پاسخ بماند و هزینهی اضافهای هم ندارد.
متن در choices[0].delta.content است؛ اولین رویداد معمولاً فقط نقش را دارد.
finish_reason در آخرین تکهی محتوا میآید (stop، length یا tool_calls).
پیکو هوش برای محاسبهی دقیق هزینه، گزارش usage را در رویداد نزدیک پایان درخواست میکند (معادل stream_options.include_usage)؛ ممکن است رویدادی با خالی ببینید. کلاینت شما باید این حالت را تحمل کند.
Claude Code را با متغیرهای ANTHROPIC_BASE_URL و کلید پیکو هوش روی اندپوینت سازگار با Anthropic (/v1/messages) اجرا کنید؛ مرحلهبهمرحله و با عیبیابی.
·
خبرنامه
آموزشها و تغییرات جدید را بهموقع بگیرید
فقط مطالب مفید دربارهی API هوش مصنوعی؛ بدون هرزنامه.
choices
در API استاندارد OpenAI چنین رویداد usage فقط وقتی میآید که "stream_options": {"include_usage": true} را بفرستید؛ برای کدی که روی هر ارائهدهنده کار کند، این پارامتر را صریحاً بدهید (رفتار پیشفرض پیکو هوش را از مستندات بسنجید).
جزئیات احتمالی (مثل شکل دقیق رویداد usage) ممکن است بسته به مسیر پردازش متفاوت باشد؛ پارسر شما باید به فیلدهای ناشناخته بیاعتنا باشد.
پایتون: پارس دستی با httpx
import json, os, httpx
url = "https://api.picoai.ir/v1/chat/completions"
headers = {"Authorization": f"Bearer {os.environ['PICO_API_KEY']}"}
body = {"model": "gpt-6-luna", "stream": True,
"messages": [{"role": "user", "content": "یک جمله دربارهی SSE بگو"}]}
with httpx.stream("POST", url, headers=headers, json=body, timeout=None) as r:
r.raise_for_status()
for line in r.iter_lines():
if not line.startswith("data:"):
continue
payload = line[5:].strip()
if payload == "[DONE]":
break
evt = json.loads(payload)
for ch in evt.get("choices", []):
piece = ch.get("delta", {}).get("content")
if piece:
print(piece, end="", flush=True)
if evt.get("usage"):
usage = evt["usage"]
print()
اگر raise_for_status خطا داد، یعنی خطا قبل از شروع استریم رخ داده (مثل 401 یا 402) و بدنهی JSON معمولی دارد. در عمل SDK رسمی کار را سادهتر میکند؛ راهنمای Python.
مرورگر: fetch و ReadableStream
EventSource فقط GET میپذیرد، پس برای POST از fetch استفاده کنید. هرگز کلید را در مرورگر نگذارید؛ این کد باید به سرور خودتان وصل شود که استریم پیکو هوش را عبور میدهد.
const res = await fetch('/api/chat', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ messages: [{ role: 'user', content: 'سلام' }] }),
});
const reader = res.body.getReader();
const decoder = new TextDecoder();
let buf = '';
while (true) {
const { value, done } = await reader.read();
if (done) break;
buf += decoder.decode(value, { stream: true });
const lines = buf.split('\n');
buf = lines.pop(); // خط ناقص را نگه دار
for (const line of lines) {
if (!line.startsWith('data:')) continue;
const p = line.slice(5).trim();
if (p === '[DONE]') continue;
const evt = JSON.parse(p);
const piece = evt.choices?.[0]?.delta?.content;
if (piece) output.textContent += piece;
}
}
نکتهی کلیدی buf.pop() است: یک رویداد ممکن است بین دو chunk شبکه شکسته شود و پارس مستقیم هر chunk خطا میدهد. TextDecoder با stream: true از شکستن نویسههای چندبایتی فارسی جلوگیری میکند.
سرور میانی
سرور شما باید هدرهای زیر را بفرستد تا پروکسیها بافر نکنند:
Content-Type: text/event-stream; charset=utf-8
Cache-Control: no-cache
X-Accel-Buffering: no
در Cloudflare و برخی CDNها فشردهسازی یا بافر ممکن است استریم را مختل کند؛ تنظیمات مربوط به آن مسیر را غیرفعال کنید. نمونهی کامل Express در راهنمای Node.
قطع اتصال و هزینه
اگر کلاینت وسط پاسخ اتصال را ببندد، سرور شما باید درخواست بالادستی را هم قطع کند (AbortController). هزینه بر اساس توکنهای واقعاً تولیدشده/گزارششده است. اگر مدل پیش از قطع، گزارش usage ندهد، تخمین مصرف انجام میشود (تقریبی). خطاهایی که پیش از شروع پاسخ رخ دهند بدون هزینهاند. فرمول: تخمین هزینه و توکن.
خطا در میانهی استریم
پس از ارسال 200 و هدرها، دیگر نمیتوان کد وضعیت را تغییر داد؛ بنابراین خطای میانراه بهصورت قطع شدن اتصال یا رویداد خطا دیده میشود. کلاینت باید: متن دریافتشده را نگه دارد، به کاربر پیام مناسب نشان دهد و در صورت لزوم درخواست را دوباره بفرستد. راهبرد کامل: کدهای خطا و Retry.
استریم Anthropic
مسیر /v1/messages با stream: true رویدادهای نامدار Anthropic را میفرستد (message_start، content_block_delta، message_stop و ...) که ساختارشان با OpenAI فرق دارد. SDK رسمی Anthropic آنها را مدیریت میکند؛ آموزش Claude Code.
عیبیابی
علامت
علت
راهحل
همهچیز یکجا میرسد
بافر پروکسی/CDN
proxy_buffering off، هدر X-Accel-Buffering
JSON.parse خطا میدهد
رویداد شکسته
بافر خطبهخط
فارسی خراب نمایش داده میشود
decode بدون stream: true
TextDecoder درست
choices خالی و کرش
رویداد usage
بررسی طول آرایه
قطع بعد از ~۶۰ ثانیه
timeout پروکسی
proxy_read_timeout
401/402 در شروع
خطای پیش از استریم
بدنهی JSON را بخوانید
سوالات متداول
استریم هزینهی بیشتری دارد؟
خیر؛ هزینه فقط به توکنها بستگی دارد.
آیا میتوانم تعداد توکن را در حین استریم ببینم؟
تعداد دقیق در رویداد usage نزدیک پایان میآید؛ در حین استریم میتوانید تخمین بزنید.
آیا همهی مدلها استریم دارند؟
مدلهای چت کاتالوگ استریم را پشتیبانی میکنند؛ مشخصات هر مدل در کاتالوگ.