Genkit — 架构与原理
30 秒导读: Genkit 是 Google(Firebase 团队)开源的、用来搭 AI 应用的全栈框架。它最核心的一招是:把模型、工具、提示、流程、检索器……所有能被调用的东西,都做成同一个原语
Action——一个 自描述、会校验输入输出、能被追踪、能流式返回、并由一个注册表按"/类型/名字"寻址的函数。你写的ai.generate(...)、ai.defineFlow(...)、插件里的每个模型,底下都是 Action。理解了 Action + 注册表,就理解了整个 Genkit。
1. 这是什么(零基础也能懂)
-
一句话定义: Genkit 是一套"AI 应用脚手架"——用统一的 API 接入各家大模型,帮你把提示模板、结构化输出、工具调用、多轮会话、可观测性这些活儿都标准化。
-
解决什么问题 / 给谁用: 假设你要写一个服务端功能:"用户问一句话,让 Gemini 回答,过程中模型可以自己去查天气、查数据库,最后按一个固定 JSON 结构返回"。裸调各家模型 SDK 你得自己处理:提示拼装、工具往返、把模型输出解析成类型、失败重试、还要能在本地调试看每一步。Genkit 把这些统统封好,给后端工程师用。
-
它能做什么(功能):
- 用统一接口接入 Google / OpenAI / Anthropic / Ollama 等模型(靠插件)。
- 类型安全的结构化输出(给个 Zod schema,拿回校验过的对象)。
- 工具调用 / agentic 循环:模型自己决定调哪个工具,框架自动跑完再问模型。
- Dotprompt 提示模板、多轮 Session/Chat、RAG 检索、评估器。
- 本地 Dev UI:可视化每一次 执行的 trace,单独跑某个 flow / prompt。
-
用起来什么样: 最小示例(来自 README.md:15-25):
import { genkit } from 'genkit';import { googleAI } from '@genkit-ai/google-genai';const ai = genkit({ plugins: [googleAI()] }); // 建实例、装插件const { text } = await ai.generate({ // 一次生成model: googleAI.model('gemini-flash-latest'),prompt: 'What is the meaning of life?',}); -
一句话直觉 / 类比: 把 Genkit 想成 AI 版的依赖注入容器 + 中间件框架。所有"能力"(模型、工具、提示)都注册进一个中央注册表,用一个字符串 key 就能取用;所有调用都走同一条"包了追踪和校验"的管道。
2. 顶层全景(它大概怎么转)
Genkit 的骨架就三样:门面 genkit() 负责装配,注册表 Registry 负责存取,Action 是被存取的那唯一一种东西。一次 ai.generate 调用,就是从注册表里按 key 取出模型 Action 和工具 Action,喂进"工具循环"跑完。
怎么读下面这张图: 从上到下是一次调用的生命周期;记住——每个方框里流动、被查找、被执行的,都是同一种 Action。
┌───────────────────────────────────────────────────────────┐
│ genkit({ plugins:[googleAI(), ...] }) —— 门面/装配 │
│ 建注册表、把每个插件的能力注册进去、暴露 defineXxx/generate │
└───────────────────────────┬───────────────────────────────┘
│ 注册 register
▼
┌───────────────────────────────────────────────────────────┐
│ Registry 注册表 —— 按 "/类型/名字" 寻址 │
│ 存的全是同一种东西:Action │
│ /model/googleai/gemini-... /tool/getWeather │
│ /prompt/myPrompt /flow/myFlow /util/generate │
└───────┬───────────────────────────────────────────┬───────┘
ai.generate │ lookupAction("/type/name") │ listActions
▼ ▼
┌───────────────────────────────┐ ┌────────────────────┐
│ /util/generate 工具循环 │ │ Reflection Server │
│ 模型 →(要调工具)→ 跑工具 │ │ → 本地 Dev UI │
│ → 回填结果 → 再问模型(≤5 轮) │ │ (看 trace / 试跑) │
└───────────────────────────────┘ └────────────────────┘
部件一句话职责:
| 部件 | 干什么 | 在哪个文件 |
|---|---|---|
Action | 框架唯一原语:自描述 + Zod 校验 + 可追踪 + 可流式 + 可远程调用的函数 | js/core/src/action.ts:238 |
Registry | 按 "/type/name" 存取 Action;懒加载插件;可 overlay 出子注册表 | js/core/src/registry.ts:152 |
genkit() 门面 | 建注册表、装插件、暴露 defineFlow/defineTool/generate 等 | js/genkit/src/genkit.ts:171 |
Plugin | 把 model / tool / embedder / retriever 等注册进注册表 | js/core/src/plugin.ts:27 |
| generate 工具循环 | 模型调用 + 自动工具循环(agentic 引擎) | js/ai/src/generate/action.ts:337 |
| Tool / Interrupt | 可被模型自动调用的 Action;interrupt 停机等人工 | js/ai/src/tool.ts:336 |
| Dotprompt / 格式化器 | 提示模板渲染 + 结构化输出解析(json/array/enum…) | js/ai/src/prompt.ts:248、js/ai/src/formats/ |
| Session / Agent | 多轮会话状态与快照持久化 | js/ai/src/session.ts:152 |
| Reflection Server | 把注册表暴露成 HTTP,喂给本地 Dev UI | js/core/src/reflection.ts:73 |
主线走一遍(高层,不进代码):
genkit({ plugins })建一个Registry,把/util/generate这个内置 Action、默认输出格式、以及每个插件都注册进去(genkit.ts:configure650)。插件此时只登记、不初始化。- 你调
ai.generate({ model, prompt, tools })。框架先 overlay 出一个子注册表(放本次的临时动态工具),把模型名、工具名解析成注册表里的 Action。 - 进入"工具循环":调模型 → 若模型回了
toolRequest就跑对应工具 Action → 把结果拼回消息 → 再调模型,直到模型不再要工具,或超过maxTurns(默认 5)。 - 最后按
output里的 schema/格式把模型输出解析成类型化对象返回。 - 开发时,
Reflection Server读同一个注册表,本地 Dev UI 就能列出所有 Action、单独试跑、看每一步 trace。
3. 阅读地图(建议顺序,由浅入深)
先读 01 建立"一切皆 Action"的心智模型,再顺着"能力 怎么进来(02)→ 怎么被驱动(03/04)→ 输入输出怎么塑形(05)→ 状态与观测(06)"往下走。
- Action 原语与注册表:一切皆 Action —— 最该先读的一章。Action 的类型/校验/追踪/流式,以及注册表
"/type/name"寻址、懒加载、overlay 子注册表。 - Plugin 系统与 genkit() 门面:如何把能力装进注册表 —— 插件(v1/v2)如何把模型、工具、检索器注册进来;
genkit()如何装配一切;懒加载 initializer/resolver。 - generate:模型调用与自动工具循环(agentic 引擎) ——
ai.generate到/util/generate到递归工具循环的全过程;maxTurns护栏;中间件洋葱模型;流式。 - 工具执行、中断与 human-in-the-loop —— 工具如何被解析执行;
interrupt用异常停机;respond/restart如何续跑一个被打断的工具调用。 - Dotprompt 与结构化输出:让模型吐出可校验的类型 ——
.prompt模板渲染;格式化器把模型文本解析成 Zod 类型;把 schema 作为指令注入提示。 - 会话状态与可观测性:Chat、Session 与 Dev UI —— 多轮会话状态、快照存储与 Agent;OpenTelemetry span 追踪;Reflection Server 与本地 Dev UI。
4. 巧妙之处(读者要带走的精华)
-
一切皆 Action —— 一个原语打天下。 模型、工具、提示、流程、检索器都是
Action,于是追踪、输入输出校验、流式、寻址、中间件这一整套只写一遍就人人共享。定义处的注释一句话点破它的野心:"Self-describing, validating, observable, locally and remotely callable function"(js/core/src/action.ts:236)。 -
字符串 key 即路由。 每个 Action 的 key 是
"/类型/名字"(如/model/googleai/gemini-2.5-flash),在action()里生成(js/core/src/action.ts:467),注册时按同规则入表(js/core/src/registry.ts:301)。好处:一个字符串就能定位任意能力——支持按名字引用工具、Dev UI 列举、乃至远程调用;parseRegistryKey负责反解(js/core/src/registry.ts:99)。 -
插件懒加载,冷启动便宜。 注册插件时只登记 provider,真正
initializer()推迟到lookupAction命中该插件、或首次initializeAllPlugins时才跑(js/core/src/registry.ts:242、434)。用不到的插件不付初始化代价。 -
overlay 子注册表,隔离临时状态。 每次
generate都Registry.withParent(registry)造一个子注册表(js/ai/src/generate.ts:504、js/ai/src/generate/action.ts:91),本次的动态工具/中间件工具注册在子表里,查不到再落到父表(js/core/src/registry.ts:195、258)——不污染全局。 -
agentic 循环 = 递归 + 护栏 。 工具循环不是 while,而是"处理完这一轮工具就递归调
generateHelper进下一轮",并用maxTurns ?? 5做硬上限,超了抛ABORTED(js/ai/src/generate/action.ts:514、562)。 -
中断用异常实现 human-in-the-loop。 工具里调
interrupt()抛出ToolInterruptError(js/ai/src/tool.ts:521),框架捕获后把toolRequest原样返回给上游、标finishReason:'interrupted'(generate/action.ts:531-539);人确认后用respond/restart构造续跑请求(js/ai/src/tool.ts:452、414)。 -
洋葱中间件,一套两用。 无论包装普通 Action 还是包装 model/generate,都用同一种"
dispatch(index)沿链下钻"的洋葱模型(js/core/src/action.ts:406、js/ai/src/generate/action.ts:260)。 -
Zod schema 双向发力。 同一个 schema 既在运行时校验入参/出参(
js/core/src/action.ts:497、600),又被转成 JSON schema 喂给模型当工具定义、或当结构化输出的约束指令(js/ai/src/tool.ts:260)。
5. 代码地图(导航索引)
按符号名 grep 比按行号更抗上游漂移。下面每行给出主题、文件、真实符号。
| 主题 | 文件路径 | 关键符号 |
|---|---|---|
| Action 类型定义(原语) | js/core/src/action.ts:238 | Action(type)、ActionMetadata |
| 造 Action / 追踪包裹 / 校验 | js/core/src/action.ts:450 | action()、runInNewSpan、parseSchema |
| 造 + 注册 Action | js/core/src/action.ts:692 | defineAction |
| 双向流式 Action | js/core/src/action.ts:748 | bidiAction、streamBidi |
| 注册表(存取核心) | js/core/src/registry.ts:152 | Registry、actionsById |
| Action 类型枚举 | js/core/src/registry.ts:40 | ACTION_TYPES、ActionType |
| key 寻址 / 反解 | js/core/src/registry.ts:99 | parseRegistryKey |
| 懒加载查找 | js/core/src/registry.ts:222 | lookupAction、initializePlugin |
| overlay 子注册表 | js/core/src/registry.ts:195 | Registry.withParent |
| 内置 Dotprompt 引擎挂载 | js/core/src/registry.ts:175 | this.dotprompt = new Dotprompt |
| 插件 provider 接口 | js/core/src/plugin.ts:27 | PluginProvider、InitializedPlugin |
| genkit() 门面 / 装配 | js/genkit/src/genkit.ts:171 | Genkit、genkit()、configure |
| 注册内置 generate action | js/ai/src/generate/action.ts:80 | defineGenerateAction(/util/generate) |
| 用户级 generate 入口 | js/ai/src/generate.ts:490 | generate、toGenerateActionOptions |
| 工具循环(agentic 引擎) | js/ai/src/generate/action.ts:337 | generateActionTurn、maxTurns |
| 中间件洋葱调度 | js/ai/src/generate/action.ts:260 | dispatchGenerate、dispatchModel |
| 工具解析 / 转定义 | js/ai/src/tool.ts:210 | resolveTools、toToolDefinition |
| 定义工具(注册 tool + tool.v2) | js/ai/src/tool.ts:336 | defineTool、basicTool |
| 中断 / 续跑 | js/ai/src/tool.ts:486 | interrupt、ToolInterruptError、respondTool、restartTool |
| 工具请求求解 / resume | js/ai/src/generate/resolve-tool-requests.ts:186 | resolveToolRequests、resolveResumeOption |
| Dotprompt 渲染 | js/ai/src/prompt.ts:248 | definePromptAsync、renderSystemPrompt |
| 输出格式化器 | js/ai/src/formats/index.ts | configureFormats、resolveFormat(json/array/enum/jsonl/text) |
| 会话状态 / 快照存储 | js/ai/src/session.ts:152 | Session、SessionStore |
| Agent(多轮) | js/genkit/src/genkit-beta.ts:166 | defineAgent、defineCustomAgent |
| 反射服务(Dev UI 后端) | js/core/src/reflection.ts:73 | ReflectionServer、/api/actions、/api/runAction |
| 定义模型 / 流程 | js/ai/src/model.ts:215、js/core/src/flow.ts:107 | defineModel(actionType:'model')、defineFlow(actionType:'flow') |