1. 为什么要在 Spring Boot 里折腾 Tool Calling如果你正在写一个 Spring Boot 项目想让大模型不只是“聊天”而是能真正去查数据库、算价格、调第三方接口那 Tool Calling工具调用就是绕不开的一环。简单说Tool Calling 就是让模型在回答前先“决定要不要用工具、用哪个工具、传什么参数”然后由你的 Java 代码真正执行再把结果回填给模型生成最终回复。它是 AI Agent 的最小可用闭环也是 MCPModel Context Protocol生态里最常被复用的能力单元。我这次的目标很明确在一个 Spring Boot 3.x 项目里用 Spring AI 的Tool注解定义几个 Java 方法注册成ToolCallbackProvider然后通过 TaoToken 的统一 Key 和 API 通道去调用模型触发工具执行。整条链路要能本地跑通“模型决策 → 调用 Java 方法 → 回填结果”这个闭环并且给出可复制的application.yml、注册代码和一次 curl 验证。适合谁看有 Spring Boot 基础、想快速把 Tool Calling 接进现有项目的后端同学或者已经在用 Cline、Claude Code 这类工具想理解 MCP 工具链底层怎么和 Java 服务打通的开发者。你不需要先成为大模型专家但至少要能跑起一个 Spring Boot 工程、会看 Maven 依赖。这里有个容易混淆的点先讲清楚Spring AI 里的 Tool Calling 和 MCP 不是二选一。本地Tool是“进程内工具”MCP 是“跨进程/跨应用工具协议”。入门阶段先用本地 Tool Calling 把闭环跑通理解模型怎么选工具、参数怎么传、结果怎么回填之后再迁移到 MCP 结构会顺很多。我试过直接上 MCP Server结果卡在协议调试上反而本地 Tool 十分钟就跑通了。所以这篇的路线是先定义工具类再注册ToolCallbackProvider然后配好 TaoToken 的 Base URL 和 Key最后用 curl 验证模型确实调用了 Java 方法。每一步都给完整代码和配置你照着改包名就能用。2. TaoToken 前置准备统一 Key 与 API 通道在写代码之前先把模型通道准备好。Spring AI 的 OpenAI Starter 需要一个base-url和api-keyTaoToken 在这里扮演的就是统一入口你拿一个 Key就能通过它的 API 通道调用多种模型不用在代码里为每个模型厂商维护不同的地址和鉴权方式。对 Tool Calling 来说这一点很关键因为工具调用的请求体里会带tools字段通道必须能正确透传并返回tool_calls。先做三件事。第一打开 TaoToken 官网注册并登录地址是 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 。第二进入控制台创建 API Key控制台入口在 https://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_contentconsoleutm_campaignrewrite 创建后复制那串sk-开头的 Key后面配置里要用。第三确认你要用的模型 ID比如gpt-4o、claude-3-5-sonnet这类支持 function calling 的模型模型列表可以在模型对话页查看https://taotoken.net/models?utm_sourcetaotoken_aicg_blog_endutm_contentmodelsutm_campaignrewrite 。这里要强调一个细节Spring AI 的 OpenAI 客户端默认会往{base-url}/v1/chat/completions发请求所以base-url要填 TaoToken 的 API 根地址也就是https://taotoken.net/api不要多加/v1否则会变成/v1/v1/chat/completions直接 404。API Key 就填你刚复制的那串。模型 ID 填你确认支持工具调用的那个。如果你还没决定用哪个模型建议先用一个明确支持 function calling 的比如gpt-4o或claude-3-5-sonnet。有些小模型虽然便宜但对tools字段支持不完整会出现“模型不调用工具、直接编答案”的情况排查起来很费时间。我踩过的坑就是拿一个不支持工具的模型测了半天最后发现换模型就好了。另外TaoToken 的 API 文档里有完整的请求示例和参数说明接入前扫一眼能省很多事https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite 。文档里会告诉你tools字段的格式、tool_choice怎么设、返回的tool_calls结构长什么样这些和 Spring AI 内部封装是对应的理解了对排查问题很有帮助。准备好 Key 和模型 ID 后就可以进项目了。记住三个值Base URL https://taotoken.net/apiAPI Key 你的sk-xxxModel ID 你选的模型名。后面application.yml里就填这三个。3. 可复制配置application.yml 与 ToolCallbackProvider 注册这一节是核心直接给可复制的配置和代码。先看 Maven 依赖Spring AI 用 BOM 管理版本这里用 1.0.0-M5OpenAI Starter 负责和 TaoToken 通道通信。dependencyManagement dependencies dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-bom/artifactId version1.0.0-M5/version typepom/type scopeimport/scope /dependency /dependencies /dependencyManagement dependencies dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-starter-model-openai/artifactId /dependency /dependencies然后是application.yml路径是src/main/resources/application.yml。注意base-url填 TaoToken 的 API 根地址api-key填你的 Keymodel填支持工具调用的模型 ID。server: port: 8080 spring: ai: openai: api-key: sk-你的TaoToken密钥 base-url: https://taotoken.net/api chat: options: model: gpt-4o temperature: 0.7接下来定义工具类。用Tool注解声明式定义Spring AI 会自动扫描并生成工具描述。先写一个时间工具和一个计算工具计算工具用 Java 内置的表达式解析避免引入额外脚本引擎。package com.example.ai.tool; import org.springframework.ai.tool.annotation.Tool; import org.springframework.ai.tool.annotation.ToolParam; import org.springframework.stereotype.Component; import java.time.LocalDateTime; import java.time.ZoneId; Component public class TimeTools { Tool(description 获取当前日期和时间用于回答时间相关问题) public String getCurrentTime() { return LocalDateTime.now() .atZone(ZoneId.systemDefault()) .toString(); } }package com.example.ai.tool; import org.springframework.ai.tool.annotation.Tool; import org.springframework.ai.tool.annotation.ToolParam; import org.springframework.stereotype.Component; Component public class CalcTools { Tool(description 计算两个整数相加返回结果) public String add( ToolParam(description 第一个整数) int a, ToolParam(description 第二个整数) int b) { return 计算结果: (a b); } }工具定义好后关键是注册ToolCallbackProvider。Spring AI 提供了MethodToolCallbackProvider可以把带Tool的 Bean 转成工具回调。在配置类里注册这样ChatClient就能拿到工具列表。package com.example.ai.config; import com.example.ai.tool.CalcTools; import com.example.ai.tool.TimeTools; import org.springframework.ai.tool.ToolCallbackProvider; import org.springframework.ai.tool.method.MethodToolCallbackProvider; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; Configuration public class ToolConfig { Bean public ToolCallbackProvider toolCallbackProvider(TimeTools timeTools, CalcTools calcTools) { return MethodToolCallbackProvider.builder() .toolObjects(timeTools, calcTools) .build(); } }最后是 Controller注入ChatClient.Builder和ToolCallbackProvider在调用时把工具传进去。注意.tools(toolCallbackProvider)这一步它让模型知道有哪些工具可用。package com.example.ai.controller; import org.springframework.ai.chat.client.ChatClient; import org.springframework.ai.tool.ToolCallbackProvider; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; RestController public class ToolCallingController { private final ChatClient chatClient; private final ToolCallbackProvider toolCallbackProvider; public ToolCallingController(ChatClient.Builder builder, ToolCallbackProvider toolCallbackProvider) { this.chatClient builder.build(); this.toolCallbackProvider toolCallbackProvider; } GetMapping(/ai) public String callTool(RequestParam String msg) { return chatClient.prompt(msg) .tools(toolCallbackProvider) .call() .content(); } }到这里配置和代码就齐了。三个关键点再确认一遍base-url是https://taotoken.net/apiapi-key是你的 TaoToken Keymodel是支持工具调用的模型 ID。ToolCallbackProvider把Tool方法注册进去Controller 调用时通过.tools()传给模型。下一节验证请求。4. 验证请求curl 触发模型决策与 Java 方法回填启动 Spring Boot 应用看到Tomcat started on port 8080就说明起来了。然后开一个终端用 curl 发请求。第一个测试问时间模型应该会决定调用getCurrentTime工具。curl http://localhost:8080/ai?msg现在几点了预期返回类似现在是 2025-01-15T14:30:22.12308:00。注意这个时间不是模型编的而是你的 Java 方法LocalDateTime.now()返回的模型只是把工具结果组织成了自然语言。这就是闭环跑通的标志。第二个测试问加法模型应该调用add工具参数从问题里抽取。curl http://localhost:8080/ai?msg帮我算一下 123 加 456 等于多少预期返回123 加 456 等于 579。这里的 579 是 Java 方法算出来的不是模型心算的。你可以故意问一个模型容易算错的数比如98765 加 12345如果返回111110说明工具确实被调用了。第三个测试混合场景验证模型能在多个工具间选择。curl http://localhost:8080/ai?msg现在几点另外 10 加 20 等于多少预期返回会同时包含时间和加法结果模型可能调用两个工具也可能分步调用。如果只返回了时间没算加法说明模型对多工具编排还不稳定可以换一个工具调用能力更强的模型再试。验证成功的核心信号有三个第一返回内容里包含只有 Java 方法才能产生的数据比如精确到毫秒的当前时间第二应用日志里能看到工具被调用的记录可以在Tool方法里加一行log.info第三把api-key改错请求会报 401说明请求确实走了 TaoToken 通道。如果想让验证更直观可以在TimeTools.getCurrentTime()里加日志Tool(description 获取当前日期和时间用于回答时间相关问题) public String getCurrentTime() { System.out.println( 工具被调用: getCurrentTime); return LocalDateTime.now() .atZone(ZoneId.systemDefault()) .toString(); }再次 curl控制台打印 工具被调用: getCurrentTime就百分百确认模型决策触发了 Java 方法。这个日志技巧在排查“模型到底有没有调工具”时特别有用。5. 本篇常见错排查401、local proxy failed 与 choices 解析跑不通的时候大部分问题集中在几个固定报错上。下面按真实报错对照排查。401 Unauthorized。返回体里通常是{error:{message:Invalid API key,type:invalid_request_error}}。原因就两个api-key填错或者 Key 被禁用/额度用完。检查application.yml里的api-key是不是完整的sk-开头字符串有没有多余空格。如果确认 Key 没问题去 TaoToken 控制台看下额度状态。注意不要用base-url去填 Key两者别搞混。Connection refused 或 local proxy failed。报错类似java.net.ConnectException: Connection refused或local proxy failed。这通常是base-url写错比如写成了https://taotoken.net/api/v1导致请求路径变成/api/v1/v1/chat/completions。正确写法是https://taotoken.net/apiSpring AI 会自动补/v1/chat/completions。另外检查本机网络是否能正常访问该地址可以用curl https://taotoken.net/api/v1/models -H Authorization: Bearer sk-xxx先测通道。模型不调用工具直接回答。返回内容看起来正常但 Java 方法没执行。原因通常是模型不支持 function calling或者tools没传进去。先确认model是gpt-4o、claude-3-5-sonnet这类支持工具调用的。再检查 Controller 里有没有.tools(toolCallbackProvider)漏了这行模型就不知道有工具。还有一种情况是Tool的description写得太模糊模型判断不需要调用把描述写具体一点。读取 choices 报错。报错类似Cannot deserialize value of type ... from Object value (token JsonToken.START_OBJECT)或Error reading choices。这通常是通道返回的 JSON 结构和 Spring AI 预期不一致常见于base-url指向了非标准 OpenAI 格式的地址。确认base-url是https://taotoken.net/api并且模型 ID 拼写正确。如果模型 ID 不存在有些通道会返回错误结构也会导致解析失败。OAuth 或鉴权头冲突。如果你同时配了其他鉴权方式可能出现OAuth token相关报错。Spring AI 的 OpenAI Starter 默认用Authorization: Bearer {api-key}不要再额外配spring.ai.openai.oauth之类的东西。把application.yml精简到只有api-key、base-url、model三项排除干扰。排查顺序建议先 curl 测通道通不通再看应用日志里请求有没有发出去最后看模型返回里有没有tool_calls。把logging.level.org.springframework.aiDEBUG打开能看到完整的请求和响应体定位问题最快。6. 从本地 Tool 到 MCP下一步怎么走本地 Tool Calling 跑通后你已经掌握了 AI Agent 最核心的闭环模型决策、工具执行、结果回填。接下来如果要做跨应用、跨语言的工具复用就可以往 MCP 方向迁移。MCP 把工具定义标准化成 Server任何支持 MCP 的客户端包括 Claude Code、Cline 等都能调用不再局限于你的 Spring Boot 进程内。迁移思路是把现在Tool方法里的逻辑抽成一个独立的 MCP Server用 stdio 或 HTTP 暴露工具列表Spring Boot 这边作为 MCP Client 去连接。这样你的 Java 业务逻辑可以同时被多个 AI 客户端复用。不过在迁移之前建议先把本地 Tool 的description和参数校验打磨好因为 MCP 的工具描述质量直接决定模型选工具的准确率。如果你打算长期做编码类 Agent或者需要把工具链接到 CI 流程里可以了解下 Coding Plan 的用法https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding-planutm_campaignrewrite 。它更适合持续性的编码和 Agent 场景和单次 API 调用是互补的。最后给一个实用建议把ToolCallbackProvider的注册和ChatClient的构建分开工具注册用配置类调用用 Controller这样新增工具时只改配置类Controller 不用动。工具多了以后可以按业务域拆成多个ToolCallbackProvider调用时按需传入避免一次给模型太多工具导致选择困难。这套结构在本地跑通后迁移到 MCP 也只是换个 Provider 实现的事。