
一、系列回顾与本篇定位
1.1 系列回顾
- 第一篇 :完成 Spring AI 与阿里云百炼的基础集成,基于
ChatModel实现同步对话与 API Key 安全注入。 - 第二篇 :解锁
ChatClient,实现全局统一配置与链式调用,告别重复样板代码。 - 第三篇:实现 DeepSeek/Qwen 双模型无缝共存与动态切换,完成双版本流式输出。
- 第四篇:深度拆解 Prompt 工程全体系,从底层 Message 到模板化动态生成,掌握与大模型高效沟通的方法论。
- 第五篇:掌握结构化输出能力,实现大模型输出到 Java 实体的自动映射,解决业务系统对接痛点。
系列栏目:Spring AI
Spring AI 实战系列(二):ChatClient封装,告别大模型开发样板代码
Spring AI 实战系列(三):多模型共存+双版本流式输出
Spring AI 实战系列(四):Prompt工程深度实战
1.2 本篇定位
大模型虽然具备强大的知识储备与推理能力,但它也有明确的能力边界:
- 它无法获取实时数据(如当前时间、实时天气、最新股价)。
- 它无法直接操作业务系统(如查询订单状态、创建工单、发送短信)。
- 它无法执行复杂的逻辑计算(如精确的日期计算、数学公式推导、数据校验)。
而Tool Calling(函数调用),正是解决这些问题的核心能力:它让大模型不再只是 "回答问题的顾问",而是变成了 "能调用工具的执行者"------ 大模型会自动识别用户意图,选择合适的工具函数调用,基于工具返回的结果生成最终回答。
本篇是系列企业级核心篇,我们将深度拆解 Spring AI Tool Calling 的全体系:
- 从核心原理出发,彻底理解 Tool Calling 的工作流程与 Spring AI 的核心抽象。
- 从零实现实用的日期计算工具,完成从工具定义、注册到调用的全流程实战。
- 覆盖 ChatModel 底层实现与 ChatClient 简化实现两种方式,对比二者的开发体验。
- 补充多工具注册、带参数工具调用、
returnDirect高级用法等企业级高频场景。 - 提供生产环境最佳实践与高频踩坑避坑指南,解决 Tool Calling 落地的核心问题。
二、核心概念拆解:Spring AI Tool Calling 全原理
2.1 什么是 Tool Calling
Tool Calling(函数调用)是当前主流大模型(如通义千问、DeepSeek、GPT-4o)都支持的核心能力,它的核心流程如下:
- 意图识别:大模型接收用户问题,判断是否需要调用工具。
- 工具选择:如果需要调用工具,大模型从注册的工具列表中选择最合适的工具。
- 参数提取:大模型从用户问题中提取工具所需的参数,生成标准的参数格式。
- 工具执行:开发者的代码接收大模型的工具调用请求,执行对应的业务逻辑,返回结果。
- 结果生成:大模型基于工具返回的结果,结合用户的原始问题,生成最终的自然语言回答。
简单来说:Tool Calling 就是给大模型 "开了外挂",让它能通过调用你的业务接口,获取实时数据、执行业务操作、完成复杂计算。
2.2 Spring AI Tool Calling 核心组件
Spring AI 对 Tool Calling 做了非常优雅的封装,核心组件只有三个:
| 核心组件 | 作用 | 典型用法 |
|---|---|---|
@Tool 注解 |
标记一个 Java 方法为 "大模型可调用的工具",通过description属性告诉大模型这个工具的用途 |
@Tool(description = "获取当前日期的详细信息") |
ToolCallback |
工具的封装对象,负责将 Java 方法转换为大模型可识别的工具定义,同时处理工具的调用逻辑 | ToolCallbacks.from(new YourToolClass()) |
ToolCallingChatOptions |
工具调用的配置对象,负责将注册的工具集传递给大模型,告诉大模型有哪些工具可用 | ToolCallingChatOptions.builder().toolCallbacks(tools).build() |
2.3 为什么优先使用ChatClient实现 Tool Calling
和之前的结构化输出、Prompt 工程一样,Spring AI 在ChatClient中对 Tool Calling 做了进一步的简化封装:
- ChatModel 实现 :需要手动创建
ToolCallback、配置ToolCallingChatOptions、组装Prompt,代码量较多,但能看到完整的底层流程。 - ChatClient 实现 :直接通过
.tools()方法注册工具,一行代码完成所有配置,代码极简,是生产环境的推荐写法。
三、实战落地:从工具定义到全流程调用
为了让教程更具实战价值,我们不再使用简单的 "获取当前时间" 工具,而是实现一个更实用的日期计算工具,包含两个核心方法:
getCurrentDateDetail():获取当前日期的详细信息(包括星期几、是否为工作日)。calculateDaysBetween(String startDate, String endDate):计算两个日期之间的天数差。
这个工具能很好地展示 Tool Calling 的价值:大模型自己无法准确判断 "今天是星期几"、"距离某个日期还有多少天" 这类需要精确逻辑计算的问题,必须调用工具。
3.1 环境前提
- 已完成 JDK 17+、Spring Boot 3.2.x 环境搭建
- 已完成 Spring AI 基础配置,项目中已注册
ChatModel和ChatClient实例 - 已配置阿里云百炼 API Key 环境变量
DASHSCOPE_API_KEY
3.2 第一步:定义工具类
创建DateCalculatorTools工具类,使用@Tool注解标记可调用的方法,同时通过description属性清晰地告诉大模型每个工具的用途、参数含义。
java
import org.springframework.ai.tool.annotation.Tool;
import org.springframework.ai.tool.annotation.ToolParam;
import java.time.DayOfWeek;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
/**
* 日期计算工具类:大模型可调用的业务工具
*/
public class DateCalculatorTools {
private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd");
/**
* 获取当前日期的详细信息
* @param returnDirect false(默认值):拿到工具返回结果后,再交给大模型生成最终回答
* true:工具直接返回结果,不走大模型二次生成(适合纯数据查询场景)
*/
@Tool(description = "获取当前日期的详细信息,包括日期、星期几、是否为工作日", returnDirect = false)
public DateDetail getCurrentDateDetail() {
LocalDate today = LocalDate.now();
DayOfWeek dayOfWeek = today.getDayOfWeek();
boolean isWorkday = dayOfWeek != DayOfWeek.SATURDAY && dayOfWeek != DayOfWeek.SUNDAY;
// 返回结构化数据,Spring AI会自动转换为JSON给大模型
return new DateDetail(
today.format(DATE_FORMATTER),
dayOfWeek.getDisplayName(java.time.format.TextStyle.FULL, java.util.Locale.CHINA),
isWorkday ? "是" : "否"
);
}
/**
* 计算两个日期之间的天数差
* @param startDate 开始日期,格式必须为yyyy-MM-dd(通过@ToolParam明确告诉大模型参数格式)
* @param endDate 结束日期,格式必须为yyyy-MM-dd
*/
@Tool(description = "计算两个日期之间的天数差,用于倒计时、日期间隔计算等场景")
public long calculateDaysBetween(
@ToolParam(description = "开始日期,格式必须为yyyy-MM-dd,例如2025-08-01") String startDate,
@ToolParam(description = "结束日期,格式必须为yyyy-MM-dd,例如2025-10-01") String endDate) {
try {
LocalDate start = LocalDate.parse(startDate, DATE_FORMATTER);
LocalDate end = LocalDate.parse(endDate, DATE_FORMATTER);
return ChronoUnit.DAYS.between(start, end);
} catch (Exception e) {
throw new IllegalArgumentException("日期格式错误,请使用yyyy-MM-dd格式,例如2025-08-01");
}
}
/**
* 日期详情记录类:用于返回结构化数据
*/
public record DateDetail(String date, String dayOfWeek, String isWorkday) {}
}
关键说明:
@Tool注解的description属性 :这是最重要的部分,必须清晰、准确地描述工具的用途,大模型就是通过这个description来判断是否调用该工具的。@ToolParam注解:用于明确告诉大模型每个参数的含义、格式要求,大幅提升大模型参数提取的准确率。- 返回结构化数据 :工具方法可以返回 Java 实体(如
DateDetailrecord),Spring AI 会自动将其转换为 JSON 格式传递给大模型,无需手动序列化。 returnDirect属性 :false(默认):工具返回结果后,大模型会基于结果生成自然语言回答(适合需要解释的场景)。true:工具直接返回结果,不走大模型二次生成(适合纯数据查询、API 透传场景)。
3.3 第二步:ChatModel 底层实现(理解原理用)
先通过ChatModel实现完整的 Tool Calling 流程,理解底层逻辑,然后再看ChatClient的简化实现。
java
import com.atguigu.study.utils.DateCalculatorTools;
import jakarta.annotation.Resource;
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;
/**
* Tool Calling 底层实现:基于ChatModel
*/
@RestController
public class ToolCallingController {
@Resource
private ChatModel chatModel;
/**
* ChatModel 实现Tool Calling全流程
* 访问示例:http://localhost:xxxx/toolcall/chat?msg=距离2026-10-01还有多少天?
*/
@GetMapping("/toolcall/chat")
public String chat(@RequestParam(name = "msg", defaultValue = "今天是星期几?") String msg) {
// 1. 将工具类注册为ToolCallback集合
ToolCallback[] tools = ToolCallbacks.from(new DateCalculatorTools());
// 2. 将工具集配置进ChatOptions,告诉大模型有哪些工具可用
ChatOptions options = ToolCallingChatOptions.builder()
.toolCallbacks(tools)
.build();
// 3. 构建Prompt,包含用户问题和工具配置
Prompt prompt = new Prompt(msg, options);
// 4. 调用大模型:Spring AI会自动处理工具选择、参数提取、工具执行、结果生成的全流程
return chatModel.call(prompt).getResult().getOutput().getText();
}
}
关键说明:
- 你不需要手动处理 "大模型什么时候调用工具"、"怎么提取参数"、"怎么把结果返回给大模型" 这些复杂逻辑,Spring AI 已经全部封装好了。
- 你只需要做三件事:定义工具、注册工具、调用模型,剩下的全交给Spring AI。
3.4 第三步:ChatClient简化实现
ChatClient对Tool Calling做了极致简化,直接通过.tools()方法注册工具,一行代码完成所有配置,代码量比ChatModel减少了60%以上。
java
import jakarta.annotation.Resource;
import org.springframework.ai.chat.client.ChatClient;
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;
/**
* Tool Calling 简化实现:基于ChatClient
*/
@RestController
public class ToolCallingClientController {
@Resource
private ChatClient chatClient;
/**
* ChatClient 实现Tool Calling(同步调用)
* 访问示例:http://localhost:8013/toolcall/chat2?msg=今天是星期几?是工作日吗?
*/
@GetMapping("/toolcall/chat2")
public String chat2(@RequestParam(name = "msg", defaultValue = "今天是星期几?") String msg) {
return chatClient.prompt(msg)
// 核心:一行代码注册工具,支持传入工具类实例或工具类的Class
.tools(new DateCalculatorTools())
.call()
.content();
}
/**
* ChatClient 实现Tool Calling(流式调用)
* 访问示例:http://localhost:8013/toolcall/chat3?msg=距离2026-10-01还有多少天?帮我详细解释一下
*/
@GetMapping(value = "/toolcall/chat3", produces = "text/html;charset=utf-8")
public Flux<String> chat3(@RequestParam(name = "msg", defaultValue = "今天是星期几?") String msg) {
return chatClient.prompt(msg)
.tools(new DateCalculatorTools())
.stream()
.content();
}
}
接口测试说明:启动项目后,访问对应接口,你会看到:
- 问 "今天是星期几?是工作日吗?":大模型会自动调用
getCurrentDateDetail()工具,基于返回的结果生成自然语言回答。 - 问 "距离 2026-10-01 还有多少天?":大模型会自动调用
calculateDaysBetween()工具,提取当前日期作为开始日期,计算天数差并生成回答。
四、进阶场景:企业级高频需求全实现
4.1 多工具同时注册
Spring AI支持同时注册多个工具,大模型会根据用户问题自动选择最合适的工具调用。我们再添加一个 "随机密码生成工具",展示多工具注册的用法。
java
import org.springframework.ai.tool.annotation.Tool;
import org.springframework.ai.tool.annotation.ToolParam;
import java.security.SecureRandom;
/**
* 随机密码生成工具
*/
public class PasswordGeneratorTools {
private static final String CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*";
private static final SecureRandom RANDOM = new SecureRandom();
@Tool(description = "生成指定长度的随机密码,包含大小写字母、数字和特殊字符")
public String generatePassword(
@ToolParam(description = "密码长度,建议8-20位") int length) {
if (length < 8 || length > 20) {
throw new IllegalArgumentException("密码长度必须在8-20位之间");
}
StringBuilder password = new StringBuilder(length);
for (int i = 0; i < length; i++) {
password.append(CHARS.charAt(RANDOM.nextInt(CHARS.length())));
}
return password.toString();
}
}
多工具注册实现:
java
@GetMapping("/toolcall/multi")
public String multiToolChat(@RequestParam(name = "msg") String msg) {
// 同时注册多个工具:日期计算 + 密码生成
return chatClient.prompt(msg)
.tools(new DateCalculatorTools(), new PasswordGeneratorTools())
.call()
.content();
}
测试时,问 "生成一个 12 位的随机密码",大模型会自动调用generatePassword()工具;问 "今天是星期几",大模型会自动调用getCurrentDateDetail()工具,完全无需手动判断。
4.2 returnDirect高级用法
当工具返回的结果本身就是用户需要的最终数据(如纯 JSON 数据、API 透传结果),不需要大模型二次生成自然语言回答时,可以将returnDirect设置为true,直接返回工具结果,提升响应速度,节省Token消耗。
我们修改getCurrentDateDetail()方法,将returnDirect设置为true:
java
@Tool(description = "获取当前日期的详细信息,直接返回JSON数据", returnDirect = true)
public DateDetail getCurrentDateDetail() {
// 方法实现不变
}
此时调用该工具,会直接返回 JSON 格式的工具结果,不再经过大模型二次生成:
java
{
"date": "2026-07-28",
"dayOfWeek": "星期二",
"isWorkday": "是"
}
4.3 带复杂参数的工具调用
工具方法支持多种参数类型:基本类型(int、long、String)、复杂对象、集合等。Spring AI 会自动处理参数的类型转换,无需开发者手动处理。
我们添加一个 "日程安排工具",展示复杂对象参数的用法:
java
import org.springframework.ai.tool.annotation.Tool;
import org.springframework.ai.tool.annotation.ToolParam;
import java.util.List;
/**
* 日程安排工具
*/
public class ScheduleTools {
public record ScheduleEvent(String title, String date, String time, String location) {}
@Tool(description = "创建日程安排,返回确认信息")
public String createSchedule(
@ToolParam(description = "日程标题") String title,
@ToolParam(description = "日程日期,格式yyyy-MM-dd") String date,
@ToolParam(description = "日程时间,格式HH:mm") String time,
@ToolParam(description = "日程地点") String location) {
return String.format("日程创建成功:【%s】于 %s %s 在 %s 举行", title, date, time, location);
}
}
五、实践建议
5.1 工具设计原则
- 单一职责:一个工具只做一件事,避免 "全能工具",工具越简单,大模型越容易正确调用。
- 清晰的 Description :
@Tool和@ToolParam的description必须清晰、准确、无歧义,这是大模型正确调用工具的核心。 - 参数校验:在工具方法中必须对参数进行严格校验(如日期格式、长度范围、非空校验),避免非法参数导致业务异常。
- 结构化返回:优先返回 Java 实体或 Record,避免返回纯字符串,结构化数据能让大模型更准确地理解工具返回的结果。
5.2 安全管控
- 权限校验:在工具方法执行前,必须校验当前用户的权限,避免未授权用户调用敏感工具(如创建订单、删除数据)。
- 输入过滤:对用户输入的参数进行过滤,防止 SQL 注入、XSS 攻击、Prompt Injection 等安全风险。
- 敏感数据脱敏:如果工具返回的结果包含敏感数据(如手机号、身份证号),必须进行脱敏处理后再返回给大模型。
- 工具白名单:生产环境建议使用工具白名单机制,只允许注册经过安全审核的工具,避免随意注册工具带来的安全风险。
5.3 异常处理与降级
- 工具异常捕获:在工具方法中捕获所有异常,返回友好的错误信息给大模型,让大模型基于错误信息生成合理的回答。
- 降级机制:当工具调用失败(如第三方 API 超时、数据库连接失败)时,提供降级返回值,避免整个请求失败。
- 超时控制:为工具调用设置超时时间,避免工具执行时间过长导致整个请求阻塞。
5.4 日志与监控
- 工具调用日志:记录每次工具调用的用户问题、工具名称、参数、返回结果、执行耗时,用于排查问题和优化工具。
- 调用次数统计:统计每个工具的调用次数、失败率,识别高频使用的工具和不稳定的工具。
- Token 消耗监控:统计 Tool Calling 的 Token 消耗(包括工具调用请求和结果返回的 Token),用于成本核算。
六、避坑指南
6.1 坑点 1:工具方法必须是public
现象 :大模型无法识别工具,提示 "没有可用的工具"。原因 :工具方法的访问修饰符不是public,Spring AI 无法通过反射调用该方法。解决方案 :确保所有被@Tool注解标记的方法都是public的。
6.2 坑点 2:参数类型不匹配
现象 :大模型调用工具时,参数提取失败,或者类型转换异常。原因 :工具方法的参数类型与大模型生成的参数类型不匹配(如方法参数是int,大模型生成了String)。解决方案:
- 优先使用基本类型的包装类(
Integer、Long)或String,避免类型转换问题。 - 通过
@ToolParam明确告诉大模型参数的类型和格式要求。
6.3 坑点 3:Description 写得不够清晰
现象 :大模型不会调用工具,或者调用错误的工具。原因 :@Tool的description写得太模糊,大模型无法理解工具的用途。解决方案:
- Description 要包含:工具的用途、适用场景、返回值的含义。
- 可以在 Description 中给出使用示例,帮助大模型理解。
6.4 坑点 4:returnDirect的误用
现象 :设置returnDirect=true后,大模型不再生成回答,直接返回了工具的原始结果。原因 :误解了returnDirect的作用,它适合纯数据查询场景,不适合需要解释的场景。解决方案:
- 需要大模型基于工具结果生成自然语言回答时,设置
returnDirect=false(默认)。 - 只需要工具返回的原始数据时,才设置
returnDirect=true。
6.5 坑点 5:流式调用与 Tool Calling 的兼容性
现象 :流式调用时,工具调用的结果无法正确展示。原因 :部分大模型在流式调用时,Tool Calling 的支持不够完善。解决方案:
- 优先使用同步调用实现 Tool Calling,稳定性更高。
- 如果必须使用流式调用,建议在工具调用完成后,再使用流式输出生成最终回答。
七、本篇总结
本篇我们深度掌握了 Spring AI Tool Calling的全体系:
- 从核心原理出发,理解了 Tool Calling 的工作流程与Spring AI的核心抽象。
- 从零实现了实用的日期计算工具,完成了从工具定义、注册到调用的全流程实战。
- 覆盖了ChatModel底层实现与 ChatClient 简化实现两种方式,明确了生产环境的推荐写法。
- 补充了多工具注册、
returnDirect高级用法、带复杂参数的工具调用等企业级高频场景。 - 提供了生产环境最佳实践与高频踩坑避坑指南,为 Tool Calling 的生产落地提供了完整的解决方案。
Tool Calling 是大模型从 "问答助手" 走向 "业务执行者" 的关键一步,只有掌握了 Tool Calling,才能真正将大模型能力深度融入业务流程,实现 "大模型 + 业务系统" 的深度融合。
八、下篇预告
本篇我们掌握了 Tool Calling 核心能力,让大模型能自动调用我们的业务接口。在本系列的下一篇中,我们将深入 Spring AI 的核心拼图 ------对话记忆(Chat Memory):
- 深度拆解对话记忆的核心原理,理解大模型如何 "记住" 历史对话。
- 从零实现基于内存的对话记忆,完成带上下文感知的多轮对话。
- 覆盖会话隔离、记忆窗口大小控制、持久化存储等企业级高频场景。
- 补充生产环境对话记忆的最佳实践与性能优化指南。
如果本文对你有帮助,欢迎点赞、收藏、评论,跟着系列教程一步步完成Spring AI应用的全流程落地。