
我们有个长任务智能体,跑一段时间就会撞到窗口上限,然后开始丢上下文。最先想到的是压缩——把前面摘成一段,重开窗口。做完之后发现效果一般,账还更贵了。后来把窗口里的内容拆开看,问题根本不在压缩。
Anthropic 官方 cookbook 给过一个实测运行,一个研究型智能体的窗口构成是这样:
我第一次看到这个比例是有点懵的。也就是说,窗口里九成六的空间被工具返回的原文占着,真正的对话和推理只有 3.7%。这部分原文在模型看完那一刻就已经没用了,但之后每一轮都还在按输入计费。
所以改造顺序应该反过来:先清工具结果,再考虑压缩。
清理的逻辑只有一句话:把旧的工具返回内容换成一个短占位串,但保留"调用过这个工具、用的是这组参数"这条记录。模型知道自己调过,只是正文没了。真需要那份数据,再调一次工具就行。
我这边落地的版本,参数对齐官方的三个字段:
def clear_old_tool_results(msgs, keep=3, trigger=100_000, clear_at_least=10_000):
"""把旧的工具返回换成占位串。
keep : 保留最近几次工具结果
trigger : 输入量达到多少才触发
clear_at_least : 地板,一次至少清掉多少 token,否则不动
"""
if count_tokens(msgs) < trigger:
return msgs
idx = [i for i, m in enumerate(msgs) if m.get("role") == "tool"]
victims = idx[:-keep] if keep > 0 else idx
if not victims:
return msgs
freed = 0
for i in victims: # 从最旧的开始
if msgs[i].get("cleared"):
continue
freed += count_tokens(msgs[i])
msgs[i]["content"] = "[工具结果已清理,如需原始内容请重新调用]"
msgs[i]["cleared"] = True
if freed >= clear_at_least:
break
return msgsclear_at_least 这个地板千万别省,原因在第四节。
效果对得上官方给的数:三份各约 4 万 Token 的文本文档,keep 设成 1,上下文从约 128,740 Token 降到约 43,060,少了 67%。而且这一刀不动推理质量,因为模型仍然能看见自己调过什么工具。
代价也得提前想清楚:走到汇总那一步,智能体手上只剩自己的笔记加最近几份读取。我这边笔记写得细,没出问题;如果你的智能体习惯"读一遍就靠记忆",清完可能会开始反复重读。重读本地文件几乎不要钱,但重调一个有限速的第三方接口,就是另一笔账了。
清理之后如果还顶得上限,再上摘要式压缩。做法是把前面一大段总结成一份摘要,用它开新窗口继续。
Claude Code 的取舍可以当参考:留架构决定、没解决的问题、实现细节;丢重复的工具输出;再带上最近访问的五个文件。
如果用的是 Google ADK,它把这件事做成了配置项(下面按官方字段写,具体以文档为准):
compaction_config = {
"compaction_interval": 3, # 每 3 个事件压一次
"overlap_size": 1, # 新汇总带上 1 个上一轮的事件
}间隔 3、重叠 1 的实际走向是:
留一个重叠是为了两份汇总能接上,代价是后面每份汇总都比第一份大。另外 ADK 里还有一条按 Token 走的策略,文档把它列为主要的那条,会故意留几个最近事件不压,让"它""那个"这类指代有得可指。
调压缩提示词有个固定顺序:先拉满召回,再往回删。反着来必翻车,压狠了丢的都是当时不起眼、后面才要命的那几句。还有一条:在真实的长轨迹上调,短对话根本试不出问题。
压缩和清理默认都在改"前面那一段",而前缀缓存的前提是前面一个字不动。官方文档把账写得很清楚:清理会让已缓存的前缀失效,每次都要付一笔写入费,之后的请求才能复用新前缀。
这就是 clear_at_least 存在的意义:一次只清出几百个 Token,却把整段缓存打掉重写,净亏。官方示例的地板是 1 万个 Token——至少能收回这么多才动手。
所以真正要控制的是频率,不是"压不压"。一次大改动摊得开,每轮一个小改动摊不掉。
有个真实的修法值得抄。某个编程智能体原先从最旧的工具结果开始清,于是每轮都从很靠前的位置把缓存打断。有人把循环反过来,从最新的开始清,前缀就逐字不变了,只有尾巴在变。代价是丢掉的是近期输出而不是旧输出——他们判断这段逻辑只在上下文超过 75% 时当应急触发,每轮全量重算的代价比那点质量损失更大。
压缩有损是共识,真正难的是丢了什么不会写进摘要。被丢掉的常常是顺带提过的一条约束,或者当时特批的一个例外。丢了之后,智能体照常往下办,没有任何提示。
反复压还有第二种问题:摘要的摘要,偏差会一层层叠上去。两种防法:
验收很土但有效:压完对着原始记录把硬约束点一遍。别看摘要读起来顺不顺——读起来顺,不代表里面的例外还在。
还有一点我踩过:压缩提示词通常是框架自带的默认值,装上就没人再改,但它决定了智能体后半程还记得哪几条约束。这个配置项基本处于"没人验收"的状态。验法不难,找一条跑满窗口的真实轨迹,压一次,逐条核对,少哪个就写进必留项。
压缩不是唯一解,官方把长任务的办法分成三样:
NOTES.md 就够。判断标准就一句:做最简单管用的那件事。
我这次的改造顺序是:先测窗口构成 → 清工具结果(-67%)→ 再看要不要压缩 → 设缓存地板 → 对原始记录验收。省钱只是副产品,真正救回来的是"模型还记不记得当初那几条约束"。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。