رفتن به محتوا

بافت ابزار و کش پرامپت

دو چیز دربارهٔ هر درخواستی که هارنس داخلی می‌فرستد درست است: تعریف‌های ابزار بزرگ‌ترین هزینهٔ ثابت آن است، و تقریباً هیچ‌یک از آن‌ها به کار نخواهد آمد. این صفحه توضیح می‌دهد که Control Center در این باره چه می‌کند و چرا پاسخش به‌سادگی «ابزارهای کمتری بفرست» نیست.

Control Center حدود ۱۲۶ ابزار ثبت می‌کند. اگر کامل فرستاده شوند، تعریف‌هایشان در هر درخواست حدود ۲۳٬۰۰۰ توکن هزینه دارد — هزینه‌ای که هیچ فشرده‌سازی‌ای به آن دست نمی‌یابد، چون تعریف‌های ابزار جدا از گفتگو و در بخش دیگری از درخواست سفر می‌کنند.

صورت‌حساب توکن شکایت آشکار است، و همراه با کش پرامپت هزینهٔ کوچک‌تر هم هست. هزینهٔ واقعی دقت انتخاب است. ارزیابی‌های منتشرشدهٔ فراخوانی ابزار هم‌داستان‌اند که مدل‌ها تا حدود ۳۰ تا ۵۰ ابزار در بافت، ابزار درست را به‌طور قابل‌اعتماد انتخاب می‌کنند و پس از آن به‌تدریج بدتر می‌شوند. علت نام‌برده‌شده شمارش توکن نیست، بلکه هم‌پوشانی معنایی است: وقتی کاتالوگی چند ابزار دارد که توضیحاتشان شبیه هم خوانده می‌شود، مدل میان هم‌شکل‌ها دست به انتخاب می‌زند، نه اینکه یک قابلیت را به یاد بیاورد.

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

بنابراین یک اجرا دو چیز را می‌بیند:

  • یک مجموعهٔ ساکن — حدود بیست‌وچند ابزار، که در هر درخواست با اسکیمای کامل فرستاده می‌شوند؛
  • یک نمایهٔ نام از باقی چیزها، گروه‌بندی‌شده زیر عنوان‌هایی مانند «Tickets and projects» یا «Memory and knowledge»، که برای کل کاتالوگ چند صد توکن هزینه دارد.

سنجش روی کاتالوگ کنونی نشان می‌دهد که این کار بلوک ابزار را از ~۲۳٫۱ هزار توکن به ~۵٫۰ هزار، حدود ۷۸٪ کوچک‌تر می‌رساند و مجموعهٔ همیشه‌نمایان را درون باندی می‌گذارد که انتخاب در آن قابل‌اعتماد است.

مجموعهٔ ساکن ابزارهایی است که یک اجرا در بیشتر اجراها به کار می‌برد: واژگان فایل و شل (read, write, edit, search, bash, …)، ابزارهای کشف، چک‌لیست کار، جست‌وجوی حافظه، و هر فعلی که حالت کنونی باید برای تحویل خروجی‌اش فراخوانی کند. فعل خروجی خودِ یک حالت همیشه ساکن است — یک اجرا هرگز نباید به دنبال تنها فراخوانی‌ای بگردد که تحویل‌دادنی‌اش را تولید می‌کند.

به تعویق افتادن به معنای در دسترس نبودن نیست

بخش با عنوان “به تعویق افتادن به معنای در دسترس نبودن نیست”

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

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

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

search_tools فقط در هارنس داخلی وجود دارد. کارخواه‌های بیرونی MCP به‌جایش search_tool_bm25 را دارند که همان نمایه را رتبه‌بندی می‌کند، ولی تنها تطبیق‌ها را توصیف می‌کند — آن کارخواه‌ها از قبل همهٔ اسکیماها را از tools/list گرفته‌اند، پس چیزی برای بارگذاری نمانده است.

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

سطح MCP که کارخواه‌های بیرونی می‌بینند تحت تأثیر قرار نمی‌گیرد. tools/list هنوز کاتالوگ کامل را برمی‌گرداند. تلاشی پیشین برای پیراستن آن، عامل‌ها را یک‌سره از کار انداخت: کارخواه‌های MCP نام ابزارها را با فهرست کش‌شدهٔ خودشان اعتبارسنجی می‌کنند و هر چیزی را که در آن فهرست نباشد، بی‌آنکه از سرور بپرسند، رد می‌کنند؛ پس ابزاری «پنهان ولی قابل فراخوانی» به‌سادگی دست‌نیافتنی بود. تعویق در هارنس زندگی می‌کند، جایی که بارگذاری یک ابزار در میانهٔ اجرا کاری است که هارنس واقعاً می‌تواند انجام دهد.

--tool-deferral=off (یا CC_SERVER_TOOL_DEFERRAL=off) هر ابزار پذیرفته‌شده را دوباره ساکن می‌کند و دقیقاً همان درخواست‌هایی را تولید می‌کند که سرور پیش از وجود تعویق می‌فرستاد. به مرجع CLI مربوط به cc_server نگاه کنید.

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

کش روی تطبیق دقیق پیشوند کار می‌کند، و همین یک قاعدهٔ حاکم به دست می‌دهد.

هرگز آنچه را پیش‌تر فرستاده شده بازنویسی نکن

بخش با عنوان “هرگز آنچه را پیش‌تر فرستاده شده بازنویسی نکن”

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

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

ارائه‌دهنده تنها شمار محدودی نشانگر کش اجازه می‌دهد، و Control Center آن‌ها را آگاهانه خرج می‌کند:

نشانگر پوشش عمر
آخرین ابزار ساکن بلوک ابزار ۱ ساعت
آخرین بلوک سیستمی ابزارها + پرامپت سیستمی ۱ ساعت
دُم نوبت پیشین تاریخچه تا آخرین درخواست ۵ دقیقه
دُم نوبت کنونی تمام تاریخچه ۵ دقیقه

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

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

زیرعامل‌ها یک پیشوند را شریک‌اند

بخش با عنوان “زیرعامل‌ها یک پیشوند را شریک‌اند”

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

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

هر دو نیمه مشاهده‌شدنی‌اند، نه فرضی:

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