Skip to content

Qwen Code:延迟工具为何要分“发现”和“执行”?

固定官方仓库 QwenLM/qwen-code@b9840886b86c23f196482cc1ee55d59cbc74df87。本篇只看普通工具声明模式下 ToolRegistry → tool_search → tool_call → CoreToolScheduler 的延迟工具桥;是静态源码剖面,不是运行实测。

Qwen Code 延迟工具的发现与执行双路径

查看原尺寸 SVG(可缩放)

图的文字版 · 可编辑图源 · 关键代码导读

一个工具有三种不同的“存在”

工具可以已经注册,却不在当前发给模型的 function-declaration 列表中。ToolRegistry.getFunctionDeclarations() 默认过滤仍隐藏的 deferred 工具;shouldDeferalwaysLoad、会话 reveal、visibleTools 等一起决定它是否在列表内。MCP 工具构造时设置 shouldDefer=true,但 alwaysLoad 等例外仍可能使其可见。声明过滤 · 隐藏判定 · MCP 工具

tool_search 可按关键词或 select:<name> 找 schema;关键词模式的候选是仍隐藏的 deferred 工具,精确模式也允许重看已可见工具。返回的是 <functions> 包裹的 schema 文本,不是将工具加入模型当前声明列表,更不是执行工具。查询模式 · 隐藏候选 · schema 返回

模型随后用 tool_call {name, arguments} 调用仍隐藏的目标。resolveDeferredToolCall() 会核对桥、目标、上下文排除规则、deferred/hidden 状态及声明能力;可见工具反而要求直接调用。调度器把 wrapper 请求改写成真实工具名和参数,再走目标工具的调度/权限路径。ToolCallTool.execute() 自己拒绝直接执行,防止绕开调度器。解析与拒绝 · 调度改写 · 不许直执行

设计取舍

这是一个“工具已注册,但完整 schema 按需给模型看”的折中:减少初始声明体积,同时保留发现和调用入口。源码在 setTools() 中取得注册表的声明列表,并向模型聊天对象设置它;tool_search 的文本回答本身不调用 setTools()setTools · tool_search 实现

但这不是绝对的“deferred 永远隐藏”:小规模 deferred schema 可被预算预加载,历史回放或会话 setup 也可 reveal;CodeModeOnly 有不同声明路径。预算预加载 · 模式分支

边界

这里的“发现”专指已注册工具 schema 的按需检索,不等同于 Skill 文件发现或插件加载;没有验证 MCP 重连、权限弹窗、hooks 的实际顺序,也不声称模型一定先调用 tool_search 才会试 tool_call。拒绝分支是在调度器里兜底检查,执行后的权限、审批和 hook 结果仍须另做实测。桥语义 · 目标策略检查

图的文字说明 · qwen-code-deferred-tools

图上方先区分两个事实:ToolRegistry 已注册目标工具,但 getFunctionDeclarations() 不把仍隐藏的 deferred schema 放进普通模型声明;模型保留可见的 tool_searchtool_call 桥。蓝色“发现”轨道是 tool_search 检索并把 <functions> schema 文本返回给模型;这只让模型读到参数形状,不执行目标,也不把目标加入函数声明。橙色“执行”轨道是模型发出 tool_call {name, arguments}resolveDeferredToolCall 检查目标与上下文,CoreToolScheduler 再把 wrapper 请求改写为真实目标并继续调度。后续仍按目标工具的策略执行。图内保留短标签,详细条件以本段和章节为准。两条轨道是机制拆解,不表示源码里有两个独立线程,也不表示每次调用前都必须先搜索。

图只针对仍隐藏的 deferred 工具。已预加载或被 reveal 的工具可直接出现在模型声明中;CodeModeOnly 则使用另一条声明机制。可见工具用 tool_call 包装会被拒绝,须直接调用。固定官方源码 b9840886b86c23f196482cc1ee55d59cbc74df87,证据定位与未验证边界见章节


在线预览稿:书稿仍在校稿,系统篇以文内固定源码版本为准;静态阅读不等于运行验收。