Spring AI 2.0 多 Agent 编程实战
从 MCP 协议到 ToolCallingAdvisor:构建可编排、可观测的多 Agent 协作系统
2026年6月12日,Spring AI 2.0.0 GA 正式发布。与 1.x 相比,这不仅仅是一次依赖升级,而是一次架构层面的重构:工具调用循环从各 Chat Model 内部提升到 Advisor 链中成为一等公民,MCP 注解从社区孵化项目并入核心模块,ToolSearchToolCallingAdvisor 让上千个工具的动态发现成为可能。
对 Java 技术栈的开发者来说,这意味着:构建生产级多 Agent 系统不再依赖 Python 生态的 LangChain 或 CrewAI——用 Spring Boot 4 + Spring AI 2.0,你可以在一个熟悉的 DI/IoC 模型里,写出类型安全、可测试、可观测的 Agentic 应用。
本文将从"IDE"的视角切入——不是 Visual Studio Code 或 IntelliJ 里的那个 IDE,而是 Agent Development Environment(多 Agent 编排开发环境)——逐一拆解 Spring AI 2.0 如何把每个核心特性变成可组合的积木,并用可运行的代码块串联起一个完整的多 Agent 协作工作流。
🎯 本文目标读者:有 Spring Boot 使用经验的 Java 开发者,想了解如何在 Spring 生态里构建 AI Agent 系统。阅读完本文后你将可以:用 @Tool 定义工具、用 ToolCallingAdvisor 观测调用链路、用 MCP 协议跨服务调用工具、用 Agentic Patterns 编排多 Agent 工作流。
一、架构全景:Spring AI 2.0 的 Agent 编程模型
先看一张心智模型图。Spring AI 2.0 把一次 LLM 调用抽象为一个通过 Advisor 链的请求:
用户请求 → ChatClient → Advisor Chain → Chat Model → 响应
MemoryAdvisor → SafeGuardAdvisor → ToolCallingAdvisor → OutputValidator →
⬆︎ 每个 Advisor 都可以拦截请求、修改 prompt、注入上下文、执行递归循环
这与 Spring Web 的 Filter Chain 或 Spring Security 的过滤器链是同构的设计思想。但关键差异在于:Advisor Chain 支持递归循环。这意味着 ToolCallingAdvisor 可以在一次请求中反复执行"发提示 → 模型请求工具 → 执行工具 → 将结果返回模型 → 模型再请求工具"的循环,直到模型认为它有了足够的信息来给出最终答案。
🧱
ChatClient
统一的入口 API,替代 1.x 中每个模型自行管理的调用逻辑。所有请求都通过 Advisor 链处理。
🔗
Advisor Chain
可插拔的拦截器链,Memory、Tool Calling、Guardrails、Output Validation 都是 Advisor。
🔧
@Tool 注解
在任意 Spring Bean 方法上加 @Tool 即注册为可调用工具,框架自动生成 JSON Schema。
🌐
MCP 协议
Model Context Protocol:跨进程、跨语言的工具调用标准协议。注解驱动,无需手写传输层。
⚡ 与 1.x 的关键区别:在 1.x 中,每个 Chat Model 内部有自己的私有工具调用循环,无法拦截、无法观测、无法替换执行策略。2.0 将工具调用循环从模型中"提升"到 Advisor 链中,使其成为可组合的一等组件。这一点是理解整个 2.0 Agent 模型的基础。
二、上手:一个 50 行的 Tool-Calling Agent
从零开始。创建一个 Spring Boot 4.0 项目,依赖只需要两个:
<!-- pom.xml -->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-bom</artifactId>
<version>2.0.0</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-model-openai</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
2.1 定义工具:@Tool 注解
定义一个查询订单的工具类:
package com.example.agent.tools;
import org.springframework.ai.tool.annotation.Tool;
import org.springframework.ai.tool.annotation.ToolParam;
import org.springframework.stereotype.Component;
@Component
public class OrderTools {
@Tool(description = "查询指定订单的当前状态")
public OrderStatus getOrderStatus(
@ToolParam(description = "订单ID,如 ORD-00123") String orderId
) {
// 实际项目中这里查询数据库或下游服务
return new OrderStatus(orderId, "SHIPPED", "2026-08-10");
}
@Tool(description = "查询客户最近N条订单记录")
public List<OrderSummary> listRecentOrders(
@ToolParam(description = "客户ID") String customerId,
@ToolParam(description = "返回数量上限,1-50", required = false) Integer limit
) {
int n = (limit != null && limit > 0) ? Math.min(limit, 50) : 10;
return orderRepo.findRecent(customerId, n);
}
public record OrderStatus(String orderId, String status, String eta) {}
public record OrderSummary(String orderId, String date, double total) {}
}
几个细节值得注意:
- description 是给模型看的——模型通过它决定何时调用哪个工具。把它写成清晰的功能说明,而非变量名注释。
- Java Record 可以直接作为返回类型——框架通过 Jackson 3 序列化为 JSON。
- @ToolParam(required = false) 会从 JSON Schema 中移除 required 约束,模型可以不传该参数。
2.2 组装 ChatClient
@RestController
public class AgentController {
private final ChatClient chatClient;
public AgentController(ChatClient.Builder builder, OrderTools orderTools) {
this.chatClient = builder
.defaultTools(orderTools) // 工具自动注册,框架生成 JSON Schema
.build();
}
@PostMapping("/api/chat")
public String chat(@RequestBody String message) {
return chatClient.prompt()
.user(message)
.call()
.content(); // 工具调用循环对业务代码完全透明
}
}
✅ 不到 50 行业务代码,你得到了什么?
① 模型自动判断何时需要调用工具(无需 if/else 路由)
② 工具执行的完整循环:模型选工具 → 执行 → 结果回传 → 模型继续思考 → 最终回答
③ 类型安全的工具定义,编译期检查参数类型
④ Spring DI 管理工具生命周期,测试时可直接 Mock
三、ToolCallingAdvisor:观测与控制工具调用链
基本工具调用只是起点。真正体现 Spring AI 2.0 架构优势的是 Advisor 的可组合性。你可以通过自定义 Advisor 来观测、拦截、甚至修改工具调用行为。
3.1 观测工具调用:构建 Claude-Style 调用指示器
用过 Claude 或 ChatGPT 的人都知道那个 "Calling tool…" 的动态指示器。在 Spring AI 2.0 中,要实现同样的效果,需要写一个 Advisor 坐到 ToolCallingAdvisor 前面来观察调用事件:
public class ToolCallObservingAdvisor implements CallAdvisor, StreamAdvisor {
private final Consumer<ToolCallEvent> eventSink;
public ToolCallObservingAdvisor(Consumer<ToolCallEvent> eventSink) {
this.eventSink = eventSink;
}
@Override
public int getOrder() {
// 排序在 ToolCallingAdvisor 之前,确保能看到每次循环
return ToolCallingAdvisor.DEFAULT_ORDER + 100;
}
@Override
public AdvisedResponse adviseCall(...) {
// 拦截 advise 调用链:当检测到工具调用时,发射事件
// 完整实现见 spring-ai-examples/advisors/ 仓库
}
}
// 在 Controller 中注入观测 Advisor:
chatClient.prompt()
.user(message)
.tools(orderTools)
.advisors(a -> a.advisors(new ToolCallObservingAdvisor(eventSink)))
.stream().content()
...
🔑 Advisor 排序是关键:getOrder() 返回的值决定了 Advisor 在链中的位置。ToolCallingAdvisor.DEFAULT_ORDER + 100 意味着你的 Advisor 在每次工具调用循环中都会被触发——而不仅仅是在请求的入口和出口。这就是"把工具调用循环从黑盒变为白盒"的核心机制。
3.2 扩展到百级工具:ToolSearchToolCallingAdvisor
当工具数量达到几十甚至上百个时,把所有工具的 Schema 都塞进每次请求的 system prompt 会导致:token 消耗巨大、模型选择工具的准确率下降。Spring AI 2.0 给出的方案是 渐进式工具披露:
// 用 ToolSearchToolCallingAdvisor 替代默认的 ToolCallingAdvisor
chatClient.prompt()
.user(message)
.advisors(a -> a.advisors(
ToolSearchToolCallingAdvisor.builder()
.toolCallbacks(allTools) // 全量工具注册到索引
.build()
))
.call()
.content();
它的工作方式是:首次请求时,将所有工具的 description 向量化存入内存索引;模型收到用户消息后,先用一次轻量的语义搜索找到相关的 3-5 个工具,只把这几个工具的 Schema 发给模型。实测中,这个方案可以将单次请求的 token 消耗降低 60-80%。
四、MCP 协议:跨服务工具调用的标准答案
当你需要让运行在 A 服务中的 Agent 调用部署在 B 服务中的工具时,Model Context Protocol(MCP) 就是为此设计的。Spring AI 2.0 将 MCP Java SDK 与注解驱动编程模型深度集成,使跨进程工具调用像本地调用一样简单。
4.1 注解驱动 MCP Server
将你的工具暴露为 MCP Server——只需在已有 @Tool 方法上添加 @McpTool 注解,再加上一个 starter 依赖:
// pom.xml 添加
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-mcp-server-webmvc</artifactId>
</dependency>
// application.properties
spring.ai.mcp.server.name=order-tools
spring.ai.mcp.server.version=1.0.0
spring.ai.mcp.server.protocol=STREAMABLE // 推荐,替代已废弃的 SSE
spring.ai.mcp.server.streamable-http.mcp-endpoint=/mcp
// OrderMcpTools.java —— 用 @McpTool 替代 @Tool
@Component
public class OrderMcpTools {
@McpTool(
name = "get_order_status",
description = "Returns the current status of an order"
)
public OrderStatus getOrderStatus(
@McpToolParam(description = "The order ID", required = true) String orderId,
McpSyncRequestContext context // 框架自动注入,不在 Schema 中
) {
context.info("Looking up order: " + orderId);
context.progress(p -> p.progress(0.5).total(1.0).message("Querying database..."));
return orderService.findById(orderId);
}
}
4.2 MCP Client:一行配置接入远端工具
在你的 Agent 服务中,只需在配置文件里声明远端 MCP Server 的地址:
// application.yml
spring:
ai:
mcp:
client:
streamable-http:
connections:
order-tools:
url: http://order-service:8081/mcp
inventory-tools:
url: http://inventory-service:8082/mcp
// AgentService.java —— 远端工具自动注入
@Service
public class AgentService {
private final ChatClient chatClient;
public AgentService(ChatClient.Builder builder,
McpToolCallbackProvider mcpToolProvider) {
this.chatClient = builder
.defaultTools(mcpToolProvider) // 所有 MCP Server 的工具自动注册
.build();
}
}
| 传输方式 | Starter | 适用场景 | 注意事项 |
|---|---|---|---|
| stdio | spring-ai-starter-mcp-server | 本地开发工具、Claude Code 集成 | 无法多用户/网络化部署 |
| Streamable HTTP (WebMVC) | spring-ai-starter-mcp-server-webmvc | 生产环境标准选择 | 需要 Web 容器 |
| Streamable HTTP (WebFlux) | spring-ai-starter-mcp-server-webflux | 高并发、响应式栈 | 调试难度更高 |
| SSE | 同上(配置 protocol=SSE) | 兼容旧客户端 | ⚠ MCP 规范已废弃,新项目不要用 |
⚠ 错误处理陷阱:工具方法中抛出的普通 RuntimeException 在早期版本中会直接终止 Agent 循环。正确的做法是抛出 ToolExecutionException,框架会将其消息序列化后返回给模型,让模型可以推理错误并决定下一步(重试、让用户澄清等)。异常消息要包含可操作的提示,而非堆栈信息。
五、编排多 Agent:五种 Agentic Pattern 的 Spring AI 实现
Anthropic 在《Building Effective Agents》研究报告中提出了五种工作流模式。Spring AI 官方文档对这五种模式给出了完整的实现参考。下面逐一拆解。
5.1 Chain Workflow(链式工作流)
适用场景:任务有明确的顺序步骤,每步输出是下一步的输入。
public class ChainWorkflow {
private final ChatClient chatClient;
public String chain(String userInput, String... prompts) {
String response = userInput;
for (String prompt : prompts) {
response = chatClient.prompt()
.user("{%s}\n {%s}".formatted(prompt, response))
.call().content();
}
return response;
}
}
// 示例:合同审查三步骤
String result = chainWorkflow.chain(contractText,
"Step 1: 识别所有风险条款",
"Step 2: 对每个风险条款给出修改建议",
"Step 3: 生成最终的风险评估报告"
);
5.2 Parallelization Workflow(并行工作流)
适用场景:大量独立子任务需要并行处理。
public class ParallelWorkflow {
public List<String> parallel(String instruction, List<String> inputs, int threads) {
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
return inputs.stream()
.map(input -> executor.submit(() ->
chatClient.prompt()
.user("{%s}\n {%s}".formatted(instruction, input))
.call().content()))
.map(Future::get)
.toList();
}
}
}
// 示例:同时对四个利益方做影响分析
List<String> results = parallelWorkflow.parallel(
"分析市场变化对该利益方的影响",
List.of("客户: ...", "员工: ...", "投资人: ...", "供应商: ..."),
4
);
5.3 Routing Workflow(路由工作流)
适用场景:不同输入类别需要不同专家处理。
public class RoutingWorkflow {
public String route(String input, Map<String, String> routes) {
var routing = chatClient.prompt()
.user("将以下输入分类到: " + routes.keySet() + "\n输入: " + input)
.call().content(); // 让模型做分类
return chatClient.prompt()
.system(routes.get(routing)) // 用对应的专家 System Prompt
.user(input)
.call().content();
}
}
5.4 Orchestrator-Workers(编排器-执行者)
适用场景:子任务无法提前预知,需要动态分解。
public class OrchestratorWorkersWorkflow {
public WorkerResponse process(String task) {
// Step 1: 编排器分析任务,动态生成子任务列表
var subtasks = chatClient.prompt()
.user("将任务分解为独立子任务: " + task)
.call().entity(Subtasks.class); // 结构化输出
// Step 2: Worker 并行执行每个子任务
var results = subtasks.items().stream().parallel()
.map(sub -> chatClient.prompt().user(sub).call().content())
.toList();
// Step 3: 汇总结果
return new WorkerResponse(subtasks.analysis(), results);
}
}
5.5 Evaluator-Optimizer(评估-优化循环)
适用场景:有明确评估标准,需要迭代优化的任务(代码生成、文案润色等)。
public class EvaluatorOptimizerWorkflow {
public RefinedResponse loop(String task, int maxRounds) {
String solution = chatClient.prompt().user(task).call().content();
for (int i = 0; i < maxRounds; i++) {
var eval = chatClient.prompt()
.user("评估以下方案并打分(1-10):\n" + solution)
.call().entity(Evaluation.class);
if (eval.score() >= 8) break; // 达到阈值,退出循环
solution = chatClient.prompt()
.user("改进方案,解决以下问题:\n方案: " + solution + "\n问题: " + eval.issues())
.call().content();
}
return new RefinedResponse(solution);
}
}
💡 选择指南:从最简单开始。Chain 适合 80% 的明确流程场景;Routing 适合多类型输入;Parallel 适合大量独立任务;Orchestrator-Workers 适合开放式复杂任务;Evaluator-Optimizer 适合质量要求极高的生成场景。绝大多数生产系统只需要前三种。
六、全流程实战:构建一个多 Agent 客服系统
现在把以上所有特性组合成一个现实的场景——智能客服系统,包含三个专用 Agent 通过 MCP 协议协作:
主 Agent
(路由 + 编排) → MCP → 订单 Agent
(订单查询/取消)
→ MCP → 库存 Agent
(库存/补货)
→ MCP → 知识库 Agent
(FAQ/文档检索)
6.1 主 Agent:Routing + Orchestrator 混合
@Service
public class CustomerServiceAgent {
private final ChatClient chatClient;
private final RoutingWorkflow router;
// 三个远端 MCP Server 的工具通过 McpToolCallbackProvider 自动注入
public CustomerServiceAgent(
ChatClient.Builder builder,
McpToolCallbackProvider mcpTools,
ChatMemory chatMemory
) {
this.chatClient = builder
.defaultTools(mcpTools)
.defaultAdvisors(
MessageChatMemoryAdvisor.builder(chatMemory).build(),
ToolSearchToolCallingAdvisor.builder() // 60+ 工具,渐进式披露
.toolCallbacks(mcpTools.getToolCallbacks())
.build(),
StructuredOutputValidationAdvisor.builder().build() // 自修正输出
)
.build();
}
public String handle(String userMessage, String conversationId) {
// 用 Routing 先分类
var category = chatClient.prompt()
.user("分类以下客户消息(订单/库存/产品咨询/投诉/其他): " + userMessage)
.call().content();
// 根据分类注入对应的 system prompt
return chatClient.prompt()
.system(systemPrompts.getOrDefault(category, defaultPrompt))
.user(userMessage)
.advisors(a -> a.param(ChatMemory.CONVERSATION_ID, conversationId))
.call().content();
}
}
6.2 完整对话流转示意
// 用户: "我的订单 ORD-88421 什么时候到?上周买的。"
//
// → Routing: 分类为 "订单"
// → ToolSearchToolCallingAdvisor: 从 60+ 工具中匹配到 getOrderStatus
// → MCP → OrderAgent.getOrderStatus("ORD-88421")
// → OrderAgent 返回: "SHIPPED, ETA 2026-08-10"
// → ChatMemory: 上下文已包含订单信息
// → 模型回答: "您的订单 ORD-88421 已于昨天发货,预计8月10日送达。"
//
// 用户(追问): "能帮我取消吗?"
// → ChatMemory: 知道 "它" = ORD-88421
// → MCP → OrderAgent.cancelOrder("ORD-88421")
// → 返回: ToolExecutionException("订单已发货,无法取消,建议申请退货")
// → 模型: "抱歉,该订单已经发货了所以无法取消。需要我帮您申请退货吗?"
✅ 这个系统的核心价值:
① 主 Agent 与子 Agent 解耦——每个子 Agent 独立部署、独立扩展
② 工具动态发现——新增子 Agent 无需修改主 Agent 代码
③ 对话记忆——ChatMemory 管理上下文,支持多轮追问
④ 错误弹性——ToolExecutionException 让模型能优雅处理业务异常
⑤ 结构化输出自修正——当模型生成的 JSON 不符合 Schema 时自动重试
七、生产部署 Checklist
以下是从开发到生产需要注意的要点:
📊
可观测性
Spring AI 2.0 集成 Micrometer 和 OpenTelemetry。工具调用耗时、Token 消耗、MCP 通信等全部自动埋点。搭配 Grafana 可构建完整的 Agent 监控面板。
🔒
MCP 安全
通过 spring-ai-community/mcp-security 实现 OAuth 2.0 和 API-Key 认证。MCP Server 与 Client 之间的双向认证防止未授权工具调用。
🧪
测试策略
使用 Spring AI Testcontainers 模块,可以在集成测试中启动真实的 Ollama 容器或 Mock 模型响应,验证完整的工具调用链路。
⚡
虚拟线程
Spring Boot 4 + Java 21 的虚拟线程对 Agent 系统是绝配:大量工具调用涉及 I/O 等待,虚拟线程避免了传统线程池的瓶颈。
关键配置项
// application.properties —— 推荐的生产配置
# 模型配置
spring.ai.openai.api-key=${OPENAI_API_KEY}
spring.ai.openai.chat.options.model=gpt-4o
spring.ai.openai.chat.options.temperature=0.3 # Agent 场景建议低温,减少幻觉
# MCP 服务端
spring.ai.mcp.server.protocol=STREAMABLE
spring.ai.mcp.server.name=customer-service-agent
spring.ai.mcp.server.version=1.0.0
spring.ai.mcp.server.streamable-http.mcp-endpoint=/mcp
# 可观测性
management.endpoints.web.exposure.include=health,metrics,prometheus
management.metrics.export.prometheus.enabled=true
# 虚拟线程(Spring Boot 4 默认开启,但显式声明不碍事)
spring.threads.virtual.enabled=true
写在最后
Spring AI 2.0 做对了一件事:把 Agent 开发的复杂性封装进 DI 容器和 Advisor 链中,而不像某些框架那样让你手动管理执行图的状态机。对于已经在用 Spring 生态的 Java 团队来说,这意味着构建 Agent 系统的学习曲线从"学一套全新范式"降到了"理解三个新注解和一个新概念"。
ToolCallingAdvisor 的可组合性、MCP 的跨服务工具调用、五种 Agentic Pattern 的开箱实现——这三个支柱共同构成了一个完整的多 Agent 编程 IDE。它不是要替代 LangChain 或 CrewAI,而是在 JVM 生态里提供了一个类型安全、可观测、生产就绪的替代方案。
2026 年下半年的趋势已经明确:Agent 不再是 Demo 里的玩具,而是要跑在生产环境里、处理真实业务流量、接受 SLA 考核的服务。Spring AI 2.0 的 GA 发布,正是在这个节点上给了 Java 开发者一把趁手的工具。
从 50 行的 Tool-Calling Agent 开始,从那里再到 MCP 多服务编排——每一步都是可落地的。不着急,一步步来。
#Spring AI 2.0 #MCP 协议 #Agentic AI #Tool Calling #ChatClient #Advisor Chain #Java Agent #Spring Boot 4 #多Agent协作 #ToolSearch
更多推荐
所有评论(0)