目录Tool Calling让 AI 调用外部函数《Spring AI 实战系列》完整目录 入门篇已完结 · 建议按顺序学习 进阶篇更新中️ 学习路径建议 先说痛点一、为什么需要 Tool Calling1.1 AI 模型的局限性1.2 Tool Calling 解决什么问题1.3 典型使用场景二、工作原理2.1 执行流程2.2 Spring AI 中的核心组件三、声明式工具定义Tool 注解3.1 基础用法最简单3.2 带参数描述3.3 多工具组合3.4 默认工具 vs 运行时工具四、编程式工具定义五、实战案例5.1 案例一AI 日程助手5.2 案例二AI 计算助手六、常见问题与最佳实践6.1 常见问题6.2 最佳实践6.3 Tool 注解参数七、系列预告 参考资料 收藏关注持续更新Tool Calling让 AI 调用外部函数系列说明本文为《Spring AI 实战系列 入门篇》第 2 篇前置知识完成第 1 篇基础概念与快速上手预计阅读时间15 分钟《Spring AI 实战系列》完整目录 学习不迷路系列持续更新中点击链接直达各篇 入门篇已完结 · 建议按顺序学习篇目标题核心内容第 1 篇✅核心概念与快速上手Model / Prompt / Embedding 第一个项目第 2 篇✅Tool Calling让 AI 调用外部函数Tool声明式 编程式工具定义第 3 篇✅VectorStore RAG构建私有知识库向量数据库集成 文档检索第 4 篇✅结构化输出AI 结果映射为 POJOBeanOutputConverter第 5 篇✅Advisors自定义 AI 中间件拦截器链 对话记忆第 6 篇✅国产模型集成指南通义千问 / 文心一言 / 智谱 GLM 进阶篇更新中篇目标题核心内容进 1✅智能客服系统多轮对话 工具调用 人工兜底进 2✅企业知识库 RAG 管线多格式 ETL 混合检索RRF进 3AI Agent 自主规划ReAct 模式 多工具编排️ 学习路径建议Week 1-3 【入门篇】第1篇 → 第2篇 → 第3篇 → 第4篇 → 第5篇 → 第6篇 └→ 掌握 Spring AI 核心能力 Week 4-6 【进阶篇】智能客服 → 企业RAG → AI Agent └→ 实战企业级 AI 应用 先说痛点你是不是也遇到过这些问题❌ AI 不知道今天天气只能说我没有实时数据❌ AI 无法查询实时股价、库存、订单状态❌ AI 不能帮你发邮件、创建日程、操作数据库❌ 想让AI做点实际的事却只能停留在对话层面看完这篇你将收获✅ 让AI查询实时天气、股价、库存✅ 用一个Tool注解让AI调用你的函数✅ 实现AI日程助手、AI计算器两个实战案例✅ 理解Tool Calling的完整执行流程一、为什么需要 Tool Calling1.1 AI 模型的局限性尽管大语言模型LLM很强大但它们有天然的缺陷局限性示例❌知识陈旧GPT-4 训练数据截至 2023 年不知道今天的天气❌无法访问实时信息无法查询实时股价、新闻、库存❌无法执行外部操作不能发邮件、下订单、控制 IoT 设备❌计算能力有限数学计算可能出错1.2 Tool Calling 解决什么问题Tool Calling 扩展 AI 的能力边界┌─────────────────────────────────────────────────────────┐ │ 没有 Tool Calling │ ├─────────────────────────────────────────────────────────┤ │ 用户北京今天多少度 │ │ AI我没有实时天气数据无法回答。❌ │ └─────────────────────────────────────────────────────────┘ ┌─────────────────────────────────────────────────────────┐ │ 有 Tool Calling │ ├─────────────────────────────────────────────────────────┤ │ 用户北京今天多少度 │ │ AI调用 getWeather(北京) │ │ ↓ │ │ 工具返回25°C晴 │ │ ↓ │ │ AI北京今天天气晴朗温度25°C。✅ │ └─────────────────────────────────────────────────────────┘1.3 典型使用场景场景工具示例️天气查询getWeather(city)日历管理createEvent(title, time)邮件处理sendEmail(to, subject, body)数据库查询query(sql)网络搜索search(query)支付处理createPayment(amount)二、工作原理2.1 执行流程┌────────────────────────────────────────────────────────────┐ │ Tool Calling 完整流程 │ ├────────────────────────────────────────────────────────────┤ │ │ │ 1️⃣ 注册工具 │ │ ┌─────────────────┐ │ │ │ 天气工具 │ │ │ │ - name: get_weather │ │ │ - desc: 获取城市天气 │ │ │ - param: city │ │ │ └────────┬─────────┘ │ │ ↓ │ │ 2️⃣ 用户提问 │ │ 北京今天多少度 │ │ ↓ │ │ 3️⃣ 模型决策 │ │ 模型判断需要调用 get_weather参数 city北京 │ │ ↓ │ │ 4️⃣ 执行工具 │ │ 应用调用 getWeather(北京) → 返回 25°C, 晴 │ │ ↓ │ │ 5️⃣ 返回结果 │ │ 工具结果发送给模型 │ │ ↓ │ │ 6️⃣ 生成回答 │ │ 北京今天天气晴朗气温25°C。 │ │ │ └────────────────────────────────────────────────────────────┘2.2 Spring AI 中的核心组件组件作用Tool声明式定义工具ToolCallback工具回调接口ToolCallingManager工具执行管理器ChatClient支持.tools()方法三、声明式工具定义Tool 注解3.1 基础用法最简单一个工具只需要三步packagecom.example.demo.tools;importorg.springframework.ai.tool.annotation.Tool;importorg.springframework.stereotype.Component;Component// 需要注册为 Spring BeanpublicclassWeatherTools{Tool(description获取指定城市的当前天气)publicStringgetWeather(Stringcity){// 这里调用真实的天气 APIreturnswitch(city){case北京-25°C晴;case上海-28°C多云;case深圳-30°C雷阵雨;default-未知城市;};}}在 ChatClient 中使用RestControllerpublicclassToolController{privatefinalChatClientchatClient;privatefinalWeatherToolsweatherTools;publicToolController(ChatClient.Builderbuilder,WeatherToolsweatherTools){this.chatClientbuilder.build();this.weatherToolsweatherTools;}GetMapping(/weather)publicStringweather(RequestParamStringcity){returnchatClient.prompt().user(北京今天多少度).tools(weatherTools)// 注入工具.call().content();}}3.2 带参数描述使用ToolParam为参数添加描述帮助模型理解publicclassAdvancedTools{Tool(description设置闹钟)publicvoidsetAlarm(ToolParam(description闹钟时间格式yyyy-MM-dd HH:mm)Stringtime){// 设置闹钟逻辑System.out.println(闹钟已设置time);}Tool(description计算两个数的运算结果)publicdoublecalculate(ToolParam(description第一个数字)doublea,ToolParam(description运算符add/subtract/multiply/divide)Stringoperator,ToolParam(description第二个数字)doubleb){returnswitch(operator){caseadd-ab;casesubtract-a-b;casemultiply-a*b;casedivide-b!0?a/b:0;default-0;};}}3.3 多工具组合一个类可以定义多个工具ComponentpublicclassDateTimeTools{Tool(description获取当前日期时间)publicStringgetCurrentDateTime(){returnjava.time.LocalDateTime.now().toString();}Tool(description计算指定天数后的日期)publicStringaddDays(ToolParam(description起始日期yyyy-MM-dd格式)StringstartDate,ToolParam(description要增加的天数)intdays){// 日期计算逻辑return2026-04-01;// 返回计算结果}Tool(description获取用户所在时区)publicStringgetTimeZone(){returnjava.time.ZoneId.systemDefault().toString();}}使用多个工具GetMapping(/ask)publicStringask(RequestParamStringquestion){DateTimeToolsdateTimeToolsnewDateTimeTools();returnchatClient.prompt().user(question).tools(dateTimeTools)// 一个实例包含多个工具.call().content();}3.4 默认工具 vs 运行时工具// 方式1运行时工具仅当前请求有效chatClient.prompt().user(今天几号).tools(newDateTimeTools())// 只在这个请求中使用.call();// 方式2默认工具所有请求共享ChatClientchatClientChatClient.builder(chatModel).defaultTools(newCommonTools())// 所有请求都可用.build();四、编程式工具定义当 Tool 注解不够用时可以用MethodToolCallback手动创建importorg.springframework.ai.tool.support.MethodToolCallback;importorg.springframework.ai.tool.definition.ToolDefinition;ServicepublicclassCustomToolService{// 需要手动包装的方法publicStringsearchProducts(Stringkeyword,intlimit){// 搜索产品逻辑return找到 3 个商品商品A、商品B、商品C;}BeanpublicToolCallbacksearchToolCallback(){returnMethodToolCallback.builder().toolDefinition(ToolDefinition.builder().name(search_products).description(搜索商品列表).inputSchema( { type: object, properties: { keyword: {type: string, description: 搜索关键词}, limit: {type: integer, description: 返回数量限制} }, required: [keyword] } ).build()).toolMethod(getMethod(searchProducts,String.class,int.class)).toolObject(this).build();}privateMethodgetMethod(Stringname,Class?...paramTypes){try{returngetClass().getMethod(name,paramTypes);}catch(NoSuchMethodExceptione){thrownewRuntimeException(e);}}}五、实战案例5.1 案例一AI 日程助手ComponentpublicclassCalendarTools{privatefinalListMapString,StringeventsnewArrayList();Tool(description创建日程事件)publicStringcreateEvent(ToolParam(description事件标题)Stringtitle,ToolParam(description事件时间ISO格式)Stringdatetime,ToolParam(description事件描述)Stringdescription){MapString,StringeventMap.of(title,title,datetime,datetime,description,description);events.add(event);return日程已创建title时间datetime;}Tool(description查询指定日期的日程)publicStringgetEvents(ToolParam(description查询日期yyyy-MM-dd格式)Stringdate){ListStringdayEventsevents.stream().filter(e-e.get(datetime).startsWith(date)).map(e-- e.get(title) (e.get(datetime))).toList();returndayEvents.isEmpty()?date 没有日程安排:String.join(\n,dayEvents);}Tool(description删除日程)publicStringdeleteEvent(ToolParam(description要删除的事件标题)Stringtitle){booleanremovedevents.removeIf(e-e.get(title).equals(title));returnremoved?已删除title:未找到日程title;}}RestControllerRequestMapping(/calendar)publicclassCalendarController{privatefinalChatClientchatClient;privatefinalCalendarToolscalendarTools;publicCalendarController(ChatClient.Builderbuilder,CalendarToolstools){this.chatClientbuilder.build();this.calendarToolstools;}GetMapping(/ask)publicStringask(RequestParamStringquestion){returnchatClient.prompt().system(你是一个智能日程助手可以帮用户创建、查询和删除日程。).user(question).tools(calendarTools).call().content();}}测试# 创建日程curlhttp://localhost:8080/calendar/ask?question帮我安排一个明天上午10点的会议# 查询日程curlhttp://localhost:8080/calendar/ask?question明天有什么安排# 删除日程curlhttp://localhost:8080/calendar/ask?question取消明天的会议5.2 案例二AI 计算助手ComponentpublicclassMathTools{Tool(description进行数学运算)publicdoublecalculate(ToolParam(description第一个数)doublea,ToolParam(description运算符add/sub/mult/div/pow/sqrt)Stringop,ToolParam(description第二个数)doubleb){returnswitch(op){caseadd-ab;casesub-a-b;casemult-a*b;casediv-b!0?a/b:Double.NaN;casepow-Math.pow(a,b);casesqrt-Math.sqrt(a);default-0;};}Tool(description获取圆周率)publicdoublegetPi(){returnMath.PI;}}RestControllerRequestMapping(/math)publicclassMathController{privatefinalChatClientchatClient;publicMathController(ChatClient.Builderbuilder,MathToolstools){this.chatClientbuilder.build();}GetMapping(/solve)publicStringsolve(RequestParamStringquestion){returnchatClient.prompt().system(你是一个数学计算助手用户提出数学问题时使用 calculate 工具来精确计算。).user(question).tools(newMathTools()).call().content();}}测试curlhttp://localhost:8080/math/solve?question15的平方根是多少curlhttp://localhost:8080/math/solve?question100加200等于多少六、常见问题与最佳实践6.1 常见问题Q1: 模型不调用工具检查以下几点工具描述是否清晰明确工具参数是否有ToolParam描述提示词是否引导模型使用工具Q2: 工具返回结果格式ToolpublicStringgetData(){return返回的字符串;// 返回 String会自动转为 JSON}Q3: 工具执行失败怎么办ToolpublicStringriskyOperation(){try{// 正常逻辑return成功;}catch(Exceptione){// 返回错误信息模型会解释给用户return操作失败e.getMessage();}}6.2 最佳实践实践说明✅详细描述Tool(description 获取...)要写清楚✅参数命名参数名要有意义如cityName而非arg0✅错误处理工具内部做好 try-catch✅单工具类相关工具放一个类复杂应用多个类✅Spring Bean工具类加Component自动注入6.3 Tool 注解参数参数说明默认值name工具名称方法名description工具描述最重要方法名returnDirect是否直接返回结果falseTool(nameget_weather,description获取指定城市的当前天气包括温度和天气状况,returnDirectfalse)publicStringgetWeather(Stringcity){...}七、系列预告好消息入门篇全部 6 篇已完结进阶篇持续更新中 入门篇完结 —— 恭喜你已完成第 2 篇推荐学习路径第1篇 → 第2篇本文✅→ 第3篇RAG→ 第6篇国产模型 参考资料Spring AI Tool Calling 官方文档https://docs.spring.io/spring-ai/reference/api/tools.htmlTool 注解源码org.springframework.ai.tool.annotation.ToolMethodToolCallback 文档https://docs.spring.io/spring-ai/reference/api/tools.html#_programmatic_specification_methodtoolcallback引用说明本文核心概念与技术描述参考自 Spring AI 官方文档https://docs.spring.io/spring-ai/reference/Tool Calling 相关内容来自官方 Tools API 文档。 收藏关注持续更新如果觉得有帮助请⭐收藏本文—— Tool Calling是AI应用的核心能力随时可能用到关注公众号「AI日撰」—— 点击菜单「获取源码」获取完整代码Gitee 仓库分享给同事—— 一起学习 Spring AI少走弯路系列更新不迷路下篇推荐第 3 篇 · VectorStore RAG构建私有知识库