本章导读
本章介绍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:
- 自主创建和管理分阶段的计划
- 跨越多个推理循环执行复杂任务
- 在执行过程中动态调整计划
- 与中断机制协同实现人工干预
敬请期待!