AI 小红书文案生成器之智能体编排设计思路(一)
一、开发背景构建一套基于多智能体协作的 AI 文章生成引擎核心目标是用户只需输入一个选题系统即可自动完成从标题构思、大纲规划、正文撰写到配图插入的完整文章创作流程。1.1、核心需求与业务建模人类撰写一篇优质文章通常遵循固定的创作链路先构思吸引人的标题再列出大纲理清结构随后逐段展开正文最后为文章配上合适的插图。我们将这一人类写作过程抽象为5 个智能体组成的创作流水线标题生成智能体根据选题输出爆款标题方案大纲生成智能体基于标题规划文章结构正文创作智能体依据大纲逐段输出 Markdown 格式正文配图分析智能体阅读正文内容确定哪些章节需要配图及检索关键词 配图生成智能体调用图库 API 检索图片并插入正文对应位置。1.2、技术挑战与体验瓶颈然而仅实现 5 个智能体的串行编排还远远不够。AI 生成一篇完整的图文文章通常需要几十秒甚至一两分钟如果用户提交选题后只能面对空白页面被动等待不仅体验极差还极易因 HTTP 超时导致任务失败。此外LLM 的生成过程对用户而言是一个黑盒用户无法感知当前进行到哪一步、是否卡住。为解决上述问题我们在系统设计中重点做了以下三项体验优化流式输出在生成大纲和正文时采用 LLM 流式接口如 Spring AI 的 Flux将模型输出的每个文本片段通过 SSE 实时推送到前端实现类似打字机的逐字显示效果让用户看得见内容在生长。实时进度推送通过 SSEServer-Sent Events长连接在每个智能体步骤完成时向前端推送状态消息如 agent1_complete、agent2_streaming、image_complete 等使用户随时掌握当前所处的创作阶段。异步执行文章生成任务通过 Async(“articleExecutor”) 提交到独立的后台线程池处理Controller 层在创建任务记录后立即返回 taskId避免长时间占用 Tomcat 主线程既消除了接口超时风险又释放了系统并发能力。1.3、架构解耦设计回调驱动推送在实现实时进度推送时我们面临一个架构层面的问题智能体编排层AgentService不应直接感知 SSE 或 HTTP 等传输细节。如果让 AgentService 直接调用 SseEmitterManager.send()会导致生成逻辑与消息推送基础设施强耦合不仅难以单元测试后续更换推送方式如改为 WebSocket 或消息队列时也会牵一发而动全身。因此我们引入了 Consumer 进行解耦ArticleAgentService 作为纯粹的生成编排器只负责调用 LLM、解析结果、维护 ArticleState 状态。当它需要通知外界时仅调用入参 streamHandler.accept(message)完全不关心消息最终去向。ArticleAsyncService 在调用 AgentService 时传入一个 Lambda 表达式作为 streamHandler 的具体实现。该 Lambda 内部完成消息加工buildMessageData和 SSE 推送sseEmitterManager.send将传输层逻辑收敛在异步调度层。这种设计使得 AgentService 可以独立测试传入一个收集消息的 List 即可验证输出而 SSE 推送细节可以在不改动生成逻辑的前提下灵活替换。系统定位本系统采用 Spring Boot Spring AI SSE 技术栈底层接入 DashScope 大模型服务通过同步创建 异步生成 流式推送 回调解耦的架构模式将传统需要数小时的人工写作过程压缩到分钟级同时保证了生成过程的可观测性、用户交互的实时性以及核心生成逻辑与基础设施的解耦为自动化内容生产提供了一套可落地、可扩展的工程方案。二、智能体编排设计思路阶段一前端创建任务同步前端向服务端发送 POST /article/create 请求请求体中携带选题topic。Controller 层的createArticle 方法首先进行参数校验确保 topic 不为空。从 HTTP请求上下文中提取当前登录用户loginUser。调用 articleService.createArticleTask在数据库中创建一条任务记录状态为 PENDING并生成全局唯一的 taskId。调用 articleAsyncService.executeArticleGeneration(taskId, topic)将该任务提交到名为 articleExecutor 的异步线程池中执行。Controller 立即返回 taskId 给前端不等后台生成完成。整个同步阶段通常在 100 毫秒内结束。阶段二前端建立 SSE 长连接同步前端拿到 taskId 后立即向服务端发起 GET /article/progress/{taskId} 请求建立 SSEServer-Sent Events长连接。Controller 层的 getProgress 方法先校验用户是否有权访问该任务通过articleService.getArticleDetail 隐式鉴权。调用sseEmitterManager.createEmitter(taskId)在内存中建立一个与 taskId 绑定的 SseEmitter 对象并返回给前端。此时 SSE 连接保持打开状态前端进入等待接收实时消息的状态。阶段三后台异步执行文章生成异步线程异步线程开始执行 ArticleAsyncService.executeArticleGeneration初始化将数据库中该任务的状态更新为 PROCESSING。创建一个 ArticleState 状态对象注入 taskId 和 topic用于在整个生成流程中累积中间结果标题、大纲、正文、配图需求、配图结果、完整内容。构造一个 Consumer 类型的 streamHandlerLambda 表达式。它的逻辑是每当 Agent 层产生一条消息字符串就调用 handleAgentMessage(taskId, message, state)将消息加工为结构化 JSON再通过 sseEmitterManager.send() 推送给前端。调用articleAgentService.executeArticleGeneration(state, streamHandler)进入 5 个智能体的串行编排。Agent 1生成标题非流式agent1GenerateTitle 构造提示词调用 callLlm非流式同步调用 DashScope。DashScope 一次性返回完整的标题 JSON。解析 JSON将标题结果存入 state.title。 调用streamHandler.accept(“agent1_complete”)。handleAgentMessage 将消息包装为 {“type”:“agent1_complete”,“title”:{…}}通过 SSE 推送给前端。Agent 2生成大纲流式agent2GenerateOutline 构造提示词调用 callLlmWithStreaming。callLlmWithStreaming 内部调用 DashScope 的 stream() 方法发起流式请求。在 blockLast() 阻塞等待期间每收到一个文本片段chunk立即做两件事 ①将 chunk 追加到StringBuilder 中累积完整内容 ②调用 streamHandler.accept(“AGENT2_STREAMING:” chunk)将片段实时推送给前端。流结束后返回完整的大纲 JSON 字符串。解析 JSON将大纲存入 state.outline。调用 streamHandler.accept(“agent2_complete”)推送{“type”:“agent2_complete”,“outline”:[…]}。Agent 3生成正文流式agent3GenerateContent 将标题和大纲拼接进提示词再次调用 callLlmWithStreaming。与 Agent 2 相同在 DashScope 流式返回期间每个 chunk 都通过 streamHandler 实时推送给前端type 为 agent3_streaming。流结束后将完整正文存入 state.content。推送 agent3_complete。Agent 4分析配图需求非流式agent4AnalyzeImageRequirements 将标题和正文作为上下文调用 callLlm非流式。DashScope 返回配图需求 JSON 数组包含每张图的位置、关键词、章节标题等。解析后存入 state.imageRequirements。推送 agent4_complete。Agent 5生成配图串行执行agent5GenerateImages 遍历 state.imageRequirements 中的每一条配图需求逐个串行处理。对每一条需求调用 imageSearchService.searchImage(keywords) 检索图片。降级策略如果检索失败返回 null则调用 getFallbackImage 获取 Picsum 占位图 URL。构建 ImageResult 对象包含图片 URL、位置、检索方式等加入 state.images 列表。每完成一张图立即调用 streamHandler.accept(“IMAGE_COMPLETE:” imageJson)推送 {“type”:“image_complete”,“image”:{…}}。前端可以逐张看到配图生成进度。所有配图处理完毕后推送 agent5_complete。图文合成mergeImagesIntoContent 读取 state.content按行解析。每当遇到以 ## 开头的章节标题时检查state.images 中是否有图片的 sectionTitle 与该章节匹配。若匹配则在章节标题后插入 Markdown 图片标签 (URL)将合成后的完整 Markdown 存入 state.fullContent。推送 merge_complete携带完整的 fullContent。阶段四收尾与状态更新回到 ArticleAsyncService调用articleService.saveArticleContent(taskId,state)将标题、大纲、正文、配图、完整内容等一次性持久化到数据库。更新数据库状态为 COMPLETED。推送 all_complete 消息通知前端整个任务已结束。调用 sseEmitterManager.complete(taskId)关闭 SSE 连接释放资源。异常分支任意步骤失败如果在 Agent 1 到 Agent 5 或图文合成的任意步骤中抛出异常异常被 ArticleAsyncService.executeArticleGeneration 的 catch 块捕获。打印错误日志。更新数据库状态为 FAILED并记录错误信息。通过 SSE 推送 {“type”:“error”,“message”:“…”} 给前端。调用 sseEmitterManager.complete(taskId)关闭 SSE 连接避免前端长时间挂起。核心设计要点总结同步与异步分离Controller 只负责任务创建和 SSE 连接建立所有耗时操作都在 Async 线程中执行。流式与非流式混用Agent 2、3 使用流式调用给用户打字机般的实时体验Agent 1、4 使用非流式因为结果是结构化 JSON必须完整接收后才能解析。回调驱动推送AgentService 不直接操作 SSE而是通过 Consumer 回调将消息交给 AsyncService 层统一封装和推送实现了生成逻辑与消息推送的解耦。状态累积ArticleState 对象贯穿整个流程像流水线一样逐步填充数据最终一次性落库。