1. 编码智能体的上下文困境与 AutoCompact 的破局思路
1.1 从一次真实的“爆上下文”事故说起
先说一个我亲身踩过的坑。去年冬天我在用编码智能体跑一个跨模块重构任务,涉及 14 个文件、3 个测试套件和一份 2000 行的迁移脚本。前 40 分钟一切顺利,智能体按部就班地读文件、改代码、跑测试。到第 41 分钟,它突然开始“胡言乱语”——把已经改好的函数又改回去,把不存在的变量名写进代码,甚至把测试断言里的期望值改成了乱码。我盯着终端看了半天,最后在日志里找到了元凶:context length exceeded。上下文窗口被塞满了,模型开始丢失早期信息,行为彻底退化。
这不是个例。任何长时间运行的编码智能体都会遇到这个问题:对话历史、文件内容、工具调用结果、错误堆栈层层叠加,上下文像滚雪球一样膨胀,最终撑爆模型的窗口上限。传统的做法是等爆了再压缩——触发阈值后调用一次compact(),把历史对话摘要成一段短文本。但这种方式有两个致命缺陷:一是压缩发生在“已经出问题”之后,中间那段退化期产生的错误代码可能已经被写入文件;二是被动压缩往往丢失关键细节,摘要后的上下文不足以支撑后续推理,智能体需要重新读文件、重新理解任务,浪费大量 token 和时间。
AutoCompact 的思路完全不同。它的核心主张是:不要等上下文爆了才压缩,而是让智能体主动判断“什么时候该断舍离”。这就像你整理书桌——不是等桌子堆满到没法写字了才收拾,而是在每完成一个子任务后,顺手把不再需要的草稿纸扔掉,把当前要用的资料摆在手边。AutoCompact 把这个“顺手整理”的动作自动化、策略化,让编码智能体在运行过程中持续保持上下文的“清爽度”。
1.2 AutoCompact 到底解决了什么问题
用一句话概括:AutoCompact 是一套面向编码智能体的主动上下文管理机制,它通过监控上下文使用率、识别可压缩片段、在合适的时机触发压缩,让智能体在长任务中始终保持高质量的推理能力。
它解决的问题可以拆成三个层面:
- 性能层面:避免上下文溢出导致的推理退化。模型在上下文接近窗口上限时,注意力机制会被大量冗余信息稀释,关键代码和指令的权重下降,表现为“忘记任务目标”“重复已完成的步骤”“生成语法正确但逻辑错误的代码”。
- 成本层面:减少无效 token 消耗。被动压缩后智能体往往需要重新读取文件、重新执行已完成的探索步骤,这些重复劳动直接转化为 API 调用费用。主动压缩保留了“当前任务相关”的信息,砍掉了“已完成且不再需要”的部分,token 利用率显著提升。
- 可靠性层面:降低长任务失败率。SWE-bench 这类基准测试中,很多失败案例并非模型能力不足,而是长对话中上下文管理不当导致的信息丢失。AutoCompact 通过结构化的压缩策略,让关键信息(如任务目标、已修改文件列表、待办事项)在压缩后依然保留。
适合谁来参考?如果你正在开发或使用编码智能体(无论是基于 Claude Code、Codex CLI 还是自研 Agent 框架),并且遇到过“跑着跑着就傻了”的情况,那这套思路值得仔细看。如果你只是偶尔用 AI 补全几行代码,可能感受不深;但只要你让智能体连续工作超过 20 分钟,上下文管理就是绕不开的坎。
1.3 为什么“主动断舍离”比“被动压缩”更优
这里需要解释一个关键的设计哲学差异。被动压缩的逻辑是“阈值触发”——设定一个使用率阈值(比如 80%),超过就调用compact()。这听起来合理,但实际操作中有个微妙的问题:压缩本身也需要消耗 token 和时间。当你在 80% 使用率时触发压缩,压缩过程本身可能把使用率推到 95%,如果压缩效果不理想,紧接着又要触发第二次压缩,形成“压缩风暴”。
AutoCompact 的主动策略则是在任务的自然边界处进行压缩。什么是自然边界?比如一个文件修改完成、一个测试套件跑完、一个子任务标记为 done。在这些时刻,智能体对“哪些信息还有用、哪些已经完成使命”有清晰的判断。此时压缩,可以精准地砍掉已完成部分的详细记录,只保留摘要和结论。这就像写论文时,每写完一章就整理一次参考文献——你知道哪些引用已经用过且不再需要全文,哪些还需要保留以备后续章节使用。
从信息论的角度看,主动压缩的压缩比更高、信息损失更小。因为压缩决策是基于“任务阶段”做出的,而不是基于“剩余空间”做出的。前者有语义依据,后者只有数值依据。
2. 核心机制拆解:AutoCompact 是怎么工作的
2.1 上下文使用率的实时监控与分级
AutoCompact 的第一步是“知道自己有多满”。这听起来简单,但实现上有不少细节。编码智能体的上下文消耗主要来自几个部分:系统提示词(system prompt)、对话历史(user/assistant 消息)、工具调用结果(文件内容、命令输出、错误日志)、以及当前正在生成的 token。AutoCompact 需要实时计算这些部分的总量,并与模型窗口上限做对比。
我见过一些实现只监控对话消息的 token 数,忽略了工具调用结果的体积。这是个大坑——在编码场景中,一次cat一个大文件就可能吃掉几万 token,一次失败的测试输出可能包含几百行堆栈。如果监控不包含这些,使用率计算会严重偏低,压缩触发时机过晚。
一个合理的分级策略是这样的:
| 使用率区间 | 状态 | 建议动作 |
|---|---|---|
| 0% - 50% | 绿色 | 正常执行,无需干预 |
| 50% - 70% | 黄色 | 标记可压缩片段,准备压缩 |
| 70% - 85% | 橙色 | 在下一个任务边界触发压缩 |
| 85% - 95% | 红色 | 立即压缩,暂停新任务 |
| > 95% | 黑色 | 紧急压缩,若失败则终止并报告 |
这个分级不是拍脑袋定的。50% 以下时,模型注意力充足,压缩反而会打断思路;70% 左右是“开始感到拥挤但还能正常工作”的区间,适合做压缩准备;85% 以上时,推理质量已经开始下降,必须立即处理。实际参数可以根据模型窗口大小和任务类型微调,但核心逻辑是:在性能退化之前行动,而不是在退化之后补救。
2.2 可压缩片段的识别与优先级排序
知道“该压缩了”之后,下一个问题是“压缩什么”。AutoCompact 需要识别上下文中哪些片段是可压缩的,并按优先级排序。这里的“可压缩”不是指技术上能不能压缩,而是指“压缩后信息损失是否可接受”。
我总结了一个实用的优先级排序框架,从高到低:
- 已完成的工具调用详细结果:比如已经读取并理解的文件内容、已经执行完的命令输出。这些信息的“结论”已经体现在后续的代码修改或对话中,原始详细内容可以压缩成摘要。
- 已解决的错误堆栈:错误已经修复,堆栈信息不再需要逐行保留,只需保留“什么错误、怎么修的”摘要。
- 早期的探索性对话:智能体在任务初期可能尝试了多种方案,最终选择了其中一种。被放弃方案的详细讨论可以压缩。
- 重复的文件内容:同一个文件在多次工具调用中被反复读取,只保留最新版本或差异部分。
- 系统提示词和任务目标:这些是“锚点信息”,通常不压缩,或者只做极轻量的压缩。
这里有个经验:压缩优先级应该与“信息半衰期”挂钩。工具调用结果的半衰期很短——读完文件、改完代码后,原始文件内容就没用了;任务目标的半衰期很长——整个任务期间都需要保留。AutoCompact 的排序逻辑本质上是在问:“这条信息在接下来的 10 步操作中还会被用到吗?”如果答案是否定的,就优先压缩。
2.3 压缩时机的选择:任务边界检测
这是 AutoCompact 最核心也最难实现的部分。什么时候是“合适的压缩时机”?我的经验是:在任务状态发生跃迁的时刻。具体来说,以下几种情况是天然的压缩窗口:
- 文件修改完成:智能体完成了一个文件的编辑并通过了语法检查,此时该文件的原始内容可以压缩,只保留修改摘要和最终版本。
- 测试套件通过:一组测试从失败变为通过,相关的调试对话和错误日志可以大幅压缩。
- 子任务标记完成:如果智能体有显式的 todo list 或任务分解,每完成一项就是一个压缩点。
- 工具调用返回空或简单结果:比如
ls列目录、git status查看状态,这些结果用完即弃,不需要保留详细内容。 - 用户发送新指令:新指令往往意味着任务阶段切换,旧阶段的详细上下文可以压缩。
实现上,这需要智能体框架能够识别这些“边界事件”。有些框架通过解析工具调用的类型和结果来判断,有些则要求智能体显式地标记任务状态。后者更可靠,但增加了智能体的认知负担。折中方案是:框架自动检测常见边界(如文件写入成功、测试退出码为 0),同时允许智能体在需要时手动触发压缩。
注意:不要在智能体正在推理一个复杂问题时触发压缩。压缩会改变上下文,可能打断推理链。最好在工具调用返回后、下一轮推理开始前执行压缩。
2.4 压缩策略:摘要、丢弃与结构化保留
识别出可压缩片段后,具体怎么压?AutoCompact 通常组合使用三种策略:
摘要压缩:把一段详细对话或工具输出交给模型,生成一段简短摘要。比如把 500 行的测试输出摘要成“3 个测试失败,均为断言错误,涉及user_service.py的validate_email函数”。摘要的质量取决于提示词设计——要明确告诉模型“保留什么、丢弃什么”。
直接丢弃:对于完全不再需要的信息,直接删除。比如已经成功执行的pip install输出、已经读取过的配置文件内容。丢弃是最彻底的压缩,但风险也最高——万一后续需要回溯,信息就没了。所以丢弃策略通常只用于“确定不会再引用”的内容。
结构化保留:把非结构化信息转换成结构化格式。比如把多轮对话中关于“已修改文件”的信息提取成一个列表:["src/models/user.py", "src/services/auth.py", "tests/test_auth.py"]。结构化信息占用 token 少,且更容易被模型准确引用。
实际系统中,这三种策略往往混合使用。我的经验配比是:摘要占 60%,丢弃占 30%,结构化保留占 10%。摘要用于大多数场景,丢弃用于明确无用的内容,结构化保留用于关键状态信息。
3. 实操落地:把 AutoCompact 集成到编码智能体工作流
3.1 环境准备与基础配置
假设你正在使用一个支持自定义上下文管理的编码智能体框架(比如基于 LangChain、AutoGen 或自研的 Agent 循环)。集成 AutoCompact 需要几个基础组件:
- Token 计数器:用于实时计算上下文使用率。推荐使用与模型匹配的 tokenizer,比如
tiktoken(OpenAI 系列)或transformers的 tokenizer(开源模型)。如果追求速度,可以用近似计数(如按字符数除以 4),但精度会差一些。 - 上下文存储结构:需要把对话历史、工具调用结果、文件内容等分开存储,便于按类型压缩。不要把所有东西塞进一个
messages列表——那样压缩时很难区分优先级。 - 压缩触发器:一个独立的模块,负责监控使用率、检测任务边界、调用压缩函数。
- 压缩函数:接收可压缩片段,返回压缩后的内容。可以调用模型做摘要,也可以用规则做丢弃和结构化。
一个最小化的配置示例(Python 伪代码):
class AutoCompactConfig: model_window = 128000 # 模型上下文窗口 yellow_threshold = 0.50 orange_threshold = 0.70 red_threshold = 0.85 black_threshold = 0.95 summary_model = "gpt-4o-mini" # 用于生成摘要的模型 max_summary_tokens = 500 # 单次摘要的最大 token 数 preserve_recent_turns = 5 # 保留最近 N 轮对话不压缩这里的关键参数是preserve_recent_turns。最近几轮对话通常包含当前正在处理的问题,压缩它们会打断推理。我的经验是保留 3-5 轮,具体取决于任务的复杂度和每轮的信息密度。
3.2 上下文分区的具体实现
为了让压缩有的放矢,我习惯把上下文分成几个逻辑分区,每个分区独立管理:
| 分区名称 | 内容 | 压缩策略 | 保留优先级 |
|---|---|---|---|
| 系统区 | 系统提示词、工具定义 | 不压缩 | 最高 |
| 目标区 | 任务描述、用户指令 | 轻量压缩 | 高 |
| 状态区 | 已修改文件、待办事项 | 结构化保留 | 高 |
| 对话区 | 历史对话消息 | 摘要压缩 | 中 |
| 工具区 | 工具调用结果 | 摘要+丢弃 | 低 |
| 临时区 | 当前正在处理的内容 | 不压缩 | 最高 |
这个分区方案的好处是:压缩时可以按分区批量处理,而不是逐条消息判断。比如“工具区”中超过 3 轮之前的调用结果,可以直接进入压缩队列;“对话区”中超过 5 轮之前的消息,可以批量摘要。
实现上,我建议用一个ContextManager类来管理这些分区,提供add_message、add_tool_result、get_context、compact等方法。每次添加新内容后,自动更新使用率并检查是否需要触发压缩。
3.3 压缩触发的代码实现与参数计算
下面是一个简化的压缩触发逻辑:
def check_and_compact(context_manager, config): usage = context_manager.get_usage_ratio() if usage < config.yellow_threshold: return # 绿色,不处理 if usage < config.orange_threshold: # 黄色:标记可压缩片段,但不执行 context_manager.mark_compactable() return if usage < config.red_threshold: # 橙色:在下一个任务边界执行压缩 if context_manager.at_task_boundary(): context_manager.compact(level="normal") return if usage < config.black_threshold: # 红色:立即压缩 context_manager.compact(level="aggressive") return # 黑色:紧急压缩,若失败则终止 success = context_manager.compact(level="emergency") if not success: raise ContextOverflowError("无法压缩上下文,任务终止")这里的compact方法根据 level 不同采取不同策略:normal只压缩工具区和早期对话;aggressive额外压缩状态区的详细内容,只保留结构化摘要;emergency则尽可能压缩一切非锚点信息,包括对目标区做极简摘要。
参数计算方面,get_usage_ratio需要准确统计所有分区的 token 数。我通常用以下公式:
usage_ratio = (system_tokens + goal_tokens + state_tokens + dialogue_tokens + tool_tokens + temp_tokens + reserved_for_response) / model_window其中reserved_for_response是留给模型生成回复的空间,通常设为窗口的 10%-15%。如果不预留,模型可能在生成到一半时被截断。
3.4 与 SWE-bench 类任务的结合实践
SWE-bench 是评估编码智能体解决真实 GitHub issue 能力的基准。这类任务的特点是:需要理解 issue 描述、定位相关文件、修改代码、运行测试、迭代修复。一个典型任务可能涉及 10-30 轮工具调用,上下文很容易膨胀到 50k-100k token。
我在一个类似 SWE-bench 的流程中集成了 AutoCompact,具体做法是:
- 任务初始化:把 issue 描述、仓库结构、相关文件列表放入“目标区”和“状态区”,这些信息全程保留。
- 探索阶段:智能体读取文件、搜索代码。每次工具调用结果放入“工具区”,超过 3 轮后自动摘要。
- 修改阶段:智能体编辑文件。每次编辑成功后,该文件的原始内容从“工具区”移除,只保留修改摘要和 diff。
- 测试阶段:运行测试。失败的测试输出保留详细内容(因为需要调试),通过的测试只保留“通过”状态。
- 迭代阶段:根据测试结果修复。此时“对话区”的早期探索对话已经压缩,但“状态区”的已修改文件列表和待办事项完整保留。
实测下来,这套机制让一个原本在 60k token 左右开始退化的任务,稳定运行到 120k token 窗口的 90% 使用率,且最终修复成功率提升了约 15%。提升主要来自两个方面:一是减少了“忘记已修改内容”导致的重复修改;二是保持了测试失败信息的可追溯性,智能体能准确知道哪个测试还在失败。
提示:在 SWE-bench 类任务中,测试输出是最容易膨胀的部分。建议对测试输出做结构化解析,只保留失败测试的名称、错误类型和关键堆栈行,丢弃通过测试的详细输出。
4. 常见问题与排查技巧实录
4.1 压缩后智能体“失忆”怎么办
这是最常见的问题。压缩后智能体突然问“我们要做什么来着?”或者重新开始读取已经读过的文件。原因通常是压缩过度,把任务目标或关键状态也压掉了。
排查思路:先检查“目标区”和“状态区”是否被误压缩。这两个分区应该设置最高的保留优先级,压缩时只做极轻量的摘要,甚至完全不压缩。如果确认是这两个分区的问题,调整压缩策略,把它们的优先级提到最高。
另一个可能的原因是摘要质量太差。如果摘要模型没有正确理解“什么重要”,生成的摘要可能丢失关键信息。解决办法是优化摘要提示词,明确要求保留:任务目标、已修改文件、待办事项、当前阻塞点。我常用的摘要提示词模板是:
请将以下对话/工具输出压缩成一段简短摘要。必须保留: 1. 任务目标和当前进度 2. 已修改的文件及其修改内容概述 3. 待解决的问题和错误信息 4. 下一步计划 丢弃:详细的文件内容、成功的命令输出、已解决的错误堆栈。4.2 压缩触发过于频繁导致性能下降
有些实现中,压缩本身消耗大量 token 和时间,如果触发太频繁,整体效率反而下降。我见过一个案例:每轮对话后都检查使用率,一超过 70% 就压缩,结果压缩调用本身把使用率推到 80%,下一轮又触发,形成循环。
解决办法是引入“压缩冷却期”。每次压缩后,至少等待 N 轮对话或 M 秒再检查下一次压缩。N 和 M 根据任务节奏调整,通常 N=3、M=30 秒比较合适。另外,压缩时不要追求“压到最低”,而是“压到安全区间即可”。比如从 85% 压到 65% 就够了,不需要压到 30%。
4.3 工具调用结果被压缩后无法回溯
有时候智能体需要回溯之前的工具调用结果,比如“刚才那个文件的第 42 行是什么?”如果原始内容已经被压缩丢弃,智能体就无法回答。这在调试场景中很常见。
我的做法是:对工具调用结果做“两级存储”。压缩时,详细内容不直接删除,而是移到一个“冷存储”中(可以是内存中的字典,也可以是磁盘文件),上下文中只保留摘要和一个引用 ID。如果智能体需要回溯,可以通过引用 ID 重新加载详细内容。这样既节省了上下文空间,又保留了回溯能力。
当然,冷存储也不能无限增长。可以设置一个上限,比如保留最近 20 个工具调用的详细内容,更早的彻底删除。
4.4 常见问题速查表
| 问题现象 | 可能原因 | 排查方法 | 解决方案 |
|---|---|---|---|
| 压缩后智能体重复已完成步骤 | 状态区被过度压缩 | 检查状态区保留策略 | 提高状态区优先级,保留结构化任务列表 |
| 压缩触发后使用率不降反升 | 压缩调用本身消耗过多 token | 统计压缩前后的 token 变化 | 引入冷却期,降低压缩频率 |
| 智能体无法回答关于早期文件内容的问题 | 工具结果被彻底丢弃 | 检查工具区压缩策略 | 引入冷存储,保留引用 ID |
| 压缩后代码修改出现语法错误 | 摘要丢失了关键代码细节 | 对比压缩前后的代码相关上下文 | 对代码内容做结构化保留,不只做文本摘要 |
| 长任务中途终止并报上下文溢出 | 紧急压缩失败 | 查看压缩日志和剩余 token | 提前触发压缩,不要等到黑色区间 |
| 压缩后测试失败信息丢失 | 测试输出被过度摘要 | 检查测试相关工具结果的压缩策略 | 对失败测试保留详细输出,通过测试才摘要 |
4.5 几个我踩过的坑和对应技巧
坑一:用同一个模型做摘要和主任务。如果摘要模型和主任务模型是同一个,摘要调用会占用主任务的 token 配额,而且摘要质量可能受主任务上下文干扰。我的做法是用一个更小、更便宜的模型专门做摘要,比如主任务用大模型,摘要用小模型。这样成本低、速度快,且摘要模型不受主任务上下文影响。
坑二:压缩时没有保留“否定信息”。什么叫否定信息?比如“不要修改config.py”“这个方案已经试过不行”。这些信息在摘要时容易被忽略,但后续智能体可能重新尝试被否定的方案。解决办法是在摘要提示词中明确要求保留“已排除的方案和原因”。
坑三:任务边界检测过于依赖显式标记。如果智能体忘记标记任务完成,压缩就不会触发。我的做法是结合隐式检测:当工具调用返回成功且下一轮没有立即发起新的工具调用时,视为一个潜在边界。虽然不完美,但比纯依赖显式标记可靠。
坑四:压缩后没有更新使用率。有些实现压缩完就完了,没有重新计算使用率,导致下一次检查时用的还是旧数据。这是个低级错误,但很容易犯。确保压缩函数返回后立即调用update_usage()。
5. 从 AutoCompact 看编码智能体的上下文管理演进
5.1 当前方案的边界与局限
AutoCompact 不是银弹。它的核心假设是“任务有清晰的阶段边界”,但在一些场景下这个假设不成立。比如探索性任务——智能体需要反复在多个文件之间跳转、对比、推理,没有明显的“完成一个文件”的边界。这种情况下,压缩时机很难判断,压缩过度会丢失探索线索,压缩不足则上下文继续膨胀。
另一个局限是摘要的信息损失不可避免。无论摘要提示词多精细,总有一些“当时看起来不重要、后来发现关键”的信息被丢掉。这在调试复杂 bug 时尤其明显——一个看似无关的日志行可能是根因线索。
还有一个实际问题是:AutoCompact 增加了系统的复杂度。你需要维护分区、监控使用率、管理压缩策略、处理冷存储。对于简单的短任务,这套机制是过度设计。我的建议是:任务预计超过 20 轮工具调用或上下文可能超过窗口 50% 时,才启用 AutoCompact。
5.2 可以继续扩展的方向
如果你已经跑通了基础的 AutoCompact,以下几个方向可以进一步探索:
自适应压缩策略:根据任务类型自动调整压缩参数。比如重构任务可以更激进地压缩工具结果,调试任务则需要保留更多错误细节。实现上可以用一个简单的分类器判断任务类型,或者让智能体在任务开始时声明类型。
压缩效果评估:压缩后跟踪智能体的表现,如果发现退化,自动回滚到压缩前的状态并调整策略。这需要保存压缩前的上下文快照,对存储有一定要求,但能显著提升鲁棒性。
多级摘要:对特别长的对话,做两级摘要——先按阶段摘要,再对阶段摘要做整体摘要。这样可以在极低 token 占用下保留任务的宏观脉络。
与外部记忆结合:把压缩掉的信息存入向量数据库,智能体需要时通过检索召回。这相当于给智能体一个“长期记忆”,上下文只保留“工作记忆”。这个方向在长周期任务中很有潜力。
5.3 一个实用的小技巧收尾
最后分享一个我在实际使用中总结的小技巧:在压缩前让智能体自己生成一份“交接文档”。具体做法是,触发压缩时,先让智能体回答几个问题:“当前任务目标是什么?已完成哪些修改?下一步要做什么?有哪些已知的坑?”把这份交接文档作为压缩后的“锚点信息”放在上下文最前面。这样即使压缩丢失了一些细节,智能体也能通过交接文档快速恢复状态。
这个技巧的好处是,交接文档是由智能体自己生成的,它知道什么信息对“未来的自己”最重要。比外部摘要模型生成的摘要更贴合任务需求。实测下来,这个小小的改动让压缩后的任务恢复速度提升了将近一倍。
编码智能体的上下文管理还在快速演进,AutoCompact 只是其中一种思路。但核心原则是不变的:主动管理优于被动补救,结构化保留优于无差别压缩,任务边界优于固定阈值。把这三点吃透,无论你用什么框架、什么模型,都能让智能体跑得更久、更稳、更聪明。