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

142 lines
6.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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 行
```rust
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_call``tool_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 行),工具执行后调用:
```rust
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` | 前端流式处理 |