Action 原语与注册表:一切皆 Action
30 秒导读: Genkit 里没有"模型对象""工具对象""流程对象"这些各自为政的类型。 它把它们全部压成同一个东西——
Action:一个普通异步函数,外面包了四层能力(输入/输出 Zod 校验、每次调用开一个 OpenTelemetry span、可流式吐 chunk、可注入运行时 context)。再配一张按/类型/名字寻址的注册表。读懂这一章 = 拿到理解整个项目的钥匙:后面每一章的主角(generate、tool、flow、dotprompt……)都只是"某个 actionType 的 Action"。
1. 这是什么(零基础也能懂)
一句话定义: Action 是 Genkit 的地基抽象——一个把普通函数升级成"自描述、会校验、可观测、可远程调用"的可寻址单元。
它要解决的问题。 一个 AI 框架里有太多"可调用的东西":调大模型、跑一个工具、执行一段业务流程、渲染一个 prompt、查一个向量库。如果每种都单独设计一套类型、一套调用约定、一套可观测性接线,框架会迅速长成一堆互不相通的孤岛。
Genkit 的答案是归一化:与其做五种对象,不如只做一种原语,让这五种都变成"填了不同 actionType 的同一个 Action"。
它们共享的四样能力——这正是 action() 工厂免费送给每个被包装函数的东西:
| 能力 | 白话 | 谁提供 |
|---|---|---|
| 输入/输出校验 | 进来的参数、出去的结果都按 Zod schema 卡一遍,不合格直接抛错 | parseSchema |
| 可观测性 | 每次调用自动开一个 OpenTelemetry span,记下输入、输出、trace/span id | runInNewSpan |
| 流式 | 函数体内可以边算边 sendChunk(...) 往外吐增量结果 | ActionFnArg.sendChunk |
| context 注入 | 调用方可塞入 auth 等"侧信道"数据,函数体内用 context 读到 | runWithContext |
用起来什么样。 下面是一个最小的真实感示例,注意:你只是写了个普通函数,却立刻得到了校验 + 追踪 + 流式:
// 示意,非源码:一个 action 的最小样子
const upper = action(
{
actionType: 'custom',
name: 'upper',
inputSchema: z.string(), // 进来必须是 string
outputSchema: z.string(), // 出去必须是 string
streamSchema: z.string(), // 流式 chunk 也是 string
},
async (input, { sendChunk, streamingRequested }) => {
if (streamingRequested) sendChunk('working...'); // 想流式就吐 chunk
return input.toUpperCase();
}
);
await upper('hello'); // => 'HELLO'(且自动开了一个 span)
const { stream, output } = upper.stream('hello'); // 拿到流 + 最终结果
一句话直觉。 把 Action 想成"带说明书的 USB 设备":插进注册表这个"USB 集线器"后,谁都能按一个标准地址(/custom/upper)找到它、调用它、看它的输入输出规格、监控它的运行——不管它内部是模型、工具还是流程。