程序员学 AI(六):Tools 与 Tool Calling——AI 为什么可以调用外部能力

上一篇讲 RAG,我们解决了"模型不知道业务知识怎么办":先检索相关资料,放进 Context,再让模型有依据地回答。

但知道怎么做,不等于真的去做。用户问"怎么申请开门",客服可以解释规则;用户说"帮我把门打开",生成一句"已经打开了",门并不会因此打开。

同样的问题也出现在查订单、取消预约和发送邮件里:LLM 负责生成内容,为什么 AI 应用却能操作外部系统?

这篇就把中间那条连接拆开讲。我们先认识工具,再看模型怎样提出调用,最后用我的自习室智能客服串起一次开门操作。

复习第一篇的总架构图,LLM的AI模型推理返回了结果内容,但是实际并没有操作我们任何功能,最后还是要本地应用执行工具调用。如果没有工具调用,结果只是字符串,AI模型没有调用任何外部能力。

一、AI 能做事,是因为应用接上了执行能力

先说结论:

模型提出调用请求,应用程序执行工具,业务系统返回实际结果。

模型可以理解"帮我开门"的意思,也可以生成"调用开门工具,手机号是某个值"这样的请求。就是说LLM等AI模型预测返回了结果内容,但是实际并没有操作我们任何功能,最后还是要连接业务系统、检查预约、请求门禁接口,依然是程序在做。

Tool Calling 增加的,是自然语言到业务调用之间的一层转换,而不是让模型直接控制了所有外部系统。

从后端开发的角度看,可以把调用请求想成一张"待处理的业务单":上面写着动作和参数,但处理是否获准、最后是否成功,必须由接单的程序判断。

所以,"模型返回调用开门工具"和"门禁返回开门成功"是两个不同事件。就像前端提交支付请求,不代表支付已经完成。

本文聚焦应用自定义的业务工具。有些平台还提供由平台执行的内置搜索等工具,执行位置会不同;但"生成请求"和"执行动作"的区别仍然存在。

二、Tool 是什么,模型怎样知道有哪些工具?

Tool 就是应用开放的一项外部能力。它背后可以是一个 Java 方法,也可以是封装好的 HTTP API。以函数形式提供工具时,经常会看到 Function Calling 这个名称。

普通程序调用函数,通常由开发者写好的分支决定函数名和参数。接入模型后,则可以让模型根据用户表达,提出该调用哪个已开放的工具。

不过,模型不会自动扫描你的 Service,也不知道哪个方法能开门。你得先给它一本"工具说明书"。

工具的两面 包含什么 谁使用
工具说明 名称、用途、参数约定 模型据此选择工具、生成参数
工具实现 业务代码、接口调用、结果处理 应用负责执行和控制边界

例如,开门工具可以这样描述:

json 复制代码
{
  "type": "function",
  "function": {
    "name": "openDoor",
    "description": "用户请求开门时使用,需要预约手机号",
    "parameters": {
      "type": "object",
      "properties": {
        "tel": {
          "type": "string",
          "description": "用户提供的预约手机号"
        }
      },
      "required": ["tel"]
    }
  }
}

这里的名称告诉模型"调用谁",描述告诉模型"什么时候用",参数约定告诉模型"还需要什么信息"。

这份说明没有执行代码。 模型不需要知道门禁接口地址、设备凭证,也不需要阅读整个后端工程。应用只暴露业务能力的入口,内部实现仍由程序持有。

工具描述也不是越宽泛越好。"处理用户请求"很难让模型判断边界,而"用户请求开门时使用,需要预约手机号"就清楚得多。好的工具应表达一个明确的业务动作。

下文的 JSON 与 Java 代码均为根据客服场景简化的教学示例;手机号是示例值,代码片段不构成独立可运行工程。为突出机制,统一调用对象用 ToolInvocation(name, arguments) 表示,这是本文的教学抽象。

三、模型怎样提出调用:原生工具调用与约定 JSON

上面总结了模型LLM层只是返回了结果内容,并没有真正执行操作。最后怎么执行呢?

"给模型提供工具"之后,还要解决另一个问题:程序怎样识别模型是在正常回答,还是请求执行一个动作?

这里有两种常见接法。它们的区别首先在于调用请求放在哪里、格式由谁约定 ,而不是执行权交给谁。

有些AI模型API接口是支持返回工具调用的,有些没有。

对于新接入,如果模型和接口支持原生工具调用,我会优先使用它;约定 JSON 则作为兼容接入或理解机制的方案。它们能接到同一个执行层,但不意味着协议支持和维护成本完全相同。

1. 原生工具调用:接口提供专门的调用结构

如果所使用的模型及接口API支持原生工具调用,应用可以在请求的 tools 中提供工具定义,接口则通过专门的响应结构表达调用请求。

tool_calls 这种接口形式为例,响应中的关键片段如下:

json 复制代码
{
  "tool_calls": [
    {
      "id": "call_001",
      "type": "function",
      "function": {
        "name": "openDoor",
        "arguments": "{\"tel\":\"13800000000\"}"
      }
    }
  ]
}

这相当于模型交出一张调用单:"请执行 openDoor,参数是这个手机号。"其中 arguments 是 JSON 字符串,需要继续解析,不能当作已经执行过的结果。

注意两个名字的区别:请求中的 tools 是可用工具目录;响应中的 tool_calls 是本次调用请求。 它们不是同一份数据。

字段名也不是所有模型通用。例如 Claude 接口使用 tool_use 内容块表达工具调用,接入时应读取对应接口的定义,而不是在所有响应里寻找 tools官方工具使用说明

支持工具调用也不意味着每轮都调用工具。在允许模型自主选择的配置下,普通咨询可以直接回答,缺少信息时可以追问,需要外部能力时才提出调用。

2. 兼容方式:约定 JSON,再解析调用意图

如果当前接入没有使用原生工具调用接口,也可以在提示词里约定:需要执行动作时,按指定 JSON 格式回复;普通回答使用另一种消息类型。

例如,约定 action 表示消息用途,name 表示工具名,arguments 保存独立参数:

json 复制代码
{
  "action": "tool_call",
  "name": "openDoor",
  "arguments": {
    "tel": "13800000000"
  }
}

这里的 JSON 是模型普通回复的正文,不是接口自动提供的工具调用字段。程序先解析正文,再判断 action,最后取出函数名和参数。

不需要操作时,可以返回 action: "answer" 和回答内容。这样的区分,是为了避免把普通说明误判为执行指令。

这不是临时杜撰的做法。LangChain 的经典 Structured Chat Agent 就通过提示词约定 JSON 的 actionaction_input,表达工具名与输入。它证明这条工程路径有真实先例,但不代表它是今天所有新项目的首选。LangChain 官方示例

本文使用 actionnamearguments 是为了简单清晰,并不是引用一个统一行业标准。专业的做法,是把它明确当作应用层调用协议,而不是把自定义字段包装成模型原生能力。

这条路能把已有业务能力接到普通对话模型上,但格式识别的责任更多落在应用侧。模型可能夹带解释文字、漏字段,或者生成不在约定范围内的动作,程序必须明确拒绝或要求重新生成。

即使使用了 JSON 输出约束,也不能把"能够生成合法 JSON"等同于"能够可靠地选择工具"。语法正确、参数符合约定、业务允许执行,是三件不同的事。

3. 两种响应,进入同一个执行入口

原生方式按接口结构解析,约定 JSON 方式按应用协议解析。两条路径都要检查工具名与参数,再统一成:

text 复制代码
ToolInvocation
  name      = openDoor
  arguments = { tel: "13800000000" }

这样,后面的业务执行层不必关心上游响应来自哪个模型,也不需要在每个业务函数里分别处理两种 JSON。

这种分层带来的实际价值是:替换模型或调整消息格式时,主要修改模型适配部分;开门、查预约等业务逻辑仍然保持自己的职责边界。

原生调用也需要解析和校验,只是调用信号由接口明确表达。若要把结果回传模型,适配层还应保留调用 ID 和原始调用消息,不能在统一参数时把这些关联信息丢掉。

四、把调用单变成业务动作:我的智能客服开门案例

我的自习室智能客服既处理规则咨询,也需要帮用户开门(开门时还要校验是否有预约)、协助预约。接入工具的意义,是把用户的自然语言接到已有业务服务上。

图中展示操作型请求的简化路径;RAG 标注聚焦检索准备,后续 LLM 完成生成。并不是每条咨询都需要执行工具。

可以用下面这段对话理解处理过程:

用户:帮我开一下门。

客服:请提供预约手机号。

用户:13800000000。

应用:根据调用请求处理预约与门店信息,并请求门禁接口。

客服:根据业务返回状态提示结果。

前两轮解决的是意图和参数问题。有了必要信息,模型才能提出开门请求;程序收到请求后,再决定它是否符合实际业务条件。

执行层最核心的工作,是把已经检查过的工具名称映射到固定的业务入口:

java 复制代码
// 代码片段:请求已完成格式、工具名和参数检查
String name = invocation.name();
Map<String, Object> arguments = invocation.arguments();

switch (name) {
    case "openDoor":
        String tel = (String) arguments.get("tel");
        return industryService.openDoor(
            tel, request.getAccountType(), arguments, response
        );
    default:
        throw new IllegalArgumentException("不支持的工具");
}

这里没有执行模型生成的代码,也没有根据任意文本反射调用项目方法。模型只能提出工具目录内的动作,Java 负责把动作接到受控的业务服务。

沿着开门服务往下看,还要处理预约信息、门店定位和门禁接口。把这些规则留在业务服务里,才能让"聊天开门"和其他开门入口复用同一套业务判断。

例如,手机号只是查找预约的输入,不应该由模型凭空生成门店 ID,更不能由一句"用户说有预约"代替预约系统的数据。模型负责提取表达,业务系统负责提供事实。

工具执行之后,回复可以有两种处理方式:

  • 直接返回业务提示。 如果结果已经明确,例如预约不存在或接口返回操作成功,应用可以组织确定的提示发给用户。
  • 把结果交回模型。 如果需要结合上下文解释或汇总,应用保留原调用消息,并按接口要求关联调用 ID 回传结果,再请求模型生成回答。

第二种方式中,模型应依据工具结果组织语言,不能把失败改写成成功。第一种方式也并不缺少 Tool Calling:真实动作已经由工具完成,没有必要为了形式完整再调用一次模型。

这个案例的关键不是"模型学会了门禁协议",而是模型输出调用单,程序把调用单接到业务服务,最后用实际结果闭合这次请求。

五、工具怎么接进来:直接接入与 MCP 接入

前面讲的是模型怎样表达调用请求。现在把视线往后移一步:应用已经拿到工具名和参数,怎样连接真正的工具?

1. 直接接入:由应用自己连接业务函数或 API

最直观的方式就是前面的 Java 分发:应用在自己的代码里维护工具与函数的映射,执行时直接调用 Service,或者通过适配代码请求外部 HTTP API。

这里的"直接"描述的是接入方式,不代表所有能力都在本机。一个本地 Java 方法也可以继续请求远程门禁服务。

2. MCP 接入:通过统一协议发现和调用工具

另一种方式,是由 MCP Server 暴露工具,应用通过 MCP Client 获取工具说明,并发起工具调用。Server 再连接具体业务能力,返回结果。

应用把获取到的工具说明适配给模型;模型提出调用后,应用再把它转成对应的 MCP 调用。MCP 接通的是工具,不是让模型越过应用直接操作业务系统。

MCP Server 可以在本地运行,也可以部署在远程。因此,准确的对比是"直接接入与 MCP 接入",不是"本地工具与 MCP 工具"。MCP 官方架构说明

这两组概念属于不同层次:

要解决的问题 可采用的方式
模型怎样表达"我要调用工具" 原生工具调用,或约定 JSON
应用怎样连接并执行工具 直接接入函数/API,或通过 MCP 接入

因此,原生工具调用可以连接本地函数,也可以连接 MCP 工具;约定 JSON 在完成适配后,同样可以交给这两种执行方式。

本篇只讲清这层关系,不把"使用 MCP"当作更高级的必选项。单个业务系统可以直接接入;需要在多个应用间复用工具时,再考虑协议化接入的价值和成本。

六、让工具执行可靠,关键仍在程序

看到这里,工具调用并不神秘。真正需要认真设计的,是从"模型提出请求"到"程序允许执行"之间的边界。

第一,参数不足,先补齐。 用户只说"帮我开门",不能为了凑齐 JSON 就猜一个手机号。模型可以追问,程序也必须拦住缺少必填参数的调用。

第二,参数完整,不等于已经授权。 手机号格式正确,不代表它属于当前用户;能找到预约,也不意味着任意会话都可以操作。身份、资源归属和业务条件要由后端检查,必要的确认也不能只写在提示词里。

第三,请求已发出,不等于操作成功。 接口拒绝时应返回失败;超时且无法确认状态时,应说明结果待确认,不能直接宣称成功或无条件重试。涉及外部状态的操作还需要防重复、必要确认和操作记录。

这三条并不是 AI 独有的要求,而是业务接口原本就需要承担的责任。调用方换成模型之后,这些责任不会消失。

再与上一篇连起来看:RAG 把相关资料放入 Context,帮助模型理解和回答;Tool Calling 则把模型提出的动作连接到业务执行。检索本身也能被封装成工具,两者不是互斥关系。

蓝色区域强调检索增强生成,橙色节点强调按需执行。知识文档是示例,图中不展开普通回答和失败处理分支。

回到标题,AI 应用之所以能调用外部能力,不是因为一段回答自动变成了动作,而是因为应用建立了这条明确的连接:

工具说明让模型知道能请求什么,调用结构让程序知道要处理什么,业务实现决定能否执行以及实际结果。

下一篇继续讲 MCP:当工具需要被多个应用复用时,它怎样统一发现与调用,又和普通 API 有什么区别。

相关推荐
程序员Better1 小时前
GPT-6 Astra 到底强在哪?
人工智能
大熊背1 小时前
ISPPipeline中的图像噪声问题解析
图像处理·人工智能·计算机视觉·去噪·isppipeline
数造科技1 小时前
政务数据治理实战:统一数据底座如何破解商事注册“重复填报”与数据孤岛
大数据·人工智能·科技·政务·ai-native
HyperAI超神经2 小时前
SenseNova-U1.5-8B-MoT 统一生成与理解,解锁原生多模态创作;DeepSeek-V4-Flash-Vision-Exp 拓展视觉理解新能力
人工智能·深度学习·图像生成·多模态大模型·视觉推理
xierui1231232 小时前
Agent记忆不是聊天记录:用三层存储构建可追溯的 AI 工作流
数据库·人工智能·aigc·软件工程
程序员Better2 小时前
2026 AI Agent 开发学习路线:从小白到全栈,这波红利必须抓住!
人工智能
RAOY的AI笔记2 小时前
GPT-6打破孪生素数猜想最新纪录:AI正在进入数学研究新时代?
人工智能·gpt·算法
围炉聊科技2 小时前
Agent 的隐身术:CloakBrowser 反检测拆解
人工智能
长空任鸟飞_阿康2 小时前
三、《从零手撸 Agent》 · system prompt 与核心参数:调好你的旋钮
人工智能·python·ai·prompt