چیزی که هنگام کار با ChatGPT حس می‌کنید — متن جلوی چشمتان جاری می‌شود — یک افکت نمایشی نیست؛ نمایان‌شدن واقعیت فنی است: مدل زبانی پاسخ را مثل یک فایل آماده نمی‌خواند، می‌سازد. تولید توکن‌به‌توکن است و Streaming همان تصمیم معماری است که می‌گوید: «هر توکن که ساخته شد، همان لحظه بفرستش.» برای کسی که محصولی روی مدل‌ها می‌سازد، فهم استریم از فهم خود مدل هم مهم‌تر است؛ چون تفاوت محصولی که «کند» حس می‌شود و محصولی که روان است، معمولاً همین‌جاست.

پاسخ چطور ساخته می‌شود؟

مدل زبانی قدم‌به‌قدم توکن بعدی را پیش‌بینی می‌کند (سازوکارش را در مطلب LLM چیست و مطلب توکن دیده‌ایم) و یک پاسخ متوسط، ده‌ها یا صدها توکن می‌خواهد. یعنی جواب کامل ممکن است ده‌ها ثانیه طول بکشد. حالا دو انتخاب دارید: یا صبر کنید تا آخرین توکن ساخته شود و همه‌ی پاسخ را یک‌جا بفرستید — حالت non-streaming — یا از همان توکن اول، هر قطعه را همان لحظه به کاربر برسانید — حالت streaming.

جالب این‌جاست که در هر دو حالت، پاسخ «همین قدر» طول می‌کشد تا کامل شود؛ تفاوت در چیزی است که کاربر تجربه می‌کند: در حالت اول، ده‌ها ثانیه خیره‌شدن به صفحه‌ی خالی؛ در حالت دوم، دیدن حرکت از همان دو ثانیه‌ی اول.

استریم سرعت کل پاسخ را زیاد نمی‌کند؛ سرعتِ «حس‌شدن» پاسخ را.

Streaming چگونه کار می‌کند؟

حمل‌کننده‌ی استاندارد این جریان، Server-Sent Events یا SSE است: کلاینت یک درخواست HTTP معمولی می‌زند و سرور اتصال را باز نگه می‌دارد و به‌جای یک پاسخ بزرگ، رشته‌ای از رویدادهای کوچک می‌فرستد. هر رویداد یک قطعه از پاسخ است — معمولاً چند توکن — که در فرمت سازگار با OpenAI چنین شکلی دارد:

data: {"choices":[{"delta":{"content":"سلام"}}]}

data: {"choices":[{"delta":{"content":"! چطور"}}]}

data: {"choices":[{"delta":{"content":" می‌توانم"}}]}

data: [DONE]

سه نکته در همین چند خط است: هر رویداد فقط یک «افزایش» (delta) را حمل می‌کند و کلاینت مسئول چسباندن قطعه‌ها به هم است؛ در پایان رویداد [DONE] می‌آید که یعنی جریان تمام شد؛ و چون اتصال شبکه است، ممکن است وسط راه بریزد — برای همین کلاینت‌های جدی، قطع‌شدن را مدیریت می‌کنند. نسخه‌ی غیراستریم همان پاسخ است، فقط همه‌ی این قطعه‌ها یک‌جا و در یک JSON کامل.

چه چیزی عوض می‌شود و چه چیزی نه؟

انتظار طبیعی این است که استریم «سریع‌تر» باشد، اما جدول واقعی این است:

  • هزینه: عوض نمی‌شود. همان توکن‌ها تولید می‌شوند و صورت‌حساب فرقی نمی‌کند (منطق قیمت‌گذاری در هزینه‌ی API).
  • زمان تکمیل کل پاسخ: تقریباً عوض نمی‌شود. مدل همان سرعت تولید را دارد؛ استریم یخ‌زدنِ داده‌ها را باز نمی‌کند.
  • زمان تا اولین توکن (TTFT): این‌جا برون‌رفت اصلی است. از «صبر تا پایان» به «چند صد میلی‌ثانیه تا اولین واژه» می‌رسد و همین عدد است که کیفیت ادراکی محصول را می‌سازد.
  • پیچیدگی پیاده‌سازی: بیشتر می‌شود. اتصال باز، مدیریت قطع‌شدن، رندر تدریجی و حالت‌های خطای وسط جریان.

برای همین در نمونه‌های واقعی، گاهی استریمِ فعال، هزینه‌ی پیاده‌سازی دارد که ارزشش را ندارد؛ گاهی برعکس، تنها راه نجات تجربه‌ی کاربر است. تصمیم را باید با شناخت سناریو گرفت، نه با پیش‌فرض.

کِی استریم نکنیم؟

دو خانواده‌ی سناریو هست که استریم به‌کارشان نمی‌آید یا حتی مضر است. اول، جایی که خروجی باید قبل از دیدن کاربر پردازش شود: پاسخ JSON که می‌خواهید قبل از نمایش اعتبارسنجی و parse کنید، خروجی‌ای که می‌خواهید در دیتابیس یا فایل ذخیره شود، کارهای زمان‌بندی‌شده و هر جایی که «نمایش زنده» وجود ندارد — این‌ها با پاسخ کامل راحت‌تر و مطمئن‌ترند. دوم، جایی که رابط کاربری اجازه‌ی نمایش تدریجی نمی‌دهد: مثلاً جدول یا نموداری که تا کامل‌شدن داده معنا ندارد؛ در این‌جا استریم فقط پیچیدگی اضافه می‌کند.

یک حالت میانی هم دارید: استریم بگیرید اما نمایش ندهید — فقط برای اینکه وسطِ تولید، اگر جواب قطعاً نامناسب شد یا کاربر انصراف داد، اتصال را قطع کنید. قطع استریمِ در جریان، تولید بقیه‌ی توکن‌ها را متوقف می‌کند و یعنی صرفه‌جویی واقعی در هزینه؛ چیزی که با پاسخ کامل ممکن نیست چون مدل تا آخر می‌سازد و شما تا آخر پرداخت می‌کنید.

نکته‌های پیاده‌سازی

اگر به سمت استریم می‌روید، چند تجربه‌ی پرتکرار را از قبل بدانید: رندر تدریجی متن فارسی در وسط کلمه‌ها یا وسط ساختار Markdown می‌تواند لحظه‌های به‌هم‌ریخته بسازد — یک رندر با تحمل خطا لازم دارید (دام‌های زبان فارسی در مطلب پرامپت فارسی آمده است)؛ اسکرول خودکار را طوری ببندید که اگر کاربر خودش بالا رفته، صفحه را پایین نکشید؛ و خطای وسط جریان را با پیام واضح و امکان «ادامه بده» مدیریت کنید، نه با یک جواب نصفه بی‌سرانجام.

از سمت سرویس، کار ساده است: در API ماین‌دبیس که سازگار با فرمت OpenAI است، کافی است در بدنه‌ی درخواست "stream": true بگذارید و پاسخ را به‌شکل رویدادخوان بخوانید؛ همان کلاینتهایی که برای پاسخ کامل نوشته‌اید، با یک فلگ به دنیای استریم می‌رسند. برای پروژه‌های عامل‌محور هم استریم تقریباً اجباری است — در عامل‌های کدنویس دیدید که جواب‌های بلندِ چنددقیقه‌ای بدون جریان زنده، اصلاً قابل استفاده نیستند.

جمع‌بندی

Streaming یعنی ارسال توکن‌ها همان‌طور که ساخته می‌شوند: هزینه و زمان کل پاسخ ثابت می‌ماند، اما زمان تا اولین واژه از ده‌ها ثانیه به زیر یک ثانیه می‌رسد و این عدد، ادراک کاربر از سرعت محصول شماست. قاعده‌ی انتخاب: رابط چت و کاربر منتظر = استریم؛ پردازش، ذخیره و JSON = پاسخ کامل؛ و وسط راه، امکان قطع کردن را هم بگذارید که هم تجربه بهتر می‌شود هم هزینه. مسیر بعدی: هزینه‌ی API هوش مصنوعی — چون استریم همان‌طور که نمایش هزینه را روان می‌کند، کنترل هزینه را هم دست شما می‌آورد.