Files
zclaw_openfang/docs/references/zclaw-toolcall-issues.md
iven 3eb098f020
Some checks failed
CI / Lint & TypeCheck (push) Has been cancelled
CI / Unit Tests (push) Has been cancelled
CI / Build Frontend (push) Has been cancelled
CI / Rust Check (push) Has been cancelled
CI / Security Scan (push) Has been cancelled
CI / E2E Tests (push) Has been cancelled
fix(runtime): 工具调用 P1/P2/P3 全面修复
P1: 流式模式工具并行执行
- 三阶段执行: Phase 1 中间件预检(serial) → Phase 2 并行+串行分区 → Phase 3 结果排序
- ReadOnly 工具用 JoinSet + Semaphore(3) 并行,Exclusive/Interactive 串行
- 与非流式模式保持一致的执行策略

P2: OpenAI 驱动工具参数解析
- 解析失败不再静默替换为 {},改为返回 _parse_error + _raw_args
- 让 LLM 和工具能感知参数问题并自我修正

P2: ToolOutputGuard 精确匹配
- 从 to_lowercase() 关键词匹配改为 regex 精确匹配实际密钥值
- 检测 sk-xxx(20+), AKIA(16), PEM 私钥, key=value 模式
- 移除 "system:", "you are now" 等过于宽泛的注入检测
- 消除合法内容包含 "password" 等词汇时的误拦

P2: ToolErrorMiddleware per-session 计数
- 从全局 AtomicU32 改为 Mutex<HashMap<session_id, u32>>
- 每个会话独立跟踪连续失败次数,消除跨会话误触发 AbortLoop

P3: Gateway client onTool 回调语义
- 明确 tool_call 的 output 始终为空串 (start 信号)
- 添加注释说明 start/end 语义约定
2026-04-24 12:56:07 +08:00

6.2 KiB
Raw Blame History

ZCLAW 工具调用问题分析

对比 DeerFlow 工具调用系统,排查 ZCLAW 工具调用问题。 分析日期2026-04-24 更新日期2026-04-24P0+P0-stream_errored 已修复)


一、发现的问题

P0: after_tool_call 中间件从未被调用 — 已修复 (2026-04-24)

文件: crates/zclaw-runtime/src/loop_runner.rs

run()(非流式,第 400-558 行)和 run_streaming(流式,第 893-1070 行)中,工具执行后直接 push Message::tool_result 到消息历史,没有调用 middleware_chain.run_after_tool_call()

影响:

  • ToolErrorMiddleware.after_tool_call 的错误计数和恢复消息逻辑不生效
  • ToolOutputGuardMiddleware.after_tool_call 的敏感信息检测不生效
  • 工具错误只能靠工具自身的错误返回传递,中间件层的防护形同虚设

DeerFlow 对比: ToolErrorHandlingMiddleware 通过 wrap_tool_call 钩子完整包裹每次工具执行。

P0: stream_errored 跳过所有工具执行 — 已修复 (2026-04-24)

文件: crates/zclaw-runtime/src/loop_runner.rs 第 872-876 行

流式模式中,当 LLM 流出现任何错误网络超时、API 错误、驱动错误)时,stream_errored = true,然后 break 'outer 直接退出循环,跳过所有已解析的工具调用

影响:

  • ToolStart 事件已发送给前端(用户看到"执行中"按钮),但工具从未实际执行
  • ToolEnd 事件永远不会发送 → 前端工具状态卡在"执行中"
  • 已完整接收ToolUseEnd的工具调用也被丢弃

修复: 区分完整工具(收到 ToolUseEnd和不完整工具仅收到 ToolUseStart/Delta。完整工具照常执行不完整工具发送取消 ToolEnd 事件。

P1: 流式模式工具全串行 — 已修复 (2026-04-24)

文件: loop_runner.rs 流式模式工具执行段

非流式模式有 JoinSet + Semaphore(3) 并行执行 ReadOnly 工具,但流式模式用简单 for 循环串行执行所有工具。

修复: 流式模式采用三阶段执行Phase 1 中间件预检(serial) → Phase 2 并行+串行分区执行 → Phase 3 after_tool_call + 结果排序推送。

P2: OpenAI 驱动工具参数静默替换 — 已修复 (2026-04-24)

文件: crates/zclaw-runtime/src/driver/openai.rs 第 222-228 行

let parsed_args = if args.is_empty() {
    serde_json::json!({})
} else {
    serde_json::from_str(args).unwrap_or_else(|e| {
        tracing::warn!("Failed to parse tool args '{}': {}", args, e);
        serde_json::json!({})
    })
};

JSON 解析失败时静默替换为 {},结合 loop_runner.rs 的空参数处理(第 412-423 行),会注入 _fallback_query 替代实际参数。

修复: 解析失败时返回 _parse_error + _raw_args 字段,让工具和 LLM 能感知到参数问题并自我修正。

P2: ToolOutputGuard 过于激进 — 已修复 (2026-04-24)

文件: crates/zclaw-runtime/src/middleware/tool_output_guard.rs 第 109 行

使用 to_lowercase() 匹配敏感模式,合法内容中包含 "password"、"system:" 等字符串会被误拦。

修复: 改用 regex 精确匹配实际密钥值格式(如 sk-[a-zA-Z0-9]{20,}AKIA[A-Z0-9]{16}key=value 模式),不再误拦仅包含关键词的合法内容。移除了 "system:" 等过于宽泛的注入检测模式。

P2: ToolErrorMiddleware 失败计数器是全局的 — 已修复 (2026-04-24)

文件: crates/zclaw-runtime/src/middleware/tool_error.rs 第 27 行

consecutive_failures: AtomicU32 是结构体字段,所有 session 共享。高并发下 A session 失败 2 次 + B session 失败 1 次就会触发 AbortLoop阈值 3

修复: 改用 Mutex<HashMap<String, u32>> 以 session_id 为 key 存储计数,每个会话独立跟踪。

P3: Gateway 客户端 onTool 回调语义不一致 — 已修复 (2026-04-24)

文件: desktop/src/lib/gateway-client.ts 第 698-707 行

tool_calltool_result 两个 case 共用 onTool 回调,但参数约定不同,调用者必须通过 output 是否为空判断 start/end。

修复: 明确 tool_call 的 output 始终为 ''(修复了可能传递 data.output 的问题),添加清晰注释说明 start/end 语义约定。


二、根因分析

工具调用问题最常见的故障模式:

  1. LLM 返回的 tool_call 参数格式错误 → OpenAI 驱动静默替换为 {} → 工具以空参数执行 → 结果不符合预期
  2. 工具执行异常 → after_tool_call 中间件未调用 → 错误未格式化 → LLM 收到原始错误信息无法恢复
  3. 流被中断后重连 → DanglingToolMiddleware 修复悬挂 → 但如果修复逻辑本身有 bug如重复修补会导致消息膨胀

三、修复建议

修复 1: 在 loop_runner 中调用 after_tool_call

优先级: P0 影响文件: loop_runner.rs

在非流式模式的工具执行循环中(约第 530 行),工具执行后调用:

let after_result = middleware_chain.run_after_tool_call(
    &name, &input_json, &output_str, &mut ctx
).await;

在流式模式的工具执行后(约第 1020 行),同样调用。

修复 2: 将 ToolErrorMiddleware 计数器改为 per-session

优先级: P2 影响文件: middleware/tool_error.rs

使用 HashMap<String, u32> 以 session_id 为 key 存储计数。

修复 3: ToolOutputGuard 改为精确匹配

优先级: P2 影响文件: middleware/tool_output_guard.rs

只在检测到独立的密钥值时触发(如 sk-[48字符]),而非单词级匹配。


四、关键文件

文件 作用
crates/zclaw-runtime/src/loop_runner.rs 主循环,工具调度
crates/zclaw-runtime/src/tool.rs ToolRegistry + Tool trait
crates/zclaw-runtime/src/middleware/tool_error.rs 工具错误处理
crates/zclaw-runtime/src/middleware/tool_output_guard.rs 输出安全检查
crates/zclaw-runtime/src/middleware/dangling_tool.rs 断裂工具修复
crates/zclaw-runtime/src/driver/openai.rs OpenAI 兼容驱动
desktop/src/lib/gateway-client.ts 前端通信客户端
desktop/src/store/chat/streamStore.ts 前端流式处理