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

📅 发布时间:2026/9/7 22:14:23
程序员学 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也不知道哪个方法能开门。你得先给它一本“工具说明书”。工具的两面包含什么谁使用工具说明名称、用途、参数约定模型据此选择工具、生成参数工具实现业务代码、接口调用、结果处理应用负责执行和控制边界例如开门工具可以这样描述{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这种接口形式为例响应中的关键片段如下{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保存独立参数{action:tool_call,name:openDoor,arguments:{tel:13800000000}}这里的 JSON 是模型普通回复的正文不是接口自动提供的工具调用字段。程序先解析正文再判断action最后取出函数名和参数。不需要操作时可以返回action: answer和回答内容。这样的区分是为了避免把普通说明误判为执行指令。这不是临时杜撰的做法。LangChain 的经典 Structured Chat Agent 就通过提示词约定 JSON 的action和action_input表达工具名与输入。它证明这条工程路径有真实先例但不代表它是今天所有新项目的首选。LangChain 官方示例本文使用action、name、arguments是为了简单清晰并不是引用一个统一行业标准。专业的做法是把它明确当作应用层调用协议而不是把自定义字段包装成模型原生能力。这条路能把已有业务能力接到普通对话模型上但格式识别的责任更多落在应用侧。模型可能夹带解释文字、漏字段或者生成不在约定范围内的动作程序必须明确拒绝或要求重新生成。即使使用了 JSON 输出约束也不能把“能够生成合法 JSON”等同于“能够可靠地选择工具”。语法正确、参数符合约定、业务允许执行是三件不同的事。3. 两种响应进入同一个执行入口原生方式按接口结构解析约定 JSON 方式按应用协议解析。两条路径都要检查工具名与参数再统一成ToolInvocation name openDoor arguments { tel: 13800000000 }这样后面的业务执行层不必关心上游响应来自哪个模型也不需要在每个业务函数里分别处理两种 JSON。这种分层带来的实际价值是替换模型或调整消息格式时主要修改模型适配部分开门、查预约等业务逻辑仍然保持自己的职责边界。原生调用也需要解析和校验只是调用信号由接口明确表达。若要把结果回传模型适配层还应保留调用 ID 和原始调用消息不能在统一参数时把这些关联信息丢掉。四、把调用单变成业务动作我的智能客服开门案例我的自习室智能客服既处理规则咨询也需要帮用户开门开门时还要校验是否有预约、协助预约。接入工具的意义是把用户的自然语言接到已有业务服务上。图中展示操作型请求的简化路径RAG 标注聚焦检索准备后续 LLM 完成生成。并不是每条咨询都需要执行工具。可以用下面这段对话理解处理过程用户帮我开一下门。客服请提供预约手机号。用户13800000000。应用根据调用请求处理预约与门店信息并请求门禁接口。客服根据业务返回状态提示结果。前两轮解决的是意图和参数问题。有了必要信息模型才能提出开门请求程序收到请求后再决定它是否符合实际业务条件。执行层最核心的工作是把已经检查过的工具名称映射到固定的业务入口// 代码片段请求已完成格式、工具名和参数检查Stringnameinvocation.name();MapString,Objectargumentsinvocation.arguments();switch(name){caseopenDoor:Stringtel(String)arguments.get(tel);returnindustryService.openDoor(tel,request.getAccountType(),arguments,response);default:thrownewIllegalArgumentException(不支持的工具);}这里没有执行模型生成的代码也没有根据任意文本反射调用项目方法。模型只能提出工具目录内的动作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 有什么区别。