
问题【本质上下文空间是有限资源工具输出会不断挤占这个空间。】Agent 在大项目中工作时会读很多文件、跑很多命令每条工具输出都堆在 messages 列表里。上下文窗口是有限的满了之后 API直接报 prompt_too_longAgent 就卡死了。解决方案–4层压缩管线4层压缩管线便宜的先跑贵的后跑层级策略成本做什么丢什么L3tool_result_budget0 API大输出写入磁盘留预览完整输出磁盘有备份L1snip_compact0 API裁掉中间旧消息中段对话历史L2micro_compact0 API旧工具结果替换为占位符旧工具输出内容L4compact_history1 APILLM 全量摘要细节保留关键信息兜底reactive_compact1 APIAPI 报错后紧急裁剪大部分只留最近 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 之后再调 LLML4 摘要完成后用精简后的消息去调 LLM 做正事。⬇️第八步紧急兜底 – reactive_compact即使经过 L4API 仍然返回 prompt_too_long 错误比如摘要本身还是太长就触发紧急压缩再做一次 LLM摘要只保留最后几条消息。L3 tool_result_budget–大结果存磁盘1. 统计最后一条 user 消息中所有 tool_result 的总大小 2. 总量 200KB ├─ 否 → 跳过不做任何处理 └─ 是 → 按大小从大到小排序 逐个检查 ├─ 单条 ≤ 3 万字符 → 跳过小的不动 └─ 单条 3 万字符 → 存磁盘上下文替换为预览 直到总大小降到 200KB 以下 → 停止举例原始状态messages 中有一条工具结果内容是 5 万字符的 cat 输出→ 全部在上下文中占用 5 万字符L3 处理后完整内容写入磁盘文件.tool-results/toolu_xxx.txt5 万字符上下文中的内容被替换为“Full output: .tool-results/toolu_xxx.txtPreview:(前 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保留生成summarycurrent 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.jsonl160 条消息完整写入数据不丢第 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