Dotprompt でプロンプトを管理する

Firebase Genkit には、生成 AI プロンプトの作成と整理に役立つ Dotprompt プラグインとテキスト形式が用意されています。

Dotprompt は、「プロンプトはコードである」という考え方で設計されています。プロンプトは、dotprompt ファイルと呼ばれる特別な形式のファイルで作成、管理します。そして、コードに使用するのと同じバージョン管理システムを使用してプロンプトの変更を追跡し、生成 AI モデルを呼び出すコードとともにデプロイします。

Dotprompt を使用するには、まずプロジェクトのルートに prompts ディレクトリを作成し、 次に、そのディレクトリに .prompt ファイルを作成します。簡単な例を挙げましょう。 greeting.prompt を呼び出すことができます。

---
model: vertexai/gemini-1.5-flash
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 プラグインをインストールして、prompt 関数をインポートします。 @genkit-ai/dotprompt ライブラリを使用します。

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 frontmatter を使用して、テンプレートを使いインラインでプロンプトにメタデータを与えます。

入出力スキーマの定義

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: 'vertexai/gemini-1.5-pro',
  config: {
    temperature: 1.0,
  },
  input: {
    location: 'the beach',
    style: 'a fancy pirate',
  },
});

構造化出力

プロンプトの形式と出力スキーマを JSON に強制変換するように設定できます。

---
model: vertexai/gemini-1.5-flash
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.5-flash
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}}

マルチターンのプロンプトと履歴

ドットプロンプトでは、history オプションを generate メソッド:

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

デフォルトでは、履歴は、生成された最終メッセージの前に挿入されます。 表示されます。ただし、{{history}} を使用して手動で履歴をポジショニングできます。 helper:

{{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.5-flash
input:
  schema:
    photoUrl: string
---

Describe this image in a detailed paragraph:

{{media url=photoUrl}}

URL には、画像を「インライン」で使用するために、https:// URI または 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}}.'
);

コード定義の部分的要素は、すべてのプロンプトで使用できます。

プロンプトのバリアント

プロンプト ファイルはテキストにすぎないため、バージョン管理システムに commit できます(また、そうすべきです)。これにより、変更点を簡単に比較できます。調整されたバージョンのプロンプトは、本番環境で既存のバージョンと同居させた状態でテストする以外に、完全にテストできる方法がない場合がよくあります。Dotprompt は、これを バリアント機能でサポートしています。

バリアントを作成するには、[name].[variant].prompt ファイルを作成します。たとえば プロンプトで Gemini 1.5 Flash を使用していましたが、Gemini 1.5 が Pro の方がパフォーマンスが高いため、次の 2 つのファイルを作成することをおすすめします。

  • my_prompt.prompt: 「ベースライン」のプロンプト
  • my_prompt.gemini15pro.prompt: 「gemini15pro」という名前のバリアント

プロンプト バリアントを使用するには、読み込み時に variant オプションを指定します。

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

バリアント名は生成トレースのメタデータに含まれるため、Genkit トレース インスペクタを使い、バリアント間で実際のパフォーマンスを比較および対比できます。

カスタム ヘルパーの定義

カスタム ヘルパーを定義して、プロンプト内でデータを処理および管理できます。ヘルパー defineHelper を使用してグローバルに登録されます。

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

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

定義したヘルパーは、任意のプロンプトで使用できます。

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

HELLO, {{shout name}}!!!

ヘルパーに渡される引数の詳細については、 作成に関する Handlebars のドキュメント 使用できます。

プロンプトを読み込んで定義する別の方法

Dotprompt は、プロンプト ディレクトリ内の整理用に最適化されています。しかし、 プロンプトを読み込んで定義するその他の方法:

  • loadPromptFile: プロンプト ディレクトリ内のファイルからプロンプトを読み込みます。
  • loadPromptUrl: URL からプロンプトを読み込みます。
  • 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.5-flash',
    input: {
      schema: z.object({
        name: z.string(),
      }),
    },
  },
  `Hello {{name}}, how are you today?`
);