洋葱模型(中间件)
中间件嵌套组合模式:请求从最外层中间件逐层进入核心处理,响应再逐层返回,每个中间件可以同时拦截"进入"和"退出"两个时机,形如切开的洋葱断面。
定义
洋葱模型是一种中间件组合方式,得名于其执行路径的形状:请求从最外层中间件进入,穿过每一层到达核心,响应再从核心向外逐层返回,整体像洋葱的同心圆截面。
与 Pipeline 的核心区别
| 洋葱模型 | Pipeline | |
|---|---|---|
| 每个节点的视角 | 同时处理"进入"和"退出"两个时机 | 只处理"进入",单向传递 |
| 调用路径 | 外→内→外(往返) | 外→内(单向) |
| 调用路径切换权 | 每层中间件持有下一层的完整引用,可以选择调用哪个方法 | 只能看到前一节点的输出 |
vercel/ai 中的实现
typescript
// wrap-language-model.ts:L37-L41
return [...asArray(middlewareArg)]
.reverse()
.reduce((wrappedModel, middleware) => {
return doWrap({ model: wrappedModel, middleware });
}, model);
reverse() 后 reduce() 的组合实现了洋葱包裹:数组第一个中间件成为最外层(最先被调用),数组最后一个中间件紧贴核心 model。
反直觉点:数组顺序与执行包裹顺序相反。传入 [mwA, mwB, mwC],实际包裹顺序是 mwC(mwB(mwA(model)))——mwA 最外层,mwC 最内层。
为什么不用 Pipeline
在 wrapGenerate 里,每个中间件同时持有 doGenerate 和 doStream 两个引用,可以在内部决定调用哪个。这种"持有完整调用决策权"的特性是 Pipeline 无法提供的——Pipeline 每个节点只看到前一节点的输出,无法在处理过程中切换调用路径。
典型代表
- Koa.js 中间件(通过
next()实现) - Express 中间件(单向,不是标准洋葱)
- vercel/ai
LanguageModelMiddleware - ASP.NET Core 中间件管道
引用本术语的文章