五
Function Calling:让 AI 调你的 Java 方法
Tool Calling
大模型的知识有截止日期,也不知道你数据库里有啥。用户问"我订单 12345 物流到哪了?",AI 自己答不上来。Function Calling(工具调用)就是:你把"查物流"这个 Java 方法注册给 AI,AI 自己判断什么时候该调它、传什么参数,调完再把结果组织成人话回答。
论一次工具调用的完整流程
第一步:你把 Java 方法(比如 trackLogistics(orderId))连同描述一起注册给 AI。
第二步:用户提问,AI 听完判断:"这问题我得查物流",于是返回一个"我要调用 trackLogistics,参数 orderId=12345"的指令。
第三步:Spring AI 自动执行你写的 Java 方法,拿到真实物流数据,再把结果喂回给 AI。
第四步:AI 根据真实数据组织成自然语言回答用户。整个过程你只写了那个 Java 方法,AI 决定何时调、调哪个。
用 @Tool 注解把 Java 方法暴露给 AI
import org.springframework.ai.tool.annotation.Tool;
import org.springframework.stereotype.Component;
@Component
public class LogisticsTools {
// @Tool 注解:这是一个 AI 可以调用的工具
@Tool(description = "根据订单号查询物流状态,输入订单号,返回物流节点")
public String trackLogistics(@ToolParam(description = "订单号,例如 12345") String orderId) {
// 这里真正查数据库/调物流接口
return "订单 " + orderId + " 已到达南京转运中心,预计明天送达";
}
@Tool(description = "查询某个城市的天气")
public String weather(@ToolParam(description = "城市名") String city) {
return city + " 今天多云,25 度";
}
}
让 AI 在对话时自动用这些工具
@GetMapping("/ask")
public String ask(@RequestParam String q) {
return chatClient.prompt(q)
.tools(logisticsTools) // 把工具包传进去
.call()
.content();
}
// 用户问:"我订单 12345 到哪了?"
// AI 自己决定调 trackLogistics("12345"),再回答用户
// 用户问:"南京天气咋样?"
// AI 自己决定调 weather("南京")
多个工具,AI 自己挑哪个
注册了多个工具后,AI 会根据用户问题自己决定调哪个。你不用写 if-else 判断"用户问天气就调天气工具"——AI 看用户的话和工具描述,自己选。这就是 Function Calling 最爽的地方:工具越多,AI 越能干,你业务代码不用改。
注册多个工具,AI 自动选择
// 用户问"我订单12345到哪了,顺便报下南京天气"
// AI 会先调 trackLogistics("12345"),再调 weather("南京")
// 然后把两个结果拼成一段回答
@GetMapping("/ask")
public String ask(@RequestParam String q) {
return chatClient.prompt(q)
.tools(logisticsTools, weatherTools) // 多个工具一起传
.call()
.content();
}
工具描述怎么写,AI 才用得对
@Tool 的 description 不是写给你看的,是写给 AI 看的"招聘启事"——AI 靠它判断什么时候该请这个工具干活。好描述的标准:说清"能干啥、啥时候用、啥时候别用、参数啥意思"。
| 差的描述 | 好的描述 |
| "这是个工具" | "根据订单号查询物流状态,用户问『我的货到哪了』时用" |
| "查一下" | "入参是订单号字符串,格式如 12345;返回最新物流节点文字" |
| "天气" | "查询指定城市今日天气,输入中文城市名,如『南京』" |
工具里报错了怎么办
你写的 Java 方法可能抛异常(数据库挂了、订单号不存在)。别让异常直接炸到模型,捕获后返回一段友好的错误描述,让 AI 把这个错误转告用户。比如"订单号不存在,请检查",而不是一段 Java 栈信息。
工具里优雅处理错误
@Tool(description = "根据订单号查询物流")
public String trackLogistics(@ToolParam(description = "订单号") String orderId) {
try {
return logisticsService.query(orderId);
} catch (OrderNotFoundException e) {
// 不抛异常,返回友好描述,AI 会转告用户
return "没找到订单 " + orderId + ",请核对订单号";
}
}
工具描述写得烂,AI 就用错
@Tool 的 description 是给 AI 看的"说明书"。你要是写"这是个工具",AI 根本不知道什么时候该用、参数怎么填。描述要写清楚"这个工具干嘛用、什么场景该调、参数啥意思"。参数绑定错了(比如把用户昵称当订单号传进去),查出来就是错的,AI 还一本正经地胡说。
完整案例:天气 + 计算器 + 查库三个工具,AI 自动挑
注册三个工具:查天气、算数、查数据库。用户一句话混着问,看 AI 怎么自己决定调哪个、按什么顺序调。
三个 @Tool 方法(天气 / 计算器 / 查库存)
@Component
public class BizTools {
@Tool(description = "查询城市今天的天气,入参中文城市名")
public String weather(@ToolParam(description = "城市,如:南京") String city) {
return city + " 今天多云 25℃,微风。";
}
@Tool(description = "计算一个数学表达式,入参是如 '(3+5)*2' 的字符串")
public double calculator(@ToolParam(description = "数学表达式") String expr) {
// 真实项目用表达式引擎,这里示意
return 16.0;
}
@Tool(description = "查询某商品剩余库存,入参商品名")
public int stock(@ToolParam(description = "商品名,如:键盘") String product) {
if (!"键盘".equals(product)) throw new RuntimeException("没有这个商品");
return 42;
}
}
注册全部工具,让 AI 自己挑
@GetMapping("/ask")
public String ask(@RequestParam String q) {
return chatClient.prompt(q)
.tools(bizTools) // 三个工具一起注册
.call()
.content();
}
一次混合提问,完整对话过程
用户:"南京天气咋样?再帮我算下 (3+5)*2 等于几,键盘还有多少库存?"
# AI 内部决策链(打开 SimpleLoggerAdvisor 能看到):
# 1. 调 weather("南京") → "南京 今天多云 25℃,微风。"
# 2. 调 calculator("(3+5)*2") → 16.0
# 3. 调 stock("键盘") → 42
# AI 把三个结果组织成人话返回:
# "南京今天多云,25 度很舒服;(3+5)*2 等于 16;
# 键盘目前库存还有 42 件,要下单趁早~"
# 故意问不存在的商品,看错误兜底:
用户:"鼠标有货吗?"
# stock 抛 RuntimeException("没有这个商品")
# AI 拿到错误信息,转告用户:"抱歉,目前系统里没查到鼠标这个商品。"
工具报错别让异常炸到模型
上面 stock() 抛了异常,Spring AI 会把异常信息作为工具结果返回给模型,模型据此跟用户解释。但别把数据库连接串、SQL 全文这类敏感信息抛出去——模型可能原样念给用户。工具里要 catch 住,只返回友好的业务描述。
本章面试题
面试快答 · Function Calling
Q1. Function Calling 的完整流程?谁决定调哪个工具?
参考答案
① 你把 @Tool 方法+描述注册给模型;② 用户提问,模型判断需要工具,返回"要调 X、参数 Y"的指令;③ 框架执行你的 Java 方法,把结果喂回模型;④ 模型基于真实结果组织回答。何时调、调哪个由模型根据工具描述自己决定,不是你写 if-else。
Q2. RAG 和 Function Calling 怎么选?
参考答案
知识在动态业务系统里(订单、库存、实时天气)→ Function Calling;知识在静态文档里(制度、手册、FAQ)→ RAG。分不清就想:这数据会变吗?会变 → 调接口;不变 → 查文档。
Q3. @Tool 的 description 为什么重要?写烂了会怎样?
参考答案
description 是写给模型看的"招聘启事",模型靠它判断何时调用、怎么填参。写得烂("这是个工具")模型不知道何时用、乱填参数,结果就是一本正经地查错数据。要写清"干嘛用、何时用、参数啥意思"。