深度定制 Genkit 中间件:用 generateMiddleware 构建可复用、可插拔的生成管线(Genkit JS)
【免费下载链接】skillsAgent Skills for Google products and technologies项目地址: https://gitcode.com/GitHub_Trending/skills29/skills
导读
在 Genkit(Google 出品的 AI 应用开发框架)中,中间件是横切关注点(cross-cutting concerns)的统一承载机制:重试、降级、注入工具、改写请求/响应等能力都可以通过中间件挂载到生成流程上。本篇以 skills/cloud/genkit-js/references/middleware-custom.md 为骨架,系统讲解如何用generateMiddleware编写命名、可复用、带 Zod 配置校验的自定义中间件,并把它通过.plugin()注册为 Genkit 插件,让它在ai.generate、可执行 Prompt 和 Agent 上统一生效,同时能被 Genkit Dev UI 识别与可视化。
中间件机制一览:use: [...]数组
在深入自定义之前,先明确中间件挂载的基本方式。Genkit 中间件通过use: [...]数组附着到生成调用上,该数组在ai.generate/ai.generateStream、可执行 Prompt(definePrompt)和 Agent(defineAgent)上都受支持,见 使用中间件:
import { retry } from '@genkit-ai/middleware'; const res = await ai.generate({ model: googleAI.model('gemini-flash-latest'), prompt: 'Say hello', use: [retry({ maxRetries: 2 })], }); // Prompt 与 Agent 上使用同样的数组: const myPrompt = ai.definePrompt({ name: 'p', prompt: '...', use: [retry()] }); const myAgent = ai.defineAgent({ name: 'a', system: '...', use: [retry()] });注意这里的关键形态:中间件本身是一个可配置的工厂函数(如retry()/artifacts()),调用它返回一个供use: [...]使用的引用。@genkit-ai/middleware包中的七个现成中间件(retry、fallback、artifacts、agents、filesystem、skills、toolApproval)都是这种工厂形态,而generateMiddleware正是用来构建这一类自定义工厂的底层工具——这正是本篇文章的核心。
用 generateMiddleware 定义命名中间件
generateMiddleware是自定义中间件的入口,它负责两件事:
- 声明中间件的元信息:
name与description(后者用于 Dev UI 与调试展示)以及可选的configSchema; - 返回一个工厂函数:调用后得到一个可放进
use: [...]的引用,工厂内部可拿到用户传入的config以及 Genkit 的ai实例。
原文档给出了一个完整的timing.ts示例——一个记录模型调用耗时的中间件:
// timing.ts import { generateMiddleware, z } from 'genkit'; const OptionsSchema = z.object({ label: z.string().optional() }); export const timing = generateMiddleware( { name: 'timing', description: 'Logs how long the model call takes.', configSchema: OptionsSchema, }, ({ config, ai }) => { // Runs once per generate() call. Return any of the hooks below. return { model: async (req, ctx, next) => { const start = Date.now(); const res = await next(req, ctx); console.log(`[${config?.label ?? 'timing'}] ${Date.now() - start}ms`); return res; }, }; } );关键点拆解
configSchema与 Zod:configSchema使用 Zod schema 定义工厂的入参。用户调用timing({ label: 'gen' })时传入的对象会被校验,不合法时抛错,从而让中间件的配置在编译期和运行期都是类型安全的。原文档用z.object({ label: z.string().optional() })声明了一个可选字符串参数,这也呼应了 setup.md 中推荐的“schema 优先”工程实践——Genkit 中无论是 Flow 输入输出、Prompt 输入还是工具参数,都统一使用z(Zod)声明。- 工厂回调的时机:注释明确指出,工厂回调(
({ config, ai }) => ...)在每次generate()调用时执行一次,它返回的 hooks 集合定义了这一轮生成中中间件介入的具体位置。ai实例在回调中可用,这意味着中间件内部还可以进一步调用ai.generate、读取注册表等,实现更复杂的能力编排。 - 与内置中间件的同构性:
generateMiddleware产出的工厂形态与@genkit-ai/middleware包内的中间件完全一致,因此自定义中间件与内置中间件在use: [...]中可以无缝混用、按序执行。
注册为插件:让中间件进入 Dev UI
自定义中间件拿到.plugin()方法,可以直接注册进genkit({ plugins: [...] })。这是推荐做法——不注册时中间件依然能在use: [...]中工作,但无法被 Genkit Dev UI 识别与可视化:
import { genkit } from 'genkit'; import { googleAI } from '@genkit-ai/google-genai'; import { timing } from './timing.js'; export const ai = genkit({ plugins: [googleAI(), timing.plugin()], }); // Then use the factory in `use: [...]`: await ai.generate({ model: googleAI.model('gemini-flash-latest'), prompt: 'Hi', use: [timing({ label: 'gen' })], });这与内置中间件的注册方式完全一致。以retry为例,使用中间件 中的标准做法是:
export const ai = genkit({ plugins: [googleAI(), retry.plugin(), artifacts.plugin()], });注册带来的额外收益:Prompt frontmatter 中的use
注册后,中间件不仅限于代码内使用,还可以在 Dotprompt 的 frontmatter 中通过名称引用。在 dotprompt.md 中可以看到,.prompt文件的use字段接受中间件名(裸字符串)或{ name, config }映射,名称会解析到已注册在 Genkit 实例上的中间件:
use: - name: retry # bare string also works: `- retry` config: maxRetries: 4要让名称可解析,前提同样是先注册中间件插件:
const ai = genkit({ plugins: [googleAI(), retry.plugin()], promptDir: './prompts', });这意味着你的自定义中间件只要通过.plugin()注册,就能同样在.prompt文件里以use: - name: timing的方式声明式挂载,这大大提升了中间件的复用范围。
四个可用 Hook 详解
generateMiddleware的实例化回调返回一个GenerateMiddlewareDef,可以选择性地包含以下任意子集:
| Hook | 包裹的对象 | 典型用途 | 入参类型 |
|---|---|---|---|
generate(envelope, ctx, next) | 整个 generate 动作 | 注入请求参数、后处理响应、跨工具循环捕获错误 | envelope携带{ request, currentTurn, messageIndex } |
model(req, ctx, next) | 底层模型调用 | 缓存、重试、请求/响应改写 | req为GenerateRequest |
tool(req, ctx, next) | 单个工具调用 | 校验输入、缓存或覆盖工具输出 | req为ToolRequestPart,返回ToolResponsePart \| undefined |
tools: ToolAction[] | 静态注入工具 | 中间件激活时向模型注入能力(artifacts()/filesystem()正是这样注入工具的) | ToolAction[] |
一个同时声明全部 hooks 的骨架示例:
generateMiddleware({ name: 'example' }, ({ ai }) => ({ generate: async (envelope, ctx, next) => next(envelope, ctx), model: async (req, ctx, next) => next(req, ctx), tool: async (req, ctx, next) => next(req, ctx), tools: [ /* ToolAction[] */ ], }));Hook 的链式语义
每个 hook 都接受next(...)来继续调用链——可以传入被修改后的 request/envelope,并在await之后对结果做变换再返回。这种洋葱模型(onion model)与 Node.js 中间件一脉相承,也是@genkit-ai/middleware中内置中间件的实现基础。
以仓库中agents()中间件(多 Agent 编排)为例,可以直观看到tools静态注入 hook 的实际用途:agents()为每个子 Agent 注入一个delegate_to_<name>工具,并在系统提示词后追加<sub-agents>块;模型调用委托工具时,中间件运行子 Agent 并把其响应作为工具结果返回。同样,artifacts()中间件(Artifacts 用法)通过静态注入write_artifact/read_artifact两个工具并每轮注入<artifacts>清单,让 Agent 在会话中产出命名交付物。这些能力都是“在tools: ToolAction[]中返回工具定义 + 在系统提示词层面做注入”的组合拳,自定义中间件完全可以按同样的模式实现。
如何选择合适的 Hook
原文档给出了非常清晰的决策指南:
- 需要转换整个回合的 prompt/messages 或最终结果→ 选
generate。例如跨整个工具循环的统一错误处理、结果后处理、在请求中注入全局参数; - 需要缓存 / 重试 / 改写单次模型往返→ 选
model。这是性能类中间件(如内置的retry)的主战场; - 需要门控或记忆化工具执行→ 选
tool。例如校验工具入参、对高频工具结果做缓存; - 需要为模型提供额外能力→ 选
tools。静态注入一组ToolAction,让模型在中间件激活期间总是能看到这些工具。
值得注意的是内置核心中间件也遵循这一模型。在genkit核心(无需额外安装包,从genkit/model/middleware导入)中,simulateConstrainedGeneration、validateSupport、augmentWithContext等中间件都是通过改写模型请求或结果来实现能力的,例如:
import { simulateConstrainedGeneration } from 'genkit/model/middleware'; await ai.generate({ model: someModel, prompt: '...', use: [simulateConstrainedGeneration()], });理解了 hook 分工,你就能判断某个横切需求该落到哪个层级,而不是一律堆在generate上。
最佳实践与注意事项
始终通过.plugin()注册
Reminder: register custom middleware via
.plugin()(see above). It works inuse: [...]without registering, but unregistered middleware is not visible to the Genkit Dev UI.
原文档的提醒值得再次强调:不注册也能工作,但 Dev UI 看不到它。Genkit Dev UI 的中间件可视化、trace 展示和调试体验都依赖于注册信息,因此生产级代码中请务必把每个自定义中间件都加入plugins: [...]。
与内置中间件协同
自定义中间件与@genkit-ai/middleware包的内置中间件可以自由组合。例如在多 Agent 编排场景中,一个典型组合是:
use: [ agents({ agents: ['researcher', 'coder'], artifactStrategy: 'session' }), artifacts({ readonly: true }), retry(), ];你的自定义中间件可以作为这个链条中的一员,与内置中间件一起按use数组的顺序依次执行。
关注配置的校验与默认值
利用configSchema对工厂参数做 Zod 校验,并在工厂回调内部给config提供合理默认值(如config?.label ?? 'timing'),可以让中间件的调用方只传必要的参数,符合 Genkit “Be Minimal”(只指定与默认值不同的选项)的工程约定(见 SKILL.md)。
小结
generateMiddleware把“定义命名中间件 → 注册为插件 → 在use: [...]/ Prompt frontmatter 中使用”这一完整链路收拢成一个清晰的 API:用元信息声明身份,用configSchema保证配置安全,用四个 hooks(generate/model/tool/tools)在正确的层级注入横切逻辑,最后通过.plugin()让中间件成为 Genkit 实例的一等公民。掌握这套机制后,你不仅能消费内置中间件,还能像retry、artifacts、agents那样构建自己的可复用能力,让重试、观测、工具注入等逻辑以统一、可组合、可调试的方式贯穿整个生成管线。
【免费下载链接】skillsAgent Skills for Google products and technologies项目地址: https://gitcode.com/GitHub_Trending/skills29/skills
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考