跳转到内容

调整智能体的工具上下文

本指南介绍围绕两层式工具面的实际工作:查看一次运行发送了什么、修改它,以及诊断智能体为什么会选错工具。想了解这样设计的原因,请阅读工具上下文与提示缓存。

打开一个会话,点击会话头部中的上下文用量指示器。它会重建下一次派发将发送的确切工具面,而不会运行任何东西。查看更多(或 /context 命令)会打开完整的上下文浏览器,你可以在其中检查每个工具。

其中有两段值得关注:

  • 工具定义和 MCP 与动态工具 —— 常驻集合,并列出每个工具真实的 token 开销。
  • 按需加载的工具 —— 延迟加载索引。每个条目只会按其名称计费,因为请求携带的也只有一个名称;展开某个条目仍会显示完整的描述和 schema,因此你能看到如果这次运行真的把它拉进来原本会花费多少。

如果延迟加载那一段为空,说明该服务器的延迟加载已关闭。请在 cc_server CLI 中查看 --tool-deferral。

常驻集合是策略,而不是配置——它位于 ModeToolPolicy(packages/cc_domain/lib/features/mcp/domain/value_objects/mode_tool_policy.dart)中,并作用于每一次运行:

  • residentBuiltins —— 文件与 shell 词汇。
  • residentDiscovery —— search_tools 和 list_my_tools。不要移除它们;智能体正是靠它们触达其他所有工具。
  • residentMcpTools —— 少数几个值得在每次请求中都携带 schema 的 Control Center 工具。
  • 一个按模式划分的区块,用于某个给定模式在几乎每一轮都会用到的工具。

编辑它时遵守两条规则:

只有在大多数运行都会用到某个工具时才把它加进来。 每个名称都会在每一次运行的每一次请求中永久地消耗其 schema。一个用时重要、但总体少见的工具属于延迟加载的那一半——智能体仍然能找到它。

使用工具的真实名称。 与任何工具都不匹配的常驻名称是无效的:没有报错,没有警告,该工具只是被延迟加载了。test/tooling/resident_tool_names_test.dart 会捕获这种情况,并把每个模式的常驻工具数控制在 40 个以内,而这正是此事的要点——超过这个区间后,工具选择会明显劣化。

一个模式的必需动词和固定动词会自动加上,因此你无需列出该模式自身的输出动词。

当智能体用错了工具,或声称自己无法完成某件事时,请判断发生的是以下三种情况中的哪一种。

每次运行都会记录它加载了哪些延迟加载工具以及触发加载的原因——按名称直接调用,或一次 search_tools 查询。打开该运行的事件流(设置 → 工作区 → 智能体 → 该智能体 → 日志 → 查看),查找 [harness] loaded N deferred tool(s)。

  • 没有任何记录,而智能体说这项能力不存在 —— 它从未查找过。这是提示词问题,而不是检索问题:延迟加载索引就在它的系统提示词中,并且带有这样一条指令——在搜索返回空结果之前,不要宣称某项能力不存在。
  • 记录了搜索,但没有加载任何工具 —— 一次真正的检索未命中。请转到第 2 步。
  • 加载了工具,却用错了 —— 它找到的工具彼此太相似了。请转到第 3 步。

search_tools 会匹配名称、描述和参数名。未命中几乎总是意味着描述中没有包含智能体所使用的词。

把这些词加进去。描述就是检索索引,所以要加长而不是删减——写出操作者实际会输入的词汇(“assign”“hand off”“give to”),而不只是内部名词。为了省 token 而删减描述,会让你同时失去搜索命中和选择准确率,而相比 schema 本身几乎省不下什么。

如果正确的工具存在但排名靠后,这正是 limit 参数的用途;该工具自身的描述会告诉智能体去扩大一次看起来接近却未命中的搜索。

如果两个工具的描述在事先不知道区别的人看来读起来一样,那模型就是在猜。把它们合并成一个工具,并加入一个用于区分的参数。

ticket_relation 和 ticket_pr_link 就遵循这一模式:两者都通过一个用于区分的参数来处理关联与取消关联。在一次调用内部、带着请求的完整上下文做出的选择,比在两个相似描述之间做出的选择更可靠。

当工具需要不同的审批门控、不同的审计或不同的渲染方式时,就让它们保持独立——这些理由意味着合并会让你付出实实在在的代价。

缓存命中率会以缓存率的形式显示在可观测性 → 洞察中。健康的长会话会保持在较高水平;如果它持续偏低,说明有东西在每一轮重写前缀。

常见原因按可能性从高到低排列:

  • 提示词前部任何不确定的内容。 一个时间戳、一个请求 id,或者一个以不同顺序遍历的集合,都足以造成这种情况。
  • 会话中途更换模型或推理力度。 两者都会改变前缀。子智能体在这里尤其容易出错:为了省钱而让子运行使用更低的推理力度,因失去缓存共享而付出的代价可能比省下的还多。
  • 频繁压缩。 压缩按设计会重写历史,所以它的代价是实实在在的;解决办法是少而深地压缩,而不是频繁而浅地压缩。

用 --tool-deferral=off 关闭延迟加载是一种有效的 A/B 手段,可用来隔离某项行为变化是否来自两层式工具面。它会精确重现延迟加载出现之前的请求,因此如果在关闭后该行为依然存在,那么工具面就不是原因。