夜雨聆风学习资料网

ARTICLE · 1138295

AI调工具,怎么不翻车

AI调工具,怎么不翻车

AI调工具,怎么不翻车

LLM 会写字不算本事,会调工具才算长手;可工具调用,是后端接 AI 翻车最多的那一关。

01 | Function Calling,到底在调什么

很多人把 Function Calling 和"让模型吐 JSON"混为一谈,这是第一个翻车点。

  • • Structured Output:你让模型每次都按固定格式吐数据(提价值息),没有副作用,9 月 7 日那篇已经讲透。
  • • Function Calling / Tool Use:你给模型一批工具,它按需决定调哪个、传什么参数、要不要调,调完拿结果继续说人话——这才是"长手"。

一句话:Structured Output 是"每次吐数据",Function Calling 是"按需触发动作"。前者其实是后者的退化版(工具数变 1、参数固定)。理解这层包含关系,后面坑都不会跑偏。

命名差异先说清楚,免得切文档切懵,字段以各厂官方文档为准:

厂商
叫法
工具定义字段
OpenAI
Function Calling
tools[].function
Anthropic
Tool Use
tools[].input_schema
Google
Function Calling
functionDeclarations

别拿 OpenAI 的 tool_calls 往 Anthropic 上套——返回结构完全不同,这是第二大翻车点。

02 | 一个完整循环,到底几步

工具调用不是一次请求,是一个循环。把它想成一次请求就完事,是第三个翻车点。

用户提问  ↓模型 → 返回"我要调 getWeather,参数=东京"  ↓你的应用 → 真的去调 getWeather("东京") → 拿到 18°  ↓把 18° 塞回 tool_result → 模型 → "东京现在 18 度"  ↓(模型可能再发起一轮,直到它决定回纯文本,循环结束)

关键:这个 while 循环得你自己写。模型返回工具调用时,结束信号是"我有工具要调"(OpenAI 的 tool_calls、Anthropic 的 tool_use block),不是对话到底;只有它回纯文本,这轮才算完。新手最常见错误:拿到 tool_calls 直接 return 给用户,结果把"我要调工具"的 JSON 原样抛到了前端。

骨架代码(伪流程,字段以官方 SDK 为准):

# 伪代码:工具调用循环骨架whileTrue:    resp = client.chat(messages=messages, tools=tools)ifnot resp.has_tool_calls:   # 模型回纯文本了break    messages.append(resp.assistant_message)for call in resp.tool_calls:        # 可能多个,见 04        result = dispatch(call)         # 你的执行器        messages.append(tool_result(call.id, result))return resp.text

03 | 三家返回结构,别张冠李戴

这块最容易写错,把"模型要调工具"在各家返回里长什么样列清楚:

OpenAI:工具调用挂在 message.tool_calls 数组里,每个含 id、function.name、function.arguments(字符串形式 JSON)。结果用 role: "tool" + tool_call_id 回传。

Anthropic:工具调用是 content 里的一个 tool_use block(含 id、name、input 对象)。结果回传用 role: "user" + tool_result block,tool_use_id 对上。

Gemini:工具调用是 parts 里的 functionCall,结果用 functionResponse 回传。Gemini 3 起每次调用带唯一 id,多轮里 functionResponse 带对 id 才稳(来源:Google 文档相关说明)。

三个一看就明白:返回结构、result 的 role、id 机制都不一样。你做的抽象层只针对一家写死,换厂商等于重写。生产里的工具层,要么用框架(Spring AI / LangChain4j)抹平,要么自己封一层中立 ToolCall / ToolResult 对象。

一组第三方对比数据(2026 Q1 非官方评测,数字会变,仅供参考):工具调用可靠度 Anthropic 8.4、Google 7.9、OpenAI 6.3——即同一句话,OpenAI 更容易选错工具或参数缺斤少两。这只是趋势参考,不是你选型的硬依据。

04 | 并行工具调用:省一整轮延迟的加速器

这是大多数人没用起来、用了又翻车的能力。

模型可以一次返回多个工具调用(问"北京和东京天气"→ 一次返回两个 getWeather)。你的应用并发执行,全部结果回传,模型再继续——省掉"调一个、回一轮、再调下一个"的整轮往返,延迟砍半。

OpenAI 多数模型默认开 parallel_function_calls;Anthropic 默认允许并行(要关才显式设 disable_parallel_tool_use)。

但有顺序依赖时并行会出大事:工具 B 的入参要靠 A 的返回,模型却把 A、B 一次都调了,B 拿不到真实参数只能瞎编。怎么办?两条路:

  • • 系统提示写明"必须先调 A 拿到结果再调 B";
  • • 关掉并行,强制模型串行。

判断准则:独立工具才并行,有依赖的工具拆步。别为了快无脑开并行——选错工具的代价远大于多等一轮。

05 | 九个坑,一张表

坑
症状
解法
Schema 漂移
模型传了声明外的字段
开 strict mode / 严格 required
参数幻觉
必填字段缺失或编造值
arguments 当 JSON 校验,缺则带证据重试
选错工具
两个功能像的工具选反
description 写清"何时用"+ few-shot 示例
并行乱序
有依赖的工具被同时调
拆步或关掉并行
错误回传错
把异常堆栈当 result 塞回
role=tool 里塞结构化 error 字段
工具超时
长任务卡死整轮循环
设 timeout+重试,或异步化(见 9-22)
工具爆炸
定义+结果吃掉几万 token
下一代的"动态发现"
无人工确认
不可逆操作直接执行
写操作前必加 HITL 确认
流式碎 JSON
参数没截完就 parse 报错
看 finish_reason,攒齐再解析

第 5、8 两个最致命:第 5 把异常当结果,让模型把报错当"正常返回"继续编;第 8 是 Agent 安全老问题——删数据、转账、发邮件这种不可逆动作模型拍板就执行,上线就是事故。记住:工具能力越强,HITL 越不能省。

06 | 下一代:让模型自己找工具

前面九坑里 7、3 号(工具爆炸、选错工具)根子是:你把所有工具定义一次性塞进上下文。Agent 要接几十个 MCP server、上百个工具时,定义和结果能吃掉 5 万 token 以上(Anthropic 工程博客原话),模型在噪声里更容易选错。

Anthropic 给的三个新方向(官方工程博客,beta 阶段,细节以官方文档为准):

  • • 动态工具发现:工具不预先塞进 context,模型按任务按需加载相关工具。接 500 个 MCP 工具时只加载当下要的那几个,省上下文也降低选错率。
  • • 代码执行调用工具:让模型用代码编排"循环、条件、数据转换"那类逻辑,而不是每个中间步都过一次推理。每个工具调用都要一次 inference,中间结果堆在 context 里越来越脏——代码天然适合做这种 orchestration。
  • • 从样例学习用法:JSON Schema 只能保证"结构合法",说不出"这个可选参数什么时候该传、哪些组合有意义"。这种用法约定,靠 few-shot 示例教,比靠 schema 约束更管用。

这三件套直接拆解第 7、3 号坑。趋势很清楚:工具调用正在从"我把工具定义全塞给你"走向"你自己按需找工具、用代码编排、看样例学规矩"。

07 | Java 落地两件套

生产里别手撸 HTTP 抹平三家差异,用框架。两套都支持 @Tool 注解把普通方法直接挂成工具(方法签名跟版本走,照你那份 javadoc 来)。

Spring AI(核实时最新 v2.0.1):

// 骨架:方法即工具,字段以 javadoc 为准@BeanToolCallbackProvider weatherTools(WeatherSvc svc) {return MethodToolCallbackProvider.builder()        .toolMethods(newWeatherTools(svc)).build();}classWeatherTools {@Tool(description = "查某城市当前天气")    String getWeather(@ToolParam("城市名") String city) {return svc.current(city);    }}

LangChain4j(核实时最新 1.20.0):

// 骨架:接口+注解,AI 对象即 AgentinterfaceWeatherAgent {@SystemMessage("你是天气助手,按需调工具")    String ask(String q);}WeatherAgentagent= AiServices.builder(WeatherAgent.class)    .chatLanguageModel(model)    .tools(newWeatherTools(svc))    .build();

两者差异:Spring AI 把工具方法做成 ToolCallbackProvider Bean,适合熔进现有 Spring DI;LangChain4j 用 AiServices 接口代理,更接近"声明一个 Agent"的心智。挑哪个看你团队栈,不是能力高下。

什么时候别上 Function Calling

不是所有 AI 接入都要工具调用。几种情况别硬上:

  • • 纯文本生成:写文案、总结、改写,直接补全就够,加工具是给自己挖坑。
  • • 固定流程的 RAG:检索→拼 prompt→回答,没有"决策调啥"的空间,工具调用多余(Rerank 见 9-16)。
  • • 无副作用查询:只是查库取数,结构化输出(9-07)已够,套工具壳只增复杂度。

反过来,只要模型需要"决定下一步动作"——调 API、查多源、按结果分支——Function Calling 就是正确武器。

写在最后

工具调用把 LLM 从"会说话的引擎"变成"能干活的 Agent"。它不是一次请求,是一个循环;不是一种格式,是三家不一样的协议;不是工具塞越多越强,是塞太多反而选错。把循环写对、并行用对、HITL 守住,再往"动态发现"那一代演进——这关就过了。

觉得有用?点赞 + 在看 + 转发 ❤️

相关学习资料