首页 / 课程 / D02 深挖
Part 1: 核心架构 · 第 4 讲

24 讲路线 · 与 S02 配对

D02: Tool System 深挖 · 工具系统

本讲在 S02 主线之上,聚焦实现细节、边界条件与自测;导图与主线相同模块,便于对照。

建议:先读完 S02,再按下方顺序走读源码与练习。

模块导图(与 S02 同源,便于对照):注册表、校验失败与 ToolResult 契约

🔬 深挖目标

工具不是「一个函数」,而是 Schema + 权限钩子 + 执行器 + 结果契约 四层叠在一起;本讲把每一层的失败形态对齐到可观测现象。

📐 ToolDef 契约(概念层)

// 与 S02 对照:抓住这四件事
name + description          // 给模型的「广告」
inputSchema                 // 宿主侧校验,防 JSON 胡写
hasPermission? → execute    // 顺序不能反
execute → ToolResult        // 必须可序列化回消息

🔍 失败矩阵

阶段典型原因用户可见现象
Schema 校验缺字段 / 类型错工具未执行,直接报错或模型重试
权限策略为 ask 且用户拒绝明确 deny,循环继续但无结果块
执行期超时、退出码非 0stderr / is_error 标记进入上下文

工具失败如何回灌模型、何时重试:参考答案D01 · 工具执行失败(与本表「执行期」一行对照读)。

🔗 与相邻章节

  • 扩展 Read/Write/Edit 的实践练习骨架见 D01 练习 2
  • 进入执行器之前几乎必经 D03 权限;别把「业务错误」和「策略拒绝」混在同一分支里。
  • MCP 动态工具(D07)与内置工具共用同一套「调用外壳」时,重点看名称空间是否冲突

📖 走读顺序

  1. 列出仓库里所有内置工具的注册表初始化点。
  2. 找一个「重」工具(如 bash)和一个「轻」工具(如 read),对比权限字段差异。
  3. 追踪一次校验失败:错误对象最终如何变成模型可见的文本。

✏️ 自测 1 · 参考答案:execute 为何不能「假成功」?

题干

为什么 execute 不应吞掉异常而返回「假成功」?

结论

  • 模型把 tool_result 当作地面真值;若失败被伪装成空输出或成功,下一轮会基于幻觉继续改代码、提交或删除文件。
  • 宿主无法区分「业务语义失败」(测试挂、编译错)与「传输/执行失败」;吞异常会把两类混在一起,用户也无法在日志里追责。
  • 正确做法是:结构化失败is_error 或等价位 + 人类可读摘要 + 可选 stderr 尾),与 D01 · 工具失败 一致。

✏️ 自测 2 · 参考答案:MCP 与内置同名 tool 如何消歧?

题干

若同一 tool name 被 MCP 与内置同时注册,你会如何在代码层消歧?

结论

注册表合并阶段定死唯一真相,不要让运行时随机命中:

  • 命名空间前缀(推荐):内置保持短名,MCP 工具加 mcp__serverId__toolName 或文档约定前缀;模型侧 schema 与调用必须一致。
  • 显式优先级:若允许覆盖,在合并表里记录 source: builtin | mcp,冲突时内置优先或 MCP 优先(写进项目文档),并打日志。
  • 禁止静默二义:启动或 tools/list 后若仍有重名,应 fail-fast 或自动改名并通知用户,避免「有时打到 A 有时打到 B」。

✏️ 自测(题干回顾)