常驻上下文开销
会话发送的每个请求都会在任何对话内容之前携带相同的前缀:系统提示词、所有已声明工具的 schema、你的上下文(QWEN.md)文件,以及 skill 列表。你在每个轮次都要为这个前缀付费,包括那些仅仅回答问题的轮次。本页介绍如何衡量并削减它。
Token 缓存 降低前缀的_价格_。本页降低_前缀本身_。两者可以叠加——更小的前缀在缓存后也更便宜。
查看你在为什么付费
/context detail/context 按类别打印明细;detail 会额外列出逐行条目——每个内置工具、每个 MCP 工具、每个上下文文件、每个列出的 skill——让你看到哪一项是开销最大的。在会话的第一个轮次阅读它,此时对话仍为空,你看到的全部内容都是前缀。
这些类别是 /context 报告的内容,再加上两个记账行:startupContext(作为第一个用户轮次发送的环境块)以及一个显式的残差项,用于归集类别未能归属的部分,因此各部分之和始终等于总量。
使用空闲开销,而非窗口百分比
上下文窗口的百分比不是一个你能守住的指标,因为分母是任意的。同一份配置在 1M 上下文模型上读起来是 6.5%,在 128k 模型上则是 37%——相同的文本,相同的开销,数字却天差地别。请改用:
空闲开销 —— 一个只问了一个问题且未调用任何工具的会话的输入 token 数。
它与模型和窗口无关,你也无法通过将文本从工具 schema 移到上下文文件来让它好看。另一个有用的读数是对话需要多少轮才能超过前缀:一个需要 20 轮才能摊薄的前缀,在 5 轮的会话中永远摊薄不了。
调节手段,按收益排序
1. 关闭你不使用的功能
每个向模型声明的工具都会将其 schema 加入每个请求。关闭具有大型���驻工具的可选功能因此可以节省请求 token。延迟工具改为贡献简短的目录条目以及后续发现或调用的成本。禁用功能还会从子代理中移除其工具,而下一个手段并不总是能做到这一点。
2. 将 eager 工具集保持在真正使用的范围内
tools.eager 是一个允许列表,列出的内置工具的 schema 会保留在初始请求中。其余工具变为延迟加载:仍然注册、仍然列在 /tools 中、仍然可调用——模型在确实需要时通过 tool_search → tool_call 桥接来访问它们。
根据正确的基线来衡量这个允许列表。默认按需加载的工具已经不在第一个请求中了,而且自从 tools.toolSearch.threshold 开始默认为 0 后,没有任何东西会把它们预加载回来,所以允许列表的节省只来自于它扣留的默认 eager 工具的 schema——而不是整个内置工具集。在你实际运行的版本上重新取一次 /context 读数,再为你的列表赋予一个数字。
{
"tools": {
"eager": [
"read_file",
"write_file",
"edit",
"glob",
"grep_search",
"run_shell_command",
"skill",
],
},
}使用之前需要了解四件事:
- 它不是禁用。 被降级的工具仍然可以访问。如果你的目的是移除一个工具,请使用整工具级别的
permissions.deny规则或tools.disabled。 - 某些工具不受影响,无论列表如何设置都保持正常的加载行为:
tool_search和tool_call(到达被扣留工具的两半桥接)、structured_output、plan 模式生命周期工具(enter_plan_mode、exit_plan_mode、ask_user_question)、task_stop、MCP 工具(mcp__*)以及 Computer Use 工具(computer_use__*)。task_stop和 Computer Use 系列默认就是按需加载的,因此对它们做门控不会节省任何东西;MCP 工具由tools.toolSearch.*以及每个服务器的includeTools/excludeTools过滤器管理;而tools.eager中的任何设置都无法移除其余工具中的任何一个——那需要整工具级别的permissions.deny规则、tools.disabled条目或--exclude-tools——对于桥接对,tools.toolSearch.enabled: false可以同时移除两半。移除桥接的一半并不是退出允许列表的中性方式:每个普通延迟工具(所有 MCP 工具、按需加载的 Computer Use 系列)随后会被强制声明到每个请求中,而被扣留的工具不会提供给模型也无法通过桥接加载回来——它仍然注册,因此按名称直接调用仍会经过正常审批。 permissions.allow不会节省任何东西。 它纯粹是自动审批:它从不降级、隐藏或移除工具。审批模式也不会。- 它需要桥接的两半。 被扣留的工具通过
tool_search审查并通过tool_call调用;单独的tool_search只能读取它无法调用的 schema。如果任一半未注册——tools.toolSearch.enabled: false同时拒绝两者;tool_search/tool_call的 deny 规则、--exclude-tools条目或tools.disabled条目移除其中一个——允许列表仍然扣留 schema,没有任何东西能把它们加载回来,被降级的工具不会提供给模型用于该会话(会记录一条警告);它们仍然注册,因此按名称直接调用仍会经过正常审批。普通延迟工具在这种情况下会回退为急切声明;你用tools.eager降级的工具则不会。
tools.visible 是一个逃生舱口,用于你希望某个工具即使默认延迟加载也要在开始时声明的情况。
Agent 和 Goal 协调(agent、list_agents、get_goal、update_goal 和 propose_goal)默认延迟加载;无需 tools.eager 配置。模型看到的是简短的发现条目而非完整 schema。首次使用需要通过桥接发现,因此要对比整个任务成本和成功委派/Goal 完成情况以及第一个请求。这些是普通延迟工具:tools.visible、预加载和上述不完整桥接的急切回退仍然适用。预加载对整个延迟候选池是全有或全无的,这五个声明都很大,所以曾经在会话开始时揭示所有延迟工具的 tools.toolSearch.threshold 现在可能一个都揭示不了。改动任一项后重新测量 /context。
3. 将场景指导从上下文文件移到 skill 中
上下文文件会被拼接到它适用的每个会话的每个请求中,没有相关性门控。skill 只按名称和描述列出——在一个实测样本中,84 个 skill 平均每个约 55 个 token——在被调用时才加载其正文,而一个基于 paths: 门控的 skill 在匹配的文件被触及之前甚至不会被列出。
上下文文件中只保留始终为真的内容——身份、词汇表、硬性约束——将”做 X 时,执行 Y”放在 skill 或 paths: 门控规则 中。/context detail 会列出每个上下文文件的名称,对于扩展的文件,它会列出拥有该文件的扩展。
4. 系统提示词,最后考虑
基础提示词已经是常驻类别中最小的,其中大约三分之一是不能编辑的安全和权限文本。它的门控工具指导跟随已声明的集合,bridge-reachable Agent 例外;这些条目中提到的其他工具仍然需要声明。因此裁剪工具集也可以缩小该指导。用 --system-prompt 整体替换它是可行的,也是本页风险最高的变更;如果你这样做,请在每次升级时 diff 上游提示词。
陷阱
- 子代理也会获得延迟工具。 没有声明显式工具列表的子代理会接收所有已注册工具的 schema,包括延迟工具,并且不经过 ToolSearch。
tools.eager和permissions.deny是唯一能影响它的调节手段;预加载阈值对它无效。 - 后台 memory 代理需要其完整的工具列表,且两个列表不同。 项目 Dream worker 需要六个工具(
read_file、grep_search、glob、run_shell_command、write_file、edit);自动提取需要五个——相同的集合但不包括run_shell_command,它设计上就拒绝该工具。拒绝其中任何一个代理实际持有的工具都会让它静默降级而非报错。因此不要为了移除其声明而全局拒绝run_shell_command:Dream 仍然需要它。 - 后台 memory 代理需要其完整的工具列表,且两个列表不同。 项目 Dream worker 需要六个工具(
read_file、grep_search、glob、run_shell_command、write_file、edit);自动提取需要五个——相同的集合但不包括run_shell_command,它设计上就拒绝该工具。拒绝其中任何一个代理实际持有的工具都会让它静默降级而非报错。因此不要为了移除其声明而全局拒绝run_shell_command:Dream 仍然需要它。 - Token 可能只是移动而非消失。 拿走
grep_search和glob,模型可能会通过 shell 使用grep和find,其输出会进入对话。新输出在首次发送时会增加输入 token;包含它的未变更历史在后续请求中可能会命中提供商的前缀缓存。通过每个任务的总输入 token、提供商报告的缓存和未缓存输入以及实际账单来评判变更,而不是仅看前缀。 - 恢复的会话会重新发送所需内容。 出现在恢复会话历史中的被降级工具会自动恢复其 schema;被拒绝的工具则不会。
- 揭示被扣留的工具不再重写前缀——但过去会,而旧建议假设它仍会如此。 发现通过
tool_search→tool_call桥接进行,这使已声明的工具列表保持字节稳定,因此提示词缓存前缀在会话中途发现后仍然存活;代价是在被扣留工具首次使用前多一次往返。这就是tools.toolSearch.threshold现在默认为0的原因:每轮携带延迟集不再是权衡中更便宜的一方。只有为了在确定需要它们的会话中为普通延迟工具(MCP 工具和按需加载的内置工具)赢回那次往返时才提高阈值——它永远不会预加载你用tools.eager降级的工具;那些工具在任何阈值下都留在桥接后面。预加载还在会话启动时运行,而 MCP 服务器通常在之后连接,因此新启动时无论阈值如何都不声明 MCP 工具;它们从下一个会话启动(在 CLI 中,/clear之后)到达预加载,或在QWEN_CODE_LEGACY_MCP_BLOCKING=1下立即到达。 - 前缀缓存模型会反转权衡。 对于折扣依赖于稳定前缀的模型,曾经保持声明列表不变比保持它更小更重要——这就是为什么某些部署手动禁用了 ToolSearch。揭示被扣留的工具现在使该列表保持字节稳定,因此那个特定原因已经失效——只有一个例外,在
tool_search描述本身中说明:工具集刷新可能仍会在实时历史包含对它的直接调用时重新声明被扣留的工具,而那确实会移动前缀。前缀稳定性仍然反对任何其他在会话中途重写前缀的操作,例如在轮次之间编辑上下文文件。 - 作用域会泄漏。 设置适用于读取它们的每个客户端(CLI、Web Shell、serve),因此按部署划分的工具集需要自己的设置作用域。
验证节省效果
- 记录变更前的空闲开销:一个全新会话,一个简单问题,在第一个轮次执行
/context。 - 每次应用一个调节手段并重复,重启会话——这些设置中的大多数在启动时读取。
- 在你自己的任务集上确认能力仍然存在:工具调用成功率、
tool_search被调用的频率,以及任务结果。一个模型从未想到要查找的被降级工具不会大声报错;它只是不再被使用。 - 检查账单,而不仅是前缀——参见关于 token 移入对话的陷阱。