A15第 1 / 1 课时未开始内容版本 1.0.0 · 核验于 2026-10-26

工具调用的工作过程

工具调用的工作过程 · 阅读后完成下方实验与检查题,再用「标记完成」记录进度。

学习目标

读完本课你能说清工具调用的三方职责——模型提出调用、服务端校验执行、结果回传继续生成;并追踪一个只读工具从声明到回传的完整流程。

问题场景

问学习助手「站里有哪些讲状态的课」,它自信地列了五门——其中两门根本不存在。模型没有检索能力,它只是在生成「看起来像答案的文本」。要让它基于真实数据回答,就得给它工具。

核心解释

工具调用的三方职责必须分清:

  1. 模型:提出调用,不执行。 你在请求里声明工具清单(名称、用途、参数结构),模型判断当前问题需要工具时,输出一个「调用申请」——finish_reason: "tool_calls",附带工具名和参数。它自始至终碰不到你的数据库。
  2. 服务端:校验执行。 你的代码检查工具名是否在白名单、参数是否合法,然后执行真实逻辑(查库、读文件),把结果变成一条 role: "tool" 消息。
  3. 回传与继续: 把工具消息追加进 messages 再请求一次,模型基于真实结果生成回答;它可能再次申请工具——循环与上限是 A17 课的事。

声明与申请的数据形状(本站 lib/ai/ 的真实设计):

// 服务端声明:白名单内的只读检索工具(lib/ai/tools.ts)
const searchCourses: ToolDefinition = {
  type: "function",
  function: {
    name: "search_courses",
    description: "检索站内课程:按关键词匹配标题、简介与正文。回答学习问题前先检索。",
    parameters: {
      type: "object",
      properties: {
        query: { type: "string", description: "检索关键词" },
        track: { type: "string", description: "可选,限定方向" },
      },
      required: ["query"],
    },
  },
};
// 模型的申请:finish_reason 为 "tool_calls" 时出现
{
  "choices": [{
    "finish_reason": "tool_calls",
    "message": {
      "role": "assistant",
      "content": null,
      "tool_calls": [{
        "id": "call_abc123",
        "type": "function",
        "function": { "name": "search_courses", "arguments": "{\"query\":\"状态\"}" }
      }]
    }
  }]
}

两个关键认知:

  • arguments 是字符串,不是对象——模型生成的是 JSON 文本,服务端要 JSON.parse 并校验(A16 课专门讲)。
  • description 写的是给模型看的「使用说明书」:什么时候该用、参数什么含义。写得含糊,模型就乱调或该调不调。

示例拆解

服务端执行环节(本站 executeTool 的骨架):

// 白名单校验在先,执行在后(app/api/ai/chat/route.ts)
const allowed = ASSISTANT_TOOLS.some((t) => t.function.name === name);
let result: string;
if (!allowed) {
  result = JSON.stringify({ error: "工具不在白名单内" }); // 拒绝也是结果,回传给模型
} else {
  const exec = executeTool(name, call.function.arguments);
  result = exec.result;             // 结构化 JSON 字符串
  refs.push(...exec.refs);          // 同时生成给前端展示的课程引用
}
messages.push({ role: "tool", content: result.slice(0, 8000), tool_call_id: call.id });

关键点:

  • 工具结果也过一条协议:以 tool_call_id 对应申请,内容截断到上限(本站 8000 字符),避免一次检索把上下文撑爆(A02)。
  • 拒绝也返回结构化错误而不是抛异常:让模型「知道被拒」并换路,而不是让整个请求崩掉。
  • tool 角色的结果不直接展示给用户;给用户的是服务端整理的 refs 引用(A10 事件协议里的 refs 事件)。
  • 本站三个工具全是只读检索(search_courses / get_lesson / search_documents):先只用读工具建立信任,写操作工具的风险控制后面再谈。

动手任务

  1. 在 executeTool 的入口和出口各加一行日志,问一个会触发检索的问题,在服务端控制台完整追一次「申请 → 校验 → 执行 → 回传」。
  2. 故意把问题问得与站内内容无关(如「今天天气」),观察模型是否还申请工具——想想 description 里的「回答学习问题前先检索」起了什么作用。
  3. 解释任务:为什么「模型提出调用」而不是「模型直接查库」?从安全和可控两个角度各写一句。

常见错误

  • 以为模型执行了工具:模型只会输出申请文本;执行永远发生在你的服务端代码里。
  • 白名单形同虚设:拿到工具名直接执行,不校验是否在声明清单里——模型也可能「幻觉」出不存在的工具名。
  • 工具结果原样全量塞回去:一次全文检索回传几万字,上下文瞬间爆掉;必须截断与摘要。
  • description 写成内部注释:模型靠它决定「何时用、怎么用」,含糊的描述直接导致乱调参数。

检查与复盘

自查清单:

  • 能完整说出三方职责与「申请 → 校验执行 → 回传 → 继续」的循环
  • 能默写 ToolDefinition 与 tool_calls 的关键字段
  • 知道工具结果以 tool 角色、按 tool_call_id 对应回传

参考资料

检查题共 3 题,提交后立即显示解析
答案由服务端判定;答错的题会自动进入复习计划。
Q1单选简单

工具调用中「模型、服务端、回传」三方职责的划分,正确的是?

Q2读代码中等

阅读模型返回的工具调用申请。服务端拿到后必须做哪两步处理才能执行?

{
  "choices": [{
    "finish_reason": "tool_calls",
    "message": {
      "role": "assistant",
      "content": null,
      "tool_calls": [{
        "id": "call_abc123",
        "type": "function",
        "function": { "name": "search_courses", "arguments": "{\"query\":\"状态\"}" }
      }]
    }
  }]
}
Q3多选中等(多选,全部选对才算正确)

关于工具执行与结果回传的纪律,下列说法正确的有?(多选)

读完这一课了?

标记完成后,工作台会安排接下来的学习。

Command Palette

Search for a command to run...