Agent 开发3 (4月8日报)
- Learn Claude Code 的教材更新了,学习思路貌似有点不一样了,打算重新看一遍,笔记的话就添加到之前写的了
- 打算重制一下 agent 相关的日报内容了,把知识拿出来做条理一点
上下文压缩
"上下文总会满, 要有办法腾地方" -- 三层压缩策略, 换来无限会话。
问题
上下文窗口是有限的。读一个 1000 行的文件就吃掉 ~4000 token; 读 30 个文件、跑 20 条命令, 轻松突破 100k token。不压缩, 智能体根本没法在大项目里干活
解决方案
三层压缩, 激进程度递增:
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?]
| |
no yes
| |
v v
continue [Layer 2: auto_compact]
Save transcript to .transcripts/
LLM summarizes conversation.
Replace all messages with [summary].
|
v
[Layer 3: compact tool]
Model calls compact explicitly.
Same summarization as auto_compact.工作原理
第一层 -- micro_compact: 每次 LLM 调用前, 将旧的 tool result 替换为占位符
def micro_compact(messages: list) -> list:
tool_results = []
# enumerate() 同时把索引 `i` 和里面的内容 `msg` 给你
for i, msg in enumerate(messages):
# 确认是否为 user 的消息,且内容是不是 list
if msg["role"] == "user" and isinstance(msg.get("content"), list):
for j, part in enumerate(msg["content"]):
# 判断此代码块类型是否为 'tool_result'
if isinstance(part, dict) and part.get("type") == "tool_result":
# 将坐标和对象本身存起来
tool_results.append((i, j, part))
# [:-KEEP_RECENT] 意思是:排除掉最后 1 个(最新的一次),只把前面的旧记录拿出来
if len(tool_results) <= KEEP_RECENT:
return messages
# 处理除了最近 KEEP_RECENT 条以外的所有旧记录
# 最后面是 py 的切片语法 等价于 list[0 : len(list) - KEEP_RECENT]
# 意思是 取“前面所有元素”,但去掉最后 KEEP_RECENT 个
for _, _, part in tool_results[:-KEEP_RECENT]:
# 判断:如果这个旧记录的字数超过了 100 个字
if len(part.get("content", "")) > 100:
# 将其总结为一句话 “Previous: used {tool_name}”
part["content"] = f"[Previous: used {tool_name}]"
return messages假设我们的 message 是这样的,里面有几条很长的工具调用日志
[
// i=0
{"role": "user", "content": [
// j=0
{"type": "tool_result", "content": "第1次代码运行日志:几万字..."}
]},
// i=1
{"role": "user", "content": [
// j=0
{"type": "tool_result", "content": "第2次代码运行日志:几万字..."}
]}
]我们假设 KEEP_RECENT = 1(也就是只保留最近 1 次的详细记录,其他的全折叠)
第二层 -- auto_compact: token 超过阈值时, 保存完整对话到磁盘, 让 LLM 做摘要
def auto_compact(messages: list) -> list:
# 备份
# Save transcript for recovery
transcript_path = TRANSCRIPT_DIR / f"transcript_{int(time.time())}.jsonl"
with open(transcript_path, "w") as f:
# 一行行往文件写入 json
# default 默认 str 防止报错
for msg in messages:
f.write(json.dumps(msg, default=str) + "\n")
# LLM summarizes
response = client.messages.create(
model=MODEL,
messages=[{"role": "user", "content":
"Summarize this conversation for continuity..."
+ json.dumps(messages, default=str)[:80000]}],
max_tokens=2000,
)
# 返回总结好的文字
return [
{"role": "user", "content": f"[Compressed]\n\n{response.content[0].text}"},
{"role": "assistant", "content": "Understood. Continuing."},
]第三层 - manual compact: compact 工具按需触发同样的摘要机制
第四层
循环整合三层
def agent_loop(messages: list):
while True:
micro_compact(messages) # Layer 1
# estima_tokens 是估算 token 使用的函数
# 如果大于阈值则进行
if estimate_tokens(messages) > THRESHOLD:
messages[:] = auto_compact(messages) # Layer 2
response = client.messages.create(...)
# ... tool execution ...
if manual_compact:
messages[:] = auto_compact(messages) # Layer 3完整历史通过 transcript 保存在磁盘上。信息没有真正丢失, 只是移出了活跃上下文
