Spring AI 2.0实战:Java多模型、Tools、MCP与Agent完整打通指南

发布时间:2026/8/31 15:57:42
Spring AI 2.0实战:Java多模型、Tools、MCP与Agent完整打通指南 最近在整理 Spring AI 实战时发现很多同学已经不是卡在“模型能不能打通”而是卡在更麻烦的环节Tools 注册了但模型就是不调用MCP 服务接上了但 Agent 不会选工具Skills 文档一大堆却不知道和自己的业务怎么结合。这篇文章把 Java Spring AI 2.0 从多模型、Tools、MCP、Skills 到 Agent 完整串一遍直接用订单客服场景做例子适合有 Java 和 Spring Boot 基础的开发者也适合刚学完 Spring AI 基础、想一步到位做 Agent 项目的同学。我会按“概念 → 环境 → 多模型 → Tools → MCP → Skills → Agent → 排错 → 实践建议”的顺序展开。每一步都会给出可复制的代码和配置还会说明为什么这样做。文章最后会整理一份高频报错对照表和工程化建议帮你少走弯路。1. 背景与核心概念1.1 为什么需要 Spring AI过去我们要在 Java 项目里接入大模型通常是自己封装 HTTP 请求、处理 SSE 流式响应、拼接 Prompt、解析 JSON。一旦项目里有多个模型或者需要让模型调用外部工具代码就会变得非常零散。Spring AI 的出现就是把这些重复工作沉淀为统一封装提供了类似 Spring 风格的 Bean 管理、流式响应、结构化输出、Prompt 模板、工具调用和 MCP 集成。它的定位类似 Java 生态里的 LangChain不直接绑定某个大模型厂商而是通过统一的ChatModel、ChatClient等抽象层把 OpenAI、通义千问、豆包、Ollama 本地模型等接入方式收敛成一套代码。切换到不同模型时业务代码改动很小只需要改配置或调整注入的 Bean。对于后端团队来说这意味着团队里的 Java 开发者不需要深入了解每个厂商的 SDK也能快速把 AI 能力集成到业务系统。1.2 几个容易混淆的概念很多人刚接触时会被 Tools、MCP、Skills、Agent 这些词绕晕我先把它们用大白话解释一遍Tools工具普通方法变成了能被模型调用的“函数”。比如queryOrder()查询订单状态模型只需要生成一个带有参数的工具调用指令Spring AI 会帮你执行这个方法再把结果回传给模型继续生成回答。MCPModel Context Protocol一种开放协议用来规范“模型如何通过统一方式访问外部工具和数据源”。MCP 把中间件演进成 Server/Client 模式Spring AI 可以通过 MCP Client 把远端的工具接入当前应用也可以把当前应用的工具暴露成 MCP Server 给其他项目使用。Skills技能偏业务侧的可复用能力封装通常由一段系统提示词、工具列表、示例和约束条件组成。一个技能解决一类任务比如“订单查询技能”“客服话术技能”。Agent智能体一个能自主规划、调用工具、处理多步任务的运行机制。Agent 在一个循环里不断判断“下一步该做什么”直到完成用户任务。它们的关系可以这样理解MCP 是工具接入的一种标准化方式Tools 是 Agent 的“手”Skills 是 Agent 的“操作手册”Agent 本身是决策和执行的“大脑”。1.3 典型架构一个基于 Spring AI 2.0 的 Agent 服务通常会这样分层入口层Controller 接收用户请求调用 ChatClient。编排层ChatClient 负责模型选择、内存管理、工具注册。工具层Spring Bean 中的 Tool 方法或通过 MCP 接入的远程工具。模型层ChatModel 抽象连接不同大模型。基础设施层配置中心、日志、监控、权限控制。这套结构和传统 Web 服务很像最大的区别是业务流不再完全由代码写死而是由模型根据用户输入动态决策。因此工具定义的质量、 Prompt 的排版、内存上下文的长度都会直接影响 Agent 的最终效果。2. 环境准备与项目初始化2.1 环境版本说明在开始写代码之前先确认本地环境。Spring AI 2.x 要求 JDK 17 及以上实际项目建议使用 JDK 21 或更高版本因为虚拟线程、记录类型等特性会让代码更简洁。构建工具方面Maven 3.8 或 Gradle 7.6 都可以本文以 Maven 为例。模型方面你有三种选择使用 OpenAI 兼容接口的云厂商 API。使用阿里云百炼等国内模型服务。使用 Ollama 在本地跑开源模型适合学习和调试。由于 Spring AI 版本更新比较快下面代码中的版本号请根据你实际引入的版本调整。我会重点演示配置思路而不是把不确定的版本写死。建议你在创建项目时从官方文档获取当前最新的 BOM 版本。2.2 Maven 项目初始化先创建一个基础的 Spring Boot 工程。为了同时演示多模型接入我会在 pom.xml 里引入 Open AI 和 Ollama 两个 starter。如果实际项目只需要一个模型保留对应的依赖即可。parent groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-parent/artifactId version${spring-boot.version}/version relativePath/ /parent properties java.version21/java.version spring-ai.version你的Spring AI版本/spring-ai.version /properties dependencyManagement dependencies dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-bom/artifactId version${spring-ai.version}/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-openai/artifactId /dependency dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-starter-ollama/artifactId /dependency dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-test/artifactId scopetest/scope /dependency /dependencies这段配置里最关键的是spring-ai-bom它统一管理所有 Spring AI 组件的版本避免因为子模块版本不一致导致兼容性报错。在后续加入 MCP、Skills 相关依赖时也不用手动写版本号。2.3 配置文件在application.yml中配置多模型相关参数spring: application: name: spring-ai-practice ai: openai: base-url: ${OPENAI_BASE_URL:https://api.openai.com} api-key: ${OPENAI_API_KEY:sk-请换成你的Key} chat: options: model: ${OPENAI_MODEL:gpt-4o-mini} temperature: 0.7 ollama: base-url: http://localhost:11434 chat: options: model: ${OLLAMA_MODEL:qwen2.5} temperature: 0.3建议把 API Key 放在环境变量里而不是直接写到配置文件中。base-url之所以也做成可配置是因为很多企业私有化部署的模型服务都兼容 OpenAI 接口只需要替换地址就能切换。如果你使用 Ollama需要先执行ollama pull qwen2.5拉取模型然后启动服务。本地模型的好处是调试时不需要消耗云端 token适合频繁测试工具调用和 Agent 逻辑。3. 多模型接入与切换3.1 同时引入多个模型Spring AI 中一旦引入了多个模型 starter容器里就会存在多个ChatModel实现类。默认情况下直接注入ChatModel会报“存在多个 Bean”的异常。解决方式有两种使用Qualifier指定具体模型 Bean。为每个模型创建独立的ChatClientBean。在实际业务中我更喜欢用第二种方式因为ChatClient可以把模型、系统提示词、默认工具聚合在一起后续使用非常方便。3.2 编写多模型配置下面我们创建一个配置类分别生成 OpenAI 和 Ollama 两个ChatClient// 文件路径src/main/java/com/example/springai/config/ChatModelConfig.java package com.example.springai.config; import org.springframework.ai.chat.client.ChatClient; import org.springframework.ai.openai.OpenAiChatOptions; import org.springframework.ai.openai.OpenAiChatModel; import org.springframework.ai.ollama.OllamaChatOptions; import org.springframework.ai.ollama.OllamaChatModel; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; Configuration public class ChatModelConfig { Bean public ChatClient openAiChatClient(Qualifier(openAiChatModel) OpenAiChatModel openAiModel) { return ChatClient.builder(openAiModel) .defaultOptions(OpenAiChatOptions.builder() .model(gpt-4o-mini) .temperature(0.7) .build()) .build(); } Bean public ChatClient ollamaChatClient(Qualifier(ollamaChatModel) OllamaChatModel ollamaModel) { return ChatClient.builder(ollamaModel) .defaultOptions(OllamaChatOptions.builder() .model(qwen2.5) .temperature(0.3) .build()) .build(); } }这里有两个细节值得注意。第一ChatClient是线程安全的通常一个模型只需要创建一个 Bean而不是在每次请求时 new 一个。第二通过defaultOptions可以把模型名称、温度、最大 token 数等参数固化的ChatClient中。这样你在使用它时就不需要每次 Prompt 里都带一大堆模型参数。3.3 运行时切换模型有了多个ChatClientBean 之后你可以在 Service 层通过构造器注入的方式把它们都拿到再根据业务规则决定使用哪个模型// 文件路径src/main/java/com/example/springai/service/ModelSwitchService.java package com.example.springai.service; import org.springframework.ai.chat.client.ChatClient; import org.springframework.stereotype.Service; Service public class ModelSwitchService { private final ChatClient openAiChatClient; private final ChatClient ollamaChatClient; public ModelSwitchService(ChatClient openAiChatClient, ChatClient ollamaChatClient) { this.openAiChatClient openAiChatClient; this.ollamaChatClient ollamaChatClient; } public String chat(String modelType, String userMessage) { ChatClient targetClient local.equals(modelType) ? ollamaChatClient : openAiChatClient; return targetClient.prompt() .user(userMessage) .call() .content(); } }多模型的切换策略有很多种。最简单的是让调用方通过请求参数指定更高级的做法是根据任务复杂度和成本预算路由比如简单问答走 OllaMa复杂推理走 GPT-4o。在 Spring AI 中由于底层抽象统一切换逻辑只需要面对ChatClient接口模型厂商的差异被完全隔离。4. Tools让模型学会调用外部函数4.1 Tool 方法定义Spring AI 支持通过注解方式把普通 Spring Bean 方法暴露给模型。订单查询服务就是一个典型的例子用户问“我的订单什么时候到”如果模型只靠自身知识无法知道真实物流状态所以必须调用工具。// 文件路径src/main/java/com/example/springai/tool/OrderToolService.java package com.example.springai.tool; import org.springframework.ai.tool.annotation.Tool; import org.springframework.ai.tool.annotation.ToolParam; import org.springframework.stereotype.Component; import java.time.LocalDate; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; Component public class OrderToolService { private static final MapString, String ORDER_STATUS new ConcurrentHashMap(); static { ORDER_STATUS.put(2026010801, 已发货预计2026-01-12送达); ORDER_STATUS.put(2026010802, 已签收签收时间2026-01-10); } Tool(name queryOrderInfo, description 根据订单号查询订单最新状态) public String queryOrderInfo( ToolParam(description 订单号) String orderId) { return ORDER_STATUS.getOrDefault(orderId, 未查到该订单); } Tool(name cancelOrder, description 根据订单号取消未发货订单) public String cancelOrder( ToolParam(description 订单号) String orderId) { if (未发货.equals(ORDER_STATUS.get(orderId))) { ORDER_STATUS.put(orderId, 已取消); return 订单 orderId 已取消; } return 订单 orderId 当前状态不可取消; } }Tool注解中的name和description非常重要。模型根据方法名和描述来判断什么时候调用这个工具以及需要传什么参数。如果描述写得太含糊模型可能在该调用时选择不调用或者用错参数。ToolParam描述会作为参数的说明同样会影响模型生成的参数质量。4.2 注册工具让模型能够调用上面的工具只需要把OrderToolService注册到ChatClient中// 文件路径src/main/java/com/example/springai/service/OrderAssistantService.java package com.example.springai.service; import com.example.springai.tool.OrderToolService; import org.springframework.ai.chat.client.ChatClient; import org.springframework.stereotype.Service; Service public class OrderAssistantService { private final ChatClient orderChatClient; public OrderAssistantService(ChatClient.Builder builder, OrderToolService orderToolService) { this.orderChatClient builder .defaultSystem(你是电商订单助手。当用户询问订单状态时必须先调用 queryOrderInfo 工具不要使用工具前猜测订单状态。) .defaultTools(orderToolService) .build(); } public String chat(String userMessage) { return orderChatClient.prompt() .user(userMessage) .call() .content(); } }defaultTools可以传入一个或多个对象Spring AI 会自动扫描对象里所有带有Tool注解的方法。使用defaultSystem设置系统提示词后模型会更清楚自己的职责边界和调用工具的时机。4.3 调用流程这里我简单拆解一下调用链路用户输入“帮我看看订单 2026010801 到哪了”。模型收到 Prompt 后判断需要调用queryOrderInfo工具。Spring AI 框架拦截到模型返回的 Tool Call 请求在 Java 侧执行queryOrderInfo方法。执行结果以消息形式继续返回给模型。模型根据工具结果生成最终自然语言回答。这个过程对业务代码是透明的你只需要关心工具方法本身是否正确。建议一开始不要直接做成复杂的 Agent先把一个简单工具跑通再逐步叠加其他工具。5. MCP标准化接入外部工具与数据5.1 MCP 协议基础MCP 的全称是 Model Context Protocol它把工具和数据源抽象成“MCP Server”。Spring AI 项目可以作为 MCP Client 连接远端工具也可以作为 MCP Server 把自己已有的 Bean 方法暴露出去。MCP 的核心价值是标准化。以前每个外部系统都要写单独的对接代码API 风格五花八门现在只要外部系统遵循 MCP 协议集成方就能用统一方式接入。Spring AI 会把 MCP Server 上声明的工具封装成内部的ToolCallback最终和本地 Tools 一样注册给ChatClient。5.2 MCP Server 案例假设我们希望把订单查询服务暴露成 MCP Server给其他团队或 Agent 使用。首先引入 MCP Server 依赖dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-starter-mcp-server/artifactId /dependency然后在配置中启用 MCP Serverspring: ai: mcp: server: enabled: true name: order-mcp-server你不需要额外定义协议层代码原来带有Tool注解的 Spring Bean 会被自动注册到 MCP Server 中。比如上面写过的OrderToolService就可以原样复用。需要注意如果希望暴露给外部系统的工具和方法内部使用不一样可以单独定义一个只用于 MCP 暴露的工具类避免把内部敏感方法直接变成远端工具。5.3 MCP Client 接入如果我们要在另一个 Spring AI 项目中调用远端 MCP Server 上的工具需要引入 MCP Client 依赖dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-starter-mcp-client/artifactId /dependency在配置中声明连接信息spring: ai: mcp: client: enabled: true connections: - name: order-mcp-server url: http://localhost:8081/sse接入后工具列表会出现在容器里。把它们注入到 ChatClient 中// 文件路径src/main/java/com/example/springai/config/McpToolConfig.java package com.example.springai.config; import org.springframework.ai.chat.client.ChatClient; import org.springframework.ai.tool.ToolCallback; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import java.util.List; Configuration public class McpToolConfig { Bean public ChatClient mcpChatClient(ChatClient.Builder builder, ListToolCallback toolCallbacks) { return builder .defaultSystem(你是智能助手可以通过工具获取订单信息。) .defaultToolCallbacks(toolCallbacks) .build(); } }这里将容器中所有ToolCallback都注册进 ChatClient既包含本地Tool方法生成的 ToolCallback也包含 MCP Client 包装出来的远端工具。配置文件中具体的connections结构可能随版本调整建议以你使用的 Spring AI 版本官方文档为准。6. Skills沉淀可复用的技能模板6.1 Skill 的本质Skills 不是一个复杂的新框架而是一种组织方式把完成某类任务所需的系统提示词、少量示例、工具列表和约束条件打包在一起用一个明确的名称暴露给上层使用。你可以把它理解为“可复用的 Prompt 模板 工具集”。Skills 和普通 Prompt 模板的最大区别是Skills 更强调领域完整性和复用性。比如订单客服技能不仅包含“你是客服”这句话还会包含工具调用规则、拒绝回答的边界、回复语气、常见示例等。同一个技能可以在不同 ChatClient 中复用。6.2 创建技能模板在src/main/resources/skills/order-assistant/system-prompt.st中写入以下内容你是一位电商订单客服助手负责回答用户关于订单状态、物流、退换货的问题。 请遵循以下规则 1. 当用户询问订单状态时必须先调用 queryOrderInfo 工具。 2. 如果工具返回“未查到该订单”请直接告诉用户订单不存在不要编造状态。 3. 当用户要求取消订单时必须先调用 queryOrderInfo 确认订单是否可以取消。 4. 默认使用简洁、友好的中文回复不要使用表情符号。 5. 如果用户询问与订单无关的话题请礼貌说明你的职责范围。 常见表达示例 用户我的订单到哪里了 助手请提供订单号我来帮你查询。 用户订单号是 2026010801。 助手好的我查一下。Spring AI 的 Prompt 模板支持${变量}占位符和正常文本内容。把这段内容放在资源目录下可以通过Value注入资源路径然后加载成字符串。6.3 在 ChatClient 中应用在代码中加载这个技能模板并注册相关工具// 文件路径src/main/java/com/example/springai/service/OrderSkillService.java package com.example.springai.service; import com.example.springai.tool.OrderToolService; import org.springframework.ai.chat.client.ChatClient; import org.springframework.beans.factory.annotation.Value; import org.springframework.core.io.Resource; import org.springframework.stereotype.Service; import org.springframework.util.StreamUtils; import java.nio.charset.StandardCharsets; Service public class OrderSkillService { private final ChatClient skillChatClient; public OrderSkillService(ChatClient.Builder builder, OrderToolService orderToolService, Value(classpath:skills/order-assistant/system-prompt.st) Resource systemPromptResource) throws Exception { String systemPrompt StreamUtils.copyToString(systemPromptResource.getInputStream(), StandardCharsets.UTF_8); this.skillChatClient builder .defaultSystem(systemPrompt) .defaultTools(orderToolService) .build(); } public String chat(String userMessage) { return skillChatClient.prompt() .user(userMessage) .call() .content(); } }把一个复杂的系统提示词放到独立的.st文件中比直接拼接在 Java 代码里更清晰运维同学可以微调话术测试同学可以单独评审 Prompt 内容代码里也不用处理大段字符串。如果你的技能还需要支持变量可以通过PromptTemplate动态替换。7. Agent 实战把工具串成智能助手7.1 Agent 的运行原理前面几步其实已经完成了 Agent 的绝大部分基础能力模型可以调用工具工具可以返回真实数据系统提示词定义了规则。把这几者放进一个循环里就构成了最基本的 Agent。实际运行中Agent 不是一锤子买卖。第一次模型可能返回一个 Tool Call框架执行工具后需要把结果再次交给模型模型继续决定是结束还是调用下一个工具。Spring AI 会自动处理这个循环所以开发者只需要把工具和内存配置好。7.2 完整代码下面实现一个带记忆的订单客服 Agent。为了让 Agent 能保持多轮对话上下文我们引入ChatMemory// 文件路径src/main/java/com/example/springai/config/AgentConfig.java package com.example.springai.config; import org.springframework.ai.chat.memory.ChatMemory; import org.springframework.ai.chat.memory.InMemoryChatMemory; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; Configuration public class AgentConfig { Bean public ChatMemory chatMemory() { return new InMemoryChatMemory(); } }然后创建 Agent 服务类// 文件路径src/main/java/com/example/springai/service/OrderAgentService.java package com.example.springai.service; import com.example.springai.tool.OrderToolService; import org.springframework.ai.chat.client.ChatClient; import org.springframework.ai.chat.client.advisor.MessageChatMemoryAdvisor; import org.springframework.ai.chat.memory.ChatMemory; import org.springframework.stereotype.Service; Service public class OrderAgentService { private final ChatClient agentChatClient; public OrderAgentService(ChatClient.Builder builder, ChatMemory chatMemory, OrderToolService orderToolService) { this.agentChatClient builder .defaultSystem( 你是电商平台智能客服 Agent。 你可以使用工具查询订单、取消订单。 请根据用户的真实意图自主决定是否需要调用工具。 用户没有提供订单号时必须先询问订单号。 ) .defaultTools(orderToolService) .defaultAdvisors(new MessageChatMemoryAdvisor(chatMemory)) .build(); } public String chat(String userMessage) { return agentChatClient.prompt() .user(userMessage) .call() .content(); } }注意MessageChatMemoryAdvisor会把历史消息自动拼接到 Prompt 中这样用户第二句说“那取消它”时Agent 才知道“它”指的是哪个订单。7.3 运行与验证写一个简单的 Controller// 文件路径src/main/java/com/example/springai/controller/AgentController.java package com.example.springai.controller; import com.example.springai.service.OrderAgentService; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RestController; import java.util.Map; RestController public class AgentController { private final OrderAgentService orderAgentService; public AgentController(OrderAgentService orderAgentService) { this.orderAgentService orderAgentService; } PostMapping(/agent/chat) public MapString, String chat(RequestBody MapString, String request) { String reply orderAgentService.chat(request.get(message)); return Map.of(reply, reply); } }用 curl 测试curl -X POST http://localhost:8080/agent/chat \ -H Content-Type: application/json \ -d {message: 帮我查一下订单 2026010801 到哪了}预期输出类似{reply:订单 2026010801 已发货预计 2026-01-12 送达。}如果 Agent 能在一轮内自动调用工具并返回正确答案说明你已经把 Spring AI 的 Tools、Skills、Agent 链路打通了。接下来可以继续叠加 MCP 工具、RAG 知识库或者接入更多业务 API。8. 常见问题与排查思路8.1 高频错误对照表问题现象常见原因解决思路启动时报 bean 冲突引入了多个模型 starter没有用 Qualifier 区分为每个模型创建 ChatClient Bean不要直接注入 ChatModel模型调工具后回答“我不知道”工具没有正确注册到 ChatClient检查 defaultTools 是否传入工具实例确认 Tool 注解生效工具方法执行报参数类型错误模型生成的参数类型与工具方法不匹配工具参数尽量使用 String 或简单类型把复杂对象拆开传MCP 工具列表为空MCP Server 未启动或连接配置写错先单独测试 MCP Server 地址确认能访问后再接 Client内存溢出Insufficient memoryJVM 堆太小或并发请求过多调整 -Xmx 参数限制 Agent 上下文长度控制并发Agent 对话串轮次没有配置 ChatMemory添加 MessageChatMemoryAdvisor多轮对话时上下文过长历史消息全部塞进 Prompt设置窗口大小或摘要策略必要时裁剪旧消息模型返回格式不稳定依赖 Prompt 描述不够精确使用结构化输出或在后端做格式校验与重试8.2 排查 Checklist如果你遇到线上或本地问题可以按下面顺序排查先确认模型调用本身通用最简单的chatClient.prompt().user(你好).call()排除 Prompt 和工具问题。再看工具是否注册成功在日志里搜索 Tool Callback 相关输出。然后看工具执行结果是否正确可以在工具方法第一行打印入参。最后再看 Agent 循环确认模型是否在工具结果返回后继续生成最终回答。需要特别注意的是模型调用工具带有随机性。同一个 Prompt 可能这次调用工具、下次不调用因此不要让代码强依赖模型的某一次决策而是通过系统提示词、工具描述和参数约束来提高稳定性。9. 最佳实践与工程化建议9.1 配置与密钥管理API Key 绝对不能写死在代码或前端页面上。推荐使用环境变量、配置中心或密钥管理服务。不同环境使用不同模型也很常见开发环境用 Ollama 本地模型测试环境用低成本的云模型生产环境再切换到高精度模型。所有模型名称和 base-url 都应该做成可配置项避免发版时改代码。9.2 工具与 MCP 安全工具方法一旦暴露给模型相当于给你无法完全控制的输入源打开了执行入口。因此工具方法内部必须做权限校验、参数校验和操作审计。尤其是取消订单、删除数据、修改配置这类写操作建议让模型先返回“待确认”结果再由用户通过单独接口二次确认不要在 Agent 内部直接执行高风险操作。9.3 成本与性能模型调用费用和 Token 消耗是 Agent 项目最大的隐性成本。可以采取以下措施限制历史消息长度避免无限增长。对工具返回的大段数据做截断只保留必要的 JSON 字段。给复杂任务设置最大迭代次数防止模型不断循环调用工具。使用缓存对相同用户相同问题的结果做短时间缓存。监控每个请求的 Token 使用量建立成本告警。9.4 可观测性与测试Agent 的调试比传统接口困难因为每个请求的路径都可能不同。建议在日志中输出完整的调用链用户输入、模型原始输出、工具调用参数、工具执行结果、最终回答。这样即使出问题也能快速定位是哪一步决策错了。测试方面除了单元测试工具方法还可以针对典型 Prompt 做回归测试。固定模型版本和参数后写一组“必须调用工具”“不得调用工具”“工具返回空结果”的用例保证后续修改 Prompt 时不会破坏核心行为。10. 总结与下一步学习建议这篇文章已经完整拆解了 Java Spring AI 2.0 的实战链路多模型接入通过ChatClient统一管理Tools 用Tool注解暴露业务方法MCP 把远端工具标准化接入Skills 将提示词和工具组合成可复用技能Agent 则把这一切放进自主决策循环中。如果你是从零开始学建议不要急着把所有功能都堆在一起。先把最基础的多模型跑通再写一个本地工具然后尝试用 MCP 连接远端服务最后再组装 Agent。只要每一步都亲手跑一遍你会对“模型什么时候调用工具”“工具结果怎么影响回答”有更直接的体感。下一步可以继续研究 Spring AI 的 RAG 能力、结构化输出、流式聊天接口以及如何把 Agent 接入企业知识库。对大多数项目来说工具和知识的质量比模型本身的选择更重要。建议你把文中的订单 Agent 跑通后换成自己业务里的查询、写入或消息发送工具看看模型在真实场景下会怎么决策。这种调试经验比单纯背 Agent 概念要有用得多。