Spring AI:上手体验工具调用(Tool Calling)
Spring AI:上手体验工具调用(Tool Calling)
历史文章
Spring AI:对接DeepSeek实战
Spring AI:对接官方 DeepSeek-R1 模型 —— 实现推理效果
Spring AI:ChatClient实现对话效果
Spring AI:使用 Advisor 组件 - 打印请求大模型出入参日志
Spring AI:ChatMemory 实现聊天记忆功能
Spring AI:本地安装 Ollama 并运行 Qwen3 模型
Spring AI:提示词工程
Spring AI:提示词工程 - Prompt 角色分类(系统角色与用户角色)
Spring AI:基于 “助手角色” 消息实现聊天记忆功能
Spring AI:结构化输出 - 大模型响应内容
Spring AI:Docker 安装 Cassandra 5.x(限制内存占用)&& CQL
Spring AI:整合 Cassandra - 实现聊天消息持久化
Spring AI:多模态 AI 大模型
Spring AI:文生图:调用通义万相 AI 大模型
Spring AI:文生音频 - cosyvoice-V2
Spring AI:文生视频 - wanx2.1-i2v-plus
假设我们对 AI 大模型提问 “今天是几号”,你可能会想,这么简单的问题,它岂不是手拿把掐?
http://localhost:8080/v2/ai/generateStream?message=今天是几号&chatId=2
接口可以看这篇文章Spring AI:整合 Cassandra - 实现聊天消息持久化

如上图所示,结果却是大失所望,原因是 AI 大语言模型本身是 “封闭” 的:
- 知识截止: 它们的训练数据有截止日期,无法获取实时信息(如最新天气、股价、新闻)。
- 缺乏执行能力: 它们无法直接执行操作(如发送邮件、查询数据库、控制智能家居)。
- 无法访问私有数据: 无法读取你的个人邮件、公司数据库等。
为了解决上述问题,本文中,我们来学习一下 Function Call 功能,它可以让 LLM 成为连接用户自然语言指令和外部工具/服务的智能“中间件”。
什么是 Function Call?

AI 大模型中的 Function Call(函数调用), 它是一个极其重要的功能,它极大地扩展了大语言模型的能力边界,使其从单纯的文本生成器升级为能够感知、决策并操作外部世界的智能代理。
其优势与价值如下:
- 访问实时信息: 获取股票、天气、新闻、航班等最新数据。
- 操作外部系统: 发送邮件、创建日历事件、控制 IoT 设备、操作数据库。
- 执行精确计算/转换: 调用计算器、货币转换器等。
- 检索私有/专有数据: 连接企业内部数据库、知识库、CRM 系统。
- 增强可靠性: 将需要精确性的任务(如计算、数据查询)交给专门工具处理,减少模型“幻觉”。
- 构建智能代理(Agents): Function Call 是实现 AI Agent 自动规划和执行复杂任务序列(如“查天气-订机票-发通知”)的基础技术。
- 简化开发: 开发者只需定义好函数接口,模型能理解如何调用,无需复杂的自然语言解析。
- 提升用户体验: 用户可以用自然语言无缝触发复杂的后端操作。
Spring AI 中 Tool Calling 的处理流程
Spring AI 通过一套灵活的抽象机制支持工具调用 (Tool Calling),使您能够以一致的方式定义、解析和执行工具。
TIP: 在 Spring AI 框架中,Function Call 被称之为 Tool Calling。

当我们想让模型能够使用某个工具时,我们会在聊天请求中包含该工具的定义。每个工具定义包含一个名称(name)、一个描述(description)以及输入参数的模式(schema),处理流程如下:
1、模型调用: 调用 AI 大模型时,会将提示词以及可供使用的工具集,一同发送过去。由 AI 大模型决定是否需要调用某个工具,当需要时,它会反馈一个响应,其中包含工具名称以及依照定义的输入参数模式构建的输入参数。
2、执行工具: Spring AI 负责使用工具名称来识别对应的工具,并使用提供的输入参数执行该工具。
3、处理结果: Spring AI 处理工具调用的结果。
4、返回结果: Spring AI 将工具调用的结果发送回 AI 大模型。
5、模型生成最终响应: 模型利用工具调用的结果作为额外上下文,生成最终面向用户的响应。
定义 Tool 工具
接下来,针对文章开头 “今天是几号” 的场景,我们实际上手感受一下 Tool Calling 的魅力。首先,新建一个 /tools 包,并新建一个 DateTimeTools 工具类,代码如下:
@Slf4j
public class DateTimeTools {
@Tool(description = "获取当前日期和时间")
String getCurrentDateTime() {
return LocalDateTime.now().toString();
}
}
解释一下:
- 定义了一个 getCurrentDateTime() 方法,并获取当前日期和时间;
- 为方法添加 @Tool 注解,并用自然语言描述此方法的作用(非常关键! 模型依赖此理解何时调用)。
方式一:ChatClient 使用工具调用
Tool 工具定义完成后,首先,我们来演示 ChatClient 要如何使用工具调用。首先,编辑 ChatClientConfig 类,将 ChatClient 使用的模型修改为 DeepSeek:
@Configuration
public class ChatClientConfig {
@Resource
private ChatMemory chatMemory;
/**
* 初始化 ChatClient 客户端
* @param chatModel
* @return
*/
@Bean
public ChatClient chatClient(DeepSeekChatModel chatModel) {
return ChatClient.builder(chatModel)
// .defaultSystem("请你扮演一个智能客服")
.defaultAdvisors(new SimpleLoggerAdvisor(), // 添加 Spring AI 内置的日志记录功能
// new MyLoggerAdvisor(), // 添加自定义的日志打印 Advisor
MessageChatMemoryAdvisor.builder(chatMemory).build()
)
.build();
}
}
想要使用 Tool Calling 功能,首先需要明确当前使用的 AI 模型, 它是否支持,要知道,并不是所有 AI 大模型都支持工具调用功能的。而 DeepSeek 就是支持的,具体可以阅读官方文档: https://api-docs.deepseek.com/zh-cn/guides/function_calling/ 。
接着,编辑 ChatClientController 控制器中的 /v2/ai/generateStream 接口,当调用 AI 大模型时,将定义好的工具也提交给它:
/**
* 流式对话
* @param message
* @return
*/
@GetMapping(value = "/generateStream", produces = "text/html;charset=utf-8")
public Flux<String> generateStream(@RequestParam(value = "message", defaultValue = "你是谁?") String message,
@RequestParam(value = "chatId") String chatId
) {
// 流式输出
return chatClient.prompt()
.tools(new DateTimeTools()) // Function Call
// .system("请你扮演一个智能客服")
.user(message) // 提示词
.advisors(a -> a.param(ChatMemory.CONVERSATION_ID, chatId))
.stream()
.content();
}
重启后端项目,再来测试一下,看看这次 AI 大模型是否能够回答出当前日期
定义多个 Tool 工具
假设需求又变了,除了想要获知当前时间,还想知道今天的天气怎么样呢?AI 大模型支持注册多个 Tool 工具,在 /tools 包下,新建 WeatherTools 工具类:
@Slf4j
public class WeatherTools {
@Tool(description = "获取当日的天气情况,时间参数需为 ISO-8601 格式")
String getWeather(String time) {
log.info("## time: {}", time);
// TODO 调用第三方接口,获取当日天气情况
return "今天天气晴朗,最低温度 18℃,最高温度 38℃";
}
}
解释一下:
- 定义一个 getWeather 获取天气的方法,入参为一个字符串类型的 time 日期字段,表示想要查询的目标日期;
- 方法内部,我们可以调用第三方接口,来获取真实的天气数据,这里就不搞太复杂,直接写死 “天气晴朗…”;
- 为此方法添加 @Tool 注解,并用自然语言描述此方法的作用,以方便 AI 大模型理解什么时候调用它;
完成上述工作后,将 WeatherTools 工具类也一同注册到工具集中
/**
* 流式对话
* @param message
* @return
*/
@GetMapping(value = "/generateStream", produces = "text/html;charset=utf-8")
public Flux<String> generateStream(@RequestParam(value = "message", defaultValue = "你是谁?") String message,
@RequestParam(value = "chatId") String chatId
) {
// 流式输出
return chatClient.prompt()
.tools(new DateTimeTools(), new WeatherTools()) // Function Call
// .system("请你扮演一名犬小哈 Java 项目实战专栏的客服人员")
.user(message) // 提示词
.advisors(a -> a.param(ChatMemory.CONVERSATION_ID, chatId))
.stream()
.content();
}
再次重新启动项目,浏览器请求地址如下,同时询问日期和天气情况:
http://localhost:8080/v2/ai/generateStream?message=今天是几号,天气怎么样&chatId=2
你可能会出现 500 服务器内部错误,如下图所示,查看控制台日志,异常信息中提示缺失了某个 Class 类:

要想解决此问题,在 pom.xml 文件中,添加如下依赖即可:
// 省略...
<properties>
// 省略...
<jsonschema-generator.version>4.38.0</jsonschema-generator.version>
</properties>
<dependencies>
// 省略...
<dependency>
<groupId>com.github.victools</groupId>
<artifactId>jsonschema-generator</artifactId>
<version>${jsonschema-generator.version}</version>
</dependency>
</dependencies>
// 省略...
</project>
然后重启项目,再次测试,不出意外,这次 AI 大模型就能回答出当前日期,以及天气情况了:
方式二:ChatModel 使用工具调用
最后,我们再来看看 ChatModel 要如何使用工具调用功能。在 /controller 包下,新建一个 ToolCallingController 控制器,并声明 /v13/ai/generateStream 接口,相比较 ChatClient 客户端,ChatModel 使用工具调用功能要稍微复杂一点,代码如下:
@RestController
@RequestMapping("/v13/ai")
public class ToolCallingController {
@Resource
private DeepSeekChatModel chatModel;
/**
* 流式对话
* @param message
* @return
*/
@GetMapping(value = "/generateStream", produces = "text/html;charset=utf-8")
public Flux<String> generateStream(@RequestParam(value = "message") String message) {
// 将 DateTimeTools 注册到工具集中
ToolCallback[] tools = ToolCallbacks.from(new DateTimeTools());
// 构建聊天选项配置,设置工具回调功能
ChatOptions chatOptions = ToolCallingChatOptions.builder()
.toolCallbacks(tools)
.build();
// 构建提示词
Prompt prompt = new Prompt(new UserMessage(message), chatOptions);
// 流式输出
return chatModel.stream(prompt)
.mapNotNull(chatResponse -> {
// 获取响应内容
DeepSeekAssistantMessage deepSeekAssistantMessage = (DeepSeekAssistantMessage) chatResponse.getResult().getOutput();
// 推理内容
String reasoningContent = deepSeekAssistantMessage.getReasoningContent();
// 推理结束后的正式回答
String text = deepSeekAssistantMessage.getText();
return StringUtils.isNotBlank(reasoningContent) ? reasoningContent : text;
});
}
}
最终效果都是一样的,小伙伴们可以自行测试
更多推荐



所有评论(0)