Files
zclaw_openfang/wiki/middleware.md
iven 9a313e3c92
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
docs(wiki): 回复效率+建议并行化优化 wiki 同步
- middleware.md: 分波并行执行设计决策 + parallel_safe 标注 + 不变量 + 执行流
- chat.md: suggestion prefetch + 解耦 memory + prompt 重写
- log.md: 追加变更记录
- CLAUDE.md: §13 架构快照 + 最近变更
2026-04-23 23:45:28 +08:00

143 lines
7.1 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.

---
title: 中间件链
updated: 2026-04-23
status: active
tags: [module, middleware, runtime]
---
# 中间件链
> 从 [[index]] 导航。关联模块: [[chat]] [[butler]] [[memory]] [[hands-skills]]
## 1. 设计决策
**中间件是请求处理的管道,每条聊天消息都经过完整链路。**
- **WHY 优先级排序 (0-999)**: 数值越小越先执行。宽范围设计允许在任意位置插入新中间件而无需重新编号。
- **WHY 注册顺序 != 执行顺序**: `kernel/mod.rs` 中 14 次 `chain.register()` 的代码顺序与运行时顺序无关chain 按 `priority()` 升序排列后执行。
- **WHY 6 类 14 层**: 进化(70-79) -> 路由(80-99) -> 上下文(100-199) -> 能力(200-399) -> 安全(400-599) -> 遥测(600-799),优先级范围即执行阶段。
- **WHY Stop/Block/AbortLoop**: 细粒度流控 -- Stop 中断 LLM 循环Block 阻止单次工具调用AbortLoop 终止整个 Agent 循环。命中后跳过所有后续中间件。
- **WHY 分波并行 (parallel_safe)**: `before_completion` 阶段,只修改 `system_prompt` 的中间件可声明 `parallel_safe() == true`,连续的 parallel-safe 中间件通过 `tokio::spawn` 并行执行,各自持有 `MiddlewareContext` clone完成后合并 prompt 贡献。降低串行延迟 ~1-3s。
## 2. 关键文件 + 数据流
### 核心文件
| 文件 | 职责 |
|------|------|
| `crates/zclaw-runtime/src/middleware.rs` | `AgentMiddleware` trait + `MiddlewareChain` 执行引擎 |
| `crates/zclaw-runtime/src/middleware/` | 14 个中间件实现 (.rs) |
| `crates/zclaw-kernel/src/kernel/mod.rs:248-361` | `create_middleware_chain()` 注册入口 (14 次 register) |
| `crates/zclaw-saas/src/main.rs` | SaaS HTTP 中间件注册 (10 层) |
### 执行流
```
用户消息 -> AgentLoop
-> chain.run_before_completion(ctx)
-> [分波并行] 检测连续 parallel_safe 中间件
-> Wave 并行 (2+ safe): tokio::spawn 各自 ctx.clone() → 合并 prompt
-> 串行 (unsafe / 单个 safe): 逐个执行
-> Continue: 下一层 | Stop(reason): 中断循环
-> LLM 调用
-> (工具调用时) chain.run_before_tool_call()
-> Allow | Block(msg) | ReplaceInput | AbortLoop
-> 工具执行
-> chain.run_after_tool_call()
-> chain.run_after_completion()
```
### 集成契约
| 方向 | 模块 | 接口 | 触发时机 |
|------|------|------|----------|
| Called by <- | kernel | `kernel/mod.rs:create_middleware_chain()` | Kernel 启动 |
| Calls -> | runtime | `MiddlewareChain::run_before_completion()` | 每条聊天请求 |
| Called by <- | saas | HTTP relay handler | SaaS relay 路由 |
| Provides -> | all | `AgentMiddleware` trait | 14 个实现 |
## 3. 代码逻辑
### 14 层 Runtime 中间件
| 优先级 | 中间件 | 文件 | 职责 | parallel_safe | 注册条件 |
|--------|--------|------|------|---------------|----------|
| @78 | EvolutionMiddleware | `evolution.rs` | 推送进化候选项到 system prompt | ✅ | 始终 |
| @80 | ButlerRouter | `butler_router.rs` | 语义技能路由 + system prompt 增强 + XML fencing | ✅ | 始终 |
| @100 | Compaction | `compaction.rs` | 超阈值时压缩对话历史 | ❌ | `compaction_threshold > 0` |
| @150 | Memory | `memory.rs` | 对话后自动提取记忆 + 注入检索结果 | ✅ | 始终 |
| @180 | Title | `title.rs` | 自动生成会话标题 | ✅ | 始终 |
| @200 | SkillIndex | `skill_index.rs` | 注入技能索引到 system prompt | ✅ | `!skill_index.is_empty()` |
| @300 | DanglingTool | `dangling_tool.rs` | 修复缺失的工具调用结果 | ❌ | 始终 |
| @350 | ToolError | `tool_error.rs` | 格式化工具错误供 LLM 恢复 | ❌ | 始终 |
| @360 | ToolOutputGuard | `tool_output_guard.rs` | 工具输出安全检查 | ❌ | 始终 |
| @400 | Guardrail | `guardrail.rs` | shell_exec/file_write/web_fetch 安全规则 | ❌ | 始终 |
| @500 | LoopGuard | `loop_guard.rs` | 防止工具调用无限循环 | ❌ | 始终 |
| @550 | SubagentLimit | `subagent_limit.rs` | 限制并发子 agent | ❌ | 始终 |
| @650 | TrajectoryRecorder | `trajectory_recorder.rs` | 轨迹记录 + 压缩 | ❌ | 始终 |
| @700 | TokenCalibration | `token_calibration.rs` | Token 用量校准 | ❌ | 始终 |
> 注册顺序 (代码) 与执行顺序 (priority) 不同。Chain 按 priority 升序排列后执行。
### 10 层 SaaS HTTP 中间件
| 层级 | 中间件 | 职责 |
|------|--------|------|
| 公共路由 | `public_rate_limit_middleware` | 20次/分钟/IP |
| 公共+认证 | `api_version_middleware` | API 版本校验 |
| 公共+认证 | `request_id_middleware` | 请求 ID 注入 |
| 认证路由 | `rate_limit_middleware` | 5次/分钟/IP |
| 认证路由 | `auth_middleware` | JWT 认证 + 权限 |
| 认证路由 | `TimeoutLayer` | 请求超时 15s |
| Relay 路由 | `api_version_middleware` | 版本校验 |
| Relay 路由 | `request_id_middleware` | 请求 ID |
| Relay 路由 | `quota_check_middleware` | 配额检查 |
| 全局 | CORS / 其他 layer | 跨域等 |
### 不变量
- Priority 升序: 0-999, 数值越小越先执行
- 注册顺序 != 执行顺序; chain 按 priority 运行时排序
- Stop/Block/AbortLoop 立即中断, 不执行后续中间件
- parallel_safe 中间件只修改 system_prompt不修改 messages不返回 Stop
- 分波合并: 并行 wave 中每个中间件 clone context完成后按 base_prompt_len 截取增量合并
### 核心接口
```rust
trait AgentMiddleware: Send + Sync {
fn name(&self) -> &str;
fn priority(&self) -> i32 { 500 }
fn parallel_safe(&self) -> bool { false }
async fn before_completion(&self, ctx: &mut MiddlewareContext) -> Result<MiddlewareDecision>;
async fn before_tool_call(&self, ctx: &MiddlewareContext, tool_name: &str, tool_input: &Value) -> Result<ToolCallDecision>;
async fn after_tool_call(&self, ctx: &mut MiddlewareContext, tool_name: &str, result: &Value) -> Result<()>;
async fn after_completion(&self, ctx: &MiddlewareContext) -> Result<()>;
}
```
## 4. 活跃问题 + 陷阱
### 活跃问题
- **11/14 中间件无独立测试** (P2): 仅 `butler_router`(12) / `evolution`(4) / `trajectory_recorder`(4) 有测试,共 20 个。其余 11 层依赖集成测试覆盖。
- **SkillIndex 条件注册** (长期观察): 无技能时不注册,非 bug 但需关注空技能场景下的行为一致性。
### 历史陷阱
| 问题 | 根因 | 修复 |
|------|------|------|
| TrajectoryRecorder 未注册 | V13-GAP-01: 遗漏 `chain.register()` 调用 | 已在 @650 注册 |
| Admin 端点 404 而非 403 | admin_guard_middleware 返回码错误 | 已修复为 403 |
| DataMasking 中间件 | 增加延迟但无实际安全收益 | 04-22 移除 |
## 5. 变更日志
| 日期 | 变更 | 影响 |
|------|------|------|
| 04-23 | 分波并行执行: parallel_safe() + wave detection + tokio::spawn | before_completion 阶段 5 层 safe 中间件可并行,延迟降低 ~1-3s |
| 04-22 | DataMasking 中间件移除 | 14->14 层 (替换为无), 减少 1 层无收益处理 |
| 04-22 | 跨会话记忆修复 | Memory 中间件去重+跨会话注入修复 |
| 04-22 | Wiki 一致性校准 | 数字与代码验证对齐 |
| 04-21 | Embedding 接通 | SkillIndex 路由 TF-IDF->Embedding+LLM fallback |