Genkit Python Dotprompt 实战指南:用 .prompt 文件编排模型、Schema 与模板
【免费下载链接】skillsAgent Skills for Google products and technologies项目地址: https://gitcode.com/GitHub_Trending/skills29/skills
Dotprompt 是 Genkit Python 中声明式管理 Prompt 的核心机制:.prompt文件把 YAML frontmatter(模型配置、输入输出 Schema)与 Handlebars 模板合二为一,让提示词逻辑彻底脱离 Python 代码,并让"变体"(variants)的切换变得极其轻量。读完本文,你将掌握从文件格式、Python 端注册、调用与流式输出,到 Helper、Variant、Partial、工具绑定和 LLM 评测复用的完整实战路径,可直接在 skills/cloud/genkit-python 所对应的 Genkit Python 项目中落地使用。
Dotprompt 是什么:一句话定位
.prompt文件本质上是一个"可执行的提示词单元"。它以 YAML frontmatter 声明模型选择、输入输出结构、格式约束,以 Handlebars 语法编写提示词模板本体。二者合一的直接收益有三点:
- 提示词逻辑不进 Python 代码:改文案、改语气、换模型,只动
.prompt文件,无需重新部署代码; - 变体(Variant)天然易做:同一个模板通过
<name>.<variant>.prompt的文件命名即可横向扩展; - Schema 与模板同处一处:输入输出的契约就在文件顶部,人可读、工具可解析。
在 Genkit Python 的 SKILL 文档体系中,Dotprompt 被列为与 Agents、Evals、FastAPI 并列的独立专题(见 SKILL.md),并且 Evals 中的 LLM Judge、Agents 中的 Prompt Agent 都直接依赖.prompt文件,属于整个 SDK 的公共底座。
.prompt 文件格式:frontmatter + Handlebars 模板
一个典型的.prompt文件长这样:
--- model: googleai/gemini-flash-latest input: schema: food: string tone?: string # optional scalar — just `field?: type` ingredients?(array): string # optional array/object needs the parenthetical output: schema: Recipe # references a schema registered with ai.define_schema() format: json --- {{role "system"}} You are a chef. Keep recipes practical. {{role "user"}} Generate a recipe for {{food}}. {{#if ingredients}} Prefer these ingredients: {{ingredients}}. {{/if}}frontmatter 关键字段逐项拆解
| 字段 | 含义 | 说明 |
|---|---|---|
model | 模型标识 | 使用带 provider 前缀的完整 ID,如googleai/gemini-flash-latest;provider 需在插件中注册(如GoogleAI()) |
input.schema | 输入契约 | 使用 Picoschema 紧凑语法声明输入字段 |
output.schema | 输出契约 | 可内联声明,也可引用通过ai.define_schema()注册的具名 Schema(如Recipe) |
output.format | 输出格式 | 常见值json,与ai.generate的output_format语义一致(完整支持text/json/array/enum/jsonl,见 examples.md) |
Picoschema 中可选字段的两种写法(易错点)
文档特意强调了可选字段的语法差异,这是 Dotprompt 最容易踩坑的地方:
- 标量可选字段:直接加
?后缀即可,如tone?: string; - 数组/对象可选字段:必须使用括号记法
ingredients?(array): string,不能只靠?。
从仓库文档可确认,这种"标量用field?: type、复杂类型用field?(array): type"的约定同样出现在 Genkit JS 版 Dotprompt 文档中(genkit-js/references/dotprompt.md),属于跨语言统一的行为约定。
模板正文:role 指令与单消息的语义
模板正文支持两种组织方式:
- 显式 role 指令:使用
{{role "system"}}/{{role "user"}}(以及同类指令)分段,让模型看到真实的 system 指令,这是推荐做法; - 扁平正文:如果模板体只是一段连续文本而没有 role 指令,它会整体成为一条 user 消息。
结合{{#if ingredients}}...{{/if}}这类 Handlebars 条件块,可以按输入动态裁剪提示词内容。
一个重要限制:input.schema 不做本地校验
文档明确提示:input.schema描述的是"给人和工具看的契约"。在发起模型调用之前,它不会在本地校验——缺少必填字段时,模板依然会渲染并打到 API。如果你需要硬性失败(hard failure),必须在自己的 Flow 里显式校验。这是 Dotprompt 当前版本的行为边界,设计 Prompt 流水线时要把这道防线放在业务层。
Python 端接入:prompt_dir 与 Schema 注册
.prompt文件需要放在prompts/目录下,并在Genkit()初始化时用prompt_dir指向它:
from pathlib import Path from pydantic import BaseModel from genkit import Genkit from genkit_google_genai import GoogleAI ai = Genkit( plugins=[GoogleAI()], model='googleai/gemini-flash-latest', prompt_dir=Path(__file__).resolve().parent.parent / 'prompts', ) class Recipe(BaseModel): title: str steps: list[str] ai.define_schema('Recipe', Recipe)要点拆解:
prompt_dir接收Path对象,这里用Path(__file__).resolve().parent.parent向上回溯两级再拼接'prompts',保证无论从哪个工作目录启动都能正确定位;- 输出 Schema 用Pydantic
BaseModel定义(这也是为什么 setup.md 提醒使用 Dotprompt + Schema 时需要uv add pydantic); - 通过
ai.define_schema('Recipe', Recipe)把类注册为具名 Schema,frontmatter 中的output.schema: Recipe才能按名解析。
项目依赖最小集为genkit与genkit-google-genai(参见 setup.md 中的pyproject.toml示例),运行前需export GEMINI_API_KEY=your_key_here。
调用一个 Prompt:没有 execute(),直接可调用
与某些框架不同,Genkit Python没有prompt.execute()。非流式调用走的是可调用对象本身(ExecutablePrompt.__call__),并且是"双重调用"形态:先ai.prompt('name')拿到ExecutablePrompt,再对结果调用(input={...})。
# Non-streaming — await the prompt itself (double-call: ai.prompt('name') then (...)) prompt = ai.prompt('recipe') response = await prompt(input={'food': 'banana bread'}) # same as: await ai.prompt('recipe')(input={'food': 'banana bread'}) result = Recipe.model_validate(response.output) # Variant (recipe.robot.prompt file) response = await ai.prompt('recipe', variant='robot')(input={'food': 'banana bread'})几个实战注意点:
- 输入以
input={...}关键字传入,键名须与 frontmatter 中input.schema声明一致; response.output是模型返回的结构化内容,由于 frontmatter 声明了output.format: json且引用RecipeSchema,可以直接用Recipe.model_validate(response.output)还原成 Pydantic 模型继续走业务逻辑;ai.prompt('recipe', variant='robot')会去加载recipe.robot.prompt文件(Variant 机制详见下文)。
同样的"返回可调用ExecutablePrompt"设计也出现在 Genkit JS/Dart 中(见 genkit-js/references/dotprompt.md),跨语言 API 形态保持一致。
流式输出:不 await .stream,分别消费 .stream 与 .response
流式场景与ai.generate_stream的设计一致:.stream(...)本身不要 await,而是拿回一个包含stream与response两个异步产物的对象。stream用于逐块消费增量文本,response用于等待最终结果——即便提前跳出循环不读后续 chunk,await result.response依然会正常完成。
from genkit import ActionRunContext @ai.flow() async def tell_story(subject: str, ctx: ActionRunContext) -> str: result = ai.prompt('story').stream(input={'subject': subject}) full = '' async for chunk in result.stream: if chunk.text: ctx.send_chunk(chunk.text) full += chunk.text final = await result.response # completes even if you stop reading chunks early return final.text or full这里把流式 Prompt 嵌进@ai.flow(),并用ctx.send_chunk(chunk.text)把增量推给调用方(Dev UI、HTTP 前端等),最后以final.text or full兜底返回完整文本。注意判断if chunk.text过滤空块——流式输出中并非每个 chunk 都携带文本。这套"stream+response分离、response 兜底收尾"的模式与 Genkit 的generate_stream完全同构(见 examples.md)。
只渲染不生成:为 LLM-Judge 评测省 Token
ExecutablePrompt还提供.render(input={...}),它只把模板渲染成最终的 messages,不发起模型调用。这在两类场景下极其有用:
- 写 LLM-Judge 评测器时,先用自定义 judge 模型对渲染结果做检查;
- 需要"先看 messages、再决定要不要花钱"的预检流程。
rendered = await ai.prompt('my_prompt').render(input={'key': 'value'}) # Inspect roles/messages before spending tokens: # print(rendered.messages) response = await ai.generate(model='googleai/gemini-flash-latest', messages=rendered.messages)渲染产物rendered.messages可以直接喂给ai.generate(messages=...),等于把.prompt文件当成"模板工厂"复用,而真正的生成交给代码里指定的模型。Genkit Python 的 Evals 专题就完整实践了这一模式:prompts/judge.prompt定义打分模板,评测器里await ai.prompt('judge').render(input={...})渲染后交给ai.generate获取分数(见 evals.md)。这是把"提示词管理"与"评测流水线"解耦的标准姿势。
自定义 Helper:小心 Handlebars 的参数打包行为
Handlebars 的 helper 参数传递比较特殊:模板参数以打包(packed)形式到达 Python 侧。文档给出的建议是:优先在模板里做简单的字符串/列表格式化;若必须自定义 helper,注意解包方式。
def list_helper(data: object, *args, **kwargs) -> str: # Positional template args arrive packed in the first parameter. items = data[0] if isinstance(data, (list, tuple)) and data else data if not isinstance(items, list): return '' return '\n'.join(f'- {item}' for item in items) ai.define_helper('list', list_helper)之后即可在.prompt模板中写作{{list ingredients}}。文档给了一条非常实用的排错经验:
如果渲染输出看起来像 Python 对列表的
repr(比如['a', 'b']原样出现),说明 helper 解包方式不对——修 helper,而不是让模型去适应。
这是一个典型的"模板层错误不该靠改提示词兜底"的原则:渲染是确定性的,先保证渲染正确,再谈模型表现。
Variant:用文件名做 A/B 测试
变体机制完全由文件命名驱动:
- 命名规则:
<name>.<variant>.prompt,例如recipe.robot.prompt; - 调用方式:
ai.prompt('recipe', variant='robot')。
这样同一个语义(recipe)可以同时存在多个风格/模型/温度配置的文件,代码层只需切换variant参数,非常适合做 Prompt 的 A/B 对比或按渠道分发不同话术。若ai.prompt('recipe')不带 variant,加载的就是基准文件recipe.prompt。
Partials:模板片段复用
Partial 是跨.prompt文件复用的模板片段:
- 文件名以
_开头:_partial_name.prompt; - 在模板中通过
{{>partial_name param=value}}引入。
例如:
prompts/_greeting.prompt # partial body only prompts/support_reply.prompt在support_reply.prompt中:
{{>greeting}} ... main template ... {{>disclaimer}}调用方式与普通 Prompt 无异:await ai.prompt('support_reply')(input={...})。值得注意的作用域规则:父模板的输入在 partial 内默认可见;只有当你显式覆盖参数(如{{>greeting name=name}})时,partial 内部才会使用传入的值。这套"默认继承、显式覆盖"的语义让公共片段(问候语、免责声明、输出格式说明)可以安全下沉到 partial 文件,避免多文件重复维护。
Prompt + Tools + 结构化输出
在模板里点名工具是不够的——必须把工具对象在调用时传入,或者在 frontmatter 的tools:列表里写上与已注册工具同名的名字:
response = await ai.prompt('insurance_quote')( input={'age': 35, 'zip': '94105', 'coverage_tier': 'plus'}, tools=[lookup_rate], ) quote = QuoteResult.model_validate(response.output)配套约束:当 frontmatter 的output.schema按名称引用 Schema 时,需先用ai.define_schema('QuoteResult', QuoteResult)完成注册(与上文Recipe的注册方式一致)。工具对象本身用@ai.tool()定义,参数必须是 PydanticBaseModel(见 examples.md),这样才能在调用链中正确生成 tool schema 供模型选择。
从仓库可知,.prompt文件与 Agent 体系也存在直接联动:define_prompt_agent(name=...)会直接复用同名.prompt文件(见 agents.md),并提示"保持 preamble 输入稳定,把动态字段放进 user 消息或工具里"——这进一步印证了.prompt文件作为全栈共用 Prompt 资产的地位。
小结:一套文件,五种用法
回顾全文,一个放在prompts/目录下的.prompt文件可以支撑五种使用形态:
- 直接生成:
await ai.prompt('name')(input={...})获得结构化response.output; - 流式输出:
.stream(input={...})后分别消费result.stream与await result.response; - 只渲染:
.render(input={...})得到 messages,供 LLM-Judge 评测或预检复用(参考 evals.md 的 judge 模式); - 组合工具:调用时传
tools=[...],配合output.schema具名引用与ai.define_schema()注册; - 驱动 Agent:
define_prompt_agent直接以同名.prompt文件为底稿(见 agents.md)。
同时记住三条容易踩坑的边界:input.schema不做本地校验(硬校验放 Flow 层);helper 参数是打包传入的(输出像 Python repr 就是解包错了);模板里点名工具不等于绑定工具(工具要传对象或 frontmatter 列名)。把这些规则内化后,prompts/目录就是整个 Genkit Python 应用的"提示词中枢",改动文案与模型配置不再需要碰一行业务代码。
【免费下载链接】skillsAgent Skills for Google products and technologies项目地址: https://gitcode.com/GitHub_Trending/skills29/skills
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考