Genkit Model Action 规范全解:从 GenerateRequest 到流式响应的模型契约与实现
【免费下载链接】genkitOpen-source framework for building agentic apps in JavaScript, Go, Dart, and Python, built and used in production by Google项目地址: https://gitcode.com/GitHub_Trending/ge/genkit
Genkit(由 Google 构建并用于生产环境的开源 AI 应用框架,支持 JavaScript、Go、Dart 与 Python)将“模型”统一抽象为一种特殊的 Action。本文以仓库 docs/model-spec.md 为核心骨架,结合 JavaScript 与 Go 两套实现的源码证据,系统讲解模型动作的输入输出契约、Part 统一内容模型、元数据能力声明,以及系统消息、配置透传、工具调用、结构化输出等实现要求。读完本文,你将掌握在 Genkit 中定义一个新模型插件、实现流式响应与多轮工具循环的全部规范细节,并能对照源码理解这些契约在框架内部是如何被校验与执行的。
模型动作(Model Action)定义
在 Genkit 中,一个模型就是一个 Action,它具备以下固定特征(见 docs/model-spec.md):
| 特征 | 取值 |
|---|---|
| Action 类型(Action Type) | model |
| 输入 Schema(Input Schema) | GenerateRequest |
| 输出 Schema(Output Schema) | GenerateResponse |
| 流式 Schema(Streaming Schema) | GenerateResponseChunk |
在 JavaScript 实现中,这一契约被建模为 model.ts 中的ModelAction类型:
export type ModelAction<CustomOptionsSchema extends z.ZodTypeAny = z.ZodTypeAny> = Action< typeof GenerateRequestSchema, // 输入:GenerateRequest typeof GenerateResponseSchema, // 输出:GenerateResponse typeof GenerateResponseChunkSchema // 流式:GenerateResponseChunk > & { __configSchema: CustomOptionsSchema; };创建模型动作有两种方式:defineModel()(注册到 registry,供应用查找)和model()(仅创建不注册,适合插件作者从 resolver 返回模型动作)。两者最终都会调用modelActionOptions(),把输入输出 schema 与元数据打包成标准 Action 参数(model.ts):
return { actionType: 'model', name: options.name, description: label, inputSchema: GenerateRequestSchema, outputSchema: GenerateResponseSchema, metadata: { model: { label, customOptions, versions, supports } }, };在 Go 实现中,对应的是 gen.go 中的ModelAction(内嵌core.Action[*ModelRequest, *ModelResponse, *ModelResponseChunk])与 generate.go 中的Model接口——Generate(ctx, req *ModelRequest, cb ModelStreamCallback)同时承担普通调用与流式回调。
模型元数据(Metadata)
模型动作通过metadata.model声明自身能力,供框架、开发者工具(如 CLI)和上层应用做能力发现与校验。规范定义的字段如下:
label:人类可读名称,例如"Google AI - Gemini Pro"。versions:支持的版本字符串数组。supports:能力声明对象:multiturn:是否支持多轮历史消息;media:是否支持多模态输入;tools:是否支持工具调用;systemRole:是否支持system角色消息;output:支持的输出格式数组(如['json', 'text']);contentType:支持的输出内容类型数组;context:是否原生支持文档上下文(RAG grounding);constrained:原生约束生成支持级别,枚举'none'/'all'/'no-tools';toolChoice:是否支持强制指定工具选择;longRunning:是否支持长时运行操作。
stage:开发阶段,枚举'featured'/'stable'/'unstable'/'legacy'/'deprecated'。customOptions:模型特有配置的 JSON Schema,在请求中通过config暴露。
Go 端结构体ModelInfo与ModelSupports完整对应了上述字段,且为stage、constrained定义了具名常量(ModelStageFeatured…、ConstrainedSupportNone/All/NoTools),见 gen.go。
能力声明如何被框架使用
supports并非摆设,它在 model/middleware.ts 中被两类内置中间件消费:
validateSupport:在请求进入模型前做能力预检——例如supports.media === false时请求中若出现 media part、supports.tools === false时请求若带 tools、supports.multiturn === false时若传入多条消息,都会直接抛错,错误信息中会完整回显请求体,便于排查。getModelMiddleware(model.ts):当模型未声明context支持时自动挂载augmentWithContext();当constrained为'none'、或为'no-tools'且请求携带工具时,自动挂载simulateConstrainedGeneration(),用提示词模拟约束生成。
也就是说:模型作者只需如实声明能力,框架会自动补齐“不支持能力”的降级路径,这正是 Model Action 规范的价值所在。
数据契约:GenerateRequest 与 GenerateResponse
GenerateRequest(模型动作输入)
| 字段 | 类型 | 说明 |
|---|---|---|
messages | Message[] | (必填)会话历史消息列表 |
config | any | 模型特有配置(如 temperature、topK),按模型 config schema 校验 |
tools | ToolDefinition[] | 可供模型调用的工具列表 |
toolChoice | enum | 工具选择策略:'auto'、'required'、'none' |
output | OutputConfig | 期望输出格式/结构的配置 |
docs | DocumentData[] | 作为上下文使用的检索文档 |
JS 端对应 model-types.ts 中的ModelRequestSchema/GenerateRequestSchema(后者额外保留了一个已被废弃的candidates字段,注释明确说明“所有响应现在只返回单一候选”,可作为版本演进线索)。Go 端对应 gen.go 中的ModelRequest结构体。
toolChoice的三个取值在GenerateActionOptionsSchema的注释中有精确语义:auto让模型自行决定是否使用工具,required强制模型选择一个工具,none强制模型不使用任何工具,默认auto。
OutputConfig
| 字段 | 类型 | 说明 |
|---|---|---|
format | string | 期望格式(如'json'、'text') |
schema | Record<string, any> | 定义期望输出结构的 JSON Schema |
constrained | boolean | 是否原生强制 schema 约束 |
contentType | string | 输出的具体内容类型 |
JS 实现OutputConfigSchema位于 model-types.ts。此外框架内部还有更丰富的GenerateActionOutputConfig(含instructions、jsonSchema),供上层 generate action 使用。
GenerateResponse(模型动作输出)
| 字段 | 类型 | 说明 |
|---|---|---|
message | Message | 生成的消息 |
finishReason | enum | 结束原因:'stop'、'length'、'blocked'、'interrupted'、'other'、'unknown'、'failed' |
finishMessage | string | 结束原因的补充信息 |
error | RuntimeError | 当finishReason为'failed'(或被中止的模型停止)时的分类失败信息;'blocked'时不存在 |
usage | GenerationUsage | Token 与字符用量统计 |
latencyMs | number | 生成耗时(毫秒) |
custom | any | 模型特有附加信息 |
request | GenerateRequest | 触发本次响应的请求 |
值得注意的是,latencyMs并非由插件手工填充,而是defineModel/model在 runner 外层用performance.now()自动计时的(model.ts),插件只需返回普通响应即可。
finishReason在 JS 端为FinishReasonSchema枚举(model-types.ts,实际枚举含'aborted'共 8 项)。Go 端在 generate.go 中定义了isAbnormal()辅助方法:blocked、aborted、failed、interrupted、other均视为异常结束,驱动多轮工具循环的提前终止与错误归因。
usage的GenerationUsageSchema非常完整:除了 input/output/total tokens,还统计字符数、图片数、视频数、音频文件数,以及thoughtsTokens、cachedContentTokens和自定义custom计数。
GenerateResponseChunk(流式响应块)
| 字段 | 类型 | 说明 |
|---|---|---|
role | Role | 正在生成消息的角色(通常为'model') |
index | number | 响应中消息的索引(通常为 0) |
content | Part[] | (必填)本块包含的内容 parts |
aggregated | boolean | 为 true 时,本块包含到目前为止的全部累计内容 |
custom | any | 模型特有附加信息 |
JS 实现ModelResponseChunkSchema(GenerateResponseChunkSchema为其别名)位于 model-types.ts,注释明确了aggregated与增量(incremental)两种语义。
统一内容模型:Message 与 Part
Message
| 字段 | 类型 | 说明 |
|---|---|---|
role | enum | (必填)消息发送者角色:'system'、'user'、'model'、'tool' |
content | Part[] | (必填)消息内容,由一个或多个 part 组成 |
metadata | Record<string, any> | 与消息关联的任意元数据 |
RoleSchema = z.enum(['system', 'user', 'model', 'tool']),MessageSchema见 model-types.ts。
Part:统一的内容单元
Genkit 用统一的Part结构表示不同类型的内容,Part 是若干具体 part 类型的联合。JS 端的完整定义见 parts.ts,PartSchema由 8 种 part 联合而成(比规范文档多出resource类型)。
文本 Part(Text Part)
{ "text": "Hello, world!" }媒体 Part(Media Part)——多模态内容,内联数据应编码为data:URI(base64)。
图片:
{ "media": { "url": "data:image/jpeg;base64,/9j/4AAQSkZJRg...", "contentType": "image/jpeg" } }音频:
{ "media": { "url": "data:audio/L16;codec=pcm;rate=24000;base64,AAAAAA...", "contentType": "audio/L16;codec=pcm;rate=24000" } }视频:
{ "media": { "url": "https://example.com/video.mp4", "contentType": "video/mp4" } }所有 part 都可携带metadata,用于存放放不进主 schema 的提供者特有信息。常见用途包括图片/视频的mediaResolution、视频的videoMetadata(如时长、偏移量),或内部签名如thoughtSignature:
{ "media": { "url": "..." }, "metadata": { "mediaResolution": { "level": "MEDIA_RESOLUTION_HIGH" }, "videoMetadata": { "startOffset": { "seconds": 10 } } } }工具请求 Part(Tool Request Part)——模型请求执行某个工具:
{ "toolRequest": { "name": "weatherTool", "ref": "call_123", "input": { "city": "New York" } } }JS 端ToolRequestSchema还包含partial: boolean可选字段,用于流式工具调用的部分请求(见下文“部分工具请求”)。
工具响应 Part(Tool Response Part)——工具执行结果回传给模型:
{ "toolResponse": { "name": "weatherTool", "ref": "call_123", "output": { "temperature": 72 }, "content": [ ] } }注意ref必须与请求的 ref 匹配;output通常是结构化 JSON;content为可选内容 parts(例如工具返回图片等富内容)。JS 实现ToolResponseSchema支持{ output, content, metadata }的多部件(multipart)结构(parts.ts)。
自定义 Part(Custom Part)——表示未被其他类型覆盖的提供者特有内容,典型场景是服务端工具(如代码执行)的结果返回:
{ "custom": { "executableCode": { "code": "print('Hello World')", "language": "PYTHON" }, "codeExecutionResult": { "outcome": "OUTCOME_OK", "output": "Hello World\n" } } }推理 Part(Reasoning Part)——模型提供的思维链(chain-of-thought)或推理文本:
{ "reasoning": "First, I will calculate..." }数据 Part(Data Part)——规范中标注为“保留供未来使用,目前没有任何已知插件支持”,表示通用结构化数据:
{ "data": { "key": "value" } }提供者特有功能(Provider-Specific Features)
许多模型提供超出纯文本生成或客户端工具调用的服务端能力,规范要求统一通过config对象或特定 metadata 处理。
服务端工具(Server-Side Tools)
Web Search(Grounding)、Code Execution、URL Context 等通常实现为“服务端工具”——因为客户端不执行它们,所以配置在config中而非tools列表里。
Web Search 配置示例:
{ "config": { "googleSearch": {}, "tools": [{ "googleSearch": {} }] } }注:
googleSearch为提供者特有键;某些提供者可能使用tools配置键。
URL Context 配置示例:
{ "config": { "urlContext": { "urls": ["https://example.com/article"] } } }编码准则(Encoding Guidelines)
- 请求侧:启用/配置服务端功能一律使用
config;除非客户端确实要执行该工具,否则不要使用ToolRequestPart。 - 响应侧:
- 若服务端工具产生了内容(如代码执行输出),它可以作为
TextPart(若已融入回答)或CustomPart出现; - 执行相关的元数据(如搜索来源、grounding 元数据)应放在
GenerateResponse.custom字段或Message.metadata中。
- 若服务端工具产生了内容(如代码执行输出),它可以作为
行为规范(Behavior)
请求处理流程
- 校验:模型动作校验
GenerateRequest。 - 上下文:若提供了
docs,模型动作应将其纳入上下文,典型做法是增强消息历史。 - 工具:若提供了
tools,将其转换为底层模型 API 期望的格式。 - 配置:应用
config选项。
对应地,JS 框架为docs提供了augmentWithContext()中间件:把检索到的上下文文档渲染成文本追加到最后一条 user 消息,默认前言为"\n\nUse the following information to complete your task:\n\n",每条文档按[引用键]: 文本模板渲染(model/middleware.ts)。该中间件仅在模型未声明原生context支持时挂载,这正是规范第 2 条“典型做法是增强消息历史”的框架级实现。
系统消息处理
Genkit 将系统指令标准化为messages数组中的role: 'system'消息。但许多提供者(如 Google GenAI)要求系统指令作为独立配置字段而非会话历史的一部分。
实现要求(MUST):
- 模型动作必须接受输入
messages数组中的role: 'system'消息; - 若底层提供者要求独立系统指令:
- 从
messages数组中提取系统消息; - 按提供者要求转换/格式化(如
systemInstruction字段); - 若提供者不支持历史中的
system角色,确保这些消息不会被传入常规会话历史。
- 从
框架为不支持原生 system role 的模型提供了simulateSystemPrompt()中间件(model/middleware.ts):把system消息改写成一对 user 消息("SYSTEM INSTRUCTIONS:\n" + 指令)与 model 消息("Understood.")插入历史,实现模拟系统提示。
配置处理:透传模式(Passthrough)
模型插件应遵循“透传”模式处理配置,这样底层模型 API 新增的功能无需更新插件即可被用户立即使用:
- 提取已知选项:显式解构已知配置键(如
temperature、topK、topP),按 Genkit 通用 schema 或特定逻辑处理; - 透传其余选项:把所有剩余未知键直接传给底层模型 API 的配置对象。
const { temperature, topK, ...restOfConfig } = request.config || {}; const apiRequest = { model: modelName, temperature: temperature, // 处理已知键 top_k: topK, ...restOfConfig // 透传未知键 };- 合并工具:若提供者支持通过配置传工具(如
config.tools),应与标准request.tools合并,让用户能在标准 Genkit 工具之外附带提供者特有的工具定义(如服务端工具):
const tools = request.tools?.map(toProviderTool) || []; if (config.tools) { tools.push(...config.tools); }JS 框架侧,GenerationCommonConfigSchema(model-types.ts)本身就以.passthrough()声明,已定义version、temperature、maxOutputTokens、topK、topP、stopSequences(最多 5 条)、apiKey等通用键,其余键原样透传。规范中“已知键按通用 schema 处理、未知键透传”的双层设计,与插件实现侧(如google-genai的 config overrides)配合,形成完整的配置链路。
响应生成
- 内容:模型输出被解析为
Part对象——文本映射为TextPart,函数调用映射为ToolRequestPart。 - 流式:流式时模型发出
GenerateResponseChunk:- 理想情况下 chunk 应包含增量更新;
- 若底层模型在流式时只支持完整响应,则应设置
aggregated: true。
- 结束原因:模型必须把提供者特有的 finish reason 映射为 Genkit 标准枚举。
JS 端defineModel会为响应自动补latencyMs(performance.now()计时),同时modelActionOptions会把configSchema转成 JSON Schema 写入metadata.model.customOptions,供工具链校验用户传入的config。
工具处理
工具是 Genkit 模型的核心能力,实现涉及定义转换、请求处理(含流式)与响应处理三部分。
工具定义转换
模型动作必须把 Genkit 的ToolDefinition转换为提供者期望的格式:
- 名称:若提供者有严格命名规则需做清洗(例如 Gemini 把
/替换为__); - 输入 Schema:把
inputSchema中的 JSON Schema 转换为提供者的 schema 格式; - 描述:透传工具描述。
ToolDefinition在 JS 端包含name、key、description、inputSchema、outputSchema、metadata(model-types.ts)。
工具请求
当模型决定调用工具时,发出ToolRequestPart:
- ref:若提供者支持,分配稳定的
ref(call ID)用于关联响应; - input:工具参数。
部分工具请求(流式):部分模型(如 Gemini 3.0)支持流式工具调用,此时模型发出partial: true的ToolRequestPart:
partial请求中的input应包含到目前为止累计的参数(取决于插件状态管理逻辑),或当前 delta;- 工具调用的最后一个 chunk 应为
partial: false(或省略该字段)。
JS 端ToolRequestSchema的partial字段(parts.ts)正是为这一场景设计的。
工具响应
工具执行结果以role: 'tool'消息中的ToolResponsePart回传给模型:
- ref:必须匹配对应
ToolRequestPart的ref; - output:工具执行结果(通常是 JSON 对象);
- content:可选 parts 列表(如工具返回图片或其他富内容)。
多轮流程(Multi-turn Flow)
支持工具的模型必须处理如下对话循环:
User MessageModel Message(包含ToolRequestPart)Tool Message(包含ToolResponsePart)Model Message(最终回答)
Go 端 generate.go 中实现了完整的工具循环:框架负责执行工具并把结果回填(Response.ToolRequests()、FinishReason.isAbnormal()用于判断异常终止),插件只需在一次调用中正确产出ToolRequestPart并在后续收到tool角色消息后给出最终回答。JS 端GenerateActionOptionsSchema中的maxTurns(默认 5)与returnToolRequests则控制了这一循环的迭代上限与是否交由上层手动处理工具请求。
结构化输出(Structured Output)
- 若提供
output.schema,模型应尝试生成匹配该 schema 的内容; - 若
output.constrained为 true 且模型支持,则由模型生成过程原生强制 schema; - 否则,schema 可被包含在提示词指令中;
- 结果的结构化数据通常序列化在
TextPart中。
框架为不支持原生约束生成的模型提供了simulateConstrainedGeneration()中间件(model/middleware.ts):把 schema 以"Output should be in JSON format and conform to the following schema:\n\n\``\n{...}\n```"的形式注入 user 消息,同时对底层模型关闭constrained标记,实现“模拟约束生成”。而constrained声明为'all'` 的模型则直接透传,由其自身保证 schema 遵守。
跨语言一致性:一份规范,三套实现
规范文档描述的契约在仓库中并非单一语言的孤例,而是通过多语言实现保持一致:
- JavaScript/TypeScript:model-types.ts 定义全部 Zod schema(
GenerateRequestSchema、ModelResponseSchema、GenerateResponseChunkSchema、FinishReasonSchema、GenerationUsageSchema等),parts.ts 定义 Part 联合类型,model.ts 定义defineModel/model创建入口,model/middleware.ts 提供能力校验与降级中间件; - Go:gen.go 定义
ModelInfo、ModelSupports、ModelStage、ConstrainedSupport与ModelRequest,generate.go 定义Model接口、ModelAction与多轮工具循环; - Python:
py/packages/genkit包亦实现了同等的模型抽象(如genkit/ai模块),可用于对照阅读。
实战清单:实现一个符合规范的模型插件
综合规范与源码,一个合规的 Genkit 模型插件作者需要落实:
- 声明式能力:如实填写
label、versions、supports(尤其是constrained与context,它们直接决定框架是否挂载模拟中间件)与stage; - 配置透传:解构通用键后把剩余配置透传给底层 API,不要拦截未知参数;
- Part 双向转换:输入侧把提供者返回的文本/函数调用/媒体映射为
TextPart/ToolRequestPart/MediaPart,输出侧把服务端工具内容放入CustomPart; - 系统消息适配:接受
role: 'system'输入,按提供者要求提取到独立字段; - 流式语义正确:增量时发增量 chunk,只能给整段时设
aggregated: true,流式工具调用用partial标记; - finish reason 映射:把提供者结束原因归一化到标准枚举,异常结束(
blocked/failed/interrupted等)不要继续走工具循环; - 结构化输出:原生约束按
output.constrained透传,否则依赖框架的模拟约束中间件。
对照本文的契约表与源码路径,你可以逐项核对自己的模型实现,确保它能在 Genkit 的 generate action、开发者工具与多语言生态中无缝工作。
【免费下载链接】genkitOpen-source framework for building agentic apps in JavaScript, Go, Dart, and Python, built and used in production by Google项目地址: https://gitcode.com/GitHub_Trending/ge/genkit
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考