大家好我是直奔標杆专注Java开发者AI转型干货分享和大家一起从零基础吃透Spring AI稳步向AI领域进阶 今天带来《Spring AI 零基础到实战》系列的第十六课也是AI智能体Agent入门的核心一课手把手带大家解锁Tool Calling技术让大模型真正拥有“动手能力”在前面的课程中我们已经帮大模型解锁了两大技能持久化记忆能力以及通过RAG技术从私有知识库中精准检索文档的能力。但很多小伙伴在将大模型接入真实业务系统时都会遇到一个共性痛点——大模型“眼高手低”只会说不会做。比如你问它“查一下今天北京的天气”或者让它“设一个明早8点的闹钟”它要么一本正经地胡说八道要么无奈回复“无法获取实时数据无法操作设备”。相信很多同学都踩过这个坑其实问题的核心很简单大模型就像一个被关在云端“小黑屋”里的天才大脑上知天文下知地理却没有一双能触碰真实世界的“双手”没法执行查MySQL、调第三方API、发邮件等实际操作。今天这节课我们就一起打破这层枷锁吃透AI智能体的核心技术——Tool Calling工具调用。借助Spring AI的底层封装我们只需一个简单的Tool注解就能让大模型轻松调用本地Java方法实现真正的“知行合一”这也是Java开发者转型AI必须掌握的核心技能之一建议大家跟着实操有疑问随时在评论区交流本节学习目标建议收藏对照自查认知重塑打破“大模型能自己执行代码”的误区搞懂Tool Calling“跨界协作”的底层逻辑避免踩坑状态机透视看懂Spring Boot如何充当“全能助理”将复杂的多轮网络请求封装成全自动闭环状态机极简实战告别繁琐硬编码用Tool与ToolParam注解优雅定义并一键挂载外部工具新手也能快速上手源码解密扒开Spring AI底层源码搞懂反射机制与ChatModel内部拦截器的作用知其然更知其所以然。Tool Calling交互逻辑不是“越权控制”是“默契协作”很多同学听到“AI自动调用本地代码”都会觉得很神秘甚至以为是“黑客操作”。其实剥开神秘面纱就会发现它的本质就是大模型与Spring Boot应用的一次默契跨界合作分享一个通俗的类比帮大家快速理解把大模型比作坐在全封闭办公室里的天才CEO——懂的多、思路活但没法亲自下楼办事我们的Spring Boot应用就是那个随叫随到的全能助理负责把CEO的指令落到实处。两者协作的核心就是给大模型一份“工具说明书”符合特定规范的JSON Schema清晰告诉它本地有哪些工具、能做什么事、需要传递哪些参数。如果手动拼接这份JSON说明书不仅繁琐还容易出错但Spring AI用Java声明式注解直接帮我们抹平了这份复杂这也是Spring AI的强大之处极简实战从0到1实现AI调用本地Java方法附完整代码实战是掌握技术的最好方式我们从最简单的场景入手让AI获取服务器当前时间对比“未挂载工具”和“挂载工具”的差异大家跟着敲代码就能快速get核心用法。场景1未挂载工具——大模型“束手无策”先做一个对照组测试不挂载任何工具让大模型回答时间相关问题代码如下可直接复制测试/** * 作者直奔標杆CSDN * 说明未挂载工具的测试案例演示大模型的能力局限 */ Test void testWithoutTool() { ChatClient chatClient chatClientBuilder.build(); String content chatClient.prompt(今天是几号) .call() .content(); System.out.println(content); }运行结果和我们预期一致大模型无法获取实时时间抱歉我无法提供当前的日期。如果你需要知道今天的具体日期可以查看你的设备或日历应用。场景2Tool注解声明工具类——给AI“造一只手”新建一个普通Java类写一个获取当前时间的方法唯一的操作就是给方法加上Tool注解相当于给大模型提供“工具说明书”代码如下/** * 作者直奔標杆CSDN * 说明以注解方式定义工具类Spring AI自动识别 */ public class DateTimeToolsWithAnnotation { /** * 获取用户时区的当前日期和时间 * 重点说明新手必看 * 1. Tool的description参数就是给大模型看的“工具简介” * 大模型会根据这个描述判断用户提问是否需要调用该工具 * 2. 方法名getCurrentDateTime会自动作为该工具的唯一标识ID无需额外配置。 */ Tool(description 获取用户时区的当前日期和时间) public String getCurrentDateTime() { // 业务逻辑调用系统API获取真实时间可直接复用 System.out.println(Spring AI 触发了本地方法获取用户时区的当前日期和时间); return LocalDateTime.now().atZone(LocaleContextHolder.getTimeZone().toZoneId()).toString(); } }场景3一行代码挂载工具——实现全自动闭环工具类定义完成后只需在ChatClient的链式调用中加一行.tools()方法就能将工具挂载到大模型代码如下重点看注释标注的核心行Test public void testWithTool() { String content2 ollamaClient.prompt(今天星期几) // 核心代码将工具实例挂载到大模型一行搞定 .tools(new DateTimeTools()) .call() .content(); System.out.println(content2); }运行结果惊喜大模型成功调用本地方法返回真实时间Spring AI 触发了本地方法获取用户时区的当前日期和时间 今天是2026年3月31日。这里重点强调一下仅仅加了一行.tools(new DateTimeTools())Spring AI就在底层自动完成了“发送请求→判断需调用工具→反射调用本地Java方法→获取结果→二次请求大模型→生成最终回复”的全闭环完全不用我们手动干预这就是Spring AI封装的魅力也是我们Java开发者的优势——不用从零造轮子专注业务逻辑即可。进阶实战复杂参数工具调用模拟真实业务场景刚才的案例没有入参体现不出大模型“提取参数”的推理能力。在真实业务中比如查询天气、查询订单大模型需要从用户的口语化提问中精准提取关键参数比如城市、订单号这时候就需要ToolParam注解登场了我们以“查询天气”为例实战演示复杂工具调用。步骤1定义带参工具类含入参和出参public class WeatherTools { // 1. 定义工具出参用Java Record简化代码新手可直接复用 public record WeatherResponse(double temp, String unit, String condition) {} /** * 2. 带入参的工具方法模拟查询实时天气 */ Tool(description 获取指定地点的实时天气情况用于回答用户的天气查询需求) public WeatherResponse getWeather( // 3. ToolParam核心作用告诉大模型参数的意义、格式和必填性 // 这是大模型能从口语中提取“北京”这个参数的关键 ToolParam(description 城市的名称例如江苏、南京、北京必填项不可省略, required true) String location ) { System.out.println(Spring AI 触发了本地方法目标城市: location); // 模拟业务逻辑根据城市返回模拟天气真实场景可替换为调用天气API double temp location.contains(北京) ? 30.0 : 25.0; String cond 多云转晴; // 将结果返回给大模型由大模型润色成自然语言回复 return new WeatherResponse(temp, C, cond); } }步骤2挂载工具并测试Test public void testWeatherTool() { String content ollamaClient.prompt(帮我看看北京最近的天气咋样啊需要带伞吗) .tools(new WeatherTools()) .call() .content(); System.out.println(最终回复: content); }运行结果与分析运行结果Spring AI 触发了本地方法目标城市: 北京 最终回复: 北京最近的天气是多云转晴温度约为30摄氏度。目前的天气状况不需要带伞。这里大家可以重点观察大模型的推理能力它不仅判断出需要调用getWeather工具还从“帮我看看北京最近的天气咋样啊”这句口语化提问中精准提取出location北京这个参数传递给本地Java方法最后结合返回的天气数据贴心给出“不用带伞”的建议——这就是Tool Calling的核心价值让大模型从“只会说”变成“会做会说”。补充一个小技巧实战避坑ToolParam的description一定要写详细明确参数格式和必填性这样能有效避免大模型猜测参数、传递错误参数的问题这也是我实战中总结的经验分享给大家源码揭秘一行代码背后Spring AI到底做了什么很多同学可能会好奇我们只加了Tool注解和一行.tools()代码Spring AI底层到底完成了哪些操作今天就扒开核心源码带大家看透本质避免只会用不会懂的情况也方便大家后续排查问题。核心逻辑分为两步工具注册将Tool注解的方法转化为大模型可识别的格式和工具调用大模型触发后反射执行本地方法并完成闭环我们逐一看源码精简核心代码去掉冗余逻辑新手也能看懂。第一步工具注册——将Java方法转化为ToolCallback当我们调用.tools(new DateTimeTools())时底层会调用ToolCallbacks.from(toolObjects)方法将我们传入的工具对象转化为大模型可识别的ToolCallback数组核心源码如下// 核心调用链路tools()方法 → ToolCallbacks.from() → MethodToolCallbackProvider.getToolCallbacks() // 1、ToolCallbacks#from 方法入口 public static ToolCallback[] from(Object... sources) { return MethodToolCallbackProvider.builder().toolObjects(sources).build().getToolCallbacks(); } // 2、MethodToolCallbackProvider#getToolCallbacks核心逻辑提取Tool注解方法 /** * 作者直奔標杆CSDN * 核心功能扫描所有Tool注解的方法转化为ToolCallback供大模型调用 * return ToolCallback[] 工具回调数组 */ public ToolCallback[] getToolCallbacks() { // 1. 遍历所有工具对象处理每个对象中的Tool方法 var toolCallbacks this.toolObjects.stream() .map(toolObject - Stream // 2. 获取工具对象的所有方法处理AOP代理对象避免获取不到原始方法 .of(ReflectionUtils.getDeclaredMethods( AopUtils.isAopProxy(toolObject) ? AopUtils.getTargetClass(toolObject) : toolObject.getClass())) // 3. 过滤条件只保留Tool注解、非函数式、用户声明的方法 .filter(this::isToolAnnotatedMethod) .filter(toolMethod - !isFunctionalType(toolMethod)) .filter(ReflectionUtils.USER_DECLARED_METHODS::matches) // 4. 将符合条件的方法转化为ToolCallback大模型可识别的格式 .map(toolMethod - MethodToolCallback.builder() .toolDefinition(ToolDefinitions.from(toolMethod)) // 提取工具定义说明书 .toolMetadata(ToolMetadata.from(toolMethod)) // 提取工具元数据 .toolMethod(toolMethod) // 绑定要调用的方法 .toolObject(toolObject) // 绑定工具对象实例 .toolCallResultConverter(ToolUtils.getToolCallResultConverter(toolMethod)) // 结果转换器 .build()) .toArray(ToolCallback[]::new)) .flatMap(Stream::of) .toArray(ToolCallback[]::new); // 验证工具的有效性比如方法名唯一、参数完整 validateToolCallbacks(toolCallbacks); return toolCallbacks; }简单总结这一步的核心是“扫描转化”——Spring AI通过反射机制扫描工具对象中所有带Tool注解的方法提取方法信息描述、参数、返回值转化为大模型能看懂的ToolCallback数组相当于自动帮我们生成了“工具说明书”不用手动拼接JSON。第二步工具调用——闭环执行反射调用二次请求当我们调用.call()方法时会将上面生成的ToolCallback数组封装成请求发送给大模型如果大模型返回“需要调用工具”的指令Spring AI会反射调用本地Java方法获取结果后再次请求大模型生成最终回复核心源码OpenAiChatModel#internalCall如下// OpenAiChatModel#internalCall 源码精简剖析核心逻辑 public ChatResponse internalCall(Prompt prompt, ChatResponse previousChatResponse) { // 1. 发送第一趟网络请求获取大模型的初始响应 // ...省略冗余的请求构建、发送逻辑 // 2. 判断大模型是否需要调用工具由拦截器判断 if (this.toolExecutionEligibilityPredicate.isToolExecutionRequired(prompt.getOptions(), response)) { // 3. 核心操作反射调用本地Java方法获取执行结果 ToolExecutionResult toolResult this.toolCallingManager.executeToolCalls(prompt, response); // 4. 闭环判断是否直接返回结果还是二次请求大模型 // returnDirectfalse默认将结果塞入上下文二次请求大模型生成自然语言回复 // returnDirecttrue直接返回工具执行结果不经过大模型润色 return toolResult.returnDirect() ? ChatResponse.builder()...build() // 直接返回结果 : this.internalCall(new Prompt(toolResult.conversationHistory()), response); // 二次请求完成闭环 } // ...省略其他逻辑 }这就是Spring AI的强大之处所有复杂的操作反射调用、多轮网络请求、结果转换都被底层封装好了我们只需要关注“定义工具”和“挂载工具”不用关心底层细节——这也是Java开发者转型AI的优势利用现有框架快速落地AI能力。本节总结必看梳理核心要点通过本节课的学习和实战相信大家已经掌握了Spring AI Tool Calling的核心用法这里直奔標杆给大家梳理3个核心要点帮助大家巩固记忆避免踩坑认知层面大模型本身没有“执行能力”它只是“决策大脑”负责判断是否需要调用工具、提取参数真正的执行主体是我们的Spring Boot应用Tool Calling本质是“大脑大模型 双手Spring Boot工具”的协作。实战层面用Tool注解定义工具给大模型写说明书用ToolParam注解定义参数帮助大模型提取参数一行.tools()代码挂载工具就能实现全自动闭环新手可直接复用本节课的代码模板。源码层面核心是“反射机制”和“状态机闭环”——Spring AI通过反射扫描Tool方法转化为大模型可识别的格式调用时通过拦截器判断反射执行本地方法再通过递归调用完成二次请求实现全自动交互。其实Tool Calling并不复杂关键是多实操、多思考本节课的代码大家可以多敲几遍感受Spring AI封装的便捷性。只要你会写Java方法无论是查库存、发邮件还是操控设备都能通过Tool Calling让大模型帮你实现真正实现“AI赋能业务”。下节预告干货预警本节课我们学的是“注解式工具注册”属于“自动挡”玩法简单高效但实战中会遇到更复杂的场景比如需要调用第三方Jar包中的方法无法修改源码不能加Tool注解或者想把业务层已有的Function接口直接作为工具该怎么办下一节课我们将脱下“优雅的伪装”直捣黄龙——《Spring AI Tool Calling 底层核心三剑客与编程式注册源码大揭秘》拆解ToolCallback、ToolDefinition等底层接口教大家纯手工挂载工具彻底吃透Tool Calling的底层逻辑解决复杂场景下的工具调用问题。精彩继续咱们下节课见大家在实操中遇到任何问题都可以在评论区留言直奔標杆会一一回复和大家一起交流进步往期内容衔接学习循序渐进Java开发者AI转型第十三课知识库终局方案Spring AI Vector Store架构演进与ETL全链路入库实战Java开发者AI转型第十四课Spring AI向量数据库实操检索召回与相似度检索实战详解Java开发者AI转型第十五课Spring AI神技模块化RAG引擎一键闭环实战我是直奔標杆专注Java开发者AI转型干货分享每一节课都贴合实战拒绝空谈理论。关注我一起从零基础吃透Spring AI稳步成为具备AI能力的Java工程师