CC压缩机制之toolResultBudget源码实现原理技术深度解读
toolResultBudget机制在每次模型请求前自动执行,检查单个API-levelusermessage中tool_result总量是否超过200K字符,若超则将最大的工具结果落盘并替换为预览,以降低上下文噪音。该机制位于压缩流水线最前端,在microcompact之前执行,确保后续压缩更高效。
0. 目标
toolResultBudget 机制的核心目标非常明确:解决一个刚引入上下文的新工具结果体积过大,导致后续的 microcompact 和 autocompact 在压缩前不得不面对一个已经“臃肿”的上下文问题。因此,它的首要任务就是先将这些“噪声”数据降下来,让后续的压缩工作更加高效流畅。
1. 触发时机
toolResultBudget 的触发时机颇具巧思,它并非在 API 报错后才被动“救火”,也不依赖全局 token 阈值来触发。它会在每次主查询循环准备发起模型请求前,自动执行一遍。在压缩流水线的执行顺序上,它排在 microcompact 和 autocompact 之前。具体来说,每轮循环都会检查当前即将发送给模型的消息里,单个 API-level user message 中的 tool_result 总量是否超过预算。一旦超出,立即执行落盘替换操作。
对应到源码,它的执行时机非常明确:
- 在每轮 ReactLoop 中执行。
- 在 microcompact 之前执行。
- 只有超预算才会处理,默认阈值是 200_000 字符。
2. 策略
toolResultBudget 的策略并非简单地做摘要或直接删除,而是采用一种更智能的方式:如果同一轮工具结果的总量超过了预算,就把其中最大的几个 tool_result 原文保存到磁盘,然后在上下文里只保留一个稳定的预览和文件路径。
核心代码分布在以下几个关键位置:
- 入口调用:
src/query.ts:L369-L394 - 预算阈值:
src/constants/toolLimits.ts:L36-L49 - 策略主体:
src/utils/toolResultStorage.ts:L739-L909 - 候选结果收集:
src/utils/toolResultStorage.ts:L551-L638 - 最大项选择:
src/utils/toolResultStorage.ts:L669-L692 - 落盘与预览:
src/utils/toolResultStorage.ts:L137-L199 - 替换 tool_result 内容:
src/utils/toolResultStorage.ts:L699-L726
2.1 它在压缩流水线的位置
入口在 query.ts 中:
messagesForQuery=awaitapplyToolResultBudget(messagesForQuery,toolUseContext.contentReplacementState,...)
注意注释明确指出:
//RunsBEFOREmicrocompact
所以,整个压缩流水线的执行顺序是:
toolResultBudget → HistorySnip → microcompact → contextcollapse → autocompact
可以理解为,它是整个压缩流水线中最早的一层降噪,专门处理那些“刚出炉”的大工具结果。其他更久远的内容,则交给后面的环节处理。
2.2 触发条件:单个 user message 内 tool_result 总和超过 200K 字符
阈值定义在 src/constants/toolLimits.ts:L36-L49:
export const MAX_TOOL_RESULTS_PER_MESSAGE_CHARS = 200_000
这里有一个关键点:它不是统计整个会话的总量,而是针对单个 API-level user message 里的 tool_result 总和。为什么是 user message?因为 Claude 的工具结果最终会以 user message 中的 tool_result block 形式发给模型。
源码注释解释了一个非常典型的场景:
10个并行工具,每个结果40K,单个工具都没超过per-toollimit,但合起来是10×40K=400K
这种情况就会触发 toolResultBudget。
2.3 它为什么按“API-level user message”分组
候选收集逻辑在 collectCandidatesByMessage(messages) 函数中,位于 src/utils/toolResultStorage.ts:L575-L638。这里有一段很长的注释,解释了为什么必须这样分组,值得仔细看看。
简单来说,API 层会把连续的 user 消息合并成一个。如果预算检查不按同样的规则分组,就会出现本地看每个 tool_result 都低于预算,但到 API 层合并后,实际超过预算的情况。这样就会漏掉真正的超预算场景,导致策略失效。
它模拟的是 API 最终看到的消息结构:
本地:user tool_result A 80K progress user tool_result B 80K attachment user tool_result C 80K
API视角:user message: A+B+C=240K
如果不按这个逻辑分组,就会漏掉真实的超预算情况。
2.4 哪些 tool_result 可以被处理
候选提取逻辑在 src/utils/toolResultStorage.ts:L551-L573。它只收集那些满足特定条件的 tool_result:
- 是 user message
- content 是数组
- block.type === 'tool_result'
- block.content 存在
- 不是已经 compacted 的内容
- 不包含 image block
这意味着,图片类的 tool result 不会走这个落盘预览策略。已经被替换过的内容也不会再次处理,因为它会以 开头。
2.5 状态设计:seen / replacements
状态定义在 src/utils/toolResultStorage.ts:L390-L393:
export type ContentReplacementState = {
seenIds: Set<string>,
replacements: Map<string, string>
}
它有两个核心集合:
seenIds:这个 tool_result 已经被预算逻辑“看过”了。replacements:这个 tool_result 已经被落盘,并且上下文中应该替换成哪段预览文本。
这么设计的一个重要原因是:为了保持 Prompt Cache 的稳定性。一旦某个 tool result 已经完整地发给模型,后面就不能再突然把它换成预览,否则历史 Prompt 前缀就变了,缓存会失效。所以,状态分成了三类:
mustReapply:以前替换过,每轮继续用同一个预览。frozen:以前完整发过,不能再替换。fresh:第一次看到,可以决定是否替换。
对应代码在 src/utils/toolResultStorage.ts:L641-L667。
2.6 核心策略:只从 fresh 里选最大的落盘
选择逻辑的核心代码在 src/utils/toolResultStorage.ts:L675-L692:
const sorted = [...fresh].sort((a, b) => b.size - a.size)
这里的关键思路是:不是随机删除,也不是全部落盘,而是按大小从大到小排序,选择最大的 fresh tool_result,直到剩余可见内容 <= 200K。伪代码大致如下:
remaining = frozenSize + sum(freshSize)
for result of fresh.sort(desc size):
if remaining <= 200K:
break
selected.push(result)
remaining -= result.size
需要注意的是,这里的 frozen 是不能动的。如果 frozen 自己已经超过 200K,代码会接受这个超预算的情况,交给后续的 microcompact 去处理。注释在 src/utils/toolResultStorage.ts:L669-L673 中说明了这一点。
2.7 落盘:完整内容保存到 session tool-results 目录
落盘函数 persistToolResult(content, toolUseId) 位于 src/utils/toolResultStorage.ts:L137-L184。它会将 tool result 的完整内容保存到磁盘,路径来自 getToolResultPath(toolUseId, isJson),目录名为 tool-results。
落盘后,它会生成一个约 2KB 的预览。最终模型看到的 replacement 类似于:
output>
Output too large (...). Full output saved to: /path/to/tool-results/toolu_xxx.txt
Preview (first 2KB):
...
output>
构造逻辑见 src/utils/toolResultStorage.ts:L189-L199。
2.8 替换:不删 tool_result,只替换 content
替换代码在 src/utils/toolResultStorage.ts:L699-L726。它的做法是:保留 tool_use 和 tool_result 的结构,只替换 tool_result.content 的内容,从完整的大文本变成预览文本。这样可以保持 Claude API 需要的 tool_use/tool_result 配对结构。
2.9 为什么 Read 经常会被跳过
在 query.ts 中,会传一个 skipToolNames:
new Set(toolUseContext.options.tools.filter(t => !Number.isFinite(t.maxResultSizeChars)).map(t => t.name),)
在 enforceToolResultBudget 里,会明确跳过那些 maxResultSizeChars: Infinity 的工具(比如 Read)。理由是:Read 自己已经有 maxTokens 控制。如果把 Read 的结果落盘,然后让模型再用 Read 去读取落盘文件,就会形成循环,这显然不是我们想要的。
2.10 Resume 时如何保持一致
ContentReplacementState 被挂在 ToolUseContext 上。REPL 初始化时会创建它,Resume 时会重建它。同时,query.ts 还会把新的 replacement 记录到 transcript 中。这保证了恢复会话后,同一个 tool_result 仍然被替换成完全相同的预览文本,从而避免 Prompt Cache 前缀漂移。
3. 代价
从性能角度来说,这个机制的代价几乎可以忽略不计,仅仅是多了字符串替换的开销,对整体性能几乎没有影响。
4. 与MC(micro Compact)如何配合
简单来说,toolResultBudget 和 microcompact 的分工非常明确。
在每次发起模型请求前,toolResultBudget 会先检查当前消息里的工具结果是不是太大。如果某个 API user message 里的多个 tool_result 加起来超过预算,就把最大的结果落盘,并替换成预览。
它必须放在 microcompact 前面,因为它是处理“新产生的大结果”,而 microcompact 处理的是“旧结果清理”。而且,cached microcompact 只看 tool_use_id,不看具体内容,所以即使这里把内容替换成了预览,也不会影响 microcompact 后续按 ID 清理。两者可以安全地组合使用,互不干扰。
如果功能没启用,contentReplacementState 不存在,这段逻辑就直接跳过。替换记录只会在可恢复的会话里持久化:主会话写 session transcript,AgentTool 写 sidechain;临时 fork agent 不写,因为它们不会 resume。
游乐网为非赢利性网站,所展示的游戏/软件/文章内容均来自于互联网或第三方用户上传分享,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系youleyoucom@outlook.com。
同类文章
WorkBuddy使用一个月避坑指南:5个常见问题及解决方案
使用WorkBuddy一个月,踩过指令模糊、未指定输出格式、反复打断任务、积分过期、未验证结果五个坑。对应解法:明确文件路径、动作、维度、格式和文件名;指定输出格式;耐心等待;优先使用快过期积分;抽查验证汇总逻辑。
图片生成任务到用户隔离:AIGC后端与PostgreSQL建模实践
基于AIGCCreativeStudio实践,后端采用Express+TypeScript与PostgreSQL17,通过users、generation_tasks、images三表模型实现任务状态机、图片本地存储及受认证访问,确保用户隔离与资源安全。
动态代码拖累SEO?用Gofair纯静态页面剔除冗余代码
静态页面加载速度快,搜索引擎爬取效率高,优于动态建站。某孕产妇用品企业改用Gofair静态建站,五天多关键词冲至谷歌首页。SEO效果需通过关键词反查验证,流量数据易被干扰。未来静态页面策略将更主流。
WorkBuddy AI工作台实操教程 零基础搞定周报与数据分析
使用WorkBuddy时需下达清晰指令,包括文件路径、输出格式和完整需求。典型场景如周报生成、Excel数据清洗与可视化,需注意指定去重列和输出格式,避免打断大文件处理。定时任务可自动化抓取新闻,轻量模型和Ask模式可节省积分。
CC压缩机制之toolResultBudget源码实现原理技术深度解读
toolResultBudget机制在每次模型请求前自动执行,检查单个API-levelusermessage中tool_result总量是否超过200K字符,若超则将最大的工具结果落盘并替换为预览,以降低上下文噪音。该机制位于压缩流水线最前端,在microcompact之前执行,确保后续压缩更高效。
- 热门数据榜
相关攻略
2026-08-05 22:59
2026-08-05 22:59
2026-08-05 22:59
2026-08-05 22:58
2026-08-05 22:46
2026-08-05 22:45
2026-08-05 22:45
2026-08-05 22:45
热门教程
- 游戏攻略
- 安卓教程
- 苹果教程
- 电脑教程

