پاسخ دستیار قطع شد یا واقعاً تمام شد؟

بسته‌شدن اتصال، کامل‌شدن پاسخ دستیار را ثابت نمی‌کند. مرز بایت، رویداد، دلیل توقف مدل و پذیرش برنامه را جدا کنید تا متن نیمه‌رسیده به پاسخ نهایی تبدیل نشود.

پاسخ دستیار قطع شد یا واقعاً تمام شد؟
نویسنده
تیم ژرف
انتشار
۱۶ مهر ۱۴۰۵
زمان مطالعه
۱۱ دقیقه

دستیار می‌نویسد: «سفارش را تأیید کنید، فقط به شرطی که…» و همان‌جا ساکت می‌شود. نشانگر انتظار ناپدید شده، درخواست کد ۲۰۰ گرفته و تاریخچه گفتگو متن را مثل یک پاسخ عادی نگه داشته است. کاربر نمی‌داند شرطی جا افتاده یا مدل حرفش را تمام کرده است.

این صحنه، مثال طراحی است؛ نه گزارش حادثه‌ای در یک محصول. مسئله‌اش برای هر تیمی که پاسخ مدل را تدریجی نمایش می‌دهد واقعی است: کِی متن را نهایی ثبت کنیم، کِی آن را پیش‌نویس ناقص نگه داریم و کِی تلاش تازه‌ای آغاز کنیم؟ طول متن و روانی جمله، پاسخ این سؤال نیست.

چهار پایان را با هم اشتباه نگیرید

رسیدن بایت‌ها، کامل‌شدن یک رویداد، پایان نوبت مدل و پذیرفته‌شدن جواب در برنامه، چهار اتفاق جدا هستند. ممکن است اتصال سالم بسته شود، ولی رویداد پایانی مدل نرسیده باشد. ممکن است رویداد پایانی برسد، اما مدل به سقف خروجی خورده باشد. حتی پایان طبیعی مدل هم درستی مبلغ یا مجازبودن عملیات را تضمین نمی‌کند.

تیم نباید همه این وضعیت‌ها را در یک متغیر «در حال بارگذاری» جا بدهد. خاموش‌شدن آن متغیر فقط می‌گوید انتظار فعلی تمام شده است؛ چیزی درباره کیفیت نتیجه نمی‌گوید. یک فیلد مستقل برای نتیجه تلاش لازم است تا خطا، توقف کاربر و پاسخ پذیرفته‌شده در تاریخچه یک‌شکل نشوند.

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

تکه شبکه لزوماً یک حرف کامل نیست

پیش از پرسیدن اینکه مدل تمام کرده یا نه، باید مطمئن شوید متن را سالم تحویل گرفته‌اید. در یک مصرف‌کننده سفارشی، مرز قطعه ورودی را مرز کاراکتر فرض نکنید. برای نمونه، بایت‌های یکی از حروف «ژرف» ممکن است در دو قطعه جدا برسند. رمزگشایی مستقل هر قطعه، آزمون مناسبی برای بازسازی متن نیست.

استاندارد Encoding در WHATWG برای ورودی تکه‌تکه، استفاده پی‌درپی از یک TextDecoder با stream: true و یک فراخوانی نهایی بدون آن را تعریف می‌کند. حالت fatal نیز خطای رمزگشایی را به استثنا تبدیل می‌کند. این گزینه، انتخاب سخت‌گیرانه برنامه شماست؛ نه ادعایی درباره رفتار پیش‌فرض همه کتابخانه‌ها.

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

const expected = "ژرف";
const bytes = new TextEncoder().encode(expected);

for (let cut = 0; cut <= bytes.length; cut++) {
  const decoder = new TextDecoder("utf-8", { fatal: true });
  const actual =
    decoder.decode(bytes.slice(0, cut), { stream: true }) +
    decoder.decode(bytes.slice(cut), { stream: true }) +
    decoder.decode();
  if (actual !== expected) throw new Error(`Bad split: ${cut}`);
}

اگر بسته رسمی، رویدادهای رمزگشایی‌شده تحویل می‌دهد، همین مسئولیت را دوباره در لایه بعد نسازید. در عوض، رفتار نسخه نصب‌شده را با ورودی بریده و بایت نامعتبر بسنجید. آزمون باید محل مسئولیت را روشن کند: چه لایه‌ای خرابی را تشخیص می‌دهد و چه لایه‌ای جلوی ثبت جواب را می‌گیرد؟

رویداد نیمه‌رسیده را با بستن اتصال کامل نکنید

در پاسخ‌های مبتنی بر SSE، متن رمزگشایی‌شده هنوز به رویداد تبدیل نشده است. طبق بخش رویدادهای سمت سرور در استاندارد HTML، خط خالی رویداد را برای ارسال آماده می‌کند؛ پایان فایل جای آن را نمی‌گیرد. داده باقی‌مانده در رویداد ناقص نباید ارسال شود. این قالب همچنین چند شکل پایان خط و چند خط data را می‌پذیرد.

بنابراین، در آزمایشگاه یک رویداد پایانی بسازید که آخرین خط خالی‌اش حذف شده باشد. اگر برنامه با بسته‌شدن اتصال آن را «تعمیر» کند و پاسخ را نهایی بداند، مرز پذیرش شما ایراد دارد. متن ظاهراً قابل‌خواندن، مجوز حدس‌زدن بخش غایب پروتکل نیست.

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

پایان قطعه با پایان پیام فرق دارد

راهنمای جریانی Anthropic شروع و پایان بلوک محتوا را از پایان پیام جدا می‌کند؛ message_stop رویداد نهایی پیام است. همین راهنما حضور پیام‌های ping و خطا در جریان را توضیح می‌دهد. پس توقف یک بلوک یا رسیدن علامت زنده‌بودن اتصال را معادل جواب کامل نگیرید.

این نام‌ها را به همه رابط‌ها تعمیم ندهید. مرجع ConverseStream در Amazon Bedrock اعضای مستقلی برای messageStop، فراداده و خطاهای جریان دارد؛ دلیل توقف نیز بخشی از پیام توقف است. مبدل هر ارائه‌دهنده باید قرارداد همان رابط را بخواند، نه اینکه صرفاً دنبال واژه مشترک «پایان» بگردد.

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

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

مدل چرا ایستاد؟

مرجع دلیل توقف Claude بین پایان طبیعی نوبت با end_turn، رسیدن به سقف با max_tokens و درخواست ابزار با tool_use تفاوت می‌گذارد. پایان طبیعی نیز فقط درباره نوبت مدل است؛ تضمین نمی‌کند که برنامه جواب مطلوب خود را گرفته باشد.

جدول زیر سیاست پیشنهادی ژرف برای یک دستیار پاسخ‌گوست. آن را با نیاز محصول خود تنظیم کنید؛ جدول، نگاشت رسمی و یکسان همه سرویس‌ها نیست.

شواهد دریافت‌شدهبرداشت برنامهتصمیم درباره متن
چند قطعه متن، بدون شاهد پایان پیامتلاش ناقص یا وضعیت نامعلومپیش‌نویس ناقص؛ نه پاسخ نهایی
پایان معتبر با دلیل سقف خروجیتولید محدود شده استنمایش محدودیت؛ تصمیم جدا برای تلاش بعد
پایان معتبر همراه درخواست ابزارکنترل باید به گردش کار برگرددهنوز پاسخ نهایی کاربر نیست
پایان طبیعی با محتوای نامعتبر یا خالینوبت تمام شده، پذیرش رد شده استثبت علت رد؛ بدون موفقیت جعلی
پایان معتبر و محتوای پذیرفته‌شدهنتیجه آماده ثبت استثبت نهایی، با کنترل هم‌زمانی

امتناع مدل نیز باید وضعیت مشخص خود را داشته باشد. محصول ممکن است آن را پایان معتبر گفتگو بداند، اما نباید در سنجه «پاسخ مفید به درخواست» با پاسخ موفق یکی شود. معیار سنجش را از قبل بنویسید، نه بعد از دیدن نمودار موفقیت.

متن نمایشی را از نسخه نهایی جدا نگه دارید

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

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

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

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

از پایان تولید تا اجازه اجرا فاصله هست

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

بسته‌شدن آکولاد JSON هم چراغ سبز کسب‌وکار نیست. تعداد منفی، شناسه متعلق به مشتری دیگر یا مبلغ با واحد اشتباه می‌تواند در متنی کاملاً سالم جا بگیرد. راهنمای قرارداد خروجی ساختاریافته همین فاصله میان شکل درست، معنای درست و اختیار اقدام را توضیح می‌دهد.

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

تلاش دوم، ادامه خودکار تلاش اول نیست

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

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

درخواست «ادامه بده» هم تلاش تازه‌ای با زمینه مشخص است، نه اثبات اینکه متن قبلی کامل بوده است. اگر پاسخ قبلی به عملی بیرون از مدل منجر شده، پیش از تکرار باید نتیجه آن عمل را روشن کنید. راهنمای تلاش دوباره و جلوگیری از عملیات تکراری برای همین مرز، میان شناسه نیت کاربر و شناسه هر تلاش فرق می‌گذارد.

آزمایش را روی جاهای قطع‌شدن بسازید

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

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

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

آزمون فشار نیز جای خود را دارد. مصرف‌کننده کند، بافر پر و محدودیت اندازه نباید باعث حذف پنهانی محتوا شوند. وقتی ظرفیت کافی نیست، وضعیت شکست روشن از پاسخ ظاهراً کامل اما بریده، قابل‌اعتمادتر است.

سرعت را تا پاسخ پذیرفته‌شده اندازه بگیرید

زمان نخستین متن برای تجربه کاربر مهم است، اما زمان رسیدن به پاسخ پذیرفته‌شده را هم ثبت کنید. راهنمای مهندسی تأخیر استنتاج نشان می‌دهد چرا یک عدد برای همه مسیر کافی نیست. در این مسئله، بررسی محتوا و ثبت نهایی هم جزئی از انتظار واقعی‌اند.

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

پس از تغییر مدل، کتابخانه یا واسط شبکه، آزمون‌های قطعه‌بندی و توقف را دوباره اجرا کنید. نشانه خطر فقط افزایش خطا نیست: جهش ظاهری موفقیت پس از حذف بررسی پایان نیز ارزش بررسی دارد. معیار نهایی ساده است: برنامه باید بتواند برای هر پاسخ نهایی، شاهد پایان، دلیل توقف و نتیجه پذیرش همان تلاش را نشان دهد. اگر نمی‌تواند، هنوز یک پیش‌نویس را با اطمینانی بیش از شواهدش عرضه می‌کند.

یادداشت منابع — بازبینی ۸ اکتبر ۲۰۲۶

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

#پاسخ جریانی#قابلیت اطمینان#مهندسی نرم‌افزار#اعتبارسنجی#رابط برنامه‌نویسی

مطالب مرتبط

این ابزار را در تیم خودتان راه بیندازید

اگر می‌خواهید ایجنت‌های هوش مصنوعی و اتوماسیون این راهنما را در تیم نرم‌افزار یا فرایندهای سازمانتان به کار بگیرید، از یک آزمایش کوچک شروع کنید که بتوانید نتیجه‌اش را بسنجید.