Files
zclaw_openfang/docs/references/zclaw-toolcall-issues.md
iven c12b64150b
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): 工具调用 P0 修复 — after_tool_call 接入 + stream_errored 工具抢救
P0-1: after_tool_call 中间件从未被调用
- 流式模式(run_streaming)和非流式模式(run)均添加 middleware_chain.run_after_tool_call()
- ToolErrorMiddleware 错误计数恢复逻辑现在生效
- ToolOutputGuardMiddleware 敏感信息检测现在生效

P0-2: stream_errored 跳过所有工具执行
- 新增 completed_tool_ids 跟踪哪些工具已收到完整 ToolUseEnd
- 流式错误时区分完整工具和不完整工具
- 完整工具照常执行(产物创建等不受影响)
- 不完整工具发送取消 ToolEnd 事件(前端不再卡"执行中")
- 工具执行后若 stream_errored,break outer 阻止无效 LLM 循环

参考文档:
- docs/references/zclaw-toolcall-issues.md (10项问题分析)
- docs/references/deerflow-toolcall-reference.md (DeerFlow工具调用完整参考)
2026-04-24 12:20:14 +08:00

134 lines
5.3 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: 流式模式工具全串行
**文件**: `loop_runner.rs` 第 893-1070 行
非流式模式有 `JoinSet` + `Semaphore(3)` 并行执行 ReadOnly 工具,但流式模式用简单 `for` 循环串行执行所有工具。
**影响**: 多工具调用时延迟显著增加。
### P2: OpenAI 驱动工具参数静默替换
**文件**: `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` 替代实际参数。
### P2: ToolOutputGuard 过于激进
**文件**: `crates/zclaw-runtime/src/middleware/tool_output_guard.rs` 第 109 行
使用 `to_lowercase()` 匹配敏感模式,合法内容中包含 "password"、"system:" 等字符串会被误拦。
### P2: ToolErrorMiddleware 失败计数器是全局的
**文件**: `crates/zclaw-runtime/src/middleware/tool_error.rs` 第 27 行
`consecutive_failures: AtomicU32` 是结构体字段,所有 session 共享。高并发下 A session 失败 2 次 + B session 失败 1 次就会触发 AbortLoop阈值 3
### P3: Gateway 客户端 onTool 回调语义不一致
**文件**: `desktop/src/lib/gateway-client.ts` 第 698-707 行
`tool_call``tool_result` 两个 case 共用 `onTool` 回调,但参数约定不同,调用者必须通过 `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` | 前端流式处理 |