可暂停的Agent编排引擎

从 0 实现「可暂停、可断点续跑、可运行时改需求」的 Multi-Agent 编排引擎

本文通过一个真实的微信 AI 助手项目,拆解多智能体任务编排中的三个企业级痛点:暂停/恢复、断点续执行、运行时补充指令,并给出完整的工程实现思路。

一、为什么需要"可暂停"的 Agent 编排?

想象一个场景:你在微信里对一个 AI 助手说------

"帮我在 B 站发一条动态,配文是'周末愉快',再配一张图。"

这个任务会被拆解成多步编排:

scss 复制代码
ROUTER 分析意图 → 生成编排计划 (发动态 + 配图)
   ↓
IMAGE Agent:生成配图
   ↓
BILIBILI Agent:调用 API 发布动态

如果一切顺利,几秒钟就能跑完。但现实往往是:

  • 配文写错了,你想中途改一下
  • 图生成了,但你想换成另一张
  • 任务跑到一半,你想先停一下,过会儿再继续

传统的 Agent 调用是一次性的「请求 → 响应」,跑起来就停不下来。一旦开始执行,你只能等它跑完,或者干脆杀掉重来------这既不优雅,也浪费 Token。

这就是"可状态化的任务编排"要解决的问题。


二、三个核心能力的设计

我把这个能力拆成三个正交的维度,分别攻克:

1. 暂停 / 恢复(Pause / Resume)

难点 :Agent 执行是在虚拟线程里跑的,而"暂停"这个指令来自另一个线程(微信消息回调线程)。这是一个典型的跨线程状态同步问题。

方案 :用一个 ConcurrentHashMap 保存每个用户的控制状态,用 AtomicReference 保证可见性:

java 复制代码
public enum ControlState { NORMAL, PAUSED, CANCELLED }

private static final ConcurrentHashMap<String, AtomicReference<ControlState>> STATES =
        new ConcurrentHashMap<>();
  • 用户发"暂停" → setPaused(userId),只是设一个标志位

  • 编排执行循环里,每执行一步前主动检查 这个标志位:

    java 复制代码
    for (int i = 0; i < sorted.size(); i++) {
        if (TaskControlContext.isPaused(userId)) {
            savePausedTask(userId, snapshot);  // 保存快照
            return;                            // 优雅退出
        }
        executeStep(sorted.get(i));
    }

这就是经典的协作式中断(cooperative interruption)思想:不强制打断正在跑的 LLM 调用,而是在步骤边界上检查,让任务"体面地"停下来。

2. 断点续执行(Checkpoint Resume)

难点 :任务停了,但要能从停的地方继续,而不是从头再来。这意味着必须保存完整的执行上下文。

方案 :引入一个不可变的快照对象 PausedTaskState:

java 复制代码
public record PausedTaskState(
        List<PlanStep> steps,           // 拓扑排序后的步骤列表
        Map<Integer, String> stepOutputs, // 已完成步骤的输出
        int nextStepIndex,              // 从哪个索引继续
        String userId,
        String sessionId,               // 会话上下文
        String userText,                // 原始用户输入
        List<ReceivedContent> attachments, // 附件(图片/文档)
        String imageRunId,
        List<ReceivedContent> pendingGeneratedFiles, // 跨线程暂存文件
        String pendingModifyInstruction  // 暂停期间的补充指令
) {}

恢复时,直接复用已完成步骤的输出,只执行剩余步骤:

java 复制代码
int startIndex = state.nextStepIndex();
for (int i = startIndex; i < sorted.size(); i++) {
    executeStep(sorted.get(i));
}

这里有个极易踩的坑,也是我花最多时间调试的地方:

ThreadLocal 跨线程失效 。FILE Agent 生成的文档原本存在 ThreadLocal 里,但暂停时线程结束了,恢复时换了个新线程,ThreadLocal 里的数据就丢了。

解决办法是在快照里显式地保存这些跨线程数据:

java 复制代码
List<ReceivedContent> pendingFiles = PENDING_GENERATED_FILES.get(); // 从 ThreadLocal 取出
// 存进快照,恢复时再 set 回新的 ThreadLocal

3. 运行时补充指令(Runtime Modify)

这是最亮眼的一个能力,也是真正让 Agent 显得"智能"的地方。

场景 :任务暂停后,用户说"把配文改成'今晚八点直播见'"。这个指令要在恢复执行时,注入到还没来得及执行的步骤里,让对应 Agent 按新要求产出。

方案 :快照里加一个 pendingModifyInstruction 字段,暂停期间允许累积多条指令:

java 复制代码
public PausedTaskState withModifyInstruction(String extra) {
    String merged = (this.pendingModifyInstruction() == null || isBlank())
            ? extra
            : this.pendingModifyInstruction() + ";" + extra;  // 分号分隔累积
    return new PausedTaskState(..., merged);
}

恢复执行时,把这段指令追加到未完成步骤的上下文末尾:

java 复制代码
private String buildContextualInput(PlanStep step, ..., String modifyInstruction) {
    String base = buildContextualInput(step, ...); // 原有上下文
    if (modifyInstruction == null || modifyInstruction.isBlank()) {
        return base;
    }
    return base + "\n\n【重要·用户中途修改要求(严格遵守,覆盖原指令中冲突部分)】\n"
            + modifyInstruction;
}

关键在于提示词里那个"覆盖原指令中与之冲突的部分"------这让 LLM 知道:新指令的优先级更高。


三、完整的状态机

三个能力串起来,是一个清晰的状态机:

css 复制代码
        ┌─── 暂停 ──→ PAUSED ── 继续 ──┐
        │                               │
     NORMAL ────────────────────────────┘
        │
        └─── 取消 ──→ CANCELLED(清理资源)
  • NORMAL:正常执行
  • PAUSED:已冻结,资源保留等待恢复
  • CANCELLED:已取消,清理干净(图片缓存、附件、B站上下文等)

取消时特别注意资源清理,否则会有内存泄漏------这也是生产环境最容易出 bug 的地方。


四、为什么用 Actor 模型?

还有一个隐藏的设计:每个用户的消息用一个独立的虚拟线程(Java 21 Virtual Thread)+ 队列串行处理。这保证了:

  1. 同用户消息严格有序------不会出现"暂停"和"继续"乱序执行
  2. 不同用户完全并行------互不阻塞
java 复制代码
// Per-user Actor:每个用户一个虚拟线程 + ConcurrentLinkedQueue

这是把"并发任务控制"做对的根基:状态隔离。每个用户的状态是独立的,暂停一个用户不影响其他人。


五、踩坑总结

回顾整个实现,最有价值的教训是:

坑 表现 解法
ThreadLocal 跨线程丢失 恢复后拿不到已生成的文件 快照显式保存,恢复时回填
暂停标志位竞态 "暂停"指令晚于"继续"到达 Actor 串行 + AtomicReference
指令覆盖不彻底 LLM 忽略新指令 提示词里强调"覆盖冲突部分"
取消后资源泄漏 图片缓存残留 统一 resource cleanup 流程
多次暂停状态丢失 二次暂停覆盖首次快照 快照设计为不可变 + 累积字段

六、总结

"可暂停的 Agent 编排"是一个看起来简单、做起来细节满满的能力。它背后是几个经典的软件工程思想:

  • 协作式中断(而非强制打断)
  • 不可变快照(Checkpoint 模式)
  • 状态机(显式建模生命周期)
  • Actor 隔离(Per-user 串行)
  • 跨线程上下文传递(显式化 ThreadLocal)

这些思想不只适用于 AI Agent,任何"长任务 + 可中断 + 可恢复"的场景(批处理、ETL、流程引擎)都适用。


项目地址:一个基于 Spring AI + 通义千问的完整个人多智能体助手,已开源。 如果你也在做 Agent 编排,欢迎交流。


标签:Java Spring AI Multi-Agent 多智能体 架构设计

相关推荐
shiyan6882 小时前
PadNote:把平板笔记接入 AI 与电脑 Agent
github
wflynn3 小时前
GitHub 日榜趋势速报 | 2026-10-26
开源·github
逛逛GitHub4 小时前
在电脑和手机上玩 Switch 游戏?这 5 个GitHub 项目值得看看。
github
徐小夕5 小时前
JitKnow AI知识库 4.7 发布|多文档推理+拖拽 Agent + 技能库,打造更懂业务的 AI 知识中台
前端·vue.js·github
MicrosoftReactor8 小时前
技术速递|GitHub 的密钥检测模型:发现更多密钥,减少误报干扰
ai·github·密钥保护
得物技术10 小时前
国产算力·昇腾 910C 大模型推理优化(下):源码修改与算子优化,提速30%|得物技术
架构·llm·github
我不会插花弄玉10 小时前
GitHub实操【由浅入深-Git】
git·github
m4Rk_11 小时前
【论文阅读】Agent记忆机制(112):MemFit——保留对话原文,用多路检索降低长期记忆成本
论文阅读·人工智能·学习·开源·github
xcLeigh11 小时前
GitHub Copilot Workspace:从 Issue 到 PR 的 AI 全流程开发体验
人工智能·ai·github·copilot·issue·isuse