工具注册与分发 · 从 model 请求到工具执行

一堆异质的工具怎么装进一个集合、按什么顺序摆给模型、一次工具调用又怎么从名字走到执行。这一章拆 ToolBridge 与 FinalizedToolset:注册期的类型擦除、finalize 的定序、以及一次 call 的完整数据流。

作者 David更新于 第 17 篇(共 60 篇)

工具注册与分发 · 从 model 请求到工具执行

上一章看了「一个工具是什么」。这一章看「一堆工具怎么组织、怎么被调到」。

ToolBridge:agent 和工具之间那道缝

主循环不直接碰工具,只通过 ToolBridge(bridge.rs)。它就是「agent ↔ 工具」的接缝,对上暴露:

  • tool_definitions()——给模型的工具清单(manifest);
  • call(name, params, id)——派发一次工具调用;
  • register_mcp_tools / unregister_*——运行时增删 MCP 工具;
  • render_prompt(...)——用 TemplateRenderer 渲染 prompt。

ToolBridge 内部其实很瘦:一个 Arc<FinalizedToolset> + 一个摆在锁外的终端后端句柄(这样取消 kill_foreground_commands 能在 call() 持锁时照样工作)。

builder → finalize → 不可变集合

工具集的生命周期是「可变构建 → 定格」两段:

  • ToolRegistryBuilder:一个无序 HashMap<String, ToolEntry>。注册 register_with_params::<T, P>() 时,把具体类型 T/P 擦除进一组 boxed 闭包(output_converter、validate_params、parse_input……)——这就是异质工具能装进同一个容器的关键 Rust 技巧:用闭包把泛型擦成同一类型。
  • finalize(config, ctx):按 ToolServerConfig.tools 的顺序逐个定格成 FinalizedTool,收进不可变的 FinalizedToolset。

一个容易忽略但很重要的点:模型看到的工具顺序 = ToolServerConfig.tools 这个 Vec 的顺序。builder 的 HashMap 是无序的,顺序完全由客户端配置的 Vec 提供。工具排序会影响模型的选择倾向,所以这不是无所谓的细节。

SessionContext → Resources:依赖注入

finalize 时传入的 SessionContext 装着这个会话的一切具体依赖:终端后端、文件系统、cwd、记忆后端、各功能配置……它不被存下来,而是被转成一个 Resources——一个 HashMap<TypeId, Box<dyn Any>> 的类型化容器(依赖注入 + 运行时状态都放这)。

Resources 里用两个 newtype 区分:Params<T>(配置)与 State<T>(运行时状态,如 TodoState)——同一个 T 靠不同 TypeId 并存,只有它们会被序列化进 resources_state.json 持久化。

一次 call 的完整数据流

这是本章的核心。ToolBridge::call 最终走这条链(全在 registry/types.rs):

图表加载中…

几个要点:

  • 锁只持微秒:在 prepare_dispatch 里就把要用的东西(转换器、反向参数映射)从读锁下克隆出来,.await 之前放锁——绝不跨 await 持锁。
  • 一份结果两个去向:ToolRunResult(可转成 ToolBridgeResult)把干净的结构化 output(给 JSON/ACP/遥测)和追加了提醒的 prompt_text(给模型)分开。
  • 提醒是横切的:finalize_output 每次调用后跑所有 Reminder(LSP 诊断、技能发现、任务完成),把结果包成 <system-reminder> 追加进 prompt_text(客户端声明不投递提醒时,这一步整个跳过)——这就是主循环里那些提醒的来源。
  • 持久化:每次调用后把注册的 Params/State 防抖写盘,所以 TodoState 这类东西能跨重启恢复。

和《从 LLM 到 Coding Agent》对照

对照 tool-use:最小实现里,工具派发常是 tools[name](args) 一句话。grok 的增量全在「真实产品的工具集是动态、异质、有状态、要排序、要持久化、还要横切提醒」这些事上。下一章看一个更深的问题:这套派发怎么做到「同一份契约,工具在同进程或跨进程都能跑」。


上一篇:一个工具的完整契约 · 下一篇:工具协议 · 进程内与跨进程

本页目录