Mengelola prompt dengan Dotprompt

Firebase Genkit menyediakan plugin Dotprompt dan format teks untuk membantu Anda menulis dan mengatur prompt AI generatif Anda.

Dotprompt dirancang berdasarkan premis bahwa prompt adalah kode. Anda menulis dan mempertahankan prompt Anda dalam file berformat khusus yang disebut file dotprompt, melacak perubahannya menggunakan sistem kontrol versi yang sama dengan yang Anda gunakan untuk kode, dan men-deploy-nya bersama dengan kode yang memanggil model AI generatif.

Untuk menggunakan Dotprompt, buat direktori prompts terlebih dahulu di root project Anda dan lalu buat file .prompt di direktori tersebut. Berikut adalah contoh sederhana yang mungkin memanggil 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}}.

Untuk menggunakan perintah ini, instal plugin dotprompt, dan impor fungsi prompt dari library @genkit-ai/dotprompt:

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

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

Kemudian, muat prompt menggunakan 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());

Sintaksis titik prompt didasarkan pada bahasa template Handlebar. Anda dapat menggunakan helper if, unless, dan each untuk menambahkan bagian kondisional ke prompt Anda atau melakukan iterasi melalui konten terstruktur. Format file menggunakan frontmatter YAML guna menyediakan metadata untuk prompt yang merupakan bagian dari template.

Menentukan Skema Input/Output

Dotprompt menyertakan format definisi skema ringkas dan dioptimalkan YAML yang disebut Picoschema untuk memudahkan penentuan atribut terpenting dari suatu skema untuk penggunaan LLM. Berikut contoh skema untuk artikel:

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

Skema di atas setara dengan antarmuka TypeScript berikut:

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 mendukung jenis skalar string, integer, number, boolean, dan any. Untuk objek, array, dan enum, semuanya dilambangkan dengan tanda kurung setelah nama kolom.

Objek yang didefinisikan oleh Picoschema memiliki semua properti yang disyaratkan, kecuali dilambangkan sebagai properti opsional dengan ?, dan tidak mengizinkan properti tambahan. Ketika sebuah properti ditandai sebagai opsional, properti tersebut juga dibuat nullable untuk memberikan lebih banyak kemudahan bagi LLM agar menampilkan null daripada menghilangkan kolom.

Dalam definisi objek, kunci khusus (*) dapat digunakan untuk mendeklarasikan definisi bidang "karakter pengganti". Hal ini akan cocok dengan semua properti tambahan yang tidak disediakan oleh kunci eksplisit.

Picoschema tidak mendukung banyak kemampuan skema JSON lengkap. Jika memerlukan skema yang lebih andal, Anda dapat memberikan Skema JSON:

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

Memanfaatkan Skema yang Dapat Digunakan Kembali

Selain menentukan skema secara langsung dalam file .prompt, Anda dapat mereferensikan skema yang didaftarkan dengan defineSchema berdasarkan nama. Untuk mendaftarkan skema:

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

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

Dalam prompt, Anda dapat memberikan nama skema terdaftar:

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

Library Dotprompt akan otomatis me-resolve nama ke model skema Zod terdaftar. Anda kemudian dapat memanfaatkan skema tersebut untuk mengetik output 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();

Mengganti Metadata Prompt

Meskipun file .prompt memungkinkan Anda menyematkan metadata seperti konfigurasi model di file itu sendiri, Anda juga dapat mengganti nilai-nilai ini per panggilan:

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

Output terstruktur

Anda dapat menetapkan format dan skema output perintah untuk dipaksa masuk ke 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.

Saat membuat prompt dengan output terstruktur, gunakan helper output() untuk mengambil dan memvalidasinya:

const createMenuPrompt = await prompt('create_menu');

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

console.log(menu.output());

Kesesuaian output dicapai dengan memasukkan petunjuk tambahan ke dalam . Secara default, ID ini ditambahkan ke bagian akhir pesan terakhir yang dibuat oleh prompt. Anda dapat mengubah posisinya secara manual menggunakan {{section "output"}} .

This is a prompt that manually positions output instructions.

== Output Instructions

{{section "output"}}

== Other Instructions

This will come after the output instructions.

Prompt multi-pesan

Secara default, Dotprompt membuat satu pesan dengan peran "user". Beberapa prompt sebaiknya dinyatakan sebagai kombinasi dari beberapa pesan, seperti prompt sistem.

Helper {{role}} menyediakan cara mudah untuk membuat prompt multi-pesan:

---
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}}

Histori dan Perintah Multi Giliran

Dotprompt mendukung perintah multi-giliran dengan meneruskan opsi history ke Metode generate:

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

Secara default, histori akan disisipkan sebelum pesan terakhir yang dibuat oleh prompt tersebut. Namun, Anda dapat memosisikan histori secara manual menggunakan {{history}} bantuan:

{{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.

Prompt multi-modal

Untuk model yang mendukung input multimodal seperti gambar di samping teks, Anda bisa menggunakan helper {{media}}:

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

Describe this image in a detailed paragraph:

{{media url=photoUrl}}

URL dapat berupa URI https:// atau data: berenkode base64 untuk penggunaan gambar "inline". Dalam kode, ini akan menjadi:

const describeImagePrompt = await prompt('describe_image');

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

console.log(result.text());

Parsial

Parsial adalah template yang dapat digunakan kembali dan dapat disertakan dalam perintah apa pun. Parsial dapat sangat membantu untuk prompt terkait yang memiliki perilaku yang sama.

Saat memuat direktori prompt, file apa pun yang diawali dengan _ dianggap parsial. Jadi file _personality.prompt mungkin berisi:

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

Tindakan ini dapat disertakan dalam perintah lain:

---
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}}

Sebagian disisipkan menggunakan sintaksis {{>NAME_OF_PARTIAL args...}}. Jika tidak argumen diberikan ke parsial, argumen tersebut dieksekusi dengan konteks yang sama dengan perintah orang tua.

Parsial menerima argumen yang dinamai seperti di atas atau satu argumen posisi yang merepresentasikan konteks. Ini dapat berguna, misalnya, merender anggota daftar.

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

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

Menentukan Parsial dalam Kode

Anda juga dapat menentukan parsial dalam kode menggunakan definePartial:

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

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

Parsial yang ditentukan kode tersedia di semua perintah.

Varian Prompt

Karena file prompt berupa teks, Anda bisa (dan harus) melakukan commit ke sistem kontrol versi, yang memungkinkan Anda untuk membandingkan perubahan dari waktu ke waktu dengan mudah. Sering kali, versi prompt yang sudah diutak-atik hanya bisa diuji sepenuhnya di lingkungan produksi secara berdampingan dengan versi yang sudah ada. Dotprompt mendukung hal ini melalui fitur varian.

Untuk membuat varian, buat file [name].[variant].prompt. Misalnya, jika Anda menggunakan Gemini 1.5 Flash sesuai perintah Anda, tapi ingin tahu apakah Gemini 1.5 Pro akan berperforma lebih baik, Anda dapat membuat dua file:

  • my_prompt.prompt: prompt "dasar"
  • my_prompt.gemini15pro.prompt: varian bernama "gemini15pro"

Untuk menggunakan varian perintah, tentukan opsi variant saat memuat:

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

Nama varian disertakan dalam metadata trace pembuatan, sehingga Anda dapat membandingkan dan membedakan performa sebenarnya di antara varian dalam pemeriksa trace Genkit.

Menentukan Bantuan Kustom

Anda dapat menentukan helper kustom untuk memproses dan mengelola data di dalam perintah. Helper terdaftar secara global menggunakan defineHelper:

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

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

Setelah ditentukan, Anda dapat menggunakannya dalam perintah apa pun:

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

HELLO, {{shout name}}!!!

Untuk informasi selengkapnya tentang argumen yang diteruskan ke helper, lihat Dokumentasi Handlebar tentang cara membuat helper kustom.

Cara alternatif untuk memuat dan menentukan perintah

Dotprompt dioptimalkan untuk pengaturan di direktori prompt. Namun demikian, ada adalah beberapa cara lain untuk memuat dan menentukan prompt:

  • loadPromptFile: Memuat perintah dari file dalam direktori perintah.
  • loadPromptUrl: Memuat perintah dari URL.
  • defineDotprompt: Menentukan prompt dalam kode.

Contoh:

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?`
);