mirror of
https://github.com/shareAI-lab/learn-claude-code.git
synced 2026-08-17 12:23:35 +00:00
fix: preserve latest tool result batch during compaction
This commit is contained in:
parent
66cfd2ca91
commit
b5bd0ddfa1
6 changed files with 72 additions and 10 deletions
|
|
@ -117,7 +117,7 @@ messages = [*messages[:head_end], marker, *messages[tail_start:]]
|
|||
|
||||
## ステップ 3:micro_compact
|
||||
|
||||
`micro_compact` は、現在の履歴にあるすべての `tool_result` を収集します。最新 3 件は完全に保持し、それより古く 120 文字を超える結果を短くします。保存済みの結果にはファイルパスを残し、それ以外はプレースホルダーに置き換えます。
|
||||
`micro_compact` は最新の `tool_result` バッチを完全に保持し、さらに以前のバッチから最新 3 件を残します。それより古く 120 文字を超える結果を短くします。保存済みの結果にはファイルパスを残し、それ以外はプレースホルダーに置き換えます。
|
||||
|
||||

|
||||
|
||||
|
|
@ -305,7 +305,7 @@ s01_agent_loop から s05_todo_write までの README.md を読み、
|
|||
各ファイルの最上位見出しを比較して、命名の規則をまとめてください。
|
||||
```
|
||||
|
||||
このタスクでは少なくとも 5 件のファイル結果が生成されます。最新 3 件は完全に残り、それより前の長い結果は `[Earlier tool result omitted.]` に変わります。保存済みの結果には保存先のパスが残ります。
|
||||
このタスクでは少なくとも 5 件のファイル結果が生成されます。最新のバッチと、それ以前の最新 3 件は完全に残り、それより前の長い結果は `[Earlier tool result omitted.]` に変わります。保存済みの結果には保存先のパスが残ります。
|
||||
|
||||
### 実験 2:大きな結果を保存する
|
||||
|
||||
|
|
|
|||
|
|
@ -117,7 +117,7 @@ This step controls the number of messages. Tool results inside the retained mess
|
|||
|
||||
## Step 3: micro_compact
|
||||
|
||||
`micro_compact` collects all current `tool_result` blocks. It preserves the latest 3 results and shortens earlier results longer than 120 characters. Persisted results keep their file path; the rest become placeholders:
|
||||
`micro_compact` preserves the newest `tool_result` batch in full, then keeps the latest 3 results from earlier batches and shortens older results longer than 120 characters. Persisted results keep their file path; the rest become placeholders:
|
||||
|
||||

|
||||
|
||||
|
|
@ -305,7 +305,7 @@ Read the README.md files from s01_agent_loop through s05_todo_write.
|
|||
Compare their top-level headings and summarize the naming pattern.
|
||||
```
|
||||
|
||||
This task produces at least 5 file results. The latest 3 remain complete, while earlier long results become `[Earlier tool result omitted.]`. A persisted result retains its saved path.
|
||||
This task produces at least 5 file results. The newest batch and the latest 3 earlier results remain complete, while older long results become `[Earlier tool result omitted.]`. A persisted result retains its saved path.
|
||||
|
||||
### Experiment 2: Persist a Large Result
|
||||
|
||||
|
|
|
|||
|
|
@ -117,7 +117,7 @@ messages = [*messages[:head_end], marker, *messages[tail_start:]]
|
|||
|
||||
## 第三步:micro_compact
|
||||
|
||||
`micro_compact` 收集当前历史里的全部 `tool_result`。最近 3 条保持完整,更早且超过 120 个字符的结果会缩短。已经转存的结果保留文件路径,其他结果只留下占位符:
|
||||
`micro_compact` 会完整保留最新一批 `tool_result`,再保留更早批次中最近 3 条结果;其余超过 120 个字符的旧结果会缩短。已经转存的结果保留文件路径,其他结果只留下占位符:
|
||||
|
||||

|
||||
|
||||
|
|
@ -305,7 +305,7 @@ python s08_context_compact/code.py
|
|||
比较它们的一级标题,并总结这些标题的命名规律。
|
||||
```
|
||||
|
||||
任务会产生至少 5 条文件读取结果。最近 3 条保持完整,更早且较长的结果会变成 `[Earlier tool result omitted.]`。已经转存的结果会保留保存路径。
|
||||
任务会产生至少 5 条文件读取结果。最新一批以及更早批次中最近 3 条结果保持完整,更早且较长的结果会变成 `[Earlier tool result omitted.]`。已经转存的结果会保留保存路径。
|
||||
|
||||
### 实验二:大结果转存
|
||||
|
||||
|
|
|
|||
|
|
@ -331,7 +331,20 @@ class ContextCompactor:
|
|||
for block in message["content"]
|
||||
if isinstance(block, dict) and block.get("type") == "tool_result"
|
||||
]
|
||||
for block in results[:-self.KEEP_RECENT_RESULTS]:
|
||||
latest_batch = []
|
||||
for message in reversed(messages):
|
||||
content = message.get("content")
|
||||
if message.get("role") != "user" or not isinstance(content, list):
|
||||
continue
|
||||
latest_batch = [
|
||||
block for block in content
|
||||
if isinstance(block, dict) and block.get("type") == "tool_result"
|
||||
]
|
||||
if latest_batch:
|
||||
break
|
||||
latest_batch_ids = {id(block) for block in latest_batch}
|
||||
older_results = [block for block in results if id(block) not in latest_batch_ids]
|
||||
for block in older_results[:-self.KEEP_RECENT_RESULTS]:
|
||||
content = str(block.get("content", ""))
|
||||
if len(content) <= 120:
|
||||
continue
|
||||
|
|
|
|||
|
|
@ -1959,9 +1959,20 @@ def snip_compact(messages: list, max_messages: int = 50) -> list:
|
|||
|
||||
def micro_compact(messages: list) -> list:
|
||||
tool_results = collect_tool_results(messages)
|
||||
if len(tool_results) <= KEEP_RECENT_TOOL_RESULTS:
|
||||
return messages
|
||||
for _, _, block in tool_results[:-KEEP_RECENT_TOOL_RESULTS]:
|
||||
latest_batch = []
|
||||
for message in reversed(messages):
|
||||
content = message.get("content")
|
||||
if message.get("role") != "user" or not isinstance(content, list):
|
||||
continue
|
||||
latest_batch = [
|
||||
block for block in content
|
||||
if isinstance(block, dict) and block.get("type") == "tool_result"
|
||||
]
|
||||
if latest_batch:
|
||||
break
|
||||
latest_batch_ids = {id(block) for block in latest_batch}
|
||||
older_results = [entry for entry in tool_results if id(entry[2]) not in latest_batch_ids]
|
||||
for _, _, block in older_results[:-KEEP_RECENT_TOOL_RESULTS]:
|
||||
if len(str(block.get("content", ""))) > 120:
|
||||
block["content"] = "[Earlier tool result compacted. Re-run if needed.]"
|
||||
return messages
|
||||
|
|
|
|||
|
|
@ -86,6 +86,17 @@ def tool_result_message(tool_id="tool-1"):
|
|||
}
|
||||
|
||||
|
||||
def long_tool_result_batch(*tool_ids):
|
||||
return {
|
||||
"role": "user",
|
||||
"content": [
|
||||
{"type": "tool_result", "tool_use_id": tool_id,
|
||||
"content": f"{tool_id}: " + "x" * 160}
|
||||
for tool_id in tool_ids
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
def message_has_tool_use(message):
|
||||
content = message.get("content")
|
||||
return (
|
||||
|
|
@ -112,6 +123,33 @@ def compaction_api(module):
|
|||
|
||||
|
||||
class CompactionToolPairTests(unittest.TestCase):
|
||||
def test_micro_compact_keeps_latest_tool_result_batch(self):
|
||||
for name, path in MODULES.items():
|
||||
with self.subTest(name=name), tempfile.TemporaryDirectory() as tmp:
|
||||
messages = [
|
||||
long_tool_result_batch("old-1"),
|
||||
long_tool_result_batch("old-2"),
|
||||
long_tool_result_batch("old-3"),
|
||||
long_tool_result_batch("old-4"),
|
||||
user_text(),
|
||||
long_tool_result_batch(
|
||||
"latest-1", "latest-2", "latest-3", "latest-4"
|
||||
),
|
||||
]
|
||||
module = load_module(f"{name}_micro_batch_under_test", path, Path(tmp))
|
||||
compacted = compaction_api(module).micro_compact(messages)
|
||||
results = {
|
||||
block["tool_use_id"]: block["content"]
|
||||
for message in compacted
|
||||
if isinstance(message["content"], list)
|
||||
for block in message["content"]
|
||||
if isinstance(block, dict) and block.get("type") == "tool_result"
|
||||
}
|
||||
self.assertNotIn("old-1: ", results["old-1"])
|
||||
for tool_id in ("old-2", "old-3", "old-4",
|
||||
"latest-1", "latest-2", "latest-3", "latest-4"):
|
||||
self.assertIn(f"{tool_id}: ", results[tool_id])
|
||||
|
||||
def test_snip_compact_keeps_head_tool_pair(self):
|
||||
messages = [
|
||||
user_text(),
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue