数据截至 (上游 commit 31370a8f9b4b)
generate:模型调用与自动工具循环(agentic 引擎)
30 秒导读: 你调用一次
ai.generate({ prompt, tools }),Genkit 会替你反复跟模型 对话:模型说"我要调工具",Genkit 执行工具、把结果塞回去、再问模型,如此往复, 直到模型不再要工具为止——最多循环maxTurns(默认 5)轮,超了就抛错。这一章讲清 这个"自动工具循环"从头到尾是怎么转的。
本章聚焦循环的控制流与模型的调用。工具"内部怎么执行、如何中断做 human-in-the-loop" 留给 工具执行、中断与 human-in-the-loop;模型调用产出的结构化输出 细节见 Dotprompt 与结构化输出。想先弄懂"一切皆 Action" 和注册表,请回看 Action 原语与注册表。
1. 这是什么(零基础也能懂)
一句话定义: generate 是 Genkit 的"AI 编排核心"——把你给的 prompt、工具、输出格式,
变成对模型的一次(或多次)调用,并自动帮你把模型要求的工具调用跑完。
它解决什么问题? 现代模型不只会"回一段话",它还会说"我需要先查一下天气"(一次 tool call)。裸调 API 时,这套"模型 → 要工具 → 你执行 → 回给模型 → 模型再想 → ……" 的来回,得你自己手写循环。Genkit 把它做成了一个内建的、可控的闭环,你只管声明工具 。
给谁用? 任何想让模型"能动手做事"的开发者——写 agent、写带函数调用的助手、写要吐 结构化 JSON 的抽取器的人。
用起来什么样? 一个最小的例子:
// 示意,非源码:声明一个工具,然后 generate 自动完成整个工具循环
const getWeather = ai.defineTool(
{ name: 'getWeather', inputSchema: z.object({ city: z.string() }) },
async ({ city }) => `${city} 晴,26°C` // 工具真正干的活
);
const { text } = await ai.generate({
prompt: '北京今天适合出门吗?',
tools: [getWeather], // 把工具交给模型
maxTurns: 5, // 最多来回 5 轮(默认就是 5)
});
// 你什么循环都没写:模型自己决定调 getWeather,Genkit 执行后把结果喂回,模型再总结成 text
一句话直觉: 把 generate 想成一个尽职的中间人——它坐在你和模型之间,模型每次
"点单"要个工具,它就跑腿买回来递过去,来回跑腿,直到模型说"够了,这是最终答复"。
2. 顶层全景(它大概怎么转)
怎么读下面这张图: 从上到下是一次 ai.generate() 的生命周期;中间那个带箭头折回
的方框就是"自动工具循环"——它会一轮轮把自己再跑一遍。
ai.generate(options) generate.ts:490
│ 规整选项 → GenerateActionOptions toGenerateActionOptions
▼
generateHelper (包一层 trace span) action.ts:127
│
▼
generateActionImpl action.ts:232
│ 若有 generate 级中间件,先串成链
▼
┌─────────────────────────────────────────┐
│ generateActionTurn = 跑「一轮」 │ action.ts:337
│ ① 解析 model / tools / format │ resolveParameters
│ ② 规整成 GenerateRequest │ actionToGenerateRequest
│ ③ 过 model 中间件链 → 调模型 │ dispatchModel
│ ④ 收集 toolRequests │
│ │
│ 没有工具? ── 是 ──► 返回最终响应 │ ← 循环出口
│ │ 有 │
│ ▼ │
│ currentTurn+1 > maxTurns? ─ 是 ─► 抛 ABORTED
│ │ 否 │
│ ▼ │
│ 执行工具 → 得到 toolMessage │ resolveToolRequests(第4章)
│ │ │
│ ▼ 把模型消息 + 工具结果追加进 messages
└────────┼─────────────────────────────────┘
│ 递归:currentTurn + 1
└──────────────► 回到 generateHelper 再跑一轮
部件一句话职责:
| 部件 | 干什么 | 在哪(文件:符号) |
|---|---|---|
generate | 用户入口:规整选项、解析中间件/工具、发起循环 | generate.ts:490 generate |
toGenerateActionOptions | 把好用的 GenerateOptions 压成可序列化的 GenerateActionOptions | generate.ts:619 |
defineGenerateAction | 把整个循环注册成一个名为 generate 的 util Action | generate/action.ts:80 |
generateHelper | 给每一轮套一个 trace span(可观测性) | generate/action.ts:127 |
generateActionImpl | 若有 generate 级中间件,搭建中间件链;否则直接跑一轮 | generate/action.ts:232 |
generateActionTurn | 跑一轮:调模型 + 判断是否要继续循环 | generate/action.ts:337 |
resolveParameters | 把 model/tools/resources/format 从注册表解析成真 Action | generate/action.ts:170 |
dispatchModel | 把 model 中间件串成链,链尾调真正的模型 Action | generate/action.ts:454 |
defineModel | 把一个模型实现注册成 model 类型的 Action | model.ts:243 |
主线走一遍(高层): 你的 options →(规整)→ GenerateActionOptions →(套 trace)→
跑第一轮:调模型 → 模型回了带 toolRequest 的消息 →(执行工具)→ 把结果追加进对话 →
递归跑第二轮 → 模型这次只回文本、没工具了 → 循环结束,返回最终 GenerateResponse。
3. 核心原理(逐个机制,由浅入深)
3.1 选项规整:GenerateOptions → GenerateActionOptions → GenerateRequest
它要解决的小问题: 用户写的 GenerateOptions 很"人性化"——tools 可以是工具对象、
可以是字符串名;prompt 可以是字符串或多模态 parts;model 可以是引用或名字。但真正
喂给模型的东西必须是规整、可序列化的。所以中间要经过两层转换。
三层数据模型,各是什么:
| 层 | 类型 | 特征 | 谁生产 |
|---|---|---|---|
| 用户层 | GenerateOptions | 富对象:工具是 Action、prompt 是字符串/parts、有回调函数 | 你 |
| 动作层 | GenerateActionOptions | 扁平、可序列化:工具是字符串引用名、messages 已展开 | toGenerateActionOptions |
| 请求层 | GenerateRequest | 模型真正收到的:messages + config + tools 定义 + output schema | actionToGenerateRequest |
从 options 到 action options: toGenerateActionOptions(generate.ts:619)把 system /
messages / prompt 合并成一个 messages 数组(messagesFromOptions,generate.ts:348),
把工具对象转成引用字符串(toolsToActionRefs,generate.ts:303,形如 /tool/getWeather),
并把 maxTurns、returnToolRequests、toolChoice 等原样带上。
到请求层: 每一轮真正调模型前,actionToGenerateRequest(action.ts:572,循环内用的
那个)把动作层选项压成 ModelRequest:messages、config、tools(经 toToolDefinition 变成
给模型看的工具签名)、以及 output 约束。这里还会检查模型能力:如果你给了工具但模型
supports.tools 为假,只是 logger.warn 提醒,不报错(action.ts:583)。
另有一个独立