使用 Dotprompt 管理提示

Firebase Genkit 提供 Dotprompt 插件和文本格式,可帮助您编写和整理生成式 AI 提示。

Dotprompt 的设计基于提示即代码这一前提。您可以在特殊格式的文件(称为 dotprompt 文件)中编写和维护提示,使用与代码相同的版本控制系统跟踪对提示的更改,然后将提示与调用生成式 AI 模型的代码一起部署。

如需使用 Dotprompt,请先在项目根目录中创建 prompts 目录,然后在该目录中创建 .prompt 文件。在下面的示例中,您可以调用 greeting.prompt

---
model: vertexai/gemini-1.0-pro
config:
  temperature: 0.9
input:
  schema:
    location: string
    style?: string
    name?: string
  default:
    location: a restaurant
---

You are the world's most welcoming AI assistant and are currently working at {{location}}.

Greet a guest{{#if name}} named {{name}}{{/if}}{{#if style}} in the style of {{style}}{{/if}}.

如需使用此提示,请安装 dotprompt 插件,然后从 @genkit-ai/dotprompt 库导入 prompt 函数:

import { dotprompt, prompt } from '@genkit-ai/dotprompt';

configureGenkit({ plugins: [dotprompt()] });

然后,使用 prompt('file_name') 加载提示:

const greetingPrompt = await prompt('greeting');

const result = await greetingPrompt.generate({
  input: {
    location: 'the beach',
    style: 'a fancy pirate',
  },
});

console.log(result.text());

Dotprompt 的语法基于 Handlebars 模板语言。您可以使用 ifunlesseach 帮助程序在提示中添加条件部分或遍历结构化内容。该文件格式利用 YAML 前处理为内嵌在模板中的提示提供元数据。

定义输入/输出架构

Dotprompt 包含一种经过 YAML 优化的紧凑架构定义格式,名为 Picoschema,可让您轻松为 LLM 使用定义架构的最重要属性。下面是一个文章的架构示例:

schema:
  title: string # string, number, and boolean types are defined like this
  subtitle?: string # optional fields are marked with a `?`
  draft?: boolean, true when in draft state
  status?(enum, approval status): [PENDING, APPROVED]
  date: string, the date of publication e.g. '2024-04-09' # descriptions follow a comma
  tags(array, relevant tags for article): string # arrays are denoted via parentheses
  authors(array):
    name: string
    email?: string
  metadata?(object): # objects are also denoted via parentheses
    updatedAt?: string, ISO timestamp of last update
    approvedBy?: integer, id of approver
  extra?: any, arbitrary extra data
  (*): string, wildcard field

上面的架构等效于以下 TypeScript 接口:

interface Article {
  title: string;
  subtitle?: string | null;
  /** true when in draft state */
  draft?: boolean | null;
  /** approval status */
  status?: 'PENDING' | 'APPROVED' | null;
  /** the date of publication e.g. '2024-04-09' */
  date: string;
  /** relevant tags for article */
  tags: string[];
  authors: {
    name: string;
    email?: string | null;
  }[];
  metadata?: {
    /** ISO timestamp of last update */
    updatedAt?: string | null;
    /** id of approver */
    approvedBy?: number | null;
  } | null;
  /** arbitrary extra data */
  extra?: any;
  /** wildcard field */

}

Picoschema 支持标量类型 stringintegernumberbooleanany。对于对象、数组和枚举,它们在字段名称后用圆括号表示。

由 Picoschema 定义的对象具有必需的所有属性(除非由 ? 表示为可选属性),并且不允许添加额外的属性。当某个属性被标记为可选属性时,它也可以设为可为 null,以便 LLM 更宽松地返回 null,而不是省略某个字段。

在对象定义中,特殊键 (*) 可用于声明“通配符”字段定义。这将匹配显式键未提供的任何其他属性。

Picoschema 不支持完整 JSON 架构的许多功能。如果您需要更可靠的架构,可以改为提供 JSON 架构:

output:
  schema:
    type: object
    properties:
      field1:
        type: number
        minimum: 20

利用可重复使用的架构

除了在 .prompt 文件中直接定义架构之外,您还可以按名称引用在 defineSchema 中注册的架构。如需注册架构,请执行以下操作:

import { defineSchema } from '@genkit-ai/core';
import { z } from 'zod';

const MySchema = defineSchema(
  'MySchema',
  z.object({
    field1: z.string(),
    field2: z.number(),
  })
);

在提示中,您可以提供已注册架构的名称:

# myPrompt.prompt
---
model: vertexai/gemini-1.5-flash
output:
  schema: MySchema
---

Dotprompt 库会自动将名称解析为底层注册的 Zod 架构。然后,您可以利用该架构对 Dotprompt 的输出进行强类型输入:

import { prompt } from "@genkit-ai/dotprompt";

const myPrompt = await prompt("myPrompt");

const result = await myPrompt.generate<typeof MySchema>({...});

// now strongly typed as MySchema
result.output();

替换提示元数据

虽然 .prompt 文件允许您在文件本身中嵌入元数据(例如模型配置),但您也可以在每次调用时替换这些值:

const result = await greetingPrompt.generate({
  model: 'google-genai/gemini-pro',
  config: {
    temperature: 1.0,
  },
  input: {
    location: 'the beach',
    style: 'a fancy pirate',
  },
});

结构化输出

您可以将提示的格式和输出架构设置为强制转换为 JSON:

---
model: vertexai/gemini-1.0-pro
input:
  schema:
    theme: string
output:
  format: json
  schema:
    name: string
    price: integer
    ingredients(array): string
---

Generate a menu item that could be found at a {{theme}} themed restaurant.

生成包含结构化输出的提示时,请使用 output() 帮助程序检索和验证该提示:

const createMenuPrompt = await prompt('create_menu');

const menu = await createMenuPrompt.generate({
  input: {
    theme: 'banana',
  },
});

console.log(menu.output());

通过在提示中插入其他指令来实现输出一致性。默认情况下,它附加到提示所生成的最后一条消息的末尾。您可以使用 {{section "output"}} 辅助程序手动重新调整其位置。

This is a prompt that manually positions output instructions.

== Output Instructions

{{section "output"}}

== Other Instructions

This will come after the output instructions.

多消息提示

默认情况下,Dotprompt 会构建具有 "user" 角色的单条消息。有些提示最好用多条消息的组合表示,例如系统提示。

{{role}} 帮助程序提供了一种构建多消息提示的简单方法:

---
model: vertexai/gemini-1.0-pro
input:
  schema:
    userQuestion: string
---

{{role "system"}}
You are a helpful AI assistant that really loves to talk about food. Try to work
food items into all of your conversations.
{{role "user"}}
{{userQuestion}}

多轮提示和历史记录

Dotprompt 通过将 history 选项传递到 generate 方法支持多轮提示:

const result = await multiTurnPrompt.generate({
  history: [
    { role: 'user', content: [{ text: 'Hello.' }] },
    { role: 'model', content: [{ text: 'Hi there!' }] },
  ],
});

默认情况下,历史记录将在提示生成的最终消息之前插入。不过,您可以使用 {{history}} 辅助程序手动定位历史记录:

{{role "system"}}
This is the system prompt.
{{history}}
{{role "user"}}
This is a user message.
{{role "model"}}
This is a model message.
{{role "user"}}
This is the final user message.

多模态提示

对于支持多模态输入(例如图片和文字旁边的图片)的模型,您可以使用 {{media}} 辅助程序:

---
model: vertexai/gemini-1.0-pro-vision
input:
  schema:
    photoUrl: string
---

Describe this image in a detailed paragraph:

{{media url=photoUrl}}

该网址可以是 https:// 或用于“内嵌”图片且采用 base64 编码的 data: URI。在代码中,应如下所示:

const describeImagePrompt = await prompt('describe_image');

const result = await describeImagePrompt.generate({
  input: {
    photoUrl: 'https://example.com/image.png',
  },
});

console.log(result.text());

部分内容

部分内容是可重复使用的模板,可以包含在任何提示中。部分对具有共同行为的相关提示特别有用。

加载提示目录时,任何前缀为 _ 的文件都会被视为部分文件。因此,文件 _personality.prompt 可能包含以下内容:

You should speak like a {{#if style}}{{style}}{{else}}helpful assistant.{{/else}}.

然后,可以在其他提示中添加此信息:

---
model: vertexai/gemini-1.5-flash
input:
  schema:
    name: string
    style?: string
---

{{ role "system" }}
{{>personality style=style}}

{{ role "user" }}
Give the user a friendly greeting.

User's Name: {{name}}

使用 {{>NAME_OF_PARTIAL args...}} 语法插入部分内容。如果没有为部分提供任何参数,则会使用与父提示相同的上下文执行。

部分接受上述命名参数或表示上下文的单个位置参数。例如,这对于呈现列表成员非常有用。

# _destination.prompt
- {{name}} ({{country}})

# chooseDestination.prompt
Help the user decide between these vacation destinations:
{{#each destinations}}
{{>destination this}}{{/each}}

在代码中定义部分内容

您还可以使用 definePartial 在代码中定义部分:

import { definePartial } from '@genkit-ai/dotprompt';

definePartial(
  'personality',
  'Talk like a {{#if style}}{{style}}{{else}}helpful assistant{{/if}}.'
);

所有提示中均提供代码定义的部分。

提示变体

由于提示文件只是文本,因此您可以(也应该!)将提示文件提交到版本控制系统,从而轻松地比较一段时间内的更改。通常,提示的调整版本只能在生产环境中与现有版本并行进行全面测试。Dotprompt 通过其变体功能支持此功能。

如需创建变体,请创建一个 [name].[variant].prompt 文件。例如,如果您在问题中使用了 Gemini 1.0 Pro,但想要了解 Gemini 1.5 Pro 的表现是否会更好,则可以创建两个文件:

  • my_prompt.prompt:“基准”提示
  • my_prompt.gemini15.prompt:名为“gemini”的变体

如需使用提示变体,请在加载时指定 variant 选项:

const myPrompt = await prompt('my_prompt', { variant: 'gemini15' });

变体的名称包含在生成跟踪记录的元数据中,因此您可以在 Genkit 跟踪记录检查器中比较和对比变体之间的实际性能。

定义自定义帮助程序

您可以定义自定义帮助程序,以处理和管理提示中的数据。帮助程序使用 defineHelper 在全局范围内注册:

import { defineHelper } from '@genkit-ai/dotprompt';

defineHelper('shout', (text: string) => text.toUpperCase());

定义帮助程序后,您可以在任何提示中使用它:

---
model: vertexai/gemini-1.5-pro
input:
  schema:
    name: string
---

HELLO, {{shout name}}!!!

如需详细了解传递给辅助程序的参数,请参阅有关创建自定义辅助程序的 Handlebars 文档

加载和定义提示的备用方法

Dotprompt 针对提示目录中的组织结构进行了优化。不过,您还可以通过其他几种方式加载和定义提示:

  • loadPromptFile:从提示目录中的文件加载提示。
  • loadPromptUrl:通过网址加载提示。
  • defineDotprompt:在代码中定义提示。

示例:

import {
  loadPromptFile,
  loadPromptUrl,
  defineDotprompt,
} from '@genkit-ai/dotprompt';
import path from 'path';
import { z } from 'zod';

// Load a prompt from a file
const myPrompt = await loadPromptFile(
  path.resolve(__dirname, './path/to/my_prompt.prompt')
);

// Load a prompt from a URL
const myPrompt = await loadPromptUrl('https://example.com/my_prompt.prompt');

// Define a prompt in code
const myPrompt = defineDotprompt(
  {
    model: 'vertexai/gemini-1.0-pro',
    input: {
      schema: z.object({
        name: z.string(),
      }),
    },
  },
  `Hello {{name}}, how are you today?`
);