6. דחיסת הקשר

"ההקשר יתמלא; צריך דרך לפנות מקום"

20 דקות קריאה
💡חדש בנושא?

מה זה חלון הקשר (context window)?

הכמות הכוללת של טקסט (טוקנים) שהמודל יכול 'לראות' בבת אחת — כולל הפרומפט שלך, היסטוריית השיחה, תוצאות כלים, וכל ההודעות הקודמות. חלון ה-context של Claude גדול אבל מוגבל.

למה ה-context מתמלא?

כל קריאת כלי מוסיפה את התוצאה שלה למערך ההודעות. קריאת קובץ של 1000 שורות מוסיפה ~4000 טוקן. אחרי 30 קריאות קבצים ו-20 פקודות bash, אפשר להגיע בקלות ל-100,000+ טוקן ולגשת לגבול.

מה זה micro-compaction?

טכניקה שבה תוצאות כלים ישנות מיותר מ-3 סיבובים מוחלפות בסיכום קצר כמו '[Previous: used read_file]'. זה קוצץ בשקט פרטים ישנים תוך שמירת ה-context האחרון שלם.

הבעיה

חלון ה-context מוגבל. read_file יחיד על קובץ של 1000 שורות עולה ~4000 טוקן. אחרי קריאת 30 קבצים והרצת 20 פקודות bash, מגיעים ל-100,000+ טוקן. הסוכן לא יכול לעבוד על codebases גדולים ללא דחיסה.

הפתרון

שלוש שכבות, בעוצמה עולה:

Every turn:
+------------------+
| Tool call result |
+------------------+
        |
        v
[Layer 1: micro_compact]        (silent, every turn)
  Replace tool_result > 3 turns old
  with "[Previous: used {tool_name}]"
        |
        v
[Check: tokens > 50000?]
   |               |
  yes              no
   |               |
   v               +--- continue normally
[Layer 2: mid_compact]
  Summarize assistant messages
  Keep only last 5 tool results
   |
   v
[Check: tokens > 80000?]
   |               |
  yes              no
   |               +--- continue
   v
[Layer 3: hard_compact]
  Call LLM to write a dense summary
  Replace entire history with summary
  Inject <identity> reminder

איך זה עובד

  1. שכבה 1 — דחיסת micro רצה בשקט בכל סיבוב. תוצאות כלים ישנות מיותר מ-3 סיבובים הופכות למציין מקום של שורה אחת.
def micro_compact(messages: list) -> list:
    compacted = []
    for i, msg in enumerate(messages):
        if msg["role"] == "user" and isinstance(msg["content"], list):
            age = len(messages) - i
            if age > 6:  # older than 3 turns (user+assistant pairs)
                new_content = []
                for block in msg["content"]:
                    if block.get("type") == "tool_result":
                        tool_name = block.get("_tool_name", "tool")
                        new_content.append({
                            "type": "tool_result",
                            "tool_use_id": block["tool_use_id"],
                            "content": f"[Previous: used {tool_name}]",
                        })
                    else:
                        new_content.append(block)
                compacted.append({**msg, "content": new_content})
                continue
        compacted.append(msg)
    return compacted
  1. שכבה 2 — דחיסת mid מופעלת כשספירת הטוקנים עוברת 50,000. היא שומרת את פרומפט המערכת, את 5 תוצאות הכלים האחרונות במלואן, ומסכמת את השאר.
def count_tokens(messages: list) -> int:
    text = json.dumps(messages)
    return len(text) // 4  # rough estimate: 4 chars ≈ 1 token

def maybe_compact(messages: list) -> list:
    tokens = count_tokens(messages)
    if tokens > 80000:
        return hard_compact(messages)
    if tokens > 50000:
        return mid_compact(messages)
    return micro_compact(messages)
  1. שכבה 3 — דחיסת hard מבקשת מה-LLM עצמו לכתוב סיכום צפוף של מה שקרה, ואז מחליפה את כל ההיסטוריה בסיכום הזה בתוספת תזכורת זהות.
def hard_compact(messages: list) -> list:
    summary_prompt = (
        "Summarize the conversation so far. Include: "
        "what the user asked, what tools you used, "
        "what you found, what's left to do. Be dense."
    )
    summary_messages = messages + [{"role": "user", "content": summary_prompt}]
    response = client.messages.create(
        model=MODEL, system=SYSTEM,
        messages=summary_messages, max_tokens=2000,
    )
    summary = response.content[0].text
    return [
        {"role": "user", "content": f"<context_summary>\n{summary}\n</context_summary>"},
        {"role": "assistant", "content": "Understood. Continuing from the summary."},
    ]

מה השתנה מ-מיומנויות

רכיבלפני (מיומנויות)אחרי (דחיסת הקשר)
Contextגדל לנצחדחיסה תלת-שכבתית
תוצאות ישנותתוכן מלאמציין מקום של שורה אחת
גבול טוקניםפגיעה וקריסהגבול רך ב-50k, קשה ב-80k
היסטוריהללא גבולותנדחסת לפי דרישה

מסקנה מרכזית

דחיסת ה-context היא מה שהופך סוכנים עם ריצה ארוכה לאפשריים מעשית. האסטרטגיה התלת-שכבתית היא פרוגרסיבית: עשה את הדבר הזול ביותר קודם (micro), הסלם רק כשצריך (mid), ובתור מוצא אחרון בקש מהמודל לסכם את עצמו (hard). קוד הלולאה כמעט לא משתנה — רק עטפו את messages דרך maybe_compact() לפני כל קריאת LLM.

מדריך קוד אינטראקטיבי

אסטרטגיית הדחיסה בשלושה שלבים
1def count_tokens(messages: list) -> int:
2 text = json.dumps(messages)
3 return len(text) // 4 # rough estimate: 4 chars ≈ 1 token
4 
5def maybe_compact(messages: list) -> list:
6 tokens = count_tokens(messages)
7 if tokens > 80000:
8 return hard_compact(messages)
9 if tokens > 50000:
10 return mid_compact(messages)
11 return micro_compact(messages)
12 
13def hard_compact(messages: list) -> list:
14 summary_prompt = (
15 "Summarize the conversation so far. Include: "
16 "what the user asked, what tools you used, "
17 "what you found, what's left to do. Be dense."
18 )
19 summary_messages = messages + [{"role": "user", "content": summary_prompt}]
20 response = client.messages.create(
21 model=MODEL, system=SYSTEM,
22 messages=summary_messages, max_tokens=2000,
23 )
24 summary = response.content[0].text
25 return [
26 {"role": "user", "content": f"<context_summary>\n{summary}\n</context_summary>"},
27 {"role": "assistant", "content": "Understood. Continuing from the summary."},
28 ]
29 
ספירת טוקנים מכוונת להיות גסה — חלוקת אורך ה-JSON ב-4 נותנת הערכה מהירה. הספירה המדויקת לא חשובה; מה שחשוב הוא הפעלת הדחיסה לפני הגעה לגבול ה-API הקשיח.
שלב 1 מתוך 4
🧪 נסו בעצמכם

מלאו את חלון ההקשר על ידי מתן משימות רבות ברצף. צפו בדחיסה נכנסת לפעולה.

רמז

בדקו את ספירת הטוקנים ב-metadata של response

מצאתם טעות? דווחו ←