-
Notifications
You must be signed in to change notification settings - Fork 302
Closed
Labels
bugSomething isn't workingSomething isn't working
Description
@Tool(...)
public ToolResultBlock callAnalyst(
@ToolParam(name = "task", description = "....") String task,
ToolEmitter toolEmitter
) {
//...
log.info("=== [Analyst] End Context Debug ===");
toolEmitter.emit(ToolResultBlock.text("Starting task..."));
log.warn("emit 调用完成,未抛异常"); // 新增:确认 emit 完成
// ===== 诊断代码 =====
log.warn("toolEmitter 类型: {}", toolEmitter.getClass().getName());
// 如果是 NoOpToolEmitter → 说明 emitter 没有正确注入,chunk 全部被丢弃
// 如果是 DefaultToolEmitter → 说明 emitter 正确,继续排查 chunkCallback
// 通过反射检查 chunkCallback 是否为 null
try {
java.lang.reflect.Field callbackField = toolEmitter.getClass().getDeclaredField("chunkCallback");
callbackField.setAccessible(true);
Object callback = callbackField.get(toolEmitter);
log.warn("chunkCallback 是否为 null: {}", callback == null);
} catch (Exception e) {
log.warn("反射检查失败: {}", e.getMessage());
}
// ===== 诊断代码结束 =====日志
2026-02-12 14:41:27.295 WARN GLUDILzc1ITFMyjHj_iaDA== N [boundedElastic-3] (AnalystToolkit.java:90) - emit 调用完成,未抛异常
2026-02-12 14:41:27.295 WARN GLUDILzc1ITFMyjHj_iaDA== N [boundedElastic-3] (AnalystToolkit.java:93) - toolEmitter 类型: io.agentscope.core.tool.DefaultToolEmitter
2026-02-12 14:41:27.296 WARN GLUDILzc1ITFMyjHj_iaDA== N [boundedElastic-3] (AnalystToolkit.java:102) - chunkCallback 是否为 null: true
cursor排查结论:
核心问题:Toolkit.copy() 导致 ToolMethodInvoker 引用断裂
v1.0.4 ReActAgent.Builder.build() — 直接引用,没有 copy:
// v1.0.4 的 build()ReActAgent agent = new ReActAgent(this);// 构造函数中:this.toolkit = builder.toolkit; // 直接引用同一个 toolkit 实例
v1.0.8 ReActAgent.Builder.build() — 新增了 copy():
// v1.0.4 的 build()ReActAgent agent = new ReActAgent(this);// 构造函数中:this.toolkit = builder.toolkit; // 直接引用同一个 toolkit 实例
Bug 触发链路
- 你在外层 Builder 上注册工具 callAnalyst,registerToolMethod() 创建匿名 AgentTool,其 callAsync 闭包捕获了原始 Toolkit 的 methodInvoker(记为 old_MI)
- build() 调用 toolkit.copy() → 创建新 Toolkit(含新 ToolMethodInvoker,记为 new_MI)→ 工具引用通过 toolRegistry.copyTo() 被拷贝到新 Toolkit,但工具闭包中仍然引用 old_MI
- ReActAgent.acting() 执行 toolkit.setChunkCallback(callback) → 设置在 new_MI 上 ✅
- executeCore() 创建 DefaultToolEmitter(toolCall, chunkCallback) → 这里用的是 ToolExecutor 的 chunkCallback,是正确的 ✅ → 放入 ToolCallParam.emitter ✅
- 但是! tool.callAsync(executionParam) 走到闭包 → 调用 old_MI.invokeAsync() → old_MI.convertParameters() → 创建了一个新的 DefaultToolEmitter(toolUseBlock, old_MI.chunkCallback) → old_MI.chunkCallback 是 NULL ❌
- 这个 NULL callback 的 DefaultToolEmitter 被注入到你的 callAnalyst 方法的 ToolEmitter toolEmitter 参数,覆盖了第 4 步中正确的 emitter
关键点:ToolMethodInvoker.convertParameters() 完全忽略了 ToolCallParam.emitter,而是用自己的 chunkCallback 字段重新创建了一个 DefaultToolEmitter
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
bugSomething isn't workingSomething isn't working
Type
Projects
Status
Done