هیچ توکنی رو هدر نده
خلاصهٔ کاملتر
به گفتهٔ نویسنده (سونیل پای)، چند هفته درگیرِ این سؤال بوده که وقتی پروسهٔ اجراکنندهٔ یه ایجنت وسطِ یه نوبت (turn) میمیره چی میشه. نکتهٔ کلیدی اینه که تو از همون لحظهای که مدل توکنِ خروجی تولید میکنه، بابتش پول میدی. حالا اگه پروسهت با یه دیپلوی، eviction یا OOM جایگزین شه، تاریخچهٔ گفتگو سالم میمونه ولی خودِ درخواستِ HTTPِ در حالِ جریان که تو حافظهٔ همون پروسه بوده از بین میره.
میگه تو یه ایجنتِ واقعی که تو یه نوبت چند بار ابزار صدا میزنه، هر قطعی همهٔ توکنهای تولیدشدهٔ اون نوبتو دور میریزه. این با گرونبودنِ مدل هم بدتر میشه؛ مثلاً خروجیِ gpt-5.5 حدودِ ۳۰ دلار بهازای هر میلیون توکنه در برابرِ ۲ دلارِ نسخهٔ mini، یعنی هر retry روی مدلِ پرچمدار حدودِ ۱۵ برابر بیشتر میسوزونه. مشکل تو مسیرِ خوشبینانه دیده نمیشه، فقط بعدِ یه حادثه که توکنها رو میشمری جور درنمیاد.
راهحل اینه که اتصالِ ارائهدهنده رو از پروسه جدا کنی: یه بافر بینِ ایجنت و مدل بذاری که خودش یه دیپلویِ مستقله (Worker و Durable Objectِ مجزا). این بافر تا یه درخواست میاد، وضعیتشو ریست میکنه، تو پسزمینه اتصالِ ارائهدهنده رو میریزه تو SQLite و همزمان به فراخواننده یه استریم میده که همون ردیفها رو دنبال میکنه. چون drainکردن دیگه داخلِ ایجنت اجرا نمیشه، وقتی ایجنت evict میشه توکنهایی که پولشونو دادی همچنان میرسن تو دیتابیس.
به گفتهٔ نویسنده، همین لاگِ بادوام دو کارو با یه مکانیزم انجام میده: هم استریمِ بازیابیپذیر (وقتی مرورگر قطع و وصل میشه) و هم بازیابیِ کرش. هر chunk با یه ایندکس تو SQLite ذخیره میشه:
CREATE TABLE buffer_chunks (
chunk_index INTEGER PRIMARY KEY,
data TEXT NOT NULL
)تنها فرقِ این دو حالت اینه که آیا یه تولیدکنندهٔ زنده هنوز وصله یا نه.
یه نکتهٔ مهمِ دیگه: بهجای اینکه خودت SSEِ بافرشده رو پارس کنی (که یعنی نگهداریِ یه پارسرِ جدا برای OpenAI و Anthropic و Google تا ابد)، بایتِ خام رو ذخیره کن و موقعِ برگشت از پارسرِ خودِ ارائهدهنده استفاده کن. تو مدلِ workers-ai-provider هر پلاگین فرمتِ سیمیِ خودشو داره و resume بهصورت پیشفرض روشنه.
برای بازیابی، حینِ اجرا شناسه و آفستِ ران رو ذخیره میکنی و موقعِ برگشت بهجای صداکردنِ دوبارهٔ مدل، به همون ران دوباره وصل میشی:
const stream = createResumableStream({
binding: env.AI,
gateway: "my-gateway",
runId, // saved from cf-aig-run-id
fromEvent: savedOffset, // saved from onProgress
onResumeExpired: "accept-partial"
});نه پرداختِ دوباره، نه فراخوانیِ تکراری، نه پارسری که باید نگهداری بشه.
نویسنده بازارو هم مقایسه میکنه: OpenAI با background mode روی Responses API عملاً همین ایده رو ساخته و با یه sequence_number ادامه میدی، ولی قفلِ API خودشه؛ Anthropic و Gemini سرورساید resume ندارن و مجبورت میکنن با یه درخواستِ «ادامه بده» دوباره پول بدی؛ و resumable-streamِ وِرسل نزدیکترین مورده ولی چون داخلِ پروسهٔ خودته، دیپلویِ خودتو دوام نمیاره. جمعبندیش اینه که جای درستِ این بافر همون گیتویه (اینجا Cloudflare AI Gateway که resumeِ بادوام داره بهش اضافه میشه).
نکات کلیدی:
- از لحظهٔ تولیدِ توکنِ خروجی پول میدی؛ کرش و retry یعنی پرداختِ دوباره که تو حلقهٔ ایجنتی چند برابر میشه
- اتصالِ ارائهدهنده رو به پروسه گره نزن؛ یه بافرِ جدا که هیچوقت ریدیپلوی نمیشه استریمو زنده نگه میداره
- استریمِ بازیابیپذیر و بازیابیِ کرش یه مکانیزمان: همون لاگِ chunkِ بادوام
- بایتِ خام رو ذخیره کن و با پارسرِ خودِ ارائهدهنده replay کن؛ جای درستش گیتویه




