news 2026/9/14 19:20:19

深度定制 Genkit 中间件:用 generateMiddleware 构建可复用、可插拔的生成管线(Genkit JS)

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
深度定制 Genkit 中间件:用 generateMiddleware 构建可复用、可插拔的生成管线(Genkit JS)

深度定制 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包中的七个现成中间件(retryfallbackartifactsagentsfilesystemskillstoolApproval)都是这种工厂形态,而generateMiddleware正是用来构建这一类自定义工厂的底层工具——这正是本篇文章的核心。

用 generateMiddleware 定义命名中间件

generateMiddleware是自定义中间件的入口,它负责两件事:

  1. 声明中间件的元信息namedescription(后者用于 Dev UI 与调试展示)以及可选的configSchema
  2. 返回一个工厂函数:调用后得到一个可放进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与 ZodconfigSchema使用 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)底层模型调用缓存、重试、请求/响应改写reqGenerateRequest
tool(req, ctx, next)单个工具调用校验输入、缓存或覆盖工具输出reqToolRequestPart,返回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导入)中,simulateConstrainedGenerationvalidateSupportaugmentWithContext等中间件都是通过改写模型请求或结果来实现能力的,例如:

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 实例的一等公民。掌握这套机制后,你不仅能消费内置中间件,还能像retryartifactsagents那样构建自己的可复用能力,让重试、观测、工具注入等逻辑以统一、可组合、可调试的方式贯穿整个生成管线。

【免费下载链接】skillsAgent Skills for Google products and technologies项目地址: https://gitcode.com/GitHub_Trending/skills29/skills

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/14 19:19:58

Flutter跨平台开发:OpenHarmony个人理财App实战

1. 项目概述与核心价值这个Flutter for OpenHarmony个人理财管理App的月度报告页面&#xff0c;本质上是一个数据可视化与财务分析功能的集合体。作为个人理财应用的核心模块&#xff0c;它解决了传统记账软件"只记录不分析"的痛点。想象一下&#xff0c;你坚持记账一…

作者头像 李华
网站建设 2026/9/14 19:17:10

C盘爆满不用怕:保姆级清理流程,从磁盘分析到深度释放

C盘一红&#xff0c;很多人第一反应就是“删文件”&#xff0c;但删了一晚上&#xff0c;空间没见多多少&#xff0c;系统反而变卡了。这种场景我在帮朋友修电脑时见了太多。C盘清理不是“删点东西”那么简单&#xff0c;它更像一次给系统“排毒”的工程&#xff1a;既要清掉垃…

作者头像 李华