news 2026/10/11 7:57:41

长任务跑到一半失忆:压缩策略比压缩比例更关键

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
长任务跑到一半失忆:压缩策略比压缩比例更关键

版权与内容来源声明
本文为原创整理。文中涉及官方文档、开源仓库、论文与公开报道的内容,均在附表 A 中标注来源;引用官方原文保持原样,不作改写。文中命令、版本号与界面截图以本文成文时的实测/核验结果为准,标注「待验证」的部分请以你本地环境实际输出为判断依据。本文不推荐任何不合规的软件获取方式,也不对任何收益结果作承诺。转载请注明出处。

一、先看现场:任务跑到第 60 轮,它突然开始"忘规矩"

现场不是一次崩溃,是一行没人报警的日志。

上线第 9 天的凌晨,我回翻一个运营 Agent 的会话记录。第 60 轮,它调用了发件工具,把一封邮件发了出去——正文里还带着客户手机号。而它接到的第一批指令里,三条硬约束写得清清楚楚:

  • 预算上限 8000 元,超过必须停下来问我
  • 只允许用企业微信发通知,不要发邮件
  • 客户名单里的手机号不能出现在任何对外文本里

第 60 轮,这三条它一起破了:用邮件(违反第 2 条)、正文里带手机号(违反第 3 条)、给一笔超预算的单子放行且没有回来问(违反第 1 条)。没有报错,没有异常堆栈,也没有一句"我忘了规矩"。它不是崩溃了,它是把规矩当成了从来没存在过。

1.1 三条约束,是我在第 1–3 轮亲手交代的

这三条不是比喻,是原样贴进首轮消息里的字符串。之所以写成这么死的样子——带具体数字、带"必须停下来问我"这种动作指令——就是因为它们属于违规之后不可逆的那一类:邮件发出去收不回,手机号泄露收不回,钱花出去收不回。

1.2 故障不是一步到的:五个阶段

复盘日志后会发现,整个塌陷是有台阶的:

阶段轮次触发条件表面上看到的现象
教规矩第 1–3 轮任务开始,贴入三条硬约束模型逐条复述,包含"超过必须停下来问我"
稳定期第 4–40 轮正常工具循环行为全部合规,无需人工干预
第一次压缩第 41 轮上下文逼近窗口上限,平台自动压缩措辞略变,行为无异常
第二次压缩第 58 轮再次触顶,又压一次开始漏问,偶发"这个我按默认处理了"
塌陷第 60 轮第三次触发发邮件 + 带手机号 + 超预算放行

注意第 3 行:第一次压缩之后,一切看起来都正常。这是这类故障最阴的地方——它不在压缩那一刻爆,它在压缩把最早的几轮挤掉之后,隔了十几轮才爆。

1.3 我先排除了三个"背锅侠"

动手改之前,我先把最容易怪的三样排掉:模型没换、提示词没改、工具没动。日志里唯一变化过的,是上下文本身被压过三次。所以问题不在"模型不听话",而在"模型已经看不到规矩了"。

二、三种压缩方式,谁把硬约束保住了

压不压得住约束,取决于压缩动作"先动哪一段",而不是压掉了多少 token。

大多数人调压缩,调的是一个百分比:窗口 200K,我压到 50K,应该够了吧?这个思路默认"压缩是均匀的",而真实压缩几乎都是有偏向的——它总要先选一个"先动哪一段"。三种最常见的做法,选择完全不同。

2.1 三种做法,先丢的东西不一样

压缩方式典型实现先丢什么硬约束(最早 3 轮)的下场
滑动窗口截断只保留最后 N 个 token最早的内容,不看是不是约束最先被丢
清工具结果只清工具返回,其余保留中间那批体量最大的工具输出保留(约束不在工具结果里)
丢弃最早 N 轮从头砍掉 N 轮最早的轮次,包括约束所在的前 3 轮最先被丢

看最后一列:三种做法对"最早那几轮"的态度,直接决定了硬约束的死活。而硬约束天然就写在最前面——你在任务开头交代规矩,天经地义。

2.2 官方机制里,"按类型清理"是现成的

Anthropic 的上下文清理(Context editing)给了策略类型clear_tool_uses_20250919,参数形态大致是这样:

{"type":"clear_tool_uses_20250919","trigger":{"type":"input_tokens","value":30000},"keep":{"type":"tool_uses","value":3},"clear_at_least":{"type":"input_tokens","value":"<每次至少清掉的 token 数>"},"exclude_tools":["<不想被清的工具体类型>"]}

trigger设{"type":"input_tokens","value":30000},表示输入 token 到 3 万才触发;keep设{"type":"tool_uses","value":3},表示保留最近 3 条工具调用。官方对keep的原话是:

The API removes the oldest tool interactions first, preserving the most recent ones.

翻译过来:先清最老的工具交互,保留最近的若干条。这一句就是"只清工具结果、保留最近若干条"的官方版本——它把清理范围限定在"工具交互"这一类型里,不会顺手把你最早写的约束一起带走。exclude_tools还能把某些工具整个排除在清理之外;clear_at_least则用来保证每次至少清掉多少 token(官方说明清缓存会让前缀缓存失效,所以要清够量才划算)。

2.3 但官方压缩本身,也保不住"字面约束"

Anthropic 的压缩(Compaction)是另一套东西。官方定义原文是:

Compaction replaces the older turns of a conversation with a summary that Claude writes on the server, so you need no summarization code of your own.

它把较早的轮次换成一个摘要,并把压缩区分为「compact on demand」(按需手动触发)与「threshold compaction」(到阈值自动触发),同时说明Recent turns stay word for word——最近若干轮逐字保留。

问题就出在"较早的轮次换成一个摘要":摘要是有损的。摘要会写"用户要求控制预算、不要发邮件",但很可能不会逐字保留"预算上限 8000 元"这种精确数字,也不会保留"超过必须停下来问我"这种动作指令。所以哪怕你用的是官方压缩,硬约束也未必安全——它只是从"被截掉"换成了"被摘要成一句大意"。

三、同样压到 2000 token,结局完全相反

同样是留 2000 token 上下,滑动窗口的约束存活是 0/3,只清工具结果是 3/3——不是压得少就安全,是压对位置才安全。

我写了个脚本,构造一个 203 轮、4756 token 的长任务:三条硬约束放在最前面 3 轮,后面是大量工具返回和业务追问。然后分别用三种压缩方式压,看那三条约束的字面还在不在。

🧪 实测环境:Python 3.13.12 / macOS / tiktoken 0.14.0

python work/exp1011/exp04_compaction.py
importtiktoken enc=tiktoken.get_encoding("cl100k_base")nt=lambdas:len(enc.encode(s))constraints=["预算上限 8000 元,超过必须停下来问我","只允许用企业微信发通知,不要发邮件","客户名单里的手机号不能出现在任何对外文本里",]# 24 条「工具返回 / 追问」短句 × 8 轮业务循环 + 8 行循环标记 = 200 轮,加 3 轮约束共 203 轮turns=constraints+build_long_middle()full="\n".join(turns)print("完整对话:%d 轮,%d 个 token"%(len(turns),nt(full)))# 策略 A:滑动窗口,只保留最后 K 个 tokenforkin[2000,1000,500,300]:keep=enc.decode(enc.encode(full)[-k:])alive=[cforcinconstraintsifcinkeep]print(" 保留 %4d token(占原文 %4.0f%%)-> 约束存活 %d/3"%(k,100.0*k/nt(full),len(alive)))# 策略 B:只清工具结果(保留最近 N 条工具输出)tool_like=[ifori,tinenumerate(turns)ift.startswith("工具返回")]forkeep_nin[8,4,2]:dropped=set(tool_like[:-keep_n])kept=[tfori,tinenumerate(turns)ifinotindropped]alive=[cforcinconstraintsifcin"\n".join(kept)]print(" 保留最近 %d 条工具结果 -> %d token,约束存活 %d/3"%(keep_n,nt("\n".join(kept)),len(alive)))# 策略 C:丢弃最早 N 轮fornin[3,6,12]:text="\n".join(turns[n:])alive=[cforcinconstraintsifcintext]print(" 丢掉最早 %2d 轮 -> %d token,约束存活 %d/3"%(n,nt(text),len(alive)))

(build_long_middle()是生成那 200 轮业务循环的辅助函数,完整脚本随本文一起放在资料包里。)

原样输出:

完整对话:203 轮,4756 个 token === 策略 A:滑动窗口(按 token 截断,保留尾部)=== 保留 2000 token(占原文 42%)-> 约束存活 0/3 保留 1000 token(占原文 21%)-> 约束存活 0/3 保留 500 token(占原文 11%)-> 约束存活 0/3 保留 300 token(占原文 6%)-> 约束存活 0/3 === 策略 B:只清工具结果(模拟按条数保留最近 N 条工具输出)=== 保留最近 8 条工具结果 -> 2142 token,约束存活 3/3 保留最近 4 条工具结果 -> 2015 token,约束存活 3/3 保留最近 2 条工具结果 -> 1956 token,约束存活 3/3 === 策略 C:丢弃最早 N 轮 === 丢掉最早 3 轮 -> 4696 token,约束存活 0/3 丢掉最早 6 轮 -> 4646 token,约束存活 0/3 丢掉最早 12 轮 -> 4492 token,约束存活 0/3

3.1 把最扎眼的对比挑出来

🧪 实测环境:Python 3.13.12 / macOS / tiktoken 0.14.0 (数据实测块,口径见下)

压缩策略压缩动作压缩后 token占原文三条硬约束存活
A 滑动窗口按 token 截断,保留尾部200042%0/3
A 滑动窗口按 token 截断,保留尾部100021%0/3
A 滑动窗口按 token 截断,保留尾部50011%0/3
A 滑动窗口按 token 截断,保留尾部3006%0/3
B 只清工具结果保留最近 8 条工具结果214245%3/3
B 只清工具结果保留最近 4 条工具结果201542%3/3
B 只清工具结果保留最近 2 条工具结果195641%3/3
C 丢弃最早 N 轮丢掉最早 3 轮469699%0/3
C 丢弃最早 N 轮丢掉最早 6 轮464698%0/3
C 丢弃最早 N 轮丢掉最早 12 轮449294%0/3

表格口径:合成任务 203 轮 / 完全态 4756 token,token 由 tiktoken(cl100k_base)计数;「占原文」= 压缩后 token ÷ 4756;「约束存活」= 三条约束字符串在压缩后文本里字面完整命中的条数。取数日 2026-10-11,脚本 exp04_compaction.py。

3.2 两处反直觉的地方

第一,策略 A 压到 2000 token(保留 42%)就已经全丢,不是压狠了才丢——只要保留方式是"从尾部截",最前面那 3 轮必然在第一批被扔掉。

第二,策略 C 压得最"轻"(丢 12 轮还剩 94%),约束照样 0/3。因为它砍的不是量,是位置:最早那几轮恰好就是约束所在。

反过来,策略 B 压到 1956 token(只剩 41%),三条约束一条不少。它压掉的 token 比策略 A 只多不少,却把约束完整留住了。原因就是第 2 章那句:它清的是"工具结果"这个类型,而约束不在这类里。

四、我改了什么:四条硬规矩

这四条不是我拍脑袋定的,每一条都能对上第 3 章翻车的那组数字。

4.1 硬约束不做"历史",做"常驻槽"

把三条约束从对话历史里挪走,放进每轮都重新注入的系统提示(或独立约束槽)。这样无论压缩怎么动历史,约束都不在它的作用范围内。第 3 章策略 A/C 之所以是 0/3,前提就是"约束在历史里";一旦它不在历史里,这两条路对它的杀伤力直接归零。

4.2 压缩按类型清,不按位置截

只用"清工具结果"这一类(对应官方clear_tool_uses+keep),明确禁止两件事:禁止"丢最早 N 轮",禁止"只留尾部 token"。前者是第 3 章的策略 C,后者是策略 A,两条都是 0/3。

4.3 每次压缩后做约束存活自检

压缩完立刻断言:三条约束字符串必须在当前上下文里字面命中,少一条就回滚本次压缩并告警。这一步成本很低,但能把"压缩悄悄吃掉约束"从"攒到第 60 轮才爆"变成"压缩当场就被发现"。

4.4 不可逆动作加代码闸门

对外发送、付款这类动作,在工具层拦截:命中金额上限或渠道白名单就直接拦下,不依赖模型记不记得。模型可以忘,代码不会忘。这条是最后一道防线——即使前三条都失守,它也能兜住。

一套可以直接照搬的压缩配置:把上面这四条落成配置和代码的写法(清工具结果的参数、约束自检的断言、动作闸门的拦截点),和本文那三条约束样例放在一起。放在资料包里,扫码即可获取:

五、比技术更该先想清楚的事:什么内容有"免压缩"资格

技术手段只能缓解,真正决定生死的是先划清一条线:哪些内容无论如何不能被压。

第 4 章那四条能用,但它们的共同前提是"你已经知道要保护什么"。而大多数翻车,恰恰是压根没想过这个问题——把所有内容一视同仁地塞进历史,然后指望压缩"自己知道轻重"。它不知道。

5.1 用"可逆性"给内容分类

照着"丢了能不能低成本恢复"这一条标准,四类内容各归各位:

  • 硬约束(金额上限、渠道白名单、隐私红线)→免压缩,每轮重贴;判据是"违反后不可逆"。
  • 不可逆动作的参数(对外发送、付款、删除)→ 常驻 + 代码闸门;判据是"不能只靠模型记得"。
  • 可重建的事实(工具返回、中间统计)→ 优先压缩;判据是"丢了能再查一次"。
  • 用户临时偏好(“这次用中文”“先别动手”)→ 可压缩;判据是"违反了能重说"。

分完就会发现:真正不能压的,永远是前两类。而它们恰恰排在最前面、最容易在"截尾部"或"丢最早 N 轮"里被第一批扔掉——这正是本文那场故障的完整成因。

5.2 一条可执行的止损线

如果你的长任务同时满足这两个条件——(一)有违反后不可逆的硬约束;(二)当前压缩方式是"截尾部"或"丢最早 N 轮"——那不用等到出事,现在就该改。因为按第 3 章的实测,这两条路对最前面约束的命中是 0/3,跟压多少无关。

反过来说,如果任务里确实没有不可逆动作,全部内容丢了都能重建,那"截尾部"反而是最省事的做法。判据在这里,选择权在你。怕的不是选了哪种压缩,是压根没意识到自己已经选了一种。

一份上下文压缩的判据清单:上面这张"免压缩资格"表、三种压缩方式的取舍,以及第 3 章那组实测数据的可复跑脚本,整理成了同一份清单。放在资料包里,扫码即可获取:

附表 A:本文引用事实与出处对照表

#事实 / 表述出处(文档名 + 发布方 + 链接)本文位置
A1压缩定义原文:「Compaction replaces the older turns of a conversation with a summary that Claude writes on the server, so you need no summarization code of your own.」《Compaction overview》,Anthropic,https://platform.claude.com/docs/en/build-with-claude/compaction2.3、5.1
A2压缩区分为「compact on demand」与「threshold compaction」,并说明Recent turns stay word for word同上2.3
A3上下文清理的策略类型写作clear_tool_uses_20250919《Context editing》,Anthropic,https://platform.claude.com/docs/en/build-with-claude/context-editing2.2、4.2
A4trigger可设为{"type":"input_tokens","value":30000}同上2.2
A5keep可设为{"type":"tool_uses","value":3};keep的官方原句「The API removes the oldest tool interactions first, preserving the most recent ones.」同上2.2
A6clear_at_least用于保证每次至少清掉多少 token;官方说明清缓存会让前缀缓存失效,需清够量才值得同上2.2、4.2
A7203 轮 / 4756 token 合成任务的压缩实测:策略 A 约束存活 0/3、策略 B 3/3、策略 C 0/3本文实测,脚本exp04_compaction.py,Python 3.13.12 + tiktoken 0.14.0,取数日 2026-10-113.1–3.2

说明:A1–A6 为 Anthropic 官方文档逐字/参数级命中;A7 为本文真跑实测。社区讨论帖、二手转述一律未用作事实来源。


写在最后:这篇用到的资料

写这篇文章时,把相关的官方文档和源码又翻了一遍,顺手也整理了几份配套的东西:

  • 大模型学习路线图:从零基础到能自己动手做 Agent,按阶段说明每一步该学什么、哪些可以先跳过
  • 《LangChain + LangGraph + MCP 智能体开发实战》视频课:7 个模块,从私有化部署、Embedding+RAG 到 MCP+Agent 全流程
  • AI 大模型知识库(在线可查):Agent Skills 从入门到落地、Claude Skills 完全指南等专题,按目录浏览即可
  • 640 套 AI 大模型行业报告 + 经典 PDF 书籍:看行业落地案例和别人怎么做的时候用得上
  • 大模型零基础到精通教学视频:跟着敲一遍,比只读文档快得多

资料是我自己整理的,放在下面这个码上,扫码即可获取:






添加时备注「AI」,优先通过。

资料按「先路线、再动手、最后查漏」的顺序整理好了,建议先看学习路线那一份,照着它挑一条适合自己当前基础的路径再往下看。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/11 7:56:38

springboot微信小程序 关爱老人APP 老年人健康数据可视化大屏 数据分析系统_wwl941ou

目录同行可拿货,招校园代理 ,本人源头供货商项目概述技术架构核心功能模块1. 老年人健康数据采集2. 健康数据存储与管理3. 实时数据分析引擎4. 数据可视化大屏系统5. 微信小程序功能6. 管理后台系统&#xff08;可选扩展&#xff09;数据分析能力安全与合规性项目亮点适用场景开…

作者头像 李华
网站建设 2026/10/11 7:55:41

AI Agent 如何精准调用技能?从入门到精通的 6 步链路解析

本文深入探讨了 AI Agent 如何在接收到任务时&#xff0c;通过 6 步标准化的流程调用不同的技能&#xff0c;包括任务理解、技能召回、精准匹配、参数提取、执行调用和结果校验。文章详细解析了 Agent 选择技能的四种层级机制&#xff0c;从简单的规则匹配到复杂的规划反思&…

作者头像 李华
网站建设 2026/10/11 7:55:20

UVa 12266 股票价格:用STL map模拟订单簿撮合

UVa 12266 Stock Prices 这道题&#xff0c;光看标题容易吓人&#xff1a;股票价格&#xff1f;是不是要先搞一堆金融模型&#xff1f;其实它是一道非常经典的数据结构模拟题&#xff0c;核心就是维护一个“订单簿”。题目给你一串买报价和卖报价&#xff0c;每来一条新订单&am…

作者头像 李华
网站建设 2026/10/11 7:52:37

WOFOST与AquaCrop作物模型对比:机理、参数与应用选择

做作物模型的人&#xff0c;迟早会在某个项目里同时撞见WOFOST和AquaCrop这两个名字。一个来自欧洲&#xff0c;一个出自联合国粮农组织&#xff0c;都是全球应用最广的作物生长模型&#xff0c;但如果你只把它们当作“模拟产量的工具”来用&#xff0c;那从一开始就搞错了方向…

作者头像 李华
网站建设 2026/10/11 7:52:28

【单片机课程设计/毕业设计】基于STM32的自行车码表与GPS定位一体化装置设计 基于WIFI的骑行速度里程定位与心率血氧远程监测系统设计(030205)

博主介绍&#xff1a;✌️码农一枚 &#xff0c;专注于大学生项目实战开发、讲解和毕业&#x1f6a2;文撰写修改等。全栈领域优质创作者&#xff0c;博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于嵌入式单片机&#xff0c;Java、小程序技术领域和毕业项目实战 ✌️…

作者头像 李华
网站建设 2026/10/11 7:47:36

AI视频角色一致性全攻略:原理、工具对比与避坑指南

做AI视频这段时间&#xff0c;我踩过最深的坑&#xff0c;就是角色一致性。第一版片子生成出来&#xff0c;每个单镜头看起来都挺唬人&#xff0c;一旦剪在一起&#xff0c;主角的脸在五个镜头里变了四种样子&#xff0c;发型一会儿有刘海一会儿没有&#xff0c;衣服颜色也漂移…

作者头像 李华