最近在折腾编码智能体的时候,我发现一个很有意思的现象:很多人选模型时盯着上下文窗口大小看,1M、200K、128K,觉得窗口越大越安心,却几乎没人提前考虑上下文会被用完这件事。1M token听着很宽裕,但真到了连续开发三四个小时、中间贴过几段源码、跑过几轮测试、试过两种方案之后,该爆还是爆。等窗口真的满了,要么强硬截断,要么手动粘贴摘要,结果AI开始失忆,把之前否掉的需求又捞回来反复讨论。AutoCompact这套方案的出发点,就是把上下文压缩从“临终抢救”变成日常整理。这篇文章我会把它的触发机制、内容分级、压缩流程和配置参数全部拆开讲清楚,适合正在用编码智能体做长任务的人,也适合想给自己搭一套上下文管理工具的朋友。
1. 别等上下文爆了才开始抢救:先看真实的“爆掉”现场
1.1 一次多轮重构任务里,上下文是怎么被吃光的
设想一个典型场景:早上十点,你准备把一个单体服务的支付模块拆出去,做成独立的微服务。这活儿涉及接口梳理、依赖分析、数据表变更、迁移脚本,任何一个有经验的工程师都不会指望一轮对话搞定。
任务开始后,你让编码智能体先梳理模块现有的接口和依赖关系。它会读好几个源码文件,把关键代码片段贴出来,生成一张依赖清单,这一轮大概消耗1500到3000 token。接着你要求它对比两种拆分方案:按领域边界拆和按数据表归属拆。它会列出各自的优缺点、影响面、迁移成本,这一轮直接消耗4000到6000 token。你拍板选了方案一,让它开始实现第一个接口的抽取,它要把Controller、Service、Mapper三层代码分别读取、摘录、改写,又是七八千token。
“感觉还好?”你可能这么想。但别忘了,这过程中还夹杂着它的工具调用记录、你纠正它理解偏差的对话、两次测试运行的输出。到中午十二点半,累计消耗可能已经到6万到8万token。128K窗口还剩下一半多,看着安全。
真正的爆点往往出现在下午。你发现有个核心表被三个服务共同引用,原本的方案有风险,不得不再讨论一次调整策略。你为了让它快速理解,直接复制了一段数据库迁移脚本和线上索引信息进对话,又是两万多token。到下午三点左右,窗口使用量悄悄越过75%,模型开始出现“早期结论被忽略”的迹象——你明明上午已经排除过方案二,它却又把方案二提出来当成新思路。
这不是段子,是我实际见过很多次的轨迹。上下文的使用不遵循匀速增长,它更接近指数抬升:前期是方案讨论、代码摘录,中期是调试输出、报错日志,后期为了补救前期信息丢失,还要额外花token把旧结论重新解释一遍。
1.2 被动压缩为什么总是让人抓狂
等模型终于提示“会话上下文接近上限”时,多数人会做一件事:手动整理。把之前的方案讨论删掉,把报错日志清掉,把早先粘贴的代码删掉。这个动作看起来合理,问题在于:删除的人其实并不总能判断哪些信息对模型的后续决策仍然有意义。
举个例子,你在上午的讨论里否决方案二的理由是“它会把订单表和支付表拆到不同事务边界,补偿机制成本太高”。这个理由在当时的对话里只出现了一次,之后没有再被引用。整理上下文时你顺手删掉了一大段方案对比分析,包括这句话。模型在下午继续设计数据迁移时,不知道事务边界这个约束存在,于是再次提出了跨服务事务方案。
被动压缩的另一个问题,是它本质上打断思路。手动整理往往发生在你正在处理一个复杂问题、上下文恰恰最稀缺的时刻,就像打仗打到一半突然要清点弹药库,最耽误事。而且每手动压缩一次,模型短期内会出现一轮“适应期”,表现明显下降,因为前后的信息分布变了。
1.3 “1M上下文”为什么不是免死金牌
有人会说:“那我换支持1M token的模型总可以吧?”窗口大了,确实能延缓问题,但解决不了问题本身,原因有三。
第一,成本不是线性的。上下文从128K涨到1M,单位token的推理成本不一定等比上涨,但单次请求必须包含全部历史输入,单轮调用费用和响应延迟都会明显增加。对一个需要频繁交互的编码智能体来说,每次都背负几十万token的历史,每轮响应时间会从几秒拉到几十秒,体感通常难以接受。
第二,注意力会被稀释。深度学习模型对长上下文并不是一视同仁的,已有研究反复提到“lost in the middle”现象:处在超长上下文中间位置的信息,被模型“看见”的概率显著低于开头和结尾。就算你把所有对话都塞进去,模型也可能忽略掉夹在中间的关键决策。
第三,信息过载本身就增加幻觉风险。当模型面对太长的输入,它更倾向于基于最近一段内容做推断,早期约束自然会被遗忘。这不是模型不够聪明,而是注意力的有限分配决定的。
所以1M窗口的真实意义,是给你留出“不压缩的缓冲期”,而不是让你理直气壮地不整理。真正健康的用法,恰恰是在长窗口里也要设置压缩水位——这比在小窗口里做压缩回报更明显,因为单次压缩释放的token量更大。
2. 编码智能体需要记忆的,其实不是对话本身
2.1 上下文是工作台,不是仓库
要设计好压缩方案,先得弄清楚编码智能体的上下文里到底什么信息有价值。我的类比是:上下文是操作员面前的工作台面,而不是仓库。
仓库(你的代码仓库、文件系统、数据库)里的东西,任何时刻需要都能重新去取。比如某个文件的完整源码、某次运行的日志、某个接口的完整定义——想拿的时候再读就行,没必要一直摆在上下文中。
工作台面上的东西,是当前决策必须随时可见的材料:用户定下的最终目标、当前正在使用的方案、已经完成的事项、接下来要做的步骤。这些信息一旦被清走,模型就无法理解“现在处于任务的哪个阶段”,后面的每句话都会产生偏差。
所以压缩算法的第一原则就是:区分这两类信息。可重建的东西大胆清,不可重建的东西小心护。
2.2 编码任务的状态机:拆开看才知道什么不能删
一个编码任务本质上是一台状态机。我是这样拆的:
第一层,需求层。用户最终想要什么效果:订单超时自动退款、支付模块独立部署、日志链路完整。这层信息优先级最高,任何压缩都碰不得。
第二层,决策层。当前采用什么技术方案,以及为什么采用它。包括选型理由、排除掉的替代方案和否定的原因。“不改A表的索引结构”这种决策,比“A表索引当前长什么样”更重要,因为后者随时可以从库里查,前者一旦丢失就只能靠猜。
第三层,执行层。改过哪些文件、加过哪些接口、哪些测试通过了、哪些还有问题。这层是任务进度条,可以压缩,但必须保留成清晰的清单,不能用“已完成相关工作”这种模糊表述糊弄过去。
第四层,噪音层。调试时的报错堆栈、临时日志、工具返回的原始JSON、试错过程中的废弃代码。这层信息密度低,需要的时候重跑一遍就能重新拿到,是压缩的首要对象。
这四层划分,是AutoCompact做内容分级的基础。
2.3 顺手澄清:执行上下文和模型上下文不是一回事
搜索热词里有个“执行上下文”,还有一个“中断上下文”,得说清楚:这两个是操作系统领域的概念,和编码智能体讨论的“上下文”层级完全不同。操作系统里,执行上下文是指CPU切换任务时保存的寄存器、程序计数器、栈指针等一套运行状态;中断上下文则是处理中断时内核需保存的环境信息。它们强调的是“程序运行到哪一步了”。
编码智能体说的上下文,指的是模型输入序列中的全部内容——系统指令、用户历史消息、助手回复、工具调用记录、观察结果。它的本质是模型做下一个token预测时能“看到”的文本范围。两者的共同点只有一个:都在描述“当前状态需要保留哪些信息才能继续”。但因为底层技术完全不同,做管理时不要混用系统层面的方案来对付模型上下文。
这一点在学习资料时特别容易踩坑,搜索“上下文压缩”经常搜出操作系统课程,别被带偏了。
3. AutoCompact如何实现主动「断舍离」
3.1 触发机制:在半满的时候整理,而不是等全满再抢救
AutoCompact最关键的设计调整,是把压缩时机从“上下文即将溢出”提前到“上下文用到一半”。
我采用的触发条件是双阈值:水位阈值加增量阈值。水位阈值设为45%,意思是当上下文用量首次达到45%时,触发第一轮压缩。压缩完成之后,并不是立刻进入等待状态,后续每当窗口使用量再增加10个百分点(也就是达到55%、65%、75%),就再做一轮增量压缩。
为什么是45%而不是80%?两个原因。第一,压缩本身需要消耗token。让模型阅读当前上下文、生成摘要、归档信息,这个过程会先把窗口塞得更满一些,如果启动水位太高,压缩还没完成窗口就满了,操作直接失败。第二,45%时窗口里通常已经积累了足够多的噪音层内容(日志、输出、重复讨论),又还留有充足空间做压缩操作,这是个安全的操作窗口。
触发阈值不能设太低。如果设在20%,上下文里根本还没积累多少可压缩内容,每几轮就触发一次压缩,每次只能释放一点点空间,但付出的模型调用成本却一点不少,还会打断对话节奏。我在实验里的经验是,40%到50%之间是比较合理的水位区间。
3.2 内容价值分级:不是所有内容都配占窗口
压缩动作之前,先要对当前上下文做一次内容审计。我把它分成五个等级,每个等级对应不同的处理方式:
| 优先级 | 内容类型 | 处理方式 |
|---|---|---|
| S级 | 系统指令、任务最终目标、当前方案决策 | 原样保留,永不压缩 |
| A级 | 任务进度清单、已完成/待办事项 | 保留,只做精简表述 |
| B级 | 关键分析过程、方案对比讨论 | 压缩为决策摘要 |
| C级 | 报错日志、调试输出、工具返回数据 | 只留关键摘要,详情归档 |
| D级 | 废弃方案细节、早期重复讨论 | 直接删除或一句话带过 |
这个分级不是设计者拍脑袋定的,而是依据“不可重建性”来排序的:系统指令和任务目标不可重建,必须永久保留;报错日志和工具输出随时可以再生成,所以大胆压缩。价值分级是压缩策略的核心,“断舍离”的“舍”,也是按这个标准在执行。
3.3 三步压缩流程:摘要、归档、对齐
AutoCompact的压缩动作分三步走,每一步有明确产出。
第一步,语义摘要。对B级和C级内容,模型要生成一段固定格式的摘要:目标、结论、依据。为什么用三段式而不用自由文本?因为压缩后的上下文是给后续模型看的,它必须能在下一秒快速检索到关键事实。三段式结构相当于给它一个稳定的检索索引,后续对话如果需要某个决策理由,直接去摘要里找“依据”字段就行。
第二步,信息归档。被摘除的完整内容不能简单丢弃。尤其是调试日志和报错堆栈,可能之后还要复盘。AutoCompact会把完整内容写入工作目录下的隐藏文件夹,比如 .compact_history/,并在上下文里只留下一个指针式引用:“完整日志见 .compact_history/run_003.log”。模型需要细节时,可以按需读取文件,而不是让所有细节一直占着窗口。归档这一步,相当于给清走的物品找了个仓库柜子,而不是直接扔进垃圾桶。
第三步,状态对齐。压缩动作完成后,AutoCompact会把当前任务状态重新写到上下文最前部:任务目标是什么、已完成哪些、剩余待办是哪些、下一步先做什么。这一步是为了抵消压缩带来的“上下文断层”。有了状态对齐段落,后续对话即使面对全新的信息分布,也能立刻知道自己在哪,要去哪。
这三步流程里,最容易忽略的是第三步。很多人只做摘要和归档,结果压缩后模型虽然还记得零散信息,却丢失了“整体进度感”,话语开始变得飘。状态对齐相当于给压缩后的工作台贴了一张任务便签,价值很大。
3.4 防“失忆”自检:压缩不能靠赌
压缩做完了,怎么知道有没有压坏?AutoCompact的做法是加一轮自检。
压缩完成后,模型需要回答三个问题:当前任务目标是什么?已经完成了哪些工作?下一步要做什么?把这三个答案与压缩前记录的任务状态做一致性比对。如果答案一致,说明压缩过程没有破坏核心信息,压缩成功。如果不一致,说明压缩掉了关键信息,触发回滚——恢复到压缩前的完整上下文,并且调整压缩策略,把刚才触发问题的内容提升一个保留等级。
这套自检机制的价值在于:它把“压缩是否成功”从不可控变成了可验证。不用赌压缩会不会让模型失忆,而是每次压缩后主动确认。一开始我嫌这个自检麻烦,跳过了,结果有一次压缩后模型忘了任务里“不做跨库事务”的约束,白白浪费了两个小时的对话。后来老老实实加回来,再没出过类似问题。
4. 触发参数怎么配、压缩效果实测、踩过的坑
4.1 一套可以直接抄的初始配置
给出我目前在用的配置,供参考:
- 触发水位:45%首轮,之后每增长10%触发一轮
- 最小压缩释放量:单轮压缩至少释放15%的窗口空间,否则放弃本轮压缩
- 摘要格式:目标-结论-依据三段式
- 归档路径:工作目录/.compact_history/
- 自检要求:三个问题一致性得分低于0.8时回滚
- 永久保留层:S级与A级
几点说明。最小压缩释放量这个参数,是为了避免“空转压缩”——比如当前窗口里只有5000 token的C级内容,压缩后只能省2000 token,还不到总窗口的2%,这种压缩没有意义,跳过更划算。归档路径建议放进代码仓库忽略列表,别让这些临时文件污染版本管理,我见过有人把日志归档文件提交进仓库,后面清理起来很狼狈。
4.2 压缩前后的实测对比
拿一个真实跑过的中型任务做展示。任务是把一个Spring Boot项目里的支付模块拆分成独立服务,涉及二十多个文件、三次方案调整、两轮回归测试。
不压缩的基准组:窗口128K。跑到第65轮对话时,上下文使用量到80%,模型开始明显遗忘早期决策,把已经否掉的方案重新提出。到第95轮,窗口彻底塞满,对话被强行中断,主持人手动总结历史、新开对话重新交代背景,前后多花了大约30分钟。
开启AutoCompact的实验组,同样任务、同样模型:第一轮压缩发生在第42轮,窗口使用量45%,当时上下文里大约有22000 token的C级内容(调试日志、测试输出、工具返回),压缩归档后释放了约15000 token。后续在55%、65%水位各触发一轮增量压缩,每轮释放量在8000至12000 token之间。任务最终跑了140轮对话,窗口使用量始终压在70%以内,全程没有中断。更重要的是,把第130轮时模型对早期决策的记忆做了抽查——“当初为什么排除按数据表归属拆分方案”,它能准确回答出“订单表和支付表会落到不同事务边界,补偿成本太高”。这个细节在原始对话里出现过两次,一次是第8轮方案对比,一次是第31轮调整策略时重提。如果没有压缩保护,这个信息大概率会被后续大量日志淹没。
当然要说明,压缩不是降本神器,它本身就是一种开销。实验组多了四次压缩动作,每次压缩大概耗掉2000到4000 token的调用量,整体token用度其实比基准组还高一些。但它是划算的:多花一两万token,换来任务不中断、记忆不失真,远比重新交代背景省心。
4.3 我踩过的坑和对应的处理
第一个坑,摘要写得太“节约”。早期版本给压缩指令加了一个“尽可能简短”的约束,结果B级内容被压成一行:“讨论了方案一和方案二,决定用方案一”。后面模型反复追问“方案一是什么”,又回到原始内容找细节,token反而花得更多。后来把压缩指令改成“信息密度优先,长度其次”,并强制使用目标/结论/依据三段式,问题才消失。
注意:压缩的目标不是把token压到最低,而是保住后续对话检索所需的信息密度。压得太过,后续为找回细节付出的token比省下的还多。
第二个坑,压缩抖动。参数表里最初只设了水位阈值,没设增量阈值。结果上下文在44%到46%之间反复横跳,每几轮就触发一次压缩,对话节奏被不断打断。加了“每增长10%才触发下一轮”的门槛,并且要求距离上次压缩至少隔五轮对话,抖动才消失。
第三个坑,动过系统指令。不是每次压缩都一定不能碰系统指令,但我试过把工具使用说明压缩成摘要,结果模型在调用工具时出现了格式错误——摘要把关键参数格式的示例给丢了。从那以后,S级内容设置成永不压缩,自定义的格式说明永远保留原文。教训是:文法层的格式约束,不属于可摘要内容,它需要一字不差的精确性。
第四个坑,归档文件不清理。压缩产生的归档日志,在完成一个大任务后能攒出几百MB。别等磁盘满了再想起来,我养成的习惯是任务收尾时把.compact_history/里超过七天的归档文件清一遍。一行命令就能解决:
find .compact_history/ -type f -mtime +7 -delete如果你希望清理逻辑更可控,也可以用简单的Python脚本封装,定期在任务完成后跑一次。
如果你也在用编码智能体做长任务,我的建议是别等窗口报警再动手,从下一次任务开始就配上一套主动压缩机制。第一版参数不用完美,跑一两次任务再调都来得及。我自己的下一步计划,是把压缩归档和代码提交记录打通,让模型恢复旧任务时能直接从归档点继续。这算是在“断舍离”之后,再给清走的内容找好归处。等这套流程跑通了,再单独写一篇分享出来。