文章进阶AI 生成初稿 · deepseek-chat

工具调用:给 Agent 一双手

从零讲清工具调用(Tool Calling / Function Calling):工具是什么、模型如何选择工具、工具描述为何决定成败、一次完整调用的七步流程,以及参数错误、工具过多、权限与安全三类常见坑。面向有编程基础的中文开发者,框架无关,代码最小化并指向官方文档。

约 6 分钟发布于 2026年9月11日更新于 上次校对 2026年9月10日 · shelter123
本文目录

文档信息:本文由 AI 生成初稿;生成模型 = deepseek-chat;知识截止 = 2026-09-11; 文档抓取时间 = 2026-09-10T16:39:59.841Z;建议复核周期 = 季度。

TL;DR

  • 工具(tool)就是你自己写的函数或调用的 API:模型不执行它,只输出“我想调用谁、传什么参数”的结构化意图。
  • 模型靠工具描述(description + 参数 schema)选工具,描述写得含糊,选错工具几乎是必然结果。
  • 一次工具调用是七步往返:给工具清单 → 模型输出调用意图 → 你的代码执行 → 把结果回灌 → 模型继续推理,可循环多轮。
  • 三个高频坑:参数不符合 schema、工具数量过多导致选择退化、以及把高危操作直接暴露给模型。
  • 工具调用是 Agent 的“手”;再配上规划与记忆,才构成完整 Agent。

前置知识

需要:能用一门语言写函数、理解 JSON、知道 HTTP 请求/响应大致长什么样。读过本路径前一篇关于 Agent 四大构件的内容更好——那里把工具定位为 Agent 的“手”,本篇专门展开这只手怎么接上。

不需要:不需要机器学习背景,不需要微调经验,不需要选定任何框架。本文所有概念与框架无关,示例是伪代码,任何支持工具调用的模型 API 都能套用。

零基础?先读小白故事线《如果 Agent 是新来的实习生》。

正文

工具到底是什么:一组你写的函数

先把最容易混淆的一点钉死:工具不是模型的能力,是你系统里的代码

模型本身只能做一件事——根据输入预测输出 token。它不能查数据库、不能发邮件、不能读你的文件系统。所谓“给 Agent 一双手”,指的是你把这些能力写成函数,再把函数的签名和用途告诉模型;模型负责决定“什么时候该用哪只手”,真正动手的始终是你的进程。

一个工具通常包含四部分:

  • 名字(name):模型引用它时用的标识符,如 get_weather
  • 描述(description):自然语言说明它做什么、什么时候用、什么时候不该用。
  • 参数 schema(parameters):通常是 JSON Schema,声明每个参数的类型、是否必填、取值范围。
  • 执行体(implementation):你自己的代码,接收参数、返回结果。

以“查天气”为例,工具声明大致长这样(各家的字段名略有差异,语义一致):

{
  "name": "get_weather",
  "description": "查询指定城市当前的天气状况。仅在用户询问实时天气时使用。",
  "parameters": {
    "type": "object",
    "properties": {
      "city": { "type": "string", "description": "城市名,如 杭州" },
      "unit": { "type": "string", "enum": ["celsius", "fahrenheit"] }
    },
    "required": ["city"]
  }
}

注意这里没有任何“实现”。模型看到的只是这份说明书。这就是工具调用的核心心智模型:你提供说明书,模型提供选择,你提供执行

模型如何决定调用哪个工具

当你把工具清单随请求一起发给模型时,模型的输出空间多了一种可能:不是继续生成自然语言,而是生成一段结构化的“调用请求”。

它做判断的依据,按影响力从大到小大致是:

  1. 当前对话上下文:用户说了什么、之前几轮发生了什么。
  2. 工具描述与参数 schema:每个工具“声称”自己能做什么。
  3. 工具名字search_orderstool_1 有信息量得多。
  4. 调用策略配置:你可以强制它必须调用某个工具、禁止调用、或让它自己决定。

关键认知:模型并不“知道”你的工具能干什么,它只知道你写下的描述。描述里没提的能力等于不存在,描述里写错的能力会被它当真。这也是为什么下一节要单独讲描述。

还有一点常被误解:模型一次可以请求调用零个、一个或多个工具。多个调用可以并行发出(比如同时查三个城市的天气),是否支持并行、上限是多少,取决于具体 API 与模型版本——截至 2026-09-11,主流厂商的指南都覆盖了并行调用,细节请以官方文档为准。

工具描述为什么决定成败

把工具描述当成写给一个聪明但完全不了解你系统的同事的接口文档。它没看过你的代码库,只能靠这段文字判断该不该用。

一段合格的描述通常回答四个问题:

  • 这个工具做什么(一句话说清动作和返回)。
  • 什么时候用(触发场景)。
  • 什么时候不要用(排除场景,避免和相邻工具抢活)。
  • 参数的含义与格式(尤其是枚举值、单位、日期格式)。

对比一下两种写法:

差:查询订单。
好:根据订单号查询订单详情,返回状态、金额、下单时间。
    仅当用户提供了订单号时使用;若用户只给了手机号,
    请改用 search_orders_by_phone。

差的写法会让模型在“用户只给手机号”时也硬调这个工具,然后传一个空订单号——参数校验失败,白跑一轮。好的写法把边界写进去了,模型才有机会选对。

参数 schema 同理。{"type": "string"}{"type": "string", "enum": ["celsius", "fahrenheit"]} 的差别,就是“模型可能传 ”和“模型只能传这两个值”的差别。能用枚举就用枚举,能标 required 就标,能写 description 就写——这些约束会实实在在地降低错误率。

一次完整工具调用的流程

抛开框架,一次工具调用就是一个多轮往返。七步:

  1. 你发请求:带上用户消息 + 工具清单(+ 调用策略)。
  2. 模型返回调用意图:不是自然语言,而是结构化的 { name, arguments },可能一次多个。
  3. 你解析意图:拿到工具名和参数。
  4. 你校验参数:对照 schema 检查类型、必填、枚举。这一步是你的责任,不是模型的。
  5. 你执行函数:调用真实 API / 查库 / 读文件,捕获异常。
  6. 你把结果回灌:以“工具结果”的角色追加进对话历史,通常关联到第 2 步的调用 ID。
  7. 模型继续推理:基于结果生成最终回答,或再请求下一轮工具调用。

第 7 步之后如果又是调用意图,就回到第 3 步——这个循环就是 Agent “多步做事”的来源。循环要有终止条件:模型给出最终文本、或达到你设定的最大轮数。

最小伪代码(框架无关,只保留骨架):

// tools: 工具清单;runTool: 你实现的 { name -> fn } 映射
let messages = [{ role: "user", content: userInput }];

for (let step = 0; step < MAX_STEPS; step++) {
  const res = await model.chat({ messages, tools });

  if (!res.toolCalls?.length) return res.content; // 没有调用意图,收工

  messages.push(res.message); // 把模型的调用意图记进历史
  for (const call of res.toolCalls) {
    const args = JSON.parse(call.arguments);      // 4. 校验参数(此处略)
    const result = await runTool[call.name](args); // 5. 执行
    messages.push({                                // 6. 回灌结果
      role: "tool",
      toolCallId: call.id,
      content: JSON.stringify(result),
    });
  }
}
throw new Error("超过最大步数");

不到 20 行,但已经是一个能跑的工具调用循环。真实项目里你要补的是:参数校验、超时与重试、错误如何回灌给模型、并发执行多个调用。完整实现见文末官方文档。

常见坑之一:参数错误

模型生成的参数不保证符合 schema。常见形态:

  • 类型错:该给数字给了字符串 "3"
  • 缺必填:required 里的字段没给。
  • 幻觉值:给了 schema 里不存在的字段,或枚举外的值。
  • 嵌套结构错:把对象拍平成了字符串。

应对原则:永远在服务端校验,不要相信模型。校验失败时有两种处理——直接抛错终止,或把错误信息作为工具结果回灌给模型,让它自己修正后重试。后者在实践中往往更有效,因为模型能看到“我哪里错了”。但要有重试上限,否则会陷入“错—改—还错”的死循环。

另外,参数里的日期、单位、ID 格式最好在描述和 schema 里写死。让模型自由发挥时间格式,你会同时收到 2026-09-1109/11/2026今天

常见坑之二:工具过多

工具不是越多越好。当工具数量从 5 个涨到 50 个,会出现两个问题:

  • 选择退化:语义相近的工具互相干扰,模型选错率上升。
  • 上下文膨胀:工具清单本身占用 token,挤压真正有用的对话内容。

缓解手段,按性价比排序:

  1. 合并get_user_name / get_user_email / get_user_phone 合成一个 get_user_info
  2. 命名分层:用前缀分组,如 order_*user_*,让语义边界清晰。
  3. 动态裁剪:按当前任务只挂载相关工具子集,而不是一次全给。
  4. 写清排除条件:在描述里明确“这个工具不负责 X,请用 Y”。

一个经验判断:如果你自己看这份工具清单都要犹豫三秒该用哪个,模型大概率也会犹豫。

常见坑之三:权限与安全

工具是 Agent 伸向真实世界的手,也意味着真实世界的破坏力。几条底线:

  • 最小权限:给工具的执行体配上刚好够用的凭证。查天气的工具不该拥有写数据库的权限。
  • 高危操作要人确认:删除、支付、发信、改配置这类不可逆动作,走“模型提议 → 人确认 → 才执行”的流程,不要让模型直接落地。
  • 输入即不可信:工具返回的内容(网页、文件、第三方 API)可能包含诱导模型的指令,也就是提示注入(prompt injection)。工具结果应当被当作数据,而不是指令
  • 审计与限流:记录每次调用的工具名、参数、结果;对调用频率和单次影响范围设上限。
  • 沙箱执行:能跑代码或访问文件系统的工具,放在隔离环境里跑。

这些不是理论风险。OWASP 的 LLM 应用风险清单把提示注入列在首位,工具调用正是它最主要的落地路径。

延伸阅读

站内搜索

    搜索为本站本地索引增强;正文浏览不依赖 JavaScript。