多智能体实战:基于 Spring AI Alibaba 构建生产级高并发故事创作智能体系统
1. 引言
在很多团队的实际落地过程中,“多智能体”很容易停留在 Demo 层面:
- 能跑通几个 Agent 互相调用
- 能生成一段看起来不错的内容
- 能在单机环境完成一次链路演示
但一旦进入真实业务环境,问题会马上出现:
- 请求量上来后,模型调用延迟和成本不可控
- 多个智能体之间上下文不一致,生成结果互相冲突
- 缺少编排、限流、重试、降级、缓存、可观测,系统无法稳定运行
- 代码层面只展示了简单样例,距离生产级实现差距很大
本文不再停留在“如何写一个多 Agent Demo”,而是从架构师和资深工程师视角,系统讲清楚如何基于 Spring AI Alibaba 构建一个真正可落地的 故事创作多智能体系统。文章重点包括:
- 多智能体协作的原理与架构设计
- 面向高并发场景的工程化方案
- 生产级代码实现与核心设计模式
- 缓存、异步化、限流、降级、可观测与部署方案
- 一个贴近业务的完整案例:在线故事创作平台
如果你希望的不只是“能生成故事”,而是“能支撑业务上线”,这篇文章会更接近你真正需要的答案。
2. 业务背景与问题定义
2.1 目标业务场景
我们假设要建设一个在线 AI 创作平台,面向 C 端作者、内容社区、教育平台或互动小说应用,提供如下能力:
- 用户输入主题、风格、受众、篇幅、角色偏好
- 系统自动完成故事策划、角色设定、场景设计、章节生成、风格统一
- 支持大批量并发生成
- 支持流式返回和异步任务模式
- 支持作品二次编辑、续写、重写
典型请求示例:
{ "theme": "废土世界中的亲情与救赎", "genre": "科幻冒险", "targetAudience": "18+", "chapters": 6, "style": "偏电影化叙事", "keywords": ["AI失控", "避难所", "兄妹", "牺牲"], "language": "zh-CN" }2.2 为什么适合用多智能体
故事创作天然不是一个单点能力问题,而是一个复杂任务分解问题。一个高质量故事至少包含:
- 世界观和主线冲突设计
- 角色目标、动机与成长弧
- 场景和章节推进
- 文风统一和润色
- 结果校验与质量评估
如果把所有任务交给一个“大一统 Prompt”,常见问题是:
- 输出长度不稳定
- 结构松散
- 风格漂移
- 人物设定前后矛盾
- 场景信息遗漏
因此更合理的方式是:将复杂创作任务拆解为多个专业化智能体,由编排层协调执行,再由汇总智能体整合输出。
2.3 真实工程挑战
当系统进入生产环境,真正难的不是“写 Prompt”,而是解决以下问题:
- 如何定义智能体职责边界,避免重复劳动和上下文污染
- 如何保证多个智能体之间的数据契约一致
- 如何让模型调用具备超时、重试、熔断、限流能力
- 如何应对高并发下的线程池、连接池、消息积压与缓存击穿
- 如何支持同步、流式、异步三种调用模式
- 如何沉淀监控指标,定位慢调用、坏输出和成本异常
3. 多智能体系统的核心设计原理
3.1 从“Prompt 拼接”到“任务编排”
多智能体系统的本质不是多个 LLM 实例,而是 面向复杂任务的分工协作系统。它通常由四层组成:
- 接入层:接收用户请求,做鉴权、限流、参数校验
- 编排层:负责任务拆解、执行顺序、并行控制、超时控制
- 智能体层:执行具体任务,如情节设计、人物塑造、风格统一
- 基础设施层:模型网关、缓存、数据库、MQ、监控、日志、追踪
3.2 多智能体协作模式
常见协作模式有三种:
模式一:串行链式协作
适合强依赖型流程,例如:
- 先生成故事大纲
- 再基于大纲生成人物
- 再基于人物和大纲生成章节
优点:
- 结果可控
- 上下文依赖明确
缺点:
- 总耗时较长
- 某一步失败会阻塞整体
模式二:并行协作
适合弱依赖或可提前拆分的任务,例如:
- 角色设定
- 场景设定
- 风格约束
优点:
- 总时延低
- 适合高并发优化
缺点:
- 汇总阶段复杂
- 容易出现结果不一致
模式三:评审闭环协作
适合质量要求高的场景:
- 生成智能体产出初稿
- 评审智能体检查逻辑、风格、重复率、敏感内容
- 修订智能体进行再加工
优点:
- 内容质量更稳定
缺点:
- Token 成本更高
- 处理链路更长
实际生产中,往往是“串行 + 并行 + 评审闭环”的组合。
3.3 本文采用的协作拓扑![]()
这里有三个关键设计思想:
- 共享上下文对象:避免每个 Agent 自己拼 Prompt,导致上下文失真
- 编排层控制执行:Agent 只关注能力,不关心调度细节
- 评审闭环:将质量控制前置,而不是把低质量输出直接交给用户
4. 生产级总体架构设计
4.1 系统总体架构![]()
4.2 分层职责
1)API Gateway
职责:
- Token 校验与租户隔离
- 接口限流和黑白名单
- 请求透传 traceId
- 路由同步接口与异步接口
2)应用服务层
职责:
- 参数校验
- 幂等控制
- 返回任务单或流式响应
- 选择合适执行模式
3)编排层 Orchestrator
职责:
- 任务拆解
- 并行编排
- 超时控制
- 审核闭环
- 失败补偿
4)Agent 能力层
职责:
- 只负责领域任务
- 不耦合数据库、MQ、控制器
- 输入输出通过 DTO/Domain Object 约束
5)基础设施层
职责:
- 模型调用适配
- 缓存
- 消息驱动异步执行
- 数据持久化
- 监控和审计
4.3 为什么不建议直接在 Controller 里串 Agent
很多初学者实现是这样的:
- Controller 接收请求
- 直接 new/注入多个 Agent
- 顺序调用模型
- 拼一个结果返回
这种做法在 Demo 阶段没问题,但在生产环境存在明显风险:
- 缺少职责分离,无法扩展
- 无法复用编排逻辑
- 无法做统一超时、熔断、重试
- 测试困难
- 后续难以接入异步任务、审核流、回调机制
正确方式应该是:
- Controller 只做接入
- Application Service 只做用例编排入口
- Orchestrator 负责业务级工作流
- Agent 负责纯能力实现
5. 技术选型与理由
| 组件 | 技术选型 | 作用 | 选型理由 |
|---|---|---|---|
| Web 框架 | Spring Boot 3.x | 服务承载 | 生态成熟,便于整合监控、配置、数据层 |
| AI 框架 | Spring AI Alibaba | 模型接入 | 统一抽象 ChatModel、Prompt、Advisor、Tool 等能力 |
| 服务治理 | Spring Cloud Alibaba | 配置/注册发现 | 与 Nacos、Sentinel 等整合较强 |
| 缓存 | Redis | 结果缓存/幂等/热点保护 | 高性能,适合分布式场景 |
| 数据库 | MySQL 8.x | 请求、作品、审计持久化 | 事务与结构化查询能力稳定 |
| MQ | Kafka 或 RocketMQ | 异步生成、削峰填谷 | 解耦同步链路与离线生成 |
| 限流熔断 | Sentinel / Resilience4j | 流量治理 | 保护模型网关和下游服务 |
| 可观测 | Micrometer + Prometheus + Grafana + Zipkin/Tempo | 监控与追踪 | 便于定位慢调用和异常链路 |
| 容器化 | Docker + Kubernetes | 部署与扩缩容 | 生产环境标准配置 |
6. 领域建模:先把数据结构设计对
多智能体系统最容易失控的点之一,就是各 Agent 输入输出随意定义。生产环境里,必须先做 统一领域对象建模。
6.1 核心请求对象
package com.example.story.domain; import jakarta.validation.constraints.Max; import jakarta.validation.constraints.Min; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotEmpty; import java.util.List; public record StoryRequest( @NotBlank String requestId, @NotBlank String theme, @NotBlank String genre, @NotBlank String style, @NotBlank String targetAudience, @Min(1) @Max(20) int chapters, @NotEmpty List<String> keywords, String language ) { }6.2 共享上下文对象
package com.example.story.domain; import java.time.Instant; import java.util.ArrayList; import java.util.List; public class StoryContext { private final String requestId; private final StoryRequest request; private StoryOutline outline; private List<StoryCharacter> characters = new ArrayList<>(); private List<StoryScene> scenes = new ArrayList<>(); private List<StoryChapter> chapters = new ArrayList<>(); private StoryReview review; private Instant startedAt = Instant.now(); public StoryContext(String requestId, StoryRequest request) { this.requestId = requestId; this.request = request; } public String requestId() { return requestId; } public StoryRequest request() { return request; } public StoryOutline outline() { return outline; } public void setOutline(StoryOutline outline