阿里开源AgentScope多智能体框架解析系列(十)第10章:中断机制

本章导读

本章介绍AgentScope的中断机制 ,这是实现人机协同优雅控制的关键特性。

中断允许你在任意时刻暂停Agent执行 ,同时完整保留上下文和状态,为用户提供对长时间运行任务的实时控制能力。

本章概览

  • What(是什么):理解中断的三种场景和工作原理
  • Why(为什么):明确什么时候需要使用中断机制
  • How(怎样做):完整的实现示例和生产可用案例

10.1 中断机制设计理念(What - 是什么)

什么是中断?

中断 是一个异步操作,允许你在不丢失上下文的情况下暂停Agent的执行

关键特性:

  • 无损暂停:保留所有中间状态和上下文
  • 实时控制:随时可以暂停、恢复、取消
  • 人机协同:用户可以在关键点干预Agent决策
  • 优雅降级:遇到问题可以安全地中止

中断的三个核心场景

场景1:安全中断(Safe Pause)

复制代码
场景描述:长时间任务执行中,用户需要补充信息或修正条件

执行流程:
用户初始请求 → Agent推理(5秒)
↓
用户:「暂停,我需要补充数据」
↓
Agent立即停止,保留完整上下文(内存、消息历史)
↓
用户提供新数据 → Agent基于新数据继续推理

代码示例

java 复制代码
// Agent执行中间被暂停
ReActAgent agent = ReActAgent.builder()
    .name("SafePauseAgent")
    .model(model)
    .memory(new InMemoryMemory())
    .build();

// 启动异步执行
Flux<HookEvent> execution = agent.stream(
    Msg.builder().textContent("请分析这些销售数据并给出建议").build()
);

// 将执行流转为可订阅的流
Disposable subscription = execution.subscribe(
    event -> handleEvent(event),
    error -> System.err.println("Error: " + error),
    () -> System.out.println("Completed")
);

// 5秒后,用户要求暂停
Thread.sleep(5000);
agent.interrupt("用户要求补充数据,暂停推理").block();

// 此时Agent已暂停,内存和状态都保留
Memory memory = agent.getMemory();
List<Msg> history = memory.getHistory();
System.out.println("当前推理步数:" + history.size());

// 用户补充新数据
agent.observe(Msg.builder()
    .role(MsgRole.USER)
    .textContent("这是补充的季度数据...")
    .build()).block();

// Agent基于新数据继续
Msg result = agent.call(
    Msg.builder().textContent("继续分析").build()
).block();

场景2:优雅取消(Graceful Cancellation)

复制代码
场景描述:用户主动取消正在进行的任务

执行流程:
Agent执行工具调用(API请求)
↓
用户:「取消这个请求」
↓
Agent停止工具执行,回滚状态
↓
用户选择其他解决方案或重新开始

代码示例

java 复制代码
// Agent执行可能很耗时的工具调用
ReActAgent agent = ReActAgent.builder()
    .name("CancellableAgent")
    .toolkit(new Toolkit()
        .registerObject(new SlowToolService()))
    .build();

// 异步执行
Future<Msg> future = CompletableFuture.supplyAsync(() -> {
    Flux<HookEvent> execution = agent.stream(
        Msg.builder().textContent("请从数据库查询大量历史数据").build()
    );
    
    // 订阅并执行
    List<Msg> results = new ArrayList<>();
    execution.subscribe(
        event -> {
            if (event instanceof PostActingEvent) {
                PostActingEvent acting = (PostActingEvent) event;
                results.add(acting.getActingMessage());
            }
        }
    );
    
    return results.isEmpty() ? null : results.get(0);
});

// 用户在看到进度后决定取消
Thread.sleep(3000);

// 发送取消中断
agent.interrupt("用户取消请求").block();

// 清理资源
if (!future.isDone()) {
    future.cancel(true);
}

System.out.println("请求已安全取消,资源已释放");

场景3:人机协同(Human-in-the-Loop)

复制代码
场景描述:Agent在执行关键决策前,需要用户确认

执行流程:
Agent推理出一个解决方案
↓
Agent请求用户确认(中断)
↓
用户检查并修改方案
↓
Agent基于修改后的方案继续执行

代码示例

java 复制代码
// 支持人机协同的Agent
ReActAgent agent = ReActAgent.builder()
    .name("InteractiveAgent")
    .toolkit(new Toolkit()
        .registerObject(new PaymentService())  // 支付等关键操作
        .registerObject(new ApprovalHook()))    // 审批Hook
    .build();

// 添加人机协同Hook
List<Hook> hooks = new ArrayList<>();
hooks.add(new HumanInTheLoopHook((decision) -> {
    // 在关键点暂停,等待用户确认
    System.out.println("⚠ 需要确认的决策:" + decision);
    System.out.println("请输入:[1]批准  [2]修改  [3]拒绝");
    
    // 这里会阻塞,等待用户输入
    Scanner scanner = new Scanner(System.in);
    String userInput = scanner.nextLine();
    
    return userInput;  // 返回用户的选择
}));

agent.withHooks(hooks);

// 执行任务
Msg response = agent.call(
    Msg.builder().textContent("请处理这个订单请求:转账1000元给供应商").build()
).block();

10.2 中断工作原理(Why - 为什么需要)

为什么需要中断机制?

1. 控制长时间运行的任务

diff 复制代码
问题:Agent执行可能持续数分钟或数小时,用户无法实时控制
解决:中断允许用户在任意时刻暂停、调整或取消

场景:
- 数据分析Agent:用户发现分析方向错误,需要中止重新开始
- 内容创作Agent:用户在某个中间步骤不满意,需要修改前置信息
- 报表生成Agent:临时发现数据有误,需要停止并修正源数据

2. 实现人机协同

diff 复制代码
问题:完全自动化的Agent可能做出用户不认可的决策
解决:通过中断和Hook,用户可以在关键点介入

场景:
- 金融交易:大额转账需要用户确认
- 内容审核:敏感内容需要人工审核
- 高风险操作:删除、关闭等操作需要二次确认

3. 支持异常恢复

diff 复制代码
问题:遇到异常时,整个任务失败,无法继续
解决:通过中断和状态保存,可以恢复并重试

场景:
- 网络中断:服务临时不可用,暂停并稍后恢复
- 配额限制:API调用达到限制,暂停等待配额恢复
- 权限问题:临时权限不足,等待权限升级后继续

中断的工作原理

scss 复制代码
中断执行模型(响应式管道):

用户请求中断
    ↓
agent.interrupt(reason).block()
    ↓
当前操作完成(不强制中断进行中的操作)
    ↓
保存完整状态(内存、消息历史、Hook链)
    ↓
Agent进入暂停状态
    ↓
用户可以:
  ├─ 观察状态
  ├─ 修改上下文(Memory、消息)
  ├─ 调用observe()注入新信息
  └─ 调用call()恢复执行

10.3 中断的实现方式(How - 怎样做)

基础中断实现

java 复制代码
public class InterruptionDemo {
    
    public static void main(String[] args) throws Exception {
        // 1. 创建支持中断的Agent
        Model model = DashScopeModel.builder()
            .modelName("qwen-turbo")
            .apiKey(System.getenv("DASHSCOPE_API_KEY"))
            .build();
        
        ReActAgent agent = ReActAgent.builder()
            .name("InterruptibleAgent")
            .model(model)
            .memory(new InMemoryMemory())
            .build();
        
        // 2. 在后台执行任务
        ExecutorService executor = Executors.newSingleThreadExecutor();
        
        Flux<HookEvent> execution = agent.stream(
            Msg.builder()
                .role(MsgRole.USER)
                .textContent("请帮我分析过去一个月的销售数据并生成报告")
                .build()
        );
        
        // 3. 订阅执行结果
        List<HookEvent> events = new ArrayList<>();
        Future<?> future = executor.submit(() -> {
            execution.subscribe(
                event -> {
                    events.add(event);
                    System.out.println("Event: " + event.getClass().getSimpleName());
                },
                error -> System.err.println("Error: " + error.getMessage()),
                () -> System.out.println("Execution completed")
            );
        });
        
        // 4. 执行3秒后,触发中断
        Thread.sleep(3000);
        System.out.println("\n>>> 用户请求暂停 <<<");
        agent.interrupt("用户要求暂停,需要补充数据").block();
        
        // 5. 检查中断后的状态
        Memory memory = agent.getMemory();
        System.out.println("\n中断后的状态:");
        System.out.println("- 记忆中的消息数:" + memory.getHistory().size());
        System.out.println("- 最后一条消息:" + 
            memory.getHistory().get(memory.getHistory().size() - 1).getTextContent());
        
        // 6. 用户补充新信息
        System.out.println("\n>>> 用户补充新数据 <<<");
        agent.observe(Msg.builder()
            .role(MsgRole.USER)
            .textContent("新增信息:请重点关注南方地区的销售,这个月有促销活动")
            .build()).block();
        
        // 7. 恢复执行
        System.out.println("\n>>> 继续执行 <<<");
        Msg result = agent.call(
            Msg.builder()
                .role(MsgRole.USER)
                .textContent("基于上面的补充信息,继续完成报告")
                .build()
        ).block();
        
        System.out.println("\n最终结果:\n" + result.getTextContent());
        
        executor.shutdown();
    }
}

使用Hook实现精细化控制

java 复制代码
public class AdvancedInterruptionWithHooks {
    
    /**
     * 自定义Hook:在每个推理步骤后检查是否应该中断
     */
    public static class CheckpointHook implements Hook {
        private final AtomicBoolean shouldInterrupt = new AtomicBoolean(false);
        private final String interruptReason;
        
        public CheckpointHook(String reason) {
            this.interruptReason = reason;
        }
        
        public void triggerInterrupt() {
            shouldInterrupt.set(true);
        }
        
        @Override
        public <T extends HookEvent> Mono<T> onEvent(T event) {
            // 在Post事件时检查是否需要中断
            if (event instanceof PostReasoningEvent post) {
                if (shouldInterrupt.get()) {
                    // 记录中断点
                    System.out.println("🛑 中断点触发:" + interruptReason);
                    System.out.println("  推理次数:" + post.getReasoningCount());
                    System.out.println("  当前消息数:" + post.getInputMessages().size());
                    
                    // 返回事件,由外部处理中断
                    return Mono.just(event)
                        .doOnNext(e -> {
                            // 这里可以触发中断信号
                            System.out.println("  已保存检查点");
                        });
                }
            }
            return Mono.just(event);
        }
        
        @Override
        public int getPriority() {
            return 50;  // 较高优先级,最后执行
        }
    }
    
    public static void main(String[] args) throws Exception {
        Model model = DashScopeModel.builder()
            .modelName("qwen-turbo")
            .apiKey(System.getenv("DASHSCOPE_API_KEY"))
            .build();
        
        // 创建检查点Hook
        CheckpointHook checkpoint = new CheckpointHook("用户主动中断");
        
        ReActAgent agent = ReActAgent.builder()
            .name("CheckpointAgent")
            .model(model)
            .memory(new InMemoryMemory())
            .hooks(List.of(checkpoint))
            .build();
        
        // 在后台执行
        ExecutorService executor = Executors.newSingleThreadExecutor();
        Future<?> future = executor.submit(() -> {
            Flux<HookEvent> execution = agent.stream(
                Msg.builder()
                    .textContent("请执行一个长时间的分析任务")
                    .build()
            );
            
            execution.subscribe(
                event -> System.out.println("收到事件:" + event.getClass().getSimpleName()),
                error -> System.err.println("错误:" + error),
                () -> System.out.println("任务完成")
            );
        });
        
        // 2秒后触发中断
        Thread.sleep(2000);
        checkpoint.triggerInterrupt();
        
        // 等待执行完成
        Thread.sleep(2000);
        
        // 检查状态
        System.out.println("\n最终状态检查:");
        System.out.println("Memory中的消息数:" + agent.getMemory().getHistory().size());
        
        executor.shutdown();
    }
}

10.4 生产场景:智能客服中的中断管理

场景描述

markdown 复制代码
一个客服Agent处理客户问题:
1. 用户提出问题
2. Agent分析并提出解决方案
3. 如果涉及高风险操作(如退款),需要人工确认
4. 人工客服审批后,Agent继续执行
5. 如果被拒,Agent提出替代方案

实现代码

java 复制代码
public class CustomerServiceInterruptionSystem {
    
    /**
     * 人机协同Hook:在需要人工审批的操作前暂停
     */
    public static class ApprovalHook implements Hook {
        private final Map<String, ApprovalQueue> approvalQueues = new ConcurrentHashMap<>();
        
        @Override
        public <T extends HookEvent> Mono<T> onEvent(T event) {
            if (!(event instanceof PreActingEvent pre)) {
                return Mono.just(event);
            }
            
            // 检查即将执行的工具是否需要审批
            String toolName = extractToolName(pre.getToolCallMessage());
            
            if (needsApproval(toolName)) {
                return approveToolCall(pre.getToolCallMessage())
                    .flatMap(approved -> {
                        if (approved) {
                            System.out.println("✓ 人工已批准,继续执行:" + toolName);
                            return Mono.just(event);
                        } else {
                            System.out.println("✗ 人工拒绝,中止执行:" + toolName);
                            // 修改事件,跳过这个工具调用
                            pre.markAsCancelled();
                            return Mono.just(event);
                        }
                    });
            }
            
            return Mono.just(event);
        }
        
        private boolean needsApproval(String toolName) {
            // 高风险操作列表
            return toolName.equals("processRefund") ||
                   toolName.equals("updateOrder") ||
                   toolName.equals("deleteAccount");
        }
        
        private Mono<Boolean> approveToolCall(Msg toolCall) {
            // 提交到审批队列,等待人工审批
            System.out.println("\n⏸️  等待人工审批:");
            System.out.println("  工具调用:" + toolCall.getTextContent());
            System.out.println("  请人工审批...");
            
            // 实际应用中,这里会将请求发送到审批系统
            // 这里简化为同步等待
            return Mono.fromCallable(() -> {
                // 模拟人工审批耗时
                Thread.sleep(2000);
                System.out.println("  ✓ 人工审批完成:已批准");
                return true;
            }).subscribeOn(Schedulers.boundedElastic());
        }
        
        @Override
        public int getPriority() {
            return 10;  // 最高优先级,最早检查
        }
    }
    
    /**
     * 客服Agent主类
     */
    public static class CustomerServiceAgent {
        private final ReActAgent agent;
        private final ApprovalHook approvalHook;
        
        public CustomerServiceAgent(Model model) {
            this.approvalHook = new ApprovalHook();
            
            this.agent = ReActAgent.builder()
                .name("CustomerServiceAgent")
                .model(model)
                .memory(new InMemoryMemory())
                .toolkit(new Toolkit()
                    .registerObject(new CustomerServiceTools()))
                .hooks(List.of(approvalHook))
                .systemPrompt(getSystemPrompt())
                .build();
        }
        
        public Msg handleCustomerRequest(String customerMessage) throws Exception {
            System.out.println("\n=== 处理客户请求 ===");
            System.out.println("客户:" + customerMessage);
            
            // 异步执行Agent
            ExecutorService executor = Executors.newSingleThreadExecutor();
            
            Future<Msg> result = executor.submit(() -> {
                Flux<HookEvent> execution = agent.stream(
                    Msg.builder()
                        .role(MsgRole.USER)
                        .textContent(customerMessage)
                        .build()
                );
                
                // 执行并收集结果
                List<Msg> responses = new ArrayList<>();
                execution.subscribe(
                    event -> {
                        if (event instanceof PostActingEvent) {
                            responses.add(((PostActingEvent) event).getActingMessage());
                        }
                    }
                );
                
                return responses.isEmpty() ? null : responses.get(0);
            });
            
            try {
                Msg response = result.get(30, TimeUnit.SECONDS);
                System.out.println("客服助手:" + response.getTextContent());
                return response;
            } finally {
                executor.shutdown();
            }
        }
        
        private String getSystemPrompt() {
            return """
                你是一个专业的客服助手。
                
                职责:
                1. 认真倾听客户的问题
                2. 分析问题并提出解决方案
                3. 对于退款、订单修改等关键操作,主动调用相应工具
                4. 对于工具执行失败,提出替代方案
                
                态度:
                - 礼貌、耐心、专业
                - 优先满足客户需求
                - 当无法处理时,清晰地说明原因
                """;
        }
    }
    
    /**
     * 客服工具集
     */
    public static class CustomerServiceTools {
        
        @Tool(description = "查询订单信息")
        public String queryOrder(String orderId) {
            return "订单 " + orderId + " 的信息:状态=已发货,金额=¥299.99";
        }
        
        @Tool(description = "处理退款,需要人工审批")
        public String processRefund(String orderId, String reason) {
            return "退款已处理,退款金额已返回到原账户";
        }
        
        @Tool(description = "更新订单备注")
        public String updateOrder(String orderId, String note) {
            return "订单备注已更新";
        }
        
        @Tool(description = "提供替代方案")
        public String suggestAlternative(String productId) {
            return "推荐替代产品:ID=" + productId + "-alt,价格¥199.99,评分4.8/5";
        }
    }
    
    // 测试
    public static void main(String[] args) throws Exception {
        Model model = DashScopeModel.builder()
            .modelName("qwen-turbo")
            .apiKey(System.getenv("DASHSCOPE_API_KEY"))
            .build();
        
        CustomerServiceAgent agent = new CustomerServiceAgent(model);
        
        // 场景1:普通查询(无需中断)
        agent.handleCustomerRequest("请查询我的订单 ORD-2024-001 的状态");
        
        // 场景2:退款请求(需要人工审批中断)
        agent.handleCustomerRequest("我想要退货,订单 ORD-2024-002,因为商品不符合预期");
    }
}

10.5 最佳实践与常见问题

✅ 最佳实践

markdown 复制代码
1. 精确的中断点设计
   - 在推理循环之间检查中断信号
   - 不要在工具执行中途中断(可能导致不一致状态)
   - 清晰定义可中断和不可中断的阶段

2. 完整的状态保存
   - 中断时保存Memory、消息历史、Hook状态
   - 提供恢复接口,确保状态完整性
   - 支持检查点恢复

3. 用户体验
   - 提供明确的中断确认
   - 显示中断点的上下文信息
   - 支持继续/修改/取消多种选择

4. 生产环保
   - 加入超时机制,防止无限等待
   - 记录所有中断事件用于审计
   - 监控中断频率,识别问题模式

❓ 常见问题

Q1:中断时工具调用进行中怎么办?

A:AgentScope会等待当前工具调用完成,然后再暂停。这确保了状态的一致性。

java 复制代码
// 如果需要强制中断正在执行的工具,使用Future.cancel()
Future<?> toolFuture = executor.submit(() -> slowTool.call());
Thread.sleep(2000);
toolFuture.cancel(true);  // 强制取消

Q2:中断后状态如何恢复?

A:通过Memory恢复。AgentScope自动保存所有消息历史和内部状态。

java 复制代码
// 中断后的恢复
agent.interrupt("user pause").block();

// 检查Memory中的历史
List<Msg> history = agent.getMemory().getHistory();

// 恢复到中断点
agent.observe(newMsg).block();
Msg result = agent.call(continuationMsg).block();

Q3:能否在Hook中实现条件中断?

A:可以。通过在Hook中返回修改后的事件,可以实现条件判断和选择性中断。

java 复制代码
@Override
public <T extends HookEvent> Mono<T> onEvent(T event) {
    if (shouldInterrupt(event)) {
        return Mono.just(event)
            .doOnNext(e -> {
                // 触发外部中断信号
                system.sendInterruptSignal();
            });
    }
    return Mono.just(event);
}

10.6 与其他特性的协同

中断 + Hook系统

java 复制代码
// Hook可以在中断前执行清理操作
public class CleanupHook implements Hook {
    @Override
    public <T extends HookEvent> Mono<T> onEvent(T event) {
        if (event instanceof PostReasoningEvent) {
            // 在推理完成时进行清理
            return Mono.just(event)
                .doOnNext(e -> cleanupResources());
        }
        return Mono.just(event);
    }
}

中断 + 记忆系统

java 复制代码
// 中断时记忆系统自动保存
agent.interrupt("pause").block();

Memory memory = agent.getMemory();
// 可以查询历史消息
List<Msg> history = memory.getHistory();
// 可以获取压缩的记忆
String compressed = memory.getCompressedMemory();

中断 + Session持久化

java 复制代码
// 中断后保存会话
session.saveSessionState(userId, Map.of(
    "memory", agent.getMemory(),
    "interrupt_reason", "user request"
));

// 后续恢复
session.loadSessionState(userId, modules);

本章总结

本章介绍了AgentScope的中断机制 ,这是实现实时控制人机协同的核心特性:

核心要点

  • What:中断允许在任意时刻暂停Agent,同时保留完整状态
  • Why:支持人机协同、控制长时间任务、实现异常恢复
  • How:使用interrupt()方法和Hook实现精细化控制

关键特性

  • ✓ 无损暂停:保留所有上下文
  • ✓ 灵活控制:暂停、恢复、取消、修改
  • ✓ 人机协同:在关键点进行人工干预
  • ✓ 与其他特性协同:Hook、Memory、Session的无缝配合

使用场景

  • 长时间任务的用户控制
  • 高风险操作的人工审批
  • 异常恢复和状态管理
  • 交互式任务的逐步完成

下一章预告

第11章:PlanNotebook 任务管理

当Agent需要处理复杂的多步骤任务 时,单一的推理循环可能不够。第11章将介绍PlanNotebook系统,它允许Agent:

  • 自主创建和管理分阶段的计划
  • 跨越多个推理循环执行复杂任务
  • 在执行过程中动态调整计划
  • 与中断机制协同实现人工干预

敬请期待!

相关推荐
语落心生2 小时前
阿里开源AgentScope多智能体框架解析系列(十二)第12章:RAG(检索增强生成)
agent
语落心生7 小时前
阿里开源AgentScope多智能体框架解析系列(三)第3章:模型接口(Model)与适配器模式
agent
FreeCode7 小时前
智能体设计模式解析:交接模式(Handoffs)
langchain·agent·ai编程
语落心生7 小时前
阿里开源AgentScope多智能体框架解析系列(二)第2章:消息系统(Message)深入解析
agent
语落心生7 小时前
阿里开源AgentScope多智能体框架解析系列(一)第1章:AgentScope-Java 概述与快速入门
agent
语落心生7 小时前
阿里开源AgentScope多智能体框架解析系列(四)第4章:Agent 接口与 AgentBase 基类
agent
潘锦7 小时前
AI Agent 核心管理逻辑:工具的管理和调度
agent
Jay Kay9 小时前
Agent沙箱执行服务
agent·沙箱服务
小马过河R10 小时前
ReAct和Function Calling之间的纠葛与恩恩怨怨
人工智能·语言模型·agent·智能体