问题

【本质:上下文空间是有限资源,工具输出会不断挤占这个空间。】

Agent 在大项目中工作时,会读很多文件、跑很多命令,每条工具输出都堆在 messages 列表里。

上下文窗口是有限的,满了之后 API直接报 prompt_too_long,Agent 就卡死了。

在这里插入图片描述

解决方案–4层压缩管线

4层压缩管线(便宜的先跑,贵的后跑)

层级 策略 成本 做什么 丢什么
L3 tool_result_budget 0 API 大输出写入磁盘,留预览 完整输出(磁盘有备份)
L1 snip_compact 0 API 裁掉中间旧消息 中段对话历史
L2 micro_compact 0 API 旧工具结果替换为占位符 旧工具输出内容
L4 compact_history 1 API LLM 全量摘要 细节(保留关键信息)
兜底 reactive_compact 1 API API 报错后紧急裁剪 大部分(只留最近 5 条)

总体流程

第一步:进入管线

messages[] – 当前完整的对话消息列表。

⬇️

第二步:L3 tool_result_budget(最便宜)

做什么:检查有没有特别大的工具输出(比如 cat 了一个大文件),有就存到磁盘,用短预览(文本形式的summary)替换。

为什么先做:不涉及 LLM 调用,零成本,直接缩小消息体积。

⬇️

第三步:L1 snip_compact(便宜)

做什么:裁剪中间的消息,只保留头和尾。比如保留最近 5 条,前面的全删。

为什么第二做:也是确定性操作,不调 LLM,但比 L3 激进,会丢信息。

⬇️

第四步:L2 micro_compact(便宜)

做什么:把旧的工具结果替换成占位符。比如 10 轮前的 read_file 结果,替换为 “[tool result omitted]”。

为什么第三做:同样不调 LLM,但会丢失具体的工具输出内容。

⬇️

第五步:判断 – token 还超阈值吗?

前三层处理完后,检查消息的 token 数:

  • 没超 → 直接发给 LLM,正常工作
  • 超了 → 进入 L4

⬇️

第六步:L4 compact_history(最贵)

做什么:调用 LLM 对全部历史做摘要,用摘要替换所有旧消息。

为什么最后做:需要一次额外的 LLM API 调用,有成本,而且会丢失细节。但能把 token 数大幅降下来。

⬇️

第七步:L4 之后再调 LLM

L4 摘要完成后,用精简后的消息去调 LLM 做正事。

⬇️

第八步:紧急兜底 – reactive_compact

即使经过 L4,API 仍然返回 prompt_too_long 错误(比如摘要本身还是太长),就触发紧急压缩:再做一次 LLM
摘要,只保留最后几条消息。

L3 tool_result_budget–大结果存磁盘

1. 统计最后一条 user 消息中所有 `tool_result` 的总大小
2. 总量 > 200KB?
   ├─ 否 → 跳过,不做任何处理
   └─ 是 → 按大小从大到小排序
           逐个检查:
           ├─ 单条 ≤ 3 万字符 → 跳过(小的不动)
           └─ 单条 > 3 万字符 → 存磁盘,上下文替换为预览
           直到总大小降到 200KB 以下 → 停止

在这里插入图片描述

举例

原始状态:
messages 中有一条工具结果,内容是 5 万字符的 cat 输出

→ 全部在上下文中,占用 5 万字符

L3 处理后:

  1. 完整内容写入磁盘文件:.tool-results/toolu_xxx.txt(5 万字符)

  2. 上下文中的内容被替换为:

    Full output: .tool-results/toolu_xxx.txt
    Preview:
    (前 2000 字符的预览…)

    → 上下文只占约 2000 字符

对比

处理前 处理后
上下文中 5 万字符完整输出 2000 字符预览 + 文件路径
磁盘上 完整 5 万字符

L1 snip_compact–裁掉无关的旧对话

1. 判断:消息数 > 50?没超过就跳过
2. 计算:头保留 3 条(初始上下文),尾保留 47 条(当前工作),中间全部裁掉
3. 替换:中间的位置放一条占位符消息,告诉 LLM "这里裁掉了一段对话"

为什么保留头 3 条?

  头 3 条通常是用户最初的任务描述和 agent 的初始响应,包含当前目标,丢了就不知道在干什么了。

为什么保留尾 47 条?

  尾部是最近的工作上下文,agent 正在做的事情,丢了就没法继续。

举例

假设 messages 有 80 条:

处理前(80 条):
[msg0, msg1, msg2, msg3, msg4, msg5, ..., msg76, msg77, msg78, msg79]
 ←── 头 3 条 ──→ ←──── 中间 74 条 ────→ ←──── 尾 47 条 ────→

处理后(51 条):
[msg0, msg1, msg2, "[snipped 74 messages]", msg33, msg34, ..., msg79]
 ←─ 头 3 ─→ ←── 占位符 ──→ ←──────────── 尾 47 条 ────────────→

L2 micro_compact–旧工具结果占位

1. 收集:扫描 messages 中所有 `tool_result` 块
2. 判断:`tool_result` 数量 ≤ 3?跳过
3. 替换:除了最近 3 条,更旧的且长度 > 120 字符的替换为占位符

在这里插入图片描述

举例

  假设 messages 中有 8 条 tool_result:

  处理前(8 条 tool_result):
  [tr1(500字), tr2(800字), tr3(200字), tr4(1500字), tr5(300字), tr6(120字), tr7(900字), tr8(600字)]
   ←──────────────── 旧的 5 条 ────────────────→ ←──── 最近 3 条 ────→

  处理后:
  [tr1("[compacted]"), tr2("[compacted]"), tr3(200字), tr4("[compacted]"), tr5(300字), tr6(120字), tr7(900字),
  tr8(600字)]
   ←── 替换为占位符 ──→   ← 保留(≤120字)→   ←── 保留(最近 3 条)──→

L4 compact_history–LLM全量摘要

1.保存 transcript(备份)

完整的对话保留

2.LLM生成摘要

把对话历史发给LLM,保留生成summary

  • current goal 当前目标
  • key findings/decisions 关键发现和决策
  • files read/changed 读过/改过哪些文件
  • remaining work 还剩什么没做
  • user constraints 用户的约束条件

3.替换

所有旧消息被替换为一条摘要

在这里插入图片描述

举例

场景:Agent 在项目中工作了 30 分钟

处理前(160 条消息,节选)

  msg0  [user] 帮我给这个 Flask 项目加上用户登录功能
  msg1  [assistant] 好的,我先看看项目结构
  ...
  ...
  msg158 [tool_call] edit_file("routes.py", old, new)
  msg159 [tool_result] Edited routes.py

第 1 步:备份

write_transcript(messages)

→ .transcripts/transcript_1748280000.jsonl

160 条消息完整写入,数据不丢


第 2 步:LLM 摘要

summarize_history(messages)

LLM 看到 160 条消息,生成:

当前目标:给 Flask 项目添加用户登录功能

关键发现和决策:
  - 使用 flask-login 库实现认证
  - User 模型已添加 password_hash 字段
  - 登录路由使用 @login_required 装饰器

已修改的文件:
  - models.py:添加 User 模型和密码哈希
  - routes.py:添加 /login、/logout 路由
  - app.py:初始化 LoginManager
  - tests/test_auth.py:添加登录测试

剩余工作:
  - test_login_redirect 测试失败,需修复 routes.py 中的重定向逻辑
  - 登录页面模板 templates/login.html 尚未创建

用户约束:
  - 使用 SQLite 数据库
  - 密码用 werkzeug 加密

第 3 步:替换

处理后(1 条消息):

  [{
    "role": "user",
    "content": "[Compacted]\n\n当前目标:给 Flask 项目添加用户登录功能\n\n关键发现和决策:\n- 使用 flask-login
  库实现认证\n- User 模型已添加 password_hash 字段\n- 登录路由使用 @login_required 装饰器\n\n已修改的文件:\n-
  models.py:添加 User 模型和密码哈希\n- routes.py:添加 /login、/logout 路由\n- app.py:初始化 LoginManager\n-
  tests/test_auth.py:添加登录测试\n\n剩余工作:\n- test_login_redirect 测试失败,需修复重定向逻辑\n-
  登录页面模板尚未创建\n\n用户约束:\n- 使用 SQLite 数据库\n- 密码用 werkzeug 加密"
  }]

LLM 拿到摘要后继续工作

LLM 看到摘要 → 知道自己在做什么、做了什么、还剩什么→ 直接去修 test_login_redirect,不用重新读 160 条历史


对比:

处理前 处理后
消息数 160 条 1 条
包含内容 每条工具调用的完整输出 5 个关键信息摘要
LLM 能做什么 能看到所有细节 知道目标、进度、剩余工作
token 占用 ~50000+ ~500
Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐