中间件系统:before/after 双阶段执行管线
中间件系统:before/after 双阶段执行管线
学习目标
理解 CopilotKit 的中间件架构:
- before/after 两个生命周期阶段
- 函数内联 vs Webhook 两种执行模式
- SSE 响应解析(从流中提取 messages、threadId、runId)
- Agent 作用域中间件(限定特定 Agent)
前置知识
本章涉及 API 网关与编排层的通用原理,建议先阅读:
项目实践
中间件生命周期
中间件类型定义
BeforeRequestMiddleware:
参数:{ runtime: CopilotRuntimeLike, request: Request, path: string}返回值:Request | void // 可修改请求或仅做副作用AfterRequestMiddleware:
参数:{ runtime: CopilotRuntimeLike, response: Response, path: string, messages?: Message[], // 从 SSE 流解析的消息 threadId?: string, // 从 RUN_STARTED 提取 runId?: string // 从 RUN_STARTED 提取}返回值:voidSSE 响应解析
parseSSEResponse 从 SSE 流中提取结构化数据:
解析流程(伪代码):1. 读取 response.body(ReadableStream)2. 逐块解析 SSE 事件3. 提取 RUN_STARTED 事件中的 threadId 和 runId4. 提取 TEXT_MESSAGE_* 事件组装为 Message[]5. 返回 { messages, threadId, runId }关键挑战:
- SSE 流是逐步到达的,需要增量解析
- 消息边界需要通过事件类型识别(TEXT_MESSAGE_START → CONTENT → END)
- 非 SSE 响应(如 JSON 错误)需要跳过解析
中间件执行
callBeforeRequestMiddleware(源码验证):1. 若 middleware 是函数 → 直接调用2. 若 middleware 是其他值 → 记录警告并跳过callAfterRequestMiddleware(源码验证):1. 先调用 parseSSEResponse 提取 messages/threadId/runId2. 若 middleware 是函数 → 调用并传入解析结果Agent 作用域
中间件可以通过配置限定只对特定 Agent 生效:
// 在 CopilotRuntime 中配置beforeRequestMiddleware: authMiddleware // 对所有 Agent 生效
// OpenGenerativeUI 等自动中间件支持作用域openGenerativeUI: { agents: ["my-agent"] // 仅对 my-agent 生效}中间件链
多个中间件按注册顺序执行:
执行链(伪代码):request → mw1(request) → mw2(modifiedRequest) → mw3(furtherModifiedRequest) → handlerbefore 中间件可以修改 Request 并传递给下一个;after 中间件只能观察 Response(不可修改)。
问题与规避
| 问题 | 规避策略 |
|---|---|
| After 中间件必须等待 SSE 流结束 | 长时间运行的 Agent 会延迟审计日志;考虑异步日志队列 |
| 中间件修改 body 后未正确序列化 | 使用 request.clone() 保留原始 body;修改后重新构建 Request |
| 中间件遗漏单端点路径 | 确保中间件同时覆盖 REST 和 single-endpoint 路径 |
| SSE 解析失败导致 after 中间件拿不到数据 | 非 SSE 响应(如错误)的 messages 为空;中间件需要处理空数组 |
设计取舍
函数 vs Webhook
函数中间件(当前支持):
- 优势:类型安全、同步执行、无网络延迟
- 代价:必须在运行时进程中执行
Webhook 中间件(协议支持,未广泛使用):
- 优势:可在独立服务中执行,支持跨语言
- 代价:网络延迟、需要处理超时和重试
Before vs After
Before:
- 适合:认证、日志、请求转换、速率限制
- 可修改请求内容
After:
- 适合:审计日志、响应分析、指标上报
- 只能观察,不可修改响应(SSE 流已经发送给客户端)
参考来源
packages/runtime/src/v2/runtime/core/middleware.tspackages/runtime/src/v2/runtime/core/middleware-sse-parser.ts