diff --git a/s08_context_compact/README.en.md b/s08_context_compact/README.en.md index 6b2f23593..e10fabbdf 100644 --- a/s08_context_compact/README.en.md +++ b/s08_context_compact/README.en.md @@ -52,8 +52,21 @@ def snip_compact(messages, max_messages=50): if _is_tool_result_message(messages[tail_start]) and _message_has_tool_use(messages[tail_start - 1]): tail_start -= 1 snipped = tail_start - head_end - placeholder = {"role": "user", "content": f"[snipped {snipped} messages from conversation middle]"} - return messages[:head_end] + [placeholder] + messages[tail_start:] + + snip_text = f"\n\n[System Note: Snipped {snipped} history messages here for context compression]\n\n" + + head_part = messages[:head_end] + tail_part = [msg.copy() for msg in messages[tail_start:]] + + first_tail_msg = tail_part[0] + + if isinstance(first_tail_msg["content"], str): + first_tail_msg["content"] = snip_text + first_tail_msg["content"] + + elif isinstance(first_tail_msg["content"], list): + first_tail_msg["content"].insert(0, {"type": "text", "text": snip_text}) + + return head_part + tail_part ``` Messages are still trimmed directly; this just adds one boundary guard. `tool_result` content within remaining messages still keeps accumulating — message #34 may still hold 30KB of old file contents. → L2. diff --git a/s08_context_compact/README.ja.md b/s08_context_compact/README.ja.md index 84bfb381a..359e76834 100644 --- a/s08_context_compact/README.ja.md +++ b/s08_context_compact/README.ja.md @@ -52,8 +52,21 @@ def snip_compact(messages, max_messages=50): if _is_tool_result_message(messages[tail_start]) and _message_has_tool_use(messages[tail_start - 1]): tail_start -= 1 snipped = tail_start - head_end - placeholder = {"role": "user", "content": f"[snipped {snipped} messages from conversation middle]"} - return messages[:head_end] + [placeholder] + messages[tail_start:] + + snip_text = f"\n\n[System Note: Snipped {snipped} history messages here for context compression]\n\n" + + head_part = messages[:head_end] + tail_part = [msg.copy() for msg in messages[tail_start:]] + + first_tail_msg = tail_part[0] + + if isinstance(first_tail_msg["content"], str): + first_tail_msg["content"] = snip_text + first_tail_msg["content"] + + elif isinstance(first_tail_msg["content"], list): + first_tail_msg["content"].insert(0, {"type": "text", "text": snip_text}) + + return head_part + tail_part ``` 切り捨て自体は単純なままで、境界だけを保護する。残ったメッセージ内の `tool_result` 内容はまだ蓄積され続けている。34 番目のメッセージに 30KB の古いファイル内容が残っているかもしれない。→ L2。 diff --git a/s08_context_compact/README.md b/s08_context_compact/README.md index 22d967156..5f8d40d14 100644 --- a/s08_context_compact/README.md +++ b/s08_context_compact/README.md @@ -52,8 +52,27 @@ def snip_compact(messages, max_messages=50): if _is_tool_result_message(messages[tail_start]) and _message_has_tool_use(messages[tail_start - 1]): tail_start -= 1 snipped = tail_start - head_end - placeholder = {"role": "user", "content": f"[snipped {snipped} messages from conversation middle]"} - return messages[:head_end] + [placeholder] + messages[tail_start:] + + # 构造清晰的系统断层通知,告知 Claude 上下文有缺失,避免其产生幻觉 + snip_text = f"\n\n[System Note: Snipped {snipped} history messages here for context compression]\n\n" + + # 对后半段进行深拷贝(Deep Copy)或浅拷贝,防止直接修改和污染外部传入的原始 messages 数组 + head_part = messages[:head_end] + tail_part = [msg.copy() for msg in messages[tail_start:]] + + # 将提示信息“无损”合并到后半段第一条消息中,天然继承原有的角色交替和工具流结构 + first_tail_msg = tail_part[0] + + if isinstance(first_tail_msg["content"], str): + # 场景 A:普通的纯文本消息 + first_tail_msg["content"] = snip_text + first_tail_msg["content"] + + elif isinstance(first_tail_msg["content"], list): + # 场景 B:Claude 3 常见的多模态(含图片)或包含 tool_use/tool_result 的 Content Block 列表 + # 在列表最前端插入一个标准的 text block + first_tail_msg["content"].insert(0, {"type": "text", "text": snip_text}) + + return head_part + tail_part ``` 裁掉的是消息本身,只是在切口处多做一步保护;剩下的消息里 `tool_result` 内容仍在累积——第 34 条消息里可能躺着 30KB 的旧文件内容。→ L2。 diff --git a/s08_context_compact/code.py b/s08_context_compact/code.py index b9d78d425..c04ba397c 100644 --- a/s08_context_compact/code.py +++ b/s08_context_compact/code.py @@ -306,8 +306,27 @@ def snip_compact(messages, max_messages=50): if head_end >= tail_start: return messages snipped = tail_start - head_end - return messages[:head_end] + [{"role": "user", "content": f"[snipped {snipped} messages]"}] + messages[tail_start:] + # 构造清晰的系统断层通知,告知 Claude 上下文有缺失,避免其产生幻觉 + snip_text = f"\n\n[System Note: Snipped {snipped} history messages here for context compression]\n\n" + + # 对后半段进行深拷贝(Deep Copy)或浅拷贝,防止直接修改和污染外部传入的原始 messages 数组 + head_part = messages[:head_end] + tail_part = [msg.copy() for msg in messages[tail_start:]] + + # 将提示信息“无损”合并到后半段第一条消息中,天然继承原有的角色交替和工具流结构 + first_tail_msg = tail_part[0] + + if isinstance(first_tail_msg["content"], str): + # 场景 A:普通的纯文本消息 + first_tail_msg["content"] = snip_text + first_tail_msg["content"] + + elif isinstance(first_tail_msg["content"], list): + # 场景 B:Claude 3 常见的多模态(含图片)或包含 tool_use/tool_result 的 Content Block 列表 + # 在列表最前端插入一个标准的 text block + first_tail_msg["content"].insert(0, {"type": "text", "text": snip_text}) + + return head_part + tail_part # L2: microCompact — old result placeholders def collect_tool_results(messages):