洋葱模型(中间件)

中间件嵌套组合模式:请求从最外层中间件逐层进入核心处理,响应再逐层返回,每个中间件可以同时拦截"进入"和"退出"两个时机,形如切开的洋葱断面。

定义

洋葱模型是一种中间件组合方式,得名于其执行路径的形状:请求从最外层中间件进入,穿过每一层到达核心,响应再从核心向外逐层返回,整体像洋葱的同心圆截面。

与 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 里,每个中间件同时持有 doGeneratedoStream 两个引用,可以在内部决定调用哪个。这种"持有完整调用决策权"的特性是 Pipeline 无法提供的——Pipeline 每个节点只看到前一节点的输出,无法在处理过程中切换调用路径。

典型代表

  • Koa.js 中间件(通过 next() 实现)
  • Express 中间件(单向,不是标准洋葱)
  • vercel/ai LanguageModelMiddleware
  • ASP.NET Core 中间件管道

引用本术语的文章