第十三章 Tool Calling 函数工具调用技术
版本标注
- Spring AI:
1.1.2- Spring AI Alibaba:
1.1.2.0章节定位
- Tool Calling 仍然是最核心的能力之一。
- 在 Spring AI Alibaba
1.1.2.x中,它通常不是孤立存在,而是嵌入到ReactAgent、多智能体编排、RAG Workflow、MCP Client 和 Graph 节点中协同工作。
s01 > s02 > s03 > s04 > s05 > s06 > s07 > s08 > s09 > s10 > s11 > s12 > [ s13 ] s14 > s15 > s16 > s17 > s18
"大模型会说, 工具调用让它开始会做" -- Tool Calling 是 AI 从对话走向执行的关键一步。
一、为什么需要 Tool Calling?
1.1 AI 的能力边界
我自己第一次真正意识到 Tool Calling 的价值,不是在看概念定义的时候,而是在写接口时突然发现:模型虽然会说,但它其实不会“做”。
AI 大模型虽然很聪明,但它有一个根本限制:
它只能"生成文字",无法执行真实世界的操作
比如你问 AI:
- "今天天气怎么样?" → AI 只能猜测或回答"我不知道实时天气"
- "帮我查下北京到上海的火战票" → AI 无法真的帮你查询
- "现在几点了?" → AI 也无法获取当前时间
1.2 Tool Calling 的诞生
Tool Calling 真正解决的,不是“让模型回答得更花哨”,而是让它在需要外部信息时,不再只靠猜。
Tool Calling(函数工具调用) 让 AI 能够:
"在回答过程中,发现需要某些信息时,自动调用外部工具获取"
传统方式:
用户:现在几点 → AI:我不知道(靠猜)
Tool Calling 方式:
用户:现在几点
→ AI:发现需要获取时间 → 自动调用 getCurrentTime() 函数
→ 获取到"2025年1月1日 10:30:00" → 把时间告诉用户
1.3 可以调用的工具类型
| 工具类型 | 示例 | 说明 |
|---|---|---|
| 查询类 | 天气查询、股价查询 | 获取外部信息 |
| 操作类 | 发送邮件、订机票 | 执行实际操作 |
| 计算类 | 数学计算、代码执行 | 处理复杂运算 |
| 数据库类 | 查询订单、用户信息 | 访问业务数据 |
二、Tool Calling 核心原理
2.1 工作流程
┌─────────────────────────────────────────────────────────┐
│ Tool Calling 工作流程 │
├─────────────────────────────────────────────────────────┤
│ │
│ 1. 注册工具 │
│ 把函数(方法)注册到 AI 能识别的格式 │
│ │
│ 2. 用户提问 │
│ "北京明天天气怎么样?" │
│ │
│ 3. AI 分析 │
│ → 需要调用天气查询工具 │
│ → 自动提取参数:城市="北京",时间="明天" │
│ │
│ 4. 执行工具 │
│ 调用 getWeather(city="北京", date="明天") │
│ 返回天气数据 │
│ │
│ 5. 返回答案 │
│ 结合工具返回的结果,给出完整回答 │
│ │
└─────────────────────────────────────────────────────────┘
2.2 @Tool 注解
在 Spring AI 里,最直观的入门方式就是直接用 @Tool 暴露一个普通 Java 方法。这样写的好处是很明显的:先把工具定义清楚,再让模型学会什么时候用它。
Spring AI 提供了 @Tool 注解来声明一个可调用的方法:
public class DateTimeTools
{
@Tool(description = "获取当前时间")
public String getCurrentTime()
{
return LocalDateTime.now().toString();
}
@Tool(description = "根据城市名称获取天气")
public String getWeather(String city)
{
// 调用外部天气API
return city + "明天晴转多云,20-28度";
}
}
三、项目代码详解
3.1 定义工具类
package com.atguigu.study.utils;
import org.springframework.ai.tool.annotation.Tool;
import org.springframework.stereotype.Component;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
/**
* 日期时间工具类
* 我这里先拿“日期时间”做示例,是因为这个工具足够简单,结果也稳定,最适合观察模型到底有没有真的触发工具调用。
*/
@Component
public class DateTimeTools
{
/**
* 获取当前时间
*
* 这里真正关键的不是方法体,而是 @Tool(description = ...)
* 这段描述本质上就是给模型看的“工具说明书”。
*/
@Tool(description = "获取当前日期和时间")
public String getCurrentDateTime()
{
LocalDateTime now = LocalDateTime.now();
return now.format(DateTimeFormatter.ofPattern("yyyy年MM月dd日 HH:mm:ss"));
}
/**
* 获取当前日期
*/
@Tool(description = "获取当前日期")
public String getCurrentDate()
{
return LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy年MM月dd日"));
}
}
3.2 控制器代码
package com.atguigu.study.controller;
import com.atguigu.study.utils.DateTimeTools;
import jakarta.annotation.Resource;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.model.ChatModel;
import org.springframework.ai.chat.prompt.ChatOptions;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.ai.model.tool.ToolCallingChatOptions;
import org.springframework.ai.support.ToolCallbacks;
import org.springframework.ai.tool.ToolCallback;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Flux;
/**
* 工具调用控制器
* 展示让 AI 调用外部函数的能力
*/
@RestController
public class ToolCallingController
{
// 注入 ChatModel(底层API,更灵活)
@Resource
private ChatModel chatModel;
// 注入 ChatClient(高级API,更简洁)
@Resource
private ChatClient chatClient;
/**
* 方式一:使用底层 ChatModel API(更适合理解原理)
*
* 接口:http://localhost:8013/toolcall/chat?msg=你是谁现在几点
*/
@GetMapping("/toolcall/chat")
public String chat(@RequestParam(name = "msg", defaultValue = "你是谁现在几点") String msg)
{
// 1. 把普通 Java 工具对象转换成 Spring AI 能识别的工具列表
ToolCallback[] tools = ToolCallbacks.from(new DateTimeTools());
// 2. 在这次模型调用里显式注册工具
ChatOptions options = ToolCallingChatOptions.builder()
.toolCallbacks(tools)
.build();
// 3. 把“用户问题 + 工具能力”一起交给模型
Prompt prompt = new Prompt(msg, options);
// 4. 如果模型判断需要工具,就会先触发工具调用,再基于结果生成最终回答
return chatModel.call(prompt)
.getResult()
.getOutput()
.getText();
}
/**
* 方式二:使用高级 ChatClient API(更适合日常开发)
*
* 接口:http://localhost:8013/toolcall/chat2?msg=你是谁现在几点
*/
@GetMapping("/toolcall/chat2")
public Flux<String> chat2(@RequestParam(name = "msg", defaultValue = "你是谁现在几点") String msg)
{
return chatClient.prompt(msg)
// .tools() 方法直接注册工具
.tools(new DateTimeTools())
.stream()
.content();
}
}
3.3 ToolCallbacks.from(new DateTimeTools()) 到底在做什么?
这一句代码看起来很短,但它其实是 Tool Calling 里非常关键的一步:
ToolCallback[] tools = ToolCallbacks.from(new DateTimeTools());
很多初学者第一次看到时会以为:
是不是这里已经把
DateTimeTools里的方法执行了一遍?
其实不是。它做的事情更准确地说是:
把一个普通的 Java 工具对象,转换成 Spring AI 能识别和调用的一组
ToolCallback。
也就是说,这一步不是“执行工具”,而是“注册工具”。
3.1. new DateTimeTools() 只是一个普通对象
你自己写的这个类,本质上只是一个普通 Java 类:
public class DateTimeTools {
@Tool(description = "获取当前日期和时间")
public String getCurrentDateTime() {
... }
@Tool(description = "获取当前日期")
public String getCurrentDate() {
... }
}
如果没有 Spring AI 再做一层处理,大模型其实并不知道:
- 这个类里有哪些方法
- 哪些方法可以作为工具暴露出去
- 每个方法是干什么的
- 调用时需要什么参数
3.2. ToolCallbacks.from(...) 的作用是“翻译”
ToolCallbacks.from(new DateTimeTools()) 会去扫描这个对象里的方法,重点找:
- 哪些方法带有
@Tool - 每个方法的名称
- 每个方法的描述信息
- 方法参数结构
- 方法返回值类型
然后把这些信息包装成:
ToolCallback[]
你可以把 ToolCallback 理解成:
一个工具的说明书 + 调用代理。
它里面既包含“这个工具是干什么的”的元信息,也包含“模型决定调用它时,最终应该执行哪个 Java 方法”的调用逻辑。
3.3. 为什么返回的是数组?
因为一个工具类里可能不止一个工具方法。
比如 DateTimeTools 里就有:
getCurrentDateTime()getCurrentDate()
所以 ToolCallbacks.from(...) 最终返回的不是单个对象,而是一组 ToolCallback。
也就是说:
ToolCallback[] tools = ToolCallbacks.from(new DateTimeTools());
相当于把 DateTimeTools 这个类里可暴露的所有工具方法,统一整理成了一个“可供模型调用的工具列表”。
3.4. 这一步为什么不是“立刻执行工具”?
因为工具真正执行的时机,不是在 from(...) 这里,而是在后面模型推理时。
完整流程是这样的:
- 你先用
ToolCallbacks.from(...)把工具注册好 - 再把这些工具交给
ChatModel或ChatClient - 模型分析用户问题时,判断自己是否需要调用工具
- 如果需要,就由 Spring AI 通过
ToolCallback去真正执行对应方法 - 方法执行结果再返回给模型,模型据此组织最终回答
所以:
from(...)是注册阶段- 工具方法真正运行,是模型决策之后的执行阶段
3.5. 一句话总结
ToolCallbacks.from(new DateTimeTools()) 的本质是:
通过反射扫描
DateTimeTools对象中的@Tool方法,把它们包装成 Spring AI 可识别的ToolCallback[],从而让大模型后续能够按工具名和参数自动调用这些 Java 方法。
3.4 工具真正执行时,代码到底是怎么跑起来的?
这个问题我自己一开始也非常困惑。平常写 Java 代码时,我们已经习惯了这种执行方式:
String now = dateTimeTools.getCurrentDateTime();
也就是“代码写到哪里,就执行到哪里”。但 Tool Calling 看起来不是这样,因为你明明没有手写这一句调用,工具方法最后却真的执行了。
这里最关键的结论是:
Spring AI 不会额外启动一个新进程去执行工具,它还是在当前 Spring Boot 应用进程里完成方法调用。
只不过这次不是你在源码里写死了调用哪个方法,而是模型先返回一个“我要调用哪个工具”的结构化请求,框架再根据这个请求去定位并执行对应的 Java 方法。
整个执行链路大致可以理解成这样:
- 你先通过
ToolCallbacks.from(...)注册工具 - 模型收到用户问题后,先判断自己是否需要某个工具
- 如果需要,它会返回一个工具调用请求(例如工具名 + 参数)
- Spring AI 拿到这个请求后,在当前应用里找到对应工具方法
- 通过反射执行这个 Java 方法
- 把工具返回结果再交还给模型
- 模型基于这个结果生成最终答案
如果用更接近程序执行的方式表达,大概等价于:
String toolName = "getCurrentDateTime";
Method method = registry.find(toolName);
Object result = method.invoke(targetObject);
所以 Tool Calling 不是“随机执行代码”,也不是“AI 自己直接运行 Java”。它更像是一种数据驱动的动态方法调用:
- 普通调用:你在源码里提前写死调用谁
- Tool Calling:模型先产出调用指令,框架再运行时决定调用谁
这也是为什么 Tool Calling 看上去像“模型会用工具了”,但从程序底层看,本质上还是当前 Java 应用在执行普通方法,只不过中间多了一层“模型决策 + 框架分发”的过程。
四、自定义工具的最佳实践
4.1 @Tool 注解的关键要素
// ✅ 好的描述示例
@Tool(description = "获取指定城市的当前天气状况")
public String getWeather(String city) {
... }
// ❌ 模糊的描述(AI 不知道怎么用这个工具)
@Tool(description = "天气查询")
public String getWeather(String city) {
... }
4.2 工具方法命名
// ✅ 清晰的方法名(一看就知道干什么)
public String getWeatherByCity(String city)
public String queryUserOrderHistory(String userId)
// ⚠️ 模糊的方法名(不推荐)
public String doSomething(String param)
public String query(String id)
4.3 参数设计
// ✅ 必要的参数,清晰易懂
public String getWeather(
@Param(name = "city", description = "城市名称,如北京、上海") String city,
@Param(name = "date", description = "查询日期,格式yyyy-MM-dd") String date
)
// ✅ 不需要参数的简单工具
@Tool(description = "获取当前登录用户信息")
public UserInfo getCurrentUser() {
... }
五、本章小结
5.1 核心概念
| 概念 | 说明 |
|---|---|
| @Tool | Spring AI 注解,声明可调用的方法 |
| ToolCallback | 工具调用的封装类 |
| ToolCallingChatOptions | 配置工具调用的选项类 |
| .tools() | ChatClient 注册工具的方法 |
5.2 使用流程
1. 编写工具类(用@Component让Spring管理)
2. 在方法上添加@Tool注解
3. 编写清晰的方法描述
4. 在控制器中注册工具
5. AI自动识别并调用工具
5.3 注意事项
- 描述要清晰:AI 通过描述理解工具用途
- 参数要完整:帮助 AI 正确提取参数
- 返回值要规范:清晰的返回值便于 AI 理解结果
本章重点:
- 理解 Tool Calling 的核心原理
- 掌握 @Tool 注解的使用方法
- 学会在 ChatClient 中注册工具
下章剧透(s14):
学会了自定义 Tool 后,下一章我们将学习 MCP(Model Context Protocol)——标准化外部工具调用的协议!
💡 TIP:@Tool vs MCP 工具的区别
本章我们学习的 Tool Calling 使用的是 @Tool 注解方式,这是 Spring AI 原生的工具定义方式。而下一章要学的 MCP 是另一种标准化协议。它们区别如下:
| 特性 | @Tool 注解 | MCP 协议 |
|---|---|---|
| 定义方式 | 代码 + 注解 | 独立配置文件 |
| 适用范围 | 同项目内的方法 | 任意外部服务 |
| 连接方式 | 进程内直接调用 | HTTP/STDIO |
| 复用性 | 同项目复用 | 跨项目复用 |
| 复杂度 | 简单 | 需要额外配置 |
怎么选?
- 如果是项目内部的工具(如业务逻辑),用
@Tool更简单直接 - 如果要调用外部独立服务(如天气API、数据库),用 MCP 更标准化
本章学的 @Tool 是基础,下一章学的 MCP 是进阶。两者并不冲突,实际项目经常结合使用!
📝 编辑者:Flittly
📅 更新时间:2026年4月
🔗 相关资源:Spring AI Tool Calling