Spring AI 实战系列(六):Tool Calling深度实战,让大模型自动调用你的业务接口
一、系列回顾与本篇定位1.1 系列回顾第一篇完成 Spring AI 与阿里云百炼的基础集成基于ChatModel实现同步对话与 API Key 安全注入。第二篇解锁ChatClient实现全局统一配置与链式调用告别重复样板代码。第三篇实现 DeepSeek/Qwen 双模型无缝共存与动态切换完成双版本流式输出。第四篇深度拆解 Prompt 工程全体系从底层 Message 到模板化动态生成掌握与大模型高效沟通的方法论。第五篇掌握结构化输出能力实现大模型输出到 Java 实体的自动映射解决业务系统对接痛点。系列栏目Spring AISpring AI 实战教程一入门示例Spring AI 实战系列二ChatClient封装告别大模型开发样板代码Spring AI 实战系列三多模型共存双版本流式输出Spring AI 实战系列四Prompt工程深度实战Spring AI 实战系列五结构化输出让大模型严格适配你的业务数据模型Spring AI 实战系列六Tool Calling深度实战让大模型自动调用你的业务接口Spring AI实战系列七Chat Memory实战基于Redis实现持久化多轮对话Spring AI 实战系列八多模态能力—— 文生图、语音合成与向量嵌入实战Spring AI 实战系列九RAG检索实战 —— 私有知识库1.2 本篇定位大模型虽然具备强大的知识储备与推理能力但它也有明确的能力边界它无法获取实时数据如当前时间、实时天气、最新股价。它无法直接操作业务系统如查询订单状态、创建工单、发送短信。它无法执行复杂的逻辑计算如精确的日期计算、数学公式推导、数据校验。而Tool Calling函数调用正是解决这些问题的核心能力它让大模型不再只是 “回答问题的顾问”而是变成了 “能调用工具的执行者”—— 大模型会自动识别用户意图选择合适的工具函数调用基于工具返回的结果生成最终回答。本篇是系列企业级核心篇我们将深度拆解 Spring AI Tool Calling 的全体系从核心原理出发彻底理解 Tool Calling 的工作流程与 Spring AI 的核心抽象。从零实现实用的日期计算工具完成从工具定义、注册到调用的全流程实战。覆盖 ChatModel 底层实现与 ChatClient 简化实现两种方式对比二者的开发体验。补充多工具注册、带参数工具调用、returnDirect高级用法等企业级高频场景。提供生产环境最佳实践与高频踩坑避坑指南解决 Tool Calling 落地的核心问题。二、核心概念拆解Spring AI Tool Calling 全原理2.1 什么是 Tool CallingTool 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_KEY3.2 第一步定义工具类创建DateCalculatorTools工具类使用Tool注解标记可调用的方法同时通过description属性清晰地告诉大模型每个工具的用途、参数含义。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 实体如DateDetailrecordSpring AI 会自动将其转换为 JSON 格式传递给大模型无需手动序列化。returnDirect属性false默认工具返回结果后大模型会基于结果生成自然语言回答适合需要解释的场景。true工具直接返回结果不走大模型二次生成适合纯数据查询、API 透传场景。3.3 第二步ChatModel 底层实现理解原理用先通过ChatModel实现完整的 Tool Calling 流程理解底层逻辑然后再看ChatClient的简化实现。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%以上。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;charsetutf-8) public FluxString 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支持同时注册多个工具大模型会根据用户问题自动选择最合适的工具调用。我们再添加一个 “随机密码生成工具”展示多工具注册的用法。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(); } }多工具注册实现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.2returnDirect高级用法当工具返回的结果本身就是用户需要的最终数据如纯 JSON 数据、API 透传结果不需要大模型二次生成自然语言回答时可以将returnDirect设置为true直接返回工具结果提升响应速度节省Token消耗。我们修改getCurrentDateDetail()方法将returnDirect设置为trueTool(description 获取当前日期的详细信息直接返回JSON数据, returnDirect true) public DateDetail getCurrentDateDetail() { // 方法实现不变 }此时调用该工具会直接返回 JSON 格式的工具结果不再经过大模型二次生成{ date: 2026-07-28, dayOfWeek: 星期二, isWorkday: 是 }4.3 带复杂参数的工具调用工具方法支持多种参数类型基本类型int、long、String、复杂对象、集合等。Spring AI 会自动处理参数的类型转换无需开发者手动处理。我们添加一个 “日程安排工具”展示复杂对象参数的用法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 工具设计原则单一职责一个工具只做一件事避免 “全能工具”工具越简单大模型越容易正确调用。清晰的 DescriptionTool和ToolParam的description必须清晰、准确、无歧义这是大模型正确调用工具的核心。参数校验在工具方法中必须对参数进行严格校验如日期格式、长度范围、非空校验避免非法参数导致业务异常。结构化返回优先返回 Java 实体或 Record避免返回纯字符串结构化数据能让大模型更准确地理解工具返回的结果。5.2 安全管控权限校验在工具方法执行前必须校验当前用户的权限避免未授权用户调用敏感工具如创建订单、删除数据。输入过滤对用户输入的参数进行过滤防止 SQL 注入、XSS 攻击、Prompt Injection 等安全风险。敏感数据脱敏如果工具返回的结果包含敏感数据如手机号、身份证号必须进行脱敏处理后再返回给大模型。工具白名单生产环境建议使用工具白名单机制只允许注册经过安全审核的工具避免随意注册工具带来的安全风险。5.3 异常处理与降级工具异常捕获在工具方法中捕获所有异常返回友好的错误信息给大模型让大模型基于错误信息生成合理的回答。降级机制当工具调用失败如第三方 API 超时、数据库连接失败时提供降级返回值避免整个请求失败。超时控制为工具调用设置超时时间避免工具执行时间过长导致整个请求阻塞。5.4 日志与监控工具调用日志记录每次工具调用的用户问题、工具名称、参数、返回结果、执行耗时用于排查问题和优化工具。调用次数统计统计每个工具的调用次数、失败率识别高频使用的工具和不稳定的工具。Token 消耗监控统计 Tool Calling 的 Token 消耗包括工具调用请求和结果返回的 Token用于成本核算。六、避坑指南6.1 坑点 1工具方法必须是public现象大模型无法识别工具提示 “没有可用的工具”。原因工具方法的访问修饰符不是publicSpring AI 无法通过反射调用该方法。解决方案确保所有被Tool注解标记的方法都是public的。6.2 坑点 2参数类型不匹配现象大模型调用工具时参数提取失败或者类型转换异常。原因工具方法的参数类型与大模型生成的参数类型不匹配如方法参数是int大模型生成了String。解决方案优先使用基本类型的包装类Integer、Long或String避免类型转换问题。通过ToolParam明确告诉大模型参数的类型和格式要求。6.3 坑点 3Description 写得不够清晰现象大模型不会调用工具或者调用错误的工具。原因Tool的description写得太模糊大模型无法理解工具的用途。解决方案Description 要包含工具的用途、适用场景、返回值的含义。可以在 Description 中给出使用示例帮助大模型理解。6.4 坑点 4returnDirect的误用现象设置returnDirecttrue后大模型不再生成回答直接返回了工具的原始结果。原因误解了returnDirect的作用它适合纯数据查询场景不适合需要解释的场景。解决方案需要大模型基于工具结果生成自然语言回答时设置returnDirectfalse默认。只需要工具返回的原始数据时才设置returnDirecttrue。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实战系列七对话记忆实战基于Redis实现持久化多轮对话如果本文对你有帮助欢迎点赞、收藏、评论跟着系列教程一步步完成Spring AI应用的全流程落地。