Spring AI Alibaba 生产级落地指南:从消息契约、状态编排到多 Agent 体系化建设
面向 Java 技术栈的 AI 应用,真正困难的部分从来不是“把模型调通”,而是把一个会思考、会调用工具、会维护上下文、会跨服务协作的 Agent 系统,做成一个可治理、可扩展、可观测、可回放、可恢复的生产平台。
Spring AI Alibaba 的价值,也不在于“再封装一次大模型 API”,而在于它试图把 AI 应用从 Demo 代码提升为工程系统。
目录
1. 为什么很多 AI Demo 一上线就失控 2. 理解 Spring AI Alibaba:不是 SDK,而是一套生产框架 3. 先抓住主线:Message 才是 AI 应用的基础设施 4. 核心原理:ReAct、StateGraph 与可恢复执行 5. 生产架构设计:协议层、治理层、状态层三层拆解 6. 实战案例:构建一个生产级电商智能客服与履约协同系统 7. 代码骨架:从单 Agent 到可扩展工作流 8. 高并发与可扩展设计:真正决定系统上限的部分 9. 多 Agent 架构:什么时候该拆,怎么拆,拆完如何治理 10. 上下文工程:不是保存聊天记录,而是控制推理成本 11. 工具治理与安全护栏:避免 Agent 从“聪明”变成“危险” 12. 可观测、压测与故障演练:生产系统的生命线 13. 部署落地:Kubernetes、Redis、Nacos、消息队列如何协同 14. 常见误区与架构建议 15. 演进路线:从单体 AI 能力到企业级 Agent 平台 16. 总结
1. 为什么很多 AI Demo 一上线就失控
一个 AI Demo 从“能回答问题”走到“能承担业务”,中间隔着一整套工程鸿沟。很多团队在第一阶段进展很快:
• 接一个模型 SDK • 拼一个 Prompt • 注册几个工具 • 暴露一个聊天接口
本地效果通常不错,甚至看起来已经具备“智能客服”“智能助手”“流程自动化”的雏形。但只要开始接真实流量,问题会立刻暴露:
• 上下文越来越长,响应越来越慢,成本越来越高 • 工具调用不可控,模型偶发误用高风险操作 • 多轮对话中状态丢失,用户上一轮说过的话下一轮忘掉 • 服务重启后流程中断,退款审批、工单流转无法恢复 • 多实例部署后会话分散,某个请求打到新 Pod 就像“失忆” • 流式输出链路不稳定,前端经常收到半截响应 • 高峰期模型限流、外部工具超时、重试风暴相互放大 • 日志只看到一堆 HTTP 请求,看不到一次 Agent 决策到底做了什么
本质上,这类系统失败的原因不是模型不够强,而是把一个具备状态、决策、工具、副作用、协作关系的复杂系统,当成了普通 HTTP 接口来设计。
所以我们要先统一一个认知:
生产级 AI 应用不是“模型调用系统”,而是“以模型为决策核心的状态化分布式应用”。
这个认知一旦立住,就能理解为什么 Spring AI Alibaba 值得认真看。它提供的不是几个便捷 API,而是一套让 Java 团队可以把 AI 应用纳入熟悉工程范式的基础设施。
2. 理解 Spring AI Alibaba:不是 SDK,而是一套生产框架
如果只从表面看,Spring AI Alibaba 似乎只是围绕 Spring AI 做了一层增强封装。但从生产落地角度看,它真正解决的是四类问题:
1. 统一模型与工具抽象
让 ChatModel、Tool、Prompt、Memory、Message 等对象成为标准组件,而不是散落在业务代码里的临时拼装。2. 统一 Agent 运行时
不再把 Agent 理解成一个黑箱函数,而是理解为一个可编排、可中断、可恢复、可观测的状态机。3. 统一工程集成方式
把 AI 能力接入 Spring Boot、配置中心、服务发现、缓存、消息队列、可观测栈、Kubernetes。4. 统一生产治理能力
包括上下文控制、人工审批、工具权限、分布式会话、流式输出、指标埋点、失败恢复、成本治理。
从架构视角,可以把 Spring AI Alibaba 理解为下面这张图:
+-----------------------------------------------------------------------------------+
| Business Applications |
| 智能客服 / 企业知识助手 / 工单助手 / 数据分析助手 / 流程协同 |
+-------------------------------------------+---------------------------------------+
| Agent / Workflow Layer | Platform / Governance |
| ReactAgent / Sequential / Parallel | Observation / Eval / Admin / Studio |
| Routing / Supervisor / Loop / Graph | Prompt Ops / Policy / Human Review |
+-------------------------------------------+---------------------------------------+
| Spring AI Alibaba Runtime & Integration |
| Message / Tool / Hook / Saver / Memory / A2A / MCP / Streaming / StateGraph |
+-----------------------------------------------------------------------------------+
| Spring AI Core Abstractions |
| ChatModel / Embedding / VectorStore / Advisor / Tool |
+-----------------------------------------------------------------------------------+
| Model Providers & Enterprise Infrastructure |
| DashScope / OpenAI / DeepSeek / Ollama / Redis / MQ / Nacos / K8s / OTEL |
+-----------------------------------------------------------------------------------+这套分层意味着一个重要事实:
Spring AI Alibaba 的最佳使用方式不是“在 Controller 里直接调 Agent”,而是把它当成 AI 运行时内核,再围绕业务域构建应用层架构。
3. 先抓住主线:Message 才是 AI 应用的基础设施
很多文章讲 Spring AI Alibaba,会从 Agent、Tool Calling、多 Agent 开始。但如果站在资深架构师视角,我更建议先抓住 Message。
因为对生产系统而言,Message 不是“聊天消息 DTO”,而是三件事的统一载体:
• 协议载体:承载 user、assistant、system、tool 等角色语义 • 状态载体:承载一轮或多轮推理过程中的上下文快照 • 治理载体:承载审计、脱敏、回放、归档、成本统计、风险判定所需元数据
如果 Message 设计得太薄,后面所有能力都会变成散乱补丁。
如果 Message 设计得足够强,它就能支撑整套 AI 工程体系。
3.1 Message 为什么不是普通聊天记录
在一个生产级 Agent 系统里,一条消息往往至少包含以下信息:
system/user/assistant/tool | ||
sessionId/threadId | ||
messageId/requestId | ||
也就是说,Message 更像“AI 时代的事件对象”,而不仅仅是“聊天气泡”。
3.2 把 Message 看成协议层
所谓协议层,重点不是类名,而是边界清晰:
• 用户输入如何进入系统 • System Prompt 如何注入 • Tool Result 如何回写模型上下文 • 多 Agent 之间如何交换结果 • 前端如何消费流式 Token • 审计系统如何复盘一次决策链路
如果协议不统一,就会出现几个典型问题:
• 某些工具返回的是自然语言,某些工具返回 JSON,模型无法稳定消费 • 多 Agent 交接时字段命名随意,路由逻辑越来越脆弱 • 流式响应和非流式响应不是同一种消息模型,前后端协议分裂 • 审批节点、回放节点、重试节点各自维护一套“上下文格式”
所以在项目一开始,就应该为消息建立明确契约。
3.3 一个生产可用的消息对象骨架
下面给一个工程里常见的消息对象设计骨架。它不依赖特定版本 API,重点是表达设计原则:
public record AiMessageEnvelope(
String messageId,
String sessionId,
String traceId,
MessageRole role,
String model,
String content,
List<ContentBlock> blocks,
Map<String, Object> metadata,
Instant createdAt
) {
}
public enum MessageRole {
SYSTEM,
USER,
ASSISTANT,
TOOL
}
public sealed interface ContentBlock
permits TextBlock, ToolCallBlock, ToolResultBlock, ReasoningBlock, JsonBlock {
}
public record TextBlock(String text) implements ContentBlock {
}
public record ToolCallBlock(
String callId,
String toolName,
String argumentsJson
) implements ContentBlock {
}
public record ToolResultBlock(
String callId,
String toolName,
String resultJson,
boolean success
) implements ContentBlock {
}
public record ReasoningBlock(String summary) implements ContentBlock {
}
public record JsonBlock(String json) implements ContentBlock {
}这个模型的关键点不在“字段够不够多”,而在以下几个能力:
• 内容与元数据分离 • 文本块与工具块分离 • 对前端展示内容与内部治理内容分别建模 • 为回放、审计、脱敏、幂等等非功能需求预留空间
3.4 Message 为什么决定系统可治理性
后续所有能力,本质都依赖 Message:
• 上下文压缩:对哪些消息做摘要、保留哪些消息,依赖 Message 分类 • Tool Calling:模型输出工具调用结构,依赖 Message block 解析 • Human-in-the-Loop:中断前的待审批动作,需要作为消息或状态持久化 • 多 Agent 编排:一个 Agent 的输出,要能成为另一个 Agent 的输入 • SSE 流式输出:增量 Token 需要映射为前端可消费的事件消息 • 审计与合规:敏感词、敏感字段、敏感工具,都要能挂接到消息维度
因此,理解 Spring AI Alibaba,真正应该从 Message 开始。
它是协议层、治理层、状态层的共同基底。
4. 核心原理:ReAct、StateGraph 与可恢复执行
4.1 ReAct 并不是“让模型自己循环”
ReAct 的表面形式是:
Thought -> Action -> Observation -> Thought -> ...但在工程上,它不能只是 while(true) 调模型。
真正可上线的 ReAct 必须具备四个约束:
• 有明确的停止条件 • 有状态持久化能力 • 有工具调用边界控制 • 有异常恢复与人工接管机制
因此,ReAct 在生产里更适合被理解成一个有状态的条件图。
4.2 StateGraph 的价值:把 Agent 变成可编排状态机
无论是单 Agent、审批流、专家协作,还是多阶段推理,本质上都可以抽象成:
• 节点:一次模型调用、一次工具调用、一次规则判断、一次人工审批 • 边:节点之间的转移关系 • 状态:节点运行过程中的上下文数据
这就是 Graph 运行时的价值。它带来的不是“画图更好看”,而是以下工程收益:
• 可以中断:某个节点前暂停,等待人工审批 • 可以恢复:进程重启后,从中断点继续 • 可以分流:根据状态条件走不同分支 • 可以回放:重建某次流程的执行轨迹 • 可以并行:多个子任务同时执行并合并结果
4.3 为什么生产系统必须支持可恢复执行
以退款助手为例,一次请求并不是“用户问一句,模型答一句”这么简单,它可能经历:
1. 意图识别 2. 订单查询 3. 退款资格校验 4. 风险判断 5. 人工审批 6. 发起退款 7. 发送通知
其中任何一步失败,都不应该让整条流程完全丢失。
如果系统无法恢复,只能让用户重新发起一遍,会造成:
• 体验差 • 数据不一致 • 重复副作用 • 审计链断裂
所以真正的生产级 Agent,必须具备“长流程、可中断、可恢复、可幂等”能力。
4.4 一个典型 StateGraph 工作流骨架
@Configuration
public class RefundWorkflowConfiguration {
@Bean
public CompiledGraph refundWorkflow(
KeyStrategyFactory keyStrategyFactory,
RefundIntentNode refundIntentNode,
EligibilityNode eligibilityNode,
RiskReviewNode riskReviewNode,
HumanReviewNode humanReviewNode,
SubmitRefundNode submitRefundNode,
NotifyNode notifyNode
) {
StateGraph graph = new StateGraph(keyStrategyFactory)
.addNode("intent", node_async(refundIntentNode))
.addNode("eligibility", node_async(eligibilityNode))
.addNode("risk_review", node_async(riskReviewNode))
.addNode("human_review", node_async(humanReviewNode))
.addNode("submit_refund", node_async(submitRefundNode))
.addNode("notify", node_async(notifyNode))
.addEdge(START, "intent")
.addEdge("intent", "eligibility")
.addEdge("eligibility", "risk_review")
.addConditionalEdges(
"risk_review",
edge_async(state -> {
boolean needHumanReview = (boolean) state.value("needHumanReview").orElse(false);
return needHumanReview ? "manual" : "auto";
}),
Map.of(
"manual", "human_review",
"auto", "submit_refund"
)
)
.addEdge("human_review", "submit_refund")
.addEdge("submit_refund", "notify")
.addEdge("notify", END);
return graph.compile(CompileConfig.builder()
.interruptBefore("human_review")
.build());
}
}注意,真正让这个图具备生产能力的,不是 addNode 这些 API,而是下面这些约束:
• 每个节点输入输出都可结构化 • 每个状态字段有合并策略 • 中断点可持久化 • 副作用节点有幂等保护 • 失败节点可重试或转人工
4.5 KeyStrategy 的工程意义
状态图一旦复杂,最容易出问题的不是路由,而是状态合并。
例如:
• messages应该追加,而不是覆盖• riskScore应该覆盖,而不是累加• toolResults可能需要按 key 合并• auditTrail需要 append
一个常见设计如下:
@Bean
public KeyStrategyFactory keyStrategyFactory() {
return () -> {
Map<String, KeyStrategy> strategies = new HashMap<>();
strategies.put("messages", new AppendStrategy());
strategies.put("toolResults", new MergeStrategy());
strategies.put("riskScore", new ReplaceStrategy());
strategies.put("approved", new ReplaceStrategy());
strategies.put("auditTrail", new AppendStrategy());
strategies.put("costSnapshot", new ReplaceStrategy());
return strategies;
};
}这实际上是在做一件非常重要的事:
把 AI 运行时里的“共享内存写入规则”显式化。
这能极大降低并发分支、长流程和多 Agent 协作下的数据混乱风险。
5. 生产架构设计:协议层、治理层、状态层三层拆解
围绕 Spring AI Alibaba 落地企业级系统时,我建议把架构拆成三层:
5.1 协议层:解决“怎么说”
协议层负责统一表达:
• 用户消息如何封装 • 模型消息如何落库 • 工具调用如何描述 • 工具返回如何结构化 • SSE 如何推送增量事件 • 多 Agent 如何交换上下文
协议层最重要的原则是:
• 不把自然语言字符串当作唯一边界 • 对 tool call、tool result、risk signal、summary、event 等建立结构化契约 • 让前端、后端、模型、工具、审计系统都面向同一套消息语义
5.2 治理层:解决“能不能放行”
治理层负责控制:
• 哪些工具能被调用 • 哪些参数需要脱敏 • 哪些操作必须人工审批 • 哪些请求需要降级 • 哪些会话应做摘要 • 哪些输出不能直接回用户
治理层本质上是在给模型自由度上“护栏”。
这层如果缺失,模型越强,风险反而越大。
治理层常见能力包括:
• 工具白名单与黑名单 • 按租户/角色/环境控制工具可见性 • Prompt 模板版本管理 • Token 预算与成本限额 • 输出 JSON Schema 校验 • 高风险动作审批 • 敏感信息脱敏与回显控制
5.3 状态层:解决“记住什么、何时恢复”
状态层是 AI 应用最容易被低估的部分。它不仅要记忆对话,还要维护:
• 当前流程进行到哪个节点 • 工具调用过哪些外部系统 • 哪些副作用已经提交 • 哪些消息已摘要 • 当前风险评分是多少 • 当前是否处于人工审批等待中 • 当前线程是否可以重入
一个成熟的状态层通常会区分三类状态:
5.4 三层之间的关系
用户请求
->
协议层:消息标准化、角色标注、事件封装
->
治理层:权限校验、工具筛选、预算控制、风险评估
->
状态层:加载线程上下文、恢复中断点、写回执行状态
->
Agent / Graph Runtime 执行
->
治理层:输出审查、脱敏、合规过滤
->
协议层:转为 SSE/HTTP/A2A 响应这三层拆开后,系统会明显更稳:
• 协议问题不污染业务逻辑 • 治理逻辑不散落在每个 Tool 里 • 状态恢复不耦合 Controller
这就是从 Demo 进入工程系统的关键一步。
6. 实战案例:构建一个生产级电商智能客服与履约协同系统
为了把上面的原理落到地上,我们用一个真实感较强的案例贯穿全文。
6.1 业务目标
我们要做的不是一个“回答 FAQ 的聊天机器人”,而是一个具备以下能力的智能客服系统:
• 查询订单、物流、退款、发票、优惠券 • 基于知识库回答售前售后问题 • 能判断问题是否超出权限并转人工 • 对退款、改地址、补偿发券等动作走风险控制 • 与订单、支付、物流、会员、工单等服务协同 • 支持 Web/App/H5 多端流式会话 • 支持高峰活动期间扩容与降级
6.2 领域拆分
从领域上,我们不建议把所有能力都堆进一个大 Agent,而是至少拆成以下职责:
Conversation Gateway | |
Customer Service Agent | |
Knowledge Agent | |
Order Tool Service | |
Refund Workflow | |
Risk Engine | |
Audit Service | |
Ops Platform |
6.3 参考部署拓扑
Client(Web/App)
|
API Gateway / Ingress
|
Conversation Gateway
|
+-------------------------------+------------------------------+
| | |
| Customer Service Agent |
| | |
| +-----------------+-----------------+ |
| | | |
| Knowledge Agent Refund Workflow |
| | | |
| Vector Store / KB Risk Engine / Human Ops |
| |
+-------------------------------+------------------------------+
| | | |
Order Service Payment Service Logistics Service Ticket Service
|
Redis Cluster / MQ / Nacos / Observability / Audit Store这个拓扑体现了两个原则:
• 对话与副作用分离 • 决策与执行分离
也就是说,Agent 可以负责理解与决策,但真正的业务副作用最好交给受控工作流和业务服务去完成。
7. 代码骨架:从单 Agent 到可扩展工作流
下面开始给出更偏生产骨架的示例。
示例重点不是追求“最少代码”,而是体现可扩展性。
7.1 Maven 依赖建议
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.alibaba.cloud.ai</groupId>
<artifactId>spring-ai-alibaba-bom</artifactId>
<version>${spring-ai-alibaba.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>com.alibaba.cloud.ai</groupId>
<artifactId>spring-ai-alibaba-agent-framework</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud.ai</groupId>
<artifactId>spring-ai-alibaba-graph-core</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud.ai</groupId>
<artifactId>spring-ai-alibaba-starter-dashscope</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud.ai</groupId>
<artifactId>spring-ai-alibaba-observation-extension</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
<groupId>io.github.resilience4j</groupId>
<artifactId>resilience4j-spring-boot3</artifactId>
</dependency>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-tracing-bridge-otel</artifactId>
</dependency>
</dependencies>建议额外补齐:
• 向量检索相关依赖 • MQ 客户端 • 审计存储访问层 • 数据库访问组件
7.2 配置基线
server:
port: 8080
spring:
application:
name: customer-service-agent
ai:
dashscope:
api-key: ${AI_DASHSCOPE_API_KEY}
chat:
options:
model: qwen-max
temperature: 0.2
top-p: 0.8
data:
redis:
timeout: 2s
lettuce:
pool:
max-active: 128
max-idle: 32
min-idle: 8
cluster:
nodes:
- redis-0:6379
- redis-1:6379
- redis-2:6379
resilience4j:
circuitbreaker:
instances:
llmProvider:
sliding-window-type: count_based
sliding-window-size: 20
failure-rate-threshold: 50
wait-duration-in-open-state: 30s
toolRefund:
sliding-window-size: 20
failure-rate-threshold: 40
timelimiter:
instances:
llmProvider:
timeout-duration: 25s
toolRefund:
timeout-duration: 5s
management:
endpoints:
web:
exposure:
include: health,info,prometheus,metrics
tracing:
sampling:
probability: 1.07.3 会话请求模型
public record ChatRequest(
@NotBlank String sessionId,
@NotBlank String userId,
@NotBlank String tenantId,
@NotBlank String message,
String channel,
Map<String, Object> ext
) {
}
public record ChatResponse(
String sessionId,
String traceId,
String answer,
boolean escalatedToHuman
) {
}7.4 工具接口不要直接暴露业务服务
很多项目第一版会这样写:
@Tool
public RefundResult refund(String orderId, BigDecimal amount) {
return refundService.refund(orderId, amount);
}这在生产里风险很高,因为工具边界过于粗暴。更好的方式是引入工具门面层:
@Component
public class RefundToolFacade {
private final RefundEligibilityService eligibilityService;
private final RefundCommandService refundCommandService;
private final RiskFacade riskFacade;
public RefundToolFacade(
RefundEligibilityService eligibilityService,
RefundCommandService refundCommandService,
RiskFacade riskFacade
) {
this.eligibilityService = eligibilityService;
this.refundCommandService = refundCommandService;
this.riskFacade = riskFacade;
}
@Tool(name = "check_refund_eligibility", description = """
校验订单是否满足退款条件。
当用户明确表达退款意图时调用。
输入订单号,返回是否满足退款、原因、可退金额、是否需要人工审批。
该工具不会执行实际退款。
""")
public RefundEligibilityResult checkEligibility(
@ToolParam(description = "订单号") String orderId,
ToolExecutionContext context
) {
UserPrincipal user = context.get(UserPrincipal.class);
RefundEligibilityResult result = eligibilityService.check(user.userId(), orderId);
RiskDecision riskDecision = riskFacade.evaluate(orderId, user.userId(), result.refundableAmount());
return result.withRisk(riskDecision.level(), riskDecision.needHumanReview());
}
@Tool(name = "create_refund_ticket", description = """
创建退款申请单,不直接执行打款。
仅在用户确认后调用。
返回退款申请单号与后续处理状态。
""")
public RefundTicketResult createRefundTicket(
@ToolParam(description = "订单号") String orderId,
@ToolParam(description = "退款原因") String reason,
ToolExecutionContext context
) {
UserPrincipal user = context.get(UserPrincipal.class);
return refundCommandService.createTicket(user.userId(), orderId, reason);
}
}这个设计的意义是:
• 读操作与写操作分离 • 风险判断前置 • 真正副作用动作从“直接退款”降级为“创建工单/申请单” • 模型可调用的工具能力更加安全
7.5 Agent 装配时要把治理能力前置
@Configuration
public class CustomerServiceAgentConfiguration {
@Bean
public ReactAgent customerServiceAgent(
ChatModel chatModel,
ToolCallbackProvider toolCallbackProvider,
RedisAgentStateStore redisAgentStateStore,
HumanReviewPolicy humanReviewPolicy,
ToolVisibilityPolicy toolVisibilityPolicy
) {
return ReactAgent.builder()
.name("customer-service-agent")
.model(chatModel)
.systemPrompt("""
你是电商平台的生产级客服助手。
你的目标是优先帮助用户解决问题,但不得绕过业务规则。
规则如下:
1. 查询类请求可直接调用只读工具。
2. 对退款、改地址、补偿发券、账户信息修改等高风险动作,必须先确认并进入审批流程。
3. 对不确定信息不得编造,优先调用工具或知识库。
4. 如果工具失败,应向用户解释系统繁忙,并给出明确下一步,而不是重复无效调用。
5. 不得向用户暴露内部风险分或内部审核策略细节。
""")
.tools(toolCallbackProvider.getToolCallbacks())
.toolSelectionStrategy(context ->
toolVisibilityPolicy.selectTools(
(String) context.get("tenantId"),
(String) context.get("intent"),
(String) context.get("riskLevel")
)
)
.hooks(
new SummarizationHook(SummarizationConfig.builder()
.maxMessages(24)
.compressionThreshold(6000)
.build()),
new HumanInTheLoopHook(HumanInTheLoopConfig.builder()
.sensitiveTools(
"create_refund_ticket",
"modify_address",
"grant_coupon"
)
.build()),
new TokenBudgetHook(15000),
new OutputSchemaGuardHook()
)
.saver(redisAgentStateStore)
.maxIterations(12)
.build();
}
}这里真正体现“生产级”的点有四个:
• 不是把工具全部暴露给模型,而是可见性可控 • 不是等风险发生后拦截,而是在 Agent 层预设行为边界 • 不是只记录消息,而是显式配置状态存储 • 不是任由模型无限循环,而是限制迭代次数与预算
7.6 用应用服务而不是 Controller 直接驱动 Agent
@Service
public class ConversationApplicationService {
private final ReactAgent customerServiceAgent;
private final ConversationStateRepository conversationStateRepository;
private final AuditService auditService;
public ConversationApplicationService(
ReactAgent customerServiceAgent,
ConversationStateRepository conversationStateRepository,
AuditService auditService
) {
this.customerServiceAgent = customerServiceAgent;
this.conversationStateRepository = conversationStateRepository;
this.auditService = auditService;
}
public Flux<ChatEvent> stream(ChatRequest request) {
String traceId = UUID.randomUUID().toString();
RunnableConfig config = RunnableConfig.builder()
.threadId(request.sessionId())
.metadata(Map.of(
"tenantId", request.tenantId(),
"userId", request.userId(),
"traceId", traceId,
"channel", Objects.toString(request.channel(), "web")
))
.build();
conversationStateRepository.touch(request.sessionId(), request.userId(), traceId);
auditService.recordUserInput(request.sessionId(), request.userId(), request.message(), traceId);
return customerServiceAgent.stream(request.message(), config)
.map(output -> ChatEventMapper.toEvent(output, traceId))
.doOnNext(event -> auditService.recordEvent(request.sessionId(), traceId, event))
.doOnError(ex -> auditService.recordFailure(request.sessionId(), traceId, ex))
.doOnComplete(() -> conversationStateRepository.finishTurn(request.sessionId(), traceId));
}
}这种写法的好处是:
• Controller 只负责协议转换 • Agent 生命周期、审计、状态持久化由应用服务统一控制 • 方便后续接 WebSocket、SSE、A2A、MQ 等多个入口
7.7 SSE 接口要支持事件分型
@RestController
@RequestMapping("/api/ai/conversations")
public class ConversationController {
private final ConversationApplicationService applicationService;
public ConversationController(ConversationApplicationService applicationService) {
this.applicationService = applicationService;
}
@PostMapping(value = "/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<ServerSentEvent<String>> stream(@Valid @RequestBody ChatRequest request) {
return applicationService.stream(request)
.map(event -> ServerSentEvent.<String>builder()
.event(event.type())
.id(event.eventId())
.data(event.payload())
.build())
.timeout(Duration.ofSeconds(90));
}
}建议至少区分这些事件:
• token:流式文本片段• tool_call:模型发起工具调用• tool_result:工具返回结果• approval_required:进入人工审批• complete:本轮完成• error:发生异常
这样前端、客服工作台、审计平台都能消费同一条事件流,而不是只拿到一串字符串。
8. 高并发与可扩展设计:真正决定系统上限的部分
AI 系统一旦进入生产,高并发与可扩展设计的重要性通常会迅速超过 Prompt 调优。
8.1 性能瓶颈一般不止在模型
一次完整对话的时延,通常来自以下几个部分:
因此,优化不能只盯模型。
8.2 先区分三类请求
高并发设计里,我建议先把流量按性质拆开:
1. 纯问答类
无副作用、只读工具为主,可以追求低延迟。2. 检索增强类
需要知识库召回、重排、生成,重点优化召回链路与上下文压缩。3. 事务协同类
涉及退款、改地址、工单流转等副作用,重点是可靠性与幂等,不要盲目追求极低延迟。
不同类型请求不能用同一套 SLA 和资源池去管理。
8.3 并发模型建议:入口非阻塞,工具隔离,副作用异步化
一个比较稳妥的方案是:
• 网关层使用 WebFlux/SSE,减少连接阻塞 • 模型调用使用隔离线程池或虚拟线程 • 外部工具调用按域隔离线程池 • 耗时副作用流程通过 MQ 异步化 • 人工审批流程天然异步,不与用户对话线程强绑定
示意如下:
HTTP/SSE Request
-> Reactive Gateway
-> Agent Runtime
-> LLM Pool
-> Query Tool Pool
-> Risk Tool Pool
-> Approval Queue
-> Async Workflow Queue8.4 Redis 设计不要只存聊天记录
很多团队使用 Redis 时,只做一件事:存消息列表。
生产里远远不够。
更合理的 Redis Key 设计示例:
ai:session:{sessionId}:messages | ||
ai:session:{sessionId}:summary | ||
ai:workflow:{threadId}:state | ||
ai:workflow:{threadId}:lock | ||
ai:request:{requestId}:dedup | ||
ai:tool:{toolCallId}:result | ||
ai:budget:{tenantId}:{day} |
这背后体现的是:
Redis 在 AI 系统里既是会话存储,也是流程状态缓存、幂等辅助、限流辅助、预算辅助。
8.5 幂等性是高并发下的第一原则
涉及工具副作用时,必须把幂等设计做在模型之外。
例如退款申请:
• 用户可能重复点击 • 网关可能超时重试 • Agent 可能重复推理 • 下游服务可能响应丢失
如果没有幂等保护,就会重复创建工单,甚至重复退款。
推荐方案:
public class IdempotentRefundCommandService {
private final RefundTicketRepository refundTicketRepository;
public RefundTicketResult createTicket(String userId, String orderId, String reason, String requestId) {
return refundTicketRepository.findByRequestId(requestId)
.orElseGet(() -> {
RefundTicket ticket = RefundTicket.create(userId, orderId, reason, requestId);
refundTicketRepository.save(ticket);
return RefundTicketResult.from(ticket);
});
}
}关键点:
• 幂等键来自请求或工具调用 ID • 幂等控制放在业务服务/数据库,不依赖模型“别重复调用” • 审批前后都要考虑重复提交
8.6 缓存策略要做“语义层”和“工具层”分离
在 AI 应用里,缓存一般有三层:
1. 工具结果缓存
例如物流轨迹、商品详情、运费规则等短时变化不大的只读数据。2. 检索结果缓存
对热门 FAQ 或常见意图的知识检索结果缓存。3. 生成结果缓存
只适合强模板、强约束、低个性化场景,不适合复杂个性化回复。
不要一上来就做“模型回答全量缓存”,否则命中率低、风险高、解释性差。
8.7 限流不能只做在 API Gateway
AI 系统至少要有四层限流:
• 用户级限流:防止单用户滥用 • 租户级限流:保护整体资源池 • 模型级限流:适配 Provider QPS/TPS 配额 • 工具级限流:保护内部核心系统
尤其是工具级限流非常重要。
否则大模型可能在高峰期把订单、物流、支付接口放大成压力源。
8.8 成本治理必须内建
高并发下,AI 成本问题本质上不是“单次调用贵”,而是:
• 多轮对话导致 token 指数放大 • 冗余工具描述拖长 prompt • 低价值请求用了高阶模型 • 工具失败导致无意义重试 • 多 Agent 场景中多模型并行调用
建议至少内建以下能力:
• 按意图路由模型 • 按渠道设置预算 • 摘要与裁剪策略 • 工具动态筛选 • Prompt 模板去冗余 • 长尾低价值请求降级
9. 多 Agent 架构:什么时候该拆,怎么拆,拆完如何治理
9.1 不是任务一复杂就该上多 Agent
多 Agent 很容易被过度设计。
在不少场景下,一个设计良好的单 Agent + 工具集合就足够。
只有满足以下情况时,才建议拆分:
• 领域边界明显不同,例如售后、物流、财务、法务 • 工具集差异明显,全部暴露会造成模型混淆 • 不同子任务需要不同模型、不同 SLA、不同治理策略 • 需要跨团队独立演进和发布
9.2 六类常见协作模式
9.3 一个更实用的原则:先领域化,再 Agent 化
不要先问“我有几个 Agent”。
应该先问:
• 我有哪些领域边界 • 哪些能力是解释型,哪些是事务型 • 哪些副作用必须走强规则 • 哪些组件需要独立扩缩容
通常更稳妥的演进路径是:
1. 先按领域拆工具与服务 2. 再把高复杂度领域演化为独立 Agent 3. 最后把长流程统一收敛到 Graph/Workflow
9.4 多 Agent 拆分后的治理重点
多 Agent 一旦拆出来,最容易失控的不是模型质量,而是“系统间协议不一致”。
必须统一的内容包括:
• 消息契约 • traceId 与 threadId 传递 • 工具与子 Agent 权限边界 • 统一错误码与恢复动作 • 统一审计字段 • 统一超时与重试原则
如果这些不统一,多 Agent 只会把复杂度从单进程扩散到整个系统。
10. 上下文工程:不是保存聊天记录,而是控制推理成本
很多人把上下文工程理解成“把聊天记录塞给模型”。
这只是最原始、也是最昂贵的做法。
10.1 生产里的上下文应该分层
建议至少拆成四层:
1. 会话层
最近几轮用户与助手对话。2. 任务层
当前工作流状态、待确认动作、工具中间结果。3. 记忆层
用户画像、长期偏好、历史关键事实摘要。4. 知识层
外部知识库召回结果、规则说明、业务文档。
这四层不能简单拼成一个大 prompt,而应按场景装配。
10.2 摘要不是压缩文本,而是压缩状态
一个好的摘要策略,不只是“把历史消息变短”,而是保留后续推理真正需要的信息:
• 用户目标 • 已确认事实 • 已完成动作 • 未完成动作 • 风险决策结果 • 当前约束条件
例如:
public record ConversationSummary(
String userGoal,
List<String> confirmedFacts,
List<String> pendingActions,
List<String> riskNotes,
Instant updatedAt
) {
}这类结构化摘要,对后续推理远比自然语言长段总结更稳定。
10.3 上下文装配策略建议
一个常见的装配流程如下:
用户输入
->
意图识别
->
加载最近消息
->
加载结构化摘要
->
如果需要,加载用户长期记忆
->
如果需要,做知识检索
->
根据工具可见性筛选工具描述
->
构造最终 Prompt这里最关键的能力不是“存”,而是“选”。
上下文工程本质上是一个装配与裁剪问题。
10.4 长对话下的实用建议
• 最近消息只保留必要窗口,不要无限追加 • 对工具返回做结构化摘要,而不是原样拼接 • 高频固定规则放 system prompt,不要轮轮重复拼接 • 对知识库结果先重排再注入 • 对跨天会话优先加载摘要,不直接加载全量历史
11. 工具治理与安全护栏:避免 Agent 从“聪明”变成“危险”
11.1 工具设计的第一原则:模型负责决策,不负责越权
大模型非常擅长“决定下一步可能是什么”,但它不应该直接拥有无限业务执行权限。
因此在企业系统中,我们一般会把工具分成三类:
11.2 建议使用“意图识别 + 权限判断 + 工具可见性”三段式
public interface ToolVisibilityPolicy {
List<ToolCallback> selectTools(String tenantId, String intent, String riskLevel);
}
@Component
public class DefaultToolVisibilityPolicy implements ToolVisibilityPolicy {
private final ToolRegistry toolRegistry;
public DefaultToolVisibilityPolicy(ToolRegistry toolRegistry) {
this.toolRegistry = toolRegistry;
}
@Override
public List<ToolCallback> selectTools(String tenantId, String intent, String riskLevel) {
return toolRegistry.list().stream()
.filter(tool -> tool.supportsIntent(intent))
.filter(tool -> tool.supportsTenant(tenantId))
.filter(tool -> tool.allowForRiskLevel(riskLevel))
.map(RegisteredTool::callback)
.toList();
}
}这样做的价值非常大:
• 降低 prompt 体积 • 降低工具误选率 • 避免高风险工具暴露给不该用的请求
11.3 Human-in-the-Loop 不该只是一句“请人工确认”
人工审批真正要解决的是:
• 暂停在哪个节点 • 给审核员看到哪些上下文 • 审核超时怎么办 • 审核拒绝后如何回退 • 审核通过后如何恢复执行
因此,一套完整的人审链路至少应包含:
• 审批任务 ID • 待审批动作摘要 • 风险标签 • 审批 SLA • 审批结果回写接口 • 流程恢复机制
11.4 输出也要治理
很多团队只拦工具,不拦输出。
这同样危险。
需要治理的输出包括:
• 泄露内部策略 • 泄露其他用户信息 • 承诺超出公司政策的赔付 • 输出未校验的结构化数据 • 伪造工具执行结果
因此建议在最终输出前增加:
• 敏感词与敏感字段检查 • JSON Schema 验证 • 关键业务承诺规则校验 • 用户可见信息脱敏
12. 可观测、压测与故障演练:生产系统的生命线
一个没有观测的 Agent 系统,本质上不可运维。
12.1 至少观测四条链路
1. 对话链路
用户输入、模型响应、流式事件、完成状态。2. 工具链路
哪个工具被调用、入参是什么、耗时多久、是否失败。3. 工作流链路
当前节点、状态变更、分支决策、中断恢复。4. 成本链路
用了哪个模型、多少 token、多少钱、失败重试次数。
12.2 推荐的指标体系
ai_requests_total | |
ai_request_duration_seconds | |
ai_stream_first_token_seconds | |
ai_model_calls_total | |
ai_model_errors_total | |
ai_tool_calls_total | |
ai_tool_timeout_total | |
ai_human_review_total | |
ai_workflow_resume_total | |
ai_token_input_total | |
ai_token_output_total | |
ai_cost_total |
12.3 Trace 才是排障关键
一次用户请求最好贯穿以下组件共享同一个 traceId:
• 网关 • Agent Runtime • Tool Service • Graph Workflow • 审批系统 • 审计系统
这样在排查“为什么这次退款失败”时,才能看到完整链路:
用户请求
-> Agent 判定退款意图
-> 调用资格校验工具
-> 风险评分过高
-> 进入人工审批
-> 审批超时
-> 返回用户稍后通知没有链路追踪时,这些信息会散落在多个日志系统里,基本无法复盘。
12.4 压测不能只测 HTTP QPS
AI 系统压测至少要覆盖:
• 普通问答压测 • 知识检索压测 • 工具调用压测 • 流式连接数压测 • 审批中断恢复压测 • Provider 限流场景压测 • 下游服务超时场景压测
尤其要模拟这些异常:
• 模型 429/5xx • Redis 短时抖动 • 工具服务 3s 到 10s 延迟飙升 • SSE 客户端主动断连 • Graph 恢复点读取失败 • 审批系统回调重复投递
12.5 故障演练要从第一天开始
建议建立最基础的故障演练清单:
• 模型超时后是否降级到低阶模型或人工兜底 • 工具调用失败后是否给出明确用户提示 • 重启后是否能恢复待审批流程 • Redis 故障时是否能切换只读能力 • 审批超时后是否能关闭流程并归档
AI 系统的可靠性不是写出来的,是演练出来的。
13. 部署落地:Kubernetes、Redis、Nacos、消息队列如何协同
13.1 K8s 部署基线
apiVersion: apps/v1
kind: Deployment
metadata:
name: customer-service-agent
spec:
replicas: 4
selector:
matchLabels:
app: customer-service-agent
template:
metadata:
labels:
app: customer-service-agent
annotations:
prometheus.io/scrape: "true"
prometheus.io/port: "8080"
spec:
containers:
- name: app
image: registry.example.com/ai/customer-service-agent:1.0.0
ports:
- containerPort: 8080
env:
- name: AI_DASHSCOPE_API_KEY
valueFrom:
secretKeyRef:
name: ai-secrets
key: dashscope-api-key
- name: SPRING_DATA_REDIS_CLUSTER_NODES
value: redis-0:6379,redis-1:6379,redis-2:6379
- name: SPRING_CLOUD_NACOS_DISCOVERY_SERVER_ADDR
value: nacos-headless:8848
resources:
requests:
cpu: "2"
memory: "2Gi"
limits:
cpu: "4"
memory: "4Gi"
readinessProbe:
httpGet:
path: /actuator/health/readiness
port: 8080
livenessProbe:
httpGet:
path: /actuator/health/liveness
port: 8080几点建议:
• AI 服务不要把资源申请压得过低,否则高峰期 GC 和线程争用会非常明显 • SSE 场景要特别关注连接数上限和网关超时 • readiness 一定要覆盖关键依赖的可用性检查
13.2 Redis 在架构里的角色
Redis 通常承担:
• 会话上下文缓存 • 工作流状态恢复 • 去重锁与并发锁 • Token 预算与限流辅助 • 热点工具结果缓存
因此 Redis 对 AI 系统往往是核心依赖,而不是“可有可无的优化组件”。
13.3 Nacos 与 A2A 的价值
当多 Agent 或领域服务拆分后,Nacos 这类注册发现组件开始发挥价值:
• 发现不同 Agent 服务实例 • 动态路由到不同领域能力 • 支持按版本灰度 • 支持环境隔离
如果再结合 A2A 通讯能力,多 Agent 间调用可以更加标准化,而不是到处手写 HTTP Client。
13.4 MQ 不是为了炫架构,而是为了隔离副作用
以下场景建议天然异步化:
• 用户会话结束后的审计归档 • 长耗时知识处理 • 工单创建后的后续通知 • 风险复核回调 • 人工审批后的流程恢复事件
这样可以把对话链路 RT 和副作用链路解耦开。
13.5 Nginx 与 SSE 的配置注意点
很多流式问题不在应用,而在代理层:
location /api/ai/conversations/stream {
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_buffering off;
proxy_cache off;
proxy_read_timeout 3600s;
proxy_send_timeout 3600s;
chunked_transfer_encoding on;
}关键点:
• 关闭 buffering • 保持长连接 • 放宽读写超时
否则本地能流式,线上经常只剩“攒一坨再吐给前端”。
14. 常见误区与架构建议
14.1 误区一:把 Agent 当成 Service 方法
错误做法:
• Controller 直接调 Agent • Agent 直接调数据库 • Tool 直接做副作用
后果是边界混乱,无法治理,无法恢复,无法审计。
建议:
• Agent 负责决策 • 应用服务负责编排 • 领域服务负责业务规则 • Workflow 负责长流程与恢复
14.2 误区二:把长对话历史全量喂给模型
后果:
• 成本失控 • 延迟升高 • 噪声信息干扰推理
建议:
• 结构化摘要 • 分层记忆 • 按意图装配上下文
14.3 误区三:看到多 Agent 就兴奋
后果:
• 系统拆得太早 • 观测、协议、审计跟不上 • 实际收益不如单 Agent + Graph
建议:
• 先单 Agent 做稳 • 再按领域与 SLA 拆分
14.4 误区四:让模型直接操作核心交易
后果:
• 风险不可控 • 副作用重复 • 审计链断裂
建议:
• 通过工具门面层隔离 • 高风险动作审批 • 副作用动作幂等
14.5 误区五:只有日志,没有回放
很多 AI 问题单看日志定位不了,因为模型推理、工具调用、状态流转是一个复合过程。
建议:
• 保留消息链 • 保留工具入参与结果摘要 • 保留工作流节点轨迹 • 建立最小化回放能力
15. 演进路线:从单体 AI 能力到企业级 Agent 平台
阶段一:能力验证期
目标:
• 快速验证用户价值 • 建立最小工具集合 • 跑通基础流式会话
架构特征:
• 单体 Spring Boot • 单 Agent • Redis 做基础会话缓存
阶段二:生产接入期
目标:
• 接业务真实流量 • 建立审计、限流、回放、降级 • 引入摘要与成本治理
架构特征:
• Agent + 应用服务分层 • Tool 门面层 • SSE + Redis 状态共享 • 基础观测与告警
阶段三:流程化协同期
目标:
• 把高风险、高价值动作流程化 • 建立审批与恢复能力 • 控制副作用与幂等
架构特征:
• 引入 StateGraph • 引入人工审批与任务恢复 • 引入 MQ 异步事件
阶段四:多 Agent 平台期
目标:
• 按领域拆分能力 • 按团队独立演进 • 建立统一协议、观测、治理平台
架构特征:
• 多 Agent / A2A • 注册发现 • Prompt Ops、Eval、灰度发布 • 统一策略中心与审计中心
16. 总结
Spring AI Alibaba 真正值得企业团队重视的地方,不是它把 Tool Calling、多 Agent、Graph、SSE 这些关键词堆在了一起,而是它给 Java 体系提供了一条更像“工程建设”而不是“脚本拼装”的 AI 落地路径。
如果要把全文压缩成一句话,我会这样总结:
Spring AI Alibaba 的核心价值,是把 AI 应用从“模型调用代码”提升为“有消息契约、有状态编排、有治理护栏、有恢复能力”的生产系统。
真正的生产级落地,建议你重点抓住以下几件事:
1. 先把 Message 当成基础设施来设计,而不是聊天记录 2. 用协议层、治理层、状态层拆开 AI 系统复杂度 3. 让 Agent 负责决策,让工作流负责副作用和恢复 4. 把高并发、幂等、预算、限流、观测当成第一天就要建设的能力 5. 多 Agent 不要为了概念而拆,而要为了领域边界、SLA 和治理边界而拆
最后再强调一次,AI 应用的难点并不是“模型会不会回答”,而是:
• 回答是否可信 • 工具是否安全 • 状态是否可恢复 • 成本是否可控 • 系统是否经得住高峰和故障
如果你的目标不是做一个 Demo,而是做一个能进入生产、能承担真实业务责任的 AI 系统,那么 Spring AI Alibaba 的正确打开方式一定不是“从 Prompt 开始”,而是“从架构开始”。
附:生产落地检查清单
在项目上线前,建议至少逐项确认以下内容:
架构与边界
• 是否区分了协议层、治理层、状态层 • 是否区分了对话决策与业务副作用 • 是否避免 Controller 直接驱动复杂 Agent 逻辑
消息与状态
• 是否有统一 Message 契约 • 是否支持会话状态共享 • 是否支持长流程中断与恢复 • 是否对关键动作做了幂等控制
工具与安全
• 是否有工具白名单/可见性控制 • 是否对高风险工具做审批或规则保护 • 是否对最终输出做脱敏与合规校验
性能与成本
• 是否做上下文裁剪与摘要 • 是否做用户级、租户级、模型级、工具级限流 • 是否有模型路由与降级策略 • 是否有 Token/费用预算控制
可观测与运维
• 是否有模型、工具、流程、成本四类指标 • 是否有全链路 traceId • 是否可回放典型会话与工作流 • 是否做过限流、超时、断连、恢复等故障演练
这份清单做完,才算真正进入“生产级”。
夜雨聆风