前言
书接上文,上文提到了在LLM底层(MaaS API层,不包括各类Agent构建平台和Agent框架的封装),工具调用的实现方法大体可以归纳为四大类:❄️ ingFang SC", system-ui, -apple-system, BlinkMacSystemFont, "Helvetica Neue", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;font-size: 15px;line-height: 30px;text-align: left;" class="list-paddingleft-1">Function CallingPrompt结构化输出API结构化输出意图识别+预定义函数第一篇重点介绍了Function Calling实现工具调用的原理和本质,没看过的朋友,可以先移步阅读:MCP的迷雾,聊聊LLM工具调用的本质(一):Function Calling" data-itemshowtype="0" target="_blank" linktype="text" data-linktype="2">拨开MCP的迷雾,聊聊LLM工具调用的本质(一):Function Calling,本文则重点介绍剩下的三种方式。ingFang SC", system-ui, -apple-system, BlinkMacSystemFont, "Helvetica Neue", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;letter-spacing: 0.578px;margin-top: 0px;margin-bottom: 8px;font-size: 22px;padding-bottom: 12px;">方法2:Prompt结构化输出ingFang SC", system-ui, -apple-system, BlinkMacSystemFont, "Helvetica Neue", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;color: rgb(31, 35, 41);margin: 0px 0px 4px;word-break: break-all;min-height: 20px;"> ingFang SC", system-ui, -apple-system, BlinkMacSystemFont, "Helvetica Neue", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;color: rgb(31, 35, 41);margin: 0px 0px 4px;word-break: break-all;min-height: 20px;">实现工具调用的第二种常用方法,也是灵活性最高的方法,就是通过Prompt中引导模型输出特定的格式(譬如JSON)文本,然后通过正则表达式技术从输出文本中提取结构化信息,用于后续的工具调用。看下面例子:ingFang SC", system-ui, -apple-system, BlinkMacSystemFont, "Helvetica Neue", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;color: rgb(31, 35, 41);margin: 0px 0px 4px;word-break: break-all;min-height: 20px;">
你是一个航班查询助理,根据用户基于自然语言表达的航班查询需求,按需调用工具完成查询并回复。
【工具定义】 工具名称:flight_search 功能描述:根据用户需求查询实时航班信息 输入参数: - departure_city(必填,出发城市) -arrival_city(必填,到达城市) - date(必填,日期格式YYYY-MM-DD) - passengers(可选,默认1成人)
} 请用JSON格式返回以下信息: { "toolname": "flight_search", "params": { "departure_city": "出发城市", "arrival_city": "到达城市", "date": "YYYY-MM-DD" } }
用户问题:帮我查下后天上海飞广州的航班 ingFang SC", system-ui, -apple-system, BlinkMacSystemFont, "Helvetica Neue", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;color: rgb(31, 35, 41);margin: 0px 0px 4px;word-break: break-all;min-height: 20px;"> ingFang SC", system-ui, -apple-system, BlinkMacSystemFont, "Helvetica Neue", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;color: rgb(31, 35, 41);margin: 0px 0px 4px;word-break: break-all;min-height: 20px;">该方法的工具清单,以及工具调用回复等等行为全都在Prompt中进行了限定,因此无需依赖Function Calling的支持,任何模型都适用,因此灵活性是最好的。在收到模型的回复后,通过第三方开源的LLM Output Parser,或者通过正则表达式,可以很轻松地从输出文本中解析出json文本。如下伪代码所示:ingFang SC", system-ui, -apple-system, BlinkMacSystemFont, "Helvetica Neue", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;color: rgb(31, 35, 41);margin: 0px 0px 4px;word-break: break-all;min-height: 20px;">
importllm_output_parserfrom'xxxx'; import{flight_search}from'./tools'; // 解析响应中的输出文本 asyncfunctionparseOutput(responseText){ try{ consttoolCall = llm_output_parser.parse(responseText); if(toolCall?.toolname){ returntoolCall; }else{ returnnull; } }catch(e){ } }
// 根据响应中的函数调用,执行对应的函数 asyncfunctioninvokeFunction(){ consttoolCall =parseOutput(responseText); if(toolCall){ if(toolCall.toolname=='flight_search'){ constparams = toolCall.params; if(isValidFlightSearchParams(params)){ returnawaitflight_search(params); }else{ // 返回参数不合格的校验结果 } }else{ //。。。。 } } }
functionisValidFlightSearchParams(params){ // 检查params是否符合参数格式和必填要求 // ... } ingFang SC", system-ui, -apple-system, BlinkMacSystemFont, "Helvetica Neue", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;color: rgb(31, 35, 41);margin: 0px 0px 4px;word-break: break-all;min-height: 20px;"> ingFang SC", system-ui, -apple-system, BlinkMacSystemFont, "Helvetica Neue", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;color: rgb(31, 35, 41);margin: 0px 0px 4px;word-break: break-all;min-height: 20px;">优点:- 无需依赖特定模型版本,兼容所有支持文本生成的模型
- 格式调整灵活:支持XML/JSON/YAML等多种格式,工具定义的格式可以完全自定义进行调优,工具的数量和清单页可以随时进行动态调整。
- Token使用量可以灵活调整:可以根据实际情况,选择更精简的工具定义方法,工具较多时可以大大减少推理Token的消耗。
缺点: - 输出稳定性完全取决于Prompt质量和模型能力:不同模型的指令跟随能力不同,参数较大的旗舰模型通常可以实现100%的输出格式遵循,但参数稍小的模型,如果Prompt未经深度调优,有时会出现格式错误,无法正确解析的情况,准确率没有可靠的保障。
- 需要额外开发校验逻辑:如上代码示例所示,模型输出的文本,以及工具调用结果解析,需要额外的解析、异常处理等开发工作,比较繁琐。
- 没有统一的工具定义规范,可维护性比较差。
方法3: API参数约束法《五台山朝拜之路》
与Prompt结构化输出方法实现工具调用的路径相似,还有一种变种,就是借助部分模型支持的response_format(强制结构化输出)能力来实现,这是OpenAI在2024年8月份推出的一项API的新功能,可以保证模型100%按照预设的JSON格式输出,为开发者提供确定性。我们把这种方法叫做通过API参数约束法,就如同给模型带上“格式紧箍咒”一样。
response_format设计背景- 1.基于Prompt的LLM结构化输出不稳定:早期开发者需通过复杂的提示词设计或多次请求才能获取结构化数据,且模型可能生成无效JSON(如缺少必填字段、格式错误等),需额外验证和重试。
- 2.结构化输出是Agent应用落地的刚需:结构化数据是API集成、数据录入、多步骤智能体工作流(如数据库查询、动态UI生成)的核心需求。例如,电商订单查询需严格匹配数据库字段格式,非结构化输出会增加解析成本。
- 3.技术迭代升级:OpenAI在2023年推出JSON模式功能,但仅能生成有效JSON,无法保证模式匹配。此次更新通过算法优化和工程约束实现100%模式可靠性。
response_format功能介绍OpenAI API的response_format功能通过严格模式(strict mode)实现了对输出结构的精确控制,开发者可通过两种方式启用该功能:
- 1.函数调用模式 在工具定义中添加"strict": true参数,并明确指定JSON Schema中的required字段和additionalProperties: false,强制模型仅输出预定义字段。例如,电商订单查询工具可严格限定columns字段仅包含id、status等枚举值,避免无效字段。
- 2.response_format参数直接调用 直接在API请求中指定response_format类型为json_schema,并定义完整的Schema结构。例如数学题解答场景中,可要求输出必须包含steps数组(含解题步骤说明)和final_answer字段,且禁止额外属性,确保后续程序直接解析。
该功能还引入了动态递归支持,例如处理嵌套的anyOf类型或递归数据结构(如树状目录),通过上下文无关文法(CFG)实现复杂模式的动态约束。开发者甚至可为每个对象类型设置独立校验规则,例如在数据库查询中,conditions数组的每个元素必须包含column、operator、value三要素,且value允许字符串、数字或子对象的灵活组合。
此外,SDK集成优化显著简化了开发流程: - Python开发者可使用Pydantic模型自动生成JSON Schema,SDK会将响应反序列化为类型化对象,减少手动解析错误;
- Node.js开发者通过Zod库定义模式,API响应可直接转换为强类型数据结构;
- 新增的拒绝标识符允许程序化检测模型拒绝响应的场景(如涉及敏感信息查询),开发者可通过rejected_flag字段快速识别并处理异常
工具调用示例 // 1.定义工具函数(模拟天气查询) asyncfunctiongetCurrentWeather({ location }) { return{ temperature:Math.random()*30+10,// 模拟温度数据 condition: ["晴天","多云","小雨"][Math.floor(Math.random()*3)]// 模拟天气状态 }; }
// 2.配置工具调用参数 consttools = [{ type:"function", function: { name:"getCurrentWeather", description:"获取指定城市的天气信息", parameters: { type:"object", properties: {location: {type:"string"} }, required: ["location"] }, // 启用结构化输出 strict:true// 强制匹配参数结构 } }];
// 3.执行工具调用 asyncfunctionqueryWeather(message) { constcompletion =awaitopenai.chat.completions.create({ model:"gpt-4o-2024-08-06",// 必须使用支持结构化输出的模型 messages: [{role:"user",content: message }], tools, response_format: {// 强制结构化响应 type:"json_object", schema: { type:"object", properties: { temp: {type:"number"}, condition: {type:"string"} } } } });
// 4.解析并执行工具调用 consttoolCall = completion.choices[0].message.tool_calls[0]; returnawaitgetCurrentWeather(JSON.parse(toolCall.function.arguments)); }
// 示例调用 queryWeather("北京明天的天气如何").then(console.log);// 输出示例:{ temperature: 25.3, condition: "多云" }
response_format的实现原理算法层面主要是通过以下两种手段来组合实现: - 模型SFT训练优化:OpenAI的模型(如gpt-4o-2024-08-06)通过SFT微调训练,增强了对复杂JSON模式的理解能力,官方的数据显示,基准测试中模式匹配率从旧模型的40%提升至100%。
- 约束解码(Constrained Decoding):将JSON Schema转换为上下文无关文法(CFG),动态限制每个生成步骤的有效Token。例如,生成{"val后,模型仅允许选择符合Schema的后续Token(如数值或字符串),而非任意字符。相当于在解码阶段,人为干预了模型的输出只能使用符合Schema的Token
优缺点优点- 100%模式可靠性:通过JSON Schema强制约束输出,避免传统LLM的格式错误(如必填字段缺失)。
- 开发效率高:直接定义Schema结构,无需复杂提示词或多次请求。
- 成本优化:新版模型(如gpt-4o-2024-08-06)Token成本降低50%,且减少因错误导致的重复请求。
缺点- 模式复杂度限制:仅支持JSON Schema子集(如嵌套深度≤5、属性上限100个),无法处理递归或复杂联合类型。
- 模型兼容性差:支持该特性的模型非常少,OpenAI也仅在新版GPT-4o系列支持,旧模型需降级为JSON模式(可靠性下降)。
- 首请求延迟:新Schema首次调用需额外编译时间(约10-20%延迟),后续通过缓存缓解
方法4:意图识别+预定义函数《花竹海上日出》
意图识别实现工具调用的技术路径在大语言模型出现之前就已经被广泛应用,几乎传统的AI助手,如智能音箱、车机语音助手,基本上都是基于这套技术方案实现的。例如早期的Siri的实现方式为: 通过自然语言理解(NLU)模块将用户指令映射到预定义的意图标签,再调用固定的API接口。例如,用户说“播放周杰伦的歌”,系统解析出意图MusicPlay,触发音乐播放工具。此过程需人工编写大量意图-动作映射规则
大语言模型出现之后,这种技术路径也有了很多的变体: - 1.向基于Prompt结构化输出的方式靠拢:将意图清单和槽位清单注入到Prompt中,并在指令中要求模型必须要按照JSON格式输出最为匹配的意图和槽位值。
- 2.基于独立小尺寸模型路由分流的模式:这个方案通常应用在应用后面有多个不同的专家模型或者有几个确定性的Workflow的情况,小模型经过精心的SFT,在特定场景下可以实现非常高的可靠性。譬如通用的大模型助理产品通常会在用户提问后,先使用一个意图分类的小尺寸LLM(2B甚至更小),来识别用户问的问题类型,是时效性知识问答,还是百科问答,又或者是信息处理、逻辑推理等等。分类被识别了之后,本质上是将请求路由到了另外一个确定性的流程中,我们也可以把这些处理流程分支抽象为一个函数调用,只是这些函数大多不需要太多参数。
优点
- 传统方法依赖人工规则,意图与函数映射逻辑明确,执行结果稳定可控
- 结合小模型路由分流时(如用2B小模型分类用户问题类型),可在特定场景达到接近100%的准确性
- 小模型或规则引擎的推理成本远低于大模型,适用于边缘计算或高并发场景。
缺点
- 需预先定义所有意图和函数映射规则,无法处理未覆盖的复杂请求(如多步骤推理任务)。
- 维护成本高,新增功能需重新设计意图分类规则或训练调优意图识别小模型or指令。
结语软件开发领域有一句话叫:“软件开发没有银弹”。意思是没有任何单一技术或管理方法能彻底解决软件开发中的根本性复杂问题,这句话在这里依然适用。
任何一种实现大语言模型调用工具的方法,都有其特殊的优势和无法忽略的问题。开发Agent选择工具调用的技术方案不仅仅要了解各种实现方案的优缺点,还要结合自身的场景的特点来判断。更重要的是,优秀的软件需要给用户提供确定性,在一种方法失效或者无法正常工作的时候,是否有降级的方法来兜底,而不是直接报错或者拒绝服务。
在MCP势如破竹般要一统大语言模型工具调用的标准之时,作为Agent开发者,我们要深刻地认识到:MCP并没有制定MCP Host需要用哪种技术方案来实现工具调用的标准,自由度和选择完全取决于开发者自己。MCP Tools只是一层协议层的封装,这层封装带来的迷雾只是表象,真正的本质是Prompt和结构化输出。
|