--- title: 中间件链 updated: 2026-04-22 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 循环。命中后跳过所有后续中间件。 ## 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) -> [按 priority 升序] 每层 middleware.before_completion() -> 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 中间件 | 优先级 | 中间件 | 文件 | 职责 | 注册条件 | |--------|--------|------|------|----------| | @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 立即中断, 不执行后续中间件 ### 核心接口 ```rust trait AgentMiddleware: Send + Sync { fn name(&self) -> &str; fn priority(&self) -> i32 { 500 } async fn before_completion(&self, ctx: &mut MiddlewareContext) -> Result; async fn before_tool_call(&self, ctx: &MiddlewareContext, tool_name: &str, tool_input: &Value) -> Result; 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-22 | DataMasking 中间件移除 | 14->14 层 (替换为无), 减少 1 层无收益处理 | | 04-22 | 跨会话记忆修复 | Memory 中间件去重+跨会话注入修复 | | 04-22 | Wiki 一致性校准 | 数字与代码验证对齐 | | 04-21 | Embedding 接通 | SkillIndex 路由 TF-IDF->Embedding+LLM fallback | | 04-15 | Heartbeat 统一健康系统 | TrajectoryRecorder 痛点感知增强 |