别再把大模型当成"万能工具人":Spring AI Multi-Agent 架构的工程化落地实战单 Agent 能跑通 Demo,Multi-Agent 才能承载生产。真正的分水岭不是 Prompt 写得多漂亮,而是能否把智能体能力拆成可治理、可扩展、可观测、可灰度的工程系统。版本与适用范围本文面向 Java/Spring 技术栈,重点讨论如何基于 Spring AI、Spring AI Alibaba、A2A、Nacos、Redis、Kafka 与 Kubernetes 构建生产级 Multi-Agent 系统。示例代码以 Java 21、Spring Boot 3.x、Spring AI 1.x API 风格为主,A2A 与 Nacos 部分参考 Spring AI Alibaba 1.0.0.4+ 及 Spring AI A2A 社区实现。由于 Spring AI 生态迭代较快,生产落地时应通过 BOM 锁定依赖版本,并以官方文档中的当前 API 为准。一、为什么单 Agent 到生产一定会遇到天花板很多团队做 AI 应用的第一版,通常是这样的:一个超级系统提示词 + 一堆工具 + 一个 ChatClient = 一个看起来什么都会的 Agent这个方案在原型阶段很快,但一旦进入电商客服、金融投顾、企业知识库、售后工单、运维诊断这类真实场景,就会暴露出结构性问题。1.1 单 Agent 的四个核心问题第一,认知负载过高。一个 Agent 同时负责意图识别、订单查询、库存判断、优惠计算、退款审核、物流追踪和人工升级,模型需要在大量工具和规则之间做选择。工具越多,选择越不稳定,Prompt 越长,推理噪声越大。第二,上下文污染严重。订单问题、商品问题、售后问题、闲聊内容混在同一个上下文窗口里,模型很容易把历史信息误用于当前任务。例如用户上一轮问过 A 商品库存,下一轮查询 B 商品退款,模型可能错误复用 A 商品上下文。第三,工程扩展性差。单 Agent 无法按能力独立扩容。大促期间订单查询暴涨,本应只扩容订单 Agent,却不得不扩容整个 AI 服务,导致商品咨询、售后问答、闲聊能力也被动扩容,成本不可控。第四,治理边界不清。在企业里,订单团队、库存团队、售后团队、风控团队通常是不同组织。单 Agent 模式会把所有业务规则、工具定义和 Prompt 堆在一起,既难以审计,也难以灰度发布。1.2 Multi-Agent 的本质不是"多个机器人聊天"生产级 Multi-Agent 不是让几个大模型互相对话,而是把智能能力拆成多个具备明确职责、工具权限、上下文边界和治理策略的执行单元。一个典型智能客服系统可以拆成:Agent职责可访问工具扩容特征Intent Agent意图识别、置信度评估意图分类模型、规则库CPU 轻、低延迟Order Agent订单查询、取消、退款前置判断订单服务、支付服务大促高峰明显Product Agent商品咨询、库存、推荐商品服务、库存服务、向量检索搜索链路重AfterSale Agent物流、退换货、纠纷处理物流服务、售后系统长链路、强规则Risk Agent风控校验、敏感操作拦截风控规则、黑名单、审计系统强一致、低容错Human Handoff Agent人工升级、工单生成CRM、工单系统业务兜底架构视图如下:Multi-Agent 的工程价值在于:职责单一:每个 Agent 只处理一个业务域,Prompt 更短,工具更少,决策更稳定。独立扩容:订单 Agent、售后 Agent、商品 Agent 可以按流量特征独立伸缩。故障隔离:库存工具异常不应拖垮退款链路,推荐服务超时不应影响订单查询。独立治理:不同团队可以独立维护自己的 Agent、工具、Prompt 和评估集。安全可控:高风险能力可以挂接 Risk Agent 与人工确认,不把关键操作完全交给模型。二、生产级 Multi-Agent 总体架构2.1 分层架构这套架构有一个重要原则:LLM 只负责需要推理的部分,不负责系统治理。也就是说,权限、限流、熔断、幂等、审计、灰度、租户隔离、预算控制、结果校验,都应该由工程系统兜住,而不是写进 Prompt 后祈祷模型遵守。2.2 核心组件职责组件职责关键设计点Orchestrator接收请求、识别意图、路由 Agent、聚合结果超时预算、并发执行、降级策略Agent Registry管理本地与远程 Agent 元数据能力标签、版本、健康状态、灰度权重Context Manager管理会话、任务、链路上下文Redis 持久化、上下文压缩、租户隔离Tool Registry管理工具定义与执行入口动态工具发现、权限控制、审计Guardrail输入输出安全与业务风控敏感词、PII、越权、人工确认Event Bus异步任务和领域事件Kafka 解耦、削峰、最终一致Observability可观测与成本治理traceId、token、latency、tool error2.3 技术选型建议场景推荐方案快速原型Spring AI + ChatClient + 本地工具中小规模生产Spring AI + Redis ChatMemory/Context + Resilience4j + Micrometer多团队协作Spring AI Alibaba + Nacos A2A Registry + 独立 Agent 服务高并发场景Java 21 虚拟线程 + Reactor 流式输出 + Redis 限流 + K8s HPA企业级治理A2A + MCP + Kafka + OpenTelemetry + 评估集 + 灰度平台三、领域建模:先把 Agent 当成工程组件,而不是 Prompt3.1 Agent 元数据模型生产环境中,一个 Agent 至少要描述这些信息:package com.example.agent.core; import java.time.Duration; import java.util.Set; public record AgentDescriptor( String name, String version, String description, SetString capabilities, SetString allowedToolNames, Duration timeout, int maxConcurrency, boolean streamingSupported, AgentStatus status ) { public boolean canHandle(String capability) { return status == AgentStatus.UP capabilities.contains(capability); } } enum AgentStatus { UP, DEGRADED, DOWN }这里的重点不是字段本身,而是架构思想:Agent 必须像微服务一样被描述、注册、发现、限流、熔断和观测。3.2 统一执行接口package com.example.agent.core; import reactor.core.publisher.Flux; public interface Agent { AgentDescriptor descriptor(); AgentResult execute(AgentRequest request); default FluxAgentStreamChunk stream(AgentRequest request) { return Flux.just(AgentStreamChunk.done(execute(request).answer())); } }package com.example.agent.core; import java.time.Instant; import java.util.Map; public record AgentRequest( String requestId, String tenantId, String userId, String sessionId, String input, AgentContext context, MapString, Object attributes, Instant deadline ) { public boolean expired() { return Instant.now().isAfter(deadline); } } public record AgentResult( String agentName, String answer, double confidence, boolean requiresHuman, MapString, Object facts ) { public static AgentResult humanRequired(String agentName, String reason) { return new AgentResult(agentName, reason, 0.0, true, Map.of("reason", reason)); } } public record AgentStreamChunk(String content, boolean done) { public static AgentStreamChunk chunk(String content) { return new AgentStreamChunk(content, false); } public static AgentStreamChunk done(String content) { return new AgentStreamChunk(content, true); } }统一接口的好处是,Orchestrator 不需要关心底层是本地 Agent、远程 A2A Agent、Graph 工作流,还是一个普通 Java 服务。四、Skills 与 Tool Calling:从"能调用"升级为"可治理"Spring AI 的 Tool Calling 把 Java 方法或函数暴露给模型,让模型在需要时调用业务系统。官方抽象里,工具最终会被建模为ToolCallback,可以通过@Tool、函数式 Bean 或自定义ToolCallback提供。但生产环境不能只停留在@Tool。一个可上线的 Skill 至少要具备:元数据:名称、版本、负责人、风险等级、所属业务域。权限:哪些 Agent、租户、用户角色可以调用。治理:超时、重试、熔断、限流、降级。审计:谁在什么上下文里调用了什么工具,参数和结果是否脱敏。幂等:创建工单、取消订单、发起退款等写操作必须可重复提交。4.1 Skill 元数据package com.example.agent.skill; import java.time.Duration; import java.util.Set; public record SkillMetadata( String name, String version, String owner, String domain, SkillRiskLevel riskLevel, SetString allowedAgents, Duration timeout, int requestsPerSecond, boolean returnDirect ) { } enum SkillRiskLevel { LOW, MEDIUM, HIGH }4.2 生产级库存查询 Skill下面的代码展示一个更接近生产的工具实现:包含参数校验、限流、超时、指标、审计与异常转换。package com.example.agent.skill.inventory; import com.example.agent.skill.SkillMetadata; import com.example.agent.skill.SkillRiskLevel; import io.github.resilience4j.circuitbreaker.annotation.CircuitBreaker; import io.github.resilience4j.ratelimiter.annotation.RateLimiter; import io.micrometer.core.instrument.MeterRegistry; import io.micrometer.core.instrument.Timer; import jakarta.validation.constraints.NotBlank; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.ai.tool.annotation.Tool; import org.springframework.ai.tool.annotation.ToolParam; import org.springframework.stereotype.Component; import java.time.Duration; import java.util.Set; @Component public class InventorySkill { private static final Logger log = LoggerFactory.getLogger(InventorySkill.class); private final InventoryClient inventoryClient; private final MeterRegistry meterRegistry; public InventorySkill(InventoryClient inventoryClient, MeterRegistry meterRegistry) { this.inventoryClient = inventoryClient; this.meterRegistry = meterRegistry; } public SkillMetadata metadata() { return new SkillMetadata( "inventory_query", "1.0.0", "inventory-team", "product", SkillRiskLevel.LOW, Set.of("product-agent", "orchestrator-agent"), Duration.ofSeconds(2), 300, false ); } @Tool(name = "inventory_query", description = "查询指定 SKU 在指定仓库的可售库存。只用于库存咨询,不用于锁库存。") @CircuitBreaker(name = "inventoryClient", fallbackMethod = "fallback") @RateLimiter(name = "inventoryClient") public InventoryResponse queryInventory( @ToolParam(description = "商品 SKU 编码,例如 SKU-10086") @NotBlank String sku, @ToolParam(description = "仓库编码,可为空;为空时查询全国汇总库存") String warehouseCode ) { Timer.Sample sample = Timer.start(meterRegistry); try { InventoryResponse response = inventoryClient.query(sku, warehouseCode); meterRegistry.counter("agent.skill.success", "skill", "inventory_query").increment();