Knaph

مدیریتِ زمینه24 از 376 دقیقه مطالعه

فشرده‌سازی در کد

mini حالا اندازه‌ی هر درخواست را چاپ می‌کند، و لیلا روی پروژه‌ی بزرگش بالا رفتنش را تماشا می‌کند: ۴۰٬۰۰۰، ۹۰٬۰۰۰، ۱۴۰٬۰۰۰ توکن. جایی بعد از بودجه، باید چیزی کنار گذاشته شود. Claude Code می‌گفت گفت‌وگو «فشرده شد». حالا mini باید خودش این کار را بکند، یعنی دقیقاً تصمیم بگیرد چه چیزی بماند و درخواستِ بعدی چه شکلی داشته باشد.


لیلا راهِ آسان این است که قدیمی‌ترین پیام‌ها را دور بریزم تا جا شود.

امید در قدیمی‌ترین پیام چه هست؟

لیلا خودِ کار. باشد، پیامِ ۰ را نگه می‌دارم و از پیامِ ۱ به بعد را دور می‌ریزم.

امید پیامِ ۱ اولین پاسخِ مدل است: مثلاً یک tool_use که فایلی را می‌خواند. پیامِ ۲ نتیجه‌ی آن است، یک tool_result. اگر پیامِ ۱ را دور بریزی و پیامِ ۲ را نگه داری چه می‌شود؟

لیلا نتیجه‌ای می‌ماند برای فراخوانی‌ای که دیگر نیست.

امید و API چنین درخواستی را رد می‌کند: هر tool_result باید به یک tool_use در پیامِ درست پیش از خودش جواب دهد. برعکسش هم شکست می‌خورد: یک tool_use که بعدش نتیجه‌ای نیامده. پس هر چه را دور بریزی، فراخوانی و نتیجه‌اش با هم می‌روند.

لیلا و حتی اگر جفت‌جفت دور بریزم، آنچه آن نتیجه‌ها می‌گفتند از دست می‌رود. اگر mini در نوبتِ دوم فایلِ تنظیمات را خوانده باشد، آن دیگر رفته است.

امید برای همین بیشترِ عامل‌ها فقط دور نمی‌ریزند. اول از مدل می‌خواهند آنچه مهم است را یادداشت کند.


درخواستِ خلاصه

فشرده‌سازی (compaction) در mini یک درخواستِ اضافه است: همه‌ی آنچه تا حالا بوده، به‌اضافه‌ی یادداشتی که از مدل می‌خواهد جلسه را برای خودش خلاصه کند. بعد mini کلِ تاریخچه را با کار و آن خلاصه جایگزین می‌کند.

def compact(messages, system, tools):
    """Replace everything but the task with a summary written by the model.
    Called right after tool results were added, so the last message is the user's."""
    task = messages[0]
    ask = {"type": "text", "text": (
        "Before going on, summarise this session for yourself: the task, what is done, what is decided, "
        "which files changed, and what is left. Be specific. Don't use any tools in this reply.")}
    last = {"role": "user", "content": messages[-1]["content"] + [ask]}
    reply = client.messages.create(model=MODEL, max_tokens=4000, system=system, tools=tools,
                                   messages=messages[:-1] + [last], cache_control={"type": "ephemeral"})
    summary = "".join(b.text for b in reply.content if b.type == "text")
    if not summary:  # it asked for a tool instead: keep going, try again next turn
        return messages
    return [{"role": "user", "content": f"{task['content']}\n\nProgress so far (a summary):\n{summary}"}]
Python

به این‌ها دقت کنید:

  • یادداشت به آخرین پیامِ user می‌رود، کنارِ نتیجه‌های ابزار که از قبل آنجا هستند. نتیجه‌ها هنوز به آخرین فراخوانی‌های ابزارِ مدل جواب می‌دهند و نقش‌ها هنوز نوبتی‌اند.
  • درخواستِ خلاصه همان system و tools را دارد که همه‌ی درخواست‌های دیگر دارند. پس دقیقاً به همان شکل شروع می‌شود، و با خطِ cache_control می‌تواند تاریخچه‌ی بلند را از کش بخواند و دوباره بهای همه‌اش را ندهد (آخرین درسِ این بخش هر دو را توضیح می‌دهد). به همین دلیل همان مدل و همان تنظیمات را نگه می‌دارد: اینکه اینجا برای صرفه‌جوییِ اندک، میزانِ تلاش را تغییر دهید، باعث می‌شود بهای کاملِ کلِ تاریخچه را بپردازد.
  • ابزارها هستند، اما یادداشت هیچ‌کدام را نمی‌خواهد. اگر مدل باز هم ابزاری بخواهد، پاسخ متنی ندارد، و mini بی‌فشرده‌سازی ادامه می‌دهد و نوبتِ بعد دوباره امتحان می‌کند.
  • آنچه از خلاصه خواسته می‌شود، طراحیِ اصلی است. کار، آنچه انجام شده، آنچه تصمیم گرفته شده، فایل‌هایی که تغییر کرده‌اند، آنچه مانده: این‌ها چیزی است که مدل برای ادامه لازم دارد. خلاصه‌ای که فقط بگوید «روی گزارش کار کردیم» مدل را سرگردان می‌گذارد.

پیش از فشرده‌سازی: ردیفی بلند از پیام‌ها، اول کار، بعد جفت‌هایی از فراخوانیِ ابزارِ assistant و نتیجه‌ی ابزارِ user، و در آخر تازه‌ترین نتیجه‌ها و درخواستِ خلاصه. پس از فشرده‌سازی: یک پیامِ user که کار و خلاصه را در خود دارد.

کی فشرده کنیم

mini پس از هر نوبت بررسی می‌کند، با اندازه‌ی درخواستی که در درسِ قبل دیدید:

        u = response.usage
        if u.input_tokens + u.cache_read_input_tokens + u.cache_creation_input_tokens > CONTEXT_BUDGET:
            messages = compact(messages, system, tools)
Python

این بررسی بعد از افزوده شدنِ نتیجه‌های ابزار اجرا می‌شود، یعنی درست وقتی که پیامِ آخر یک پیامِ user است، همان‌طور که compact انتظار دارد. و از اندازه‌ی درخواستی استفاده می‌کند که همین الان فرستاده شد، که حدسِ خوبی برای درخواستِ بعدی است.

دو چیز از دست می‌رود، و باید بدانید کدام. جزئیاتِ همه‌ی آنچه پیش‌تر بود، همان‌طور که در مدیریتِ زمینه دیدید: هر چه خلاصه جا بیندازد برای همیشه رفته است. و تازه‌ترین نتیجه‌های ابزار: فایلی که mini یک لحظه پیش خواند فقط به شکلِ توصیفِ خلاصه از آن باقی می‌ماند، پس ممکن است مدل دوباره بخواندش. این هم دلیلِ دیگری است برای اینکه تصمیم‌ها را در فایل نگه دارید: نقشه‌ای که در PLAN.md نوشته شده از هر فشرده‌سازی جان سالم به در می‌برد، چون مدل همیشه می‌تواند دوباره بخواندش.

به جایش کوتاه کردن

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

اگر کوتاه می‌کنید، قاعده‌ی جفت‌ها همه‌ی کار است:

  • پیامِ ۰، یعنی کار، را نگه دارید.
  • نوبت‌های کامل را از قدیمی‌ترین دور بریزید: یک پیامِ assistant و پیامِ user بعد از آن، با هم. آن وقت هیچ tool_use جوابش، یعنی tool_result، را از دست نمی‌دهد و نقش‌ها هنوز نوبتی‌اند.
  • تازه‌ترین نوبت را نگه دارید، همان که مدل می‌خواهد به آن جواب دهد.

کوتاه کردنِ یک تاریخچه. کارتِ کار می‌ماند. قدیمی‌ترین جفت‌ها، هر کدام یک فراخوانیِ ابزارِ assistant با نتیجه‌ی ابزارِ user پس از آن، با هم حذف می‌شوند. یک نمونه‌ی خط‌خورده نشان می‌دهد چه چیزی مجاز نیست: نتیجه‌ی ابزاری که پس از حذفِ درخواستش جا مانده.

تمرینِ بعد از این درس دقیقاً همین تابع است.

امتحان کنید

در یک رونوشت از mini بگذارید CONTEXT_BUDGET = 20_000 و کاری به آن بدهید که چند فایل را می‌خواند. منتظرِ نوبتی بمانید که فشرده‌سازی می‌کند و خلاصه‌ای را که نوشت چاپ کنید. همان خلاصه‌ای است که خودتان می‌نوشتید؟ چه چیزی کم دارد؟ کلید ندارید؟ در پنج خط خلاصه‌ای را بنویسید که دوست دارید mini بعد از جلسه‌ی تصویرِ اول نگه دارد: کار، انجام‌شده، تصمیم‌گرفته‌شده، تغییرکرده، مانده.

درست به کار بردن

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

واژه‌های این درس

  • فشرده‌سازی (compaction) - جایگزین کردنِ تاریخچه‌ی عامل با کار و خلاصه‌ای که مدل می‌نویسد، تا در زمینه جا باز شود
  • کوتاه کردن (trimming) - دور ریختنِ قدیمی‌ترین نوبت‌های کاملِ تاریخچه، بی‌هیچ خلاصه‌ای؛ ارزان‌تر است و بیشتر از دست می‌دهد
  • جفتِ نوبت (turn pair) - یک پیامِ assistant و پیامِ user بعد از آن، که یک فراخوانیِ ابزار و نتیجه‌اش را در خود دارند و باید با هم نگه داشته یا دور ریخته شوند

ایده‌ی اصلی

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

دانش خود را بسنجید

ورود برای سنجش دانش خود از این درس و پیگیری پیشرفتتان.