AgentScope HarnessAgent interrupt() API 设计缺陷
概述
HarnessAgent 公开的 interrupt() / interrupt(Msg) 方法无法正确中断通过 streamEvents(List<Msg>, RuntimeContext) 启动的流式会话,因为中断始终使用 defaultSessionId 而非 RuntimeContext 中传入的真实 sessionId。
影响范围:AgentScope 2.0.0-RC5(可能影响所有 2.0.x 版本)。
问题描述
典型使用场景
// 1. 创建 agent(通常 defaultSessionId 未显式设置,回退到 name)
HarnessAgent agent = HarnessAgent.builder()
.name("coding-assistant")
.model(model)
// .defaultSessionId(...) // ← 未设置
.build();
// 2. 启动流式会话,传入自定义 sessionId
RuntimeContext ctx = RuntimeContext.builder()
.sessionId("conversation-123") // 真实会话 ID
.userId("user-1")
.build();
Flux<AgentEvent> events = agent.streamEvents(messages, ctx);
// 3. 尝试中断
agent.interrupt(new UserMessage("用户取消")); // ← 无法命中目标会话
根因
interrupt() 的调用链如下:
HarnessAgent.interrupt(Msg)
→ ReActAgent.interrupt(Msg)
→ ReActAgent.interrupt(null, this.defaultSessionId, msg)
→ getAgentState(null, defaultSessionId).interruptControl().trigger(...)
而 ReActAgent 构造函数中 defaultSessionId 的赋值逻辑为:
if (builder.defaultSessionId != null && !builder.defaultSessionId.isBlank()) {
this.defaultSessionId = builder.defaultSessionId;
} else if (builder.name != null) {
this.defaultSessionId = builder.name; // ← 多数场景走这里
} else {
this.defaultSessionId = "ReActAgent";
}
结果:interrupt() 去中断 session "coding-assistant"(agent name),而真正的流跑在 session "conversation-123" 上——完全打不中目标。
证据:底层已有正确能力,但未暴露
ReActAgent 提供了正确的重载,能从 RuntimeContext 中提取 sessionId:
// ReActAgent.interrupt(RuntimeContext, Msg) — 正确版本
public void interrupt(RuntimeContext rc, Msg msg) {
String userId = (rc != null) ? rc.getUserId() : null;
String sessionId = (rc != null) ? rc.getSessionId() : null;
if (sessionId == null || sessionId.isBlank()) {
sessionId = this.defaultSessionId;
}
this.getAgentState(userId, sessionId)
.interruptControl()
.trigger(InterruptSource.USER, msg);
}
同时,ReActAgent 在流执行期间会将当前的 RuntimeContext 写入 activeRc 字段(beforeAgentExecution 中 set,afterAgentExecution 中 clear),并通过 getRuntimeContext() 公开。
但 HarnessAgent 没有暴露 interrupt(RuntimeContext, Msg),只暴露了:
public void interrupt(); // → defaultSessionId
public void interrupt(Msg msg); // → defaultSessionId
对比
| 调用方式 |
使用的 sessionId |
是否命中 |
agent.interrupt(msg) |
defaultSessionId(agent name) |
❌ 未命中 |
agent.getDelegate().interrupt(ctx, msg) |
RuntimeContext 中的真实 sessionId |
✅ 正确命中 |
调用方不得不绕过 HarnessAgent,直接操作内部的 ReActAgent 来实现正确的中断逻辑。
建议修复
方案 A(推荐):暴露 interrupt(RuntimeContext, Msg)
在 HarnessAgent 中添加:
public void interrupt(RuntimeContext rc, Msg msg) {
this.delegate.interrupt(rc, msg);
}
这是最小改动,且与现有的 streamEvents(List<Msg>, RuntimeContext) 对称。
方案 B:interrupt() 自动使用 activeRc
修改 interrupt() / interrupt(Msg) 实现,优先使用 activeRc(如果非空):
public void interrupt(Msg msg) {
RuntimeContext active = this.delegate.getRuntimeContext();
if (active != null) {
this.delegate.interrupt(active, msg);
} else {
this.delegate.interrupt(msg); // fallback
}
}
优点是对已有调用方透明,无需改动使用代码。
当前 Workaround
调用方可在 HarnessAgent 修复前,通过 getDelegate() 绕过:
HarnessAgent agent = ...;
RuntimeContext ctx = ...; // 与 streamEvents 使用同一个 ctx
// 正确的中断方式
agent.getDelegate().interrupt(ctx, new UserMessage("用户已取消操作"));
版本信息
- AgentScope version:
2.0.0-RC5
- 验证方式:
javap 反编译字节码 + 源码逻辑分析
AgentScope HarnessAgent interrupt() API 设计缺陷
概述
HarnessAgent公开的interrupt()/interrupt(Msg)方法无法正确中断通过streamEvents(List<Msg>, RuntimeContext)启动的流式会话,因为中断始终使用defaultSessionId而非RuntimeContext中传入的真实sessionId。影响范围:AgentScope 2.0.0-RC5(可能影响所有 2.0.x 版本)。
问题描述
典型使用场景
根因
interrupt()的调用链如下:而
ReActAgent构造函数中defaultSessionId的赋值逻辑为:结果:
interrupt()去中断 session"coding-assistant"(agent name),而真正的流跑在 session"conversation-123"上——完全打不中目标。证据:底层已有正确能力,但未暴露
ReActAgent提供了正确的重载,能从RuntimeContext中提取 sessionId:同时,
ReActAgent在流执行期间会将当前的RuntimeContext写入activeRc字段(beforeAgentExecution中 set,afterAgentExecution中 clear),并通过getRuntimeContext()公开。但
HarnessAgent没有暴露interrupt(RuntimeContext, Msg),只暴露了:对比
agent.interrupt(msg)defaultSessionId(agent name)agent.getDelegate().interrupt(ctx, msg)RuntimeContext中的真实 sessionId调用方不得不绕过
HarnessAgent,直接操作内部的ReActAgent来实现正确的中断逻辑。建议修复
方案 A(推荐):暴露
interrupt(RuntimeContext, Msg)在
HarnessAgent中添加:这是最小改动,且与现有的
streamEvents(List<Msg>, RuntimeContext)对称。方案 B:
interrupt()自动使用activeRc修改
interrupt()/interrupt(Msg)实现,优先使用activeRc(如果非空):优点是对已有调用方透明,无需改动使用代码。
当前 Workaround
调用方可在
HarnessAgent修复前,通过getDelegate()绕过:版本信息
2.0.0-RC5javap反编译字节码 + 源码逻辑分析