Mengelola prompt dengan Dotprompt

Engineering prompt adalah cara utama yang dapat Anda gunakan untuk memengaruhi output model AI generatif sebagai developer aplikasi. Misalnya, saat menggunakan LLM, Anda dapat membuat perintah yang memengaruhi nada, format, panjang, dan karakteristik lainnya dari respons model.

Cara Anda menulis perintah ini akan bergantung pada model yang Anda gunakan; perintah yang ditulis untuk satu model mungkin tidak berperforma baik saat digunakan dengan model lain. Demikian pula, parameter model yang Anda tetapkan (suhu, top-k, dan sebagainya) juga akan memengaruhi output secara berbeda, bergantung pada model.

Mendapatkan ketiga faktor ini—model, parameter model, dan perintah—agar bekerja sama untuk menghasilkan output yang Anda inginkan jarang merupakan proses yang mudah dan sering kali melibatkan iterasi dan eksperimen yang substansial. Genkit menyediakan library dan format file yang disebut Dotprompt, yang bertujuan untuk membuat iterasi ini lebih cepat dan lebih praktis.

Dotprompt dirancang berdasarkan premis bahwa prompt adalah kode. Anda menentukan perintah beserta model dan parameter model yang ditujukan untuknya secara terpisah dari kode aplikasi Anda. Kemudian, Anda (atau, mungkin seseorang yang bahkan tidak terlibat dalam menulis kode aplikasi) dapat melakukan iterasi dengan cepat pada perintah dan parameter model menggunakan UI Developer Genkit. Setelah perintah berfungsi seperti yang Anda inginkan, Anda dapat mengimpornya ke aplikasi dan menjalankannya menggunakan Genkit.

Setiap definisi perintah Anda akan ditempatkan dalam file dengan ekstensi .prompt. Berikut contoh tampilan file ini:

---
model: googleai/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}}.

Bagian dalam tiga tanda hubung adalah frontmatter YAML, mirip dengan format front matter yang digunakan oleh GitHub Markdown dan Jekyll; bagian lain dari file adalah prompt, yang secara opsional dapat menggunakan template Handlebars. Bagian berikut akan membahas lebih lanjut setiap bagian yang membuat file .prompt dan cara menggunakannya.

Sebelum memulai

Sebelum membaca halaman ini, Anda harus sudah memahami konten yang dibahas di halaman Membuat konten dengan model AI.

Jika Anda ingin menjalankan contoh kode di halaman ini, selesaikan langkah-langkah dalam panduan Memulai terlebih dahulu. Semua contoh mengasumsikan bahwa Anda telah menginstal Genkit sebagai dependensi dalam project.

Membuat file perintah

Meskipun Dotprompt menyediakan beberapa cara yang berbeda untuk membuat dan memuat perintah, Dotprompt dioptimalkan untuk project yang mengatur perintahnya sebagai file .prompt dalam satu direktori (atau subdirektorinya). Bagian ini menunjukkan cara membuat dan memuat perintah menggunakan penyiapan yang direkomendasikan ini.

Membuat direktori perintah

Library Dotprompt diharapkan menemukan perintah Anda di direktori di root project dan otomatis memuat perintah apa pun yang ditemukan di sana. Secara default, direktori ini diberi nama prompts. Misalnya, menggunakan nama direktori default, struktur project Anda mungkin terlihat seperti ini:

your-project/
├── lib/
├── node_modules/
├── prompts/
│   └── hello.prompt
├── src/
├── package-lock.json
├── package.json
└── tsconfig.json

Jika ingin menggunakan direktori lain, Anda dapat menentukannya saat mengonfigurasi Genkit:

const ai = genkit({
  promptDir: './llm_prompts',
  // (Other settings...)
});

Membuat file perintah

Ada dua cara untuk membuat file .prompt: menggunakan editor teks, atau dengan UI developer.

Menggunakan editor teks

Jika Anda ingin membuat file perintah menggunakan editor teks, buat file teks dengan ekstensi .prompt di direktori perintah: misalnya, prompts/hello.prompt.

Berikut adalah contoh minimal file perintah:

---
model: vertexai/gemini-1.5-flash
---
You are the world's most welcoming AI assistant. Greet the user and offer your assistance.

Bagian dalam tanda hubung adalah frontmatter YAML, mirip dengan format frontmatter yang digunakan oleh markdown GitHub dan Jekyll; bagian lain dari file adalah perintah, yang secara opsional dapat menggunakan template Handlebars. Bagian pengantar bersifat opsional, tetapi sebagian besar file perintah setidaknya akan berisi metadata yang menentukan model. Bagian lain dari halaman ini menunjukkan cara melakukan hal-hal di luar ini, dan menggunakan fitur Dotprompt dalam file perintah Anda.

Menggunakan UI developer

Anda juga dapat membuat file perintah menggunakan runner model di UI developer. Mulai dengan kode aplikasi yang mengimpor library Genkit dan mengonfigurasinya untuk menggunakan plugin model yang Anda minati. Contoh:

import { genkit } from 'genkit';

// Import the model plugins you want to use.
import { googleAI } from '@genkit-ai/googleai';

const ai = genkit({
  // Initialize and configure the model plugins.
  plugins: [
    googleAI({
      apiKey: 'your-api-key', // Or (preferred): export GOOGLE_GENAI_API_KEY=...
    }),
  ],
});

Tidak masalah jika file berisi kode lain, tetapi kode di atas adalah satu-satunya yang diperlukan.

Muat UI developer dalam project yang sama:

genkit start -- tsx --watch src/your-code.ts

Di bagian Model, pilih model yang ingin Anda gunakan dari daftar model yang disediakan oleh plugin.

Runner model UI developer Genkit

Kemudian, bereksperimenlah dengan perintah dan konfigurasi hingga Anda mendapatkan hasil yang memuaskan. Jika sudah siap, tekan tombol Ekspor dan simpan file ke direktori perintah Anda.

Menjalankan perintah

Setelah membuat file perintah, Anda dapat menjalankannya dari kode aplikasi, atau menggunakan alat yang disediakan oleh Genkit. Terlepas dari cara Anda ingin menjalankan perintah, mulailah dengan kode aplikasi yang mengimpor library Genkit dan plugin model yang Anda minati. Contoh:

import { genkit } from 'genkit';

// Import the model plugins you want to use.
import { googleAI } from '@genkit-ai/googleai';

const ai = genkit({
  // Initialize and configure the model plugins.
  plugins: [
    googleAI({
      apiKey: 'your-api-key', // Or (preferred): export GOOGLE_GENAI_API_KEY=...
    }),
  ],
});

Tidak masalah jika file berisi kode lain, tetapi kode di atas adalah satu-satunya yang diperlukan. Jika Anda menyimpan perintah di direktori selain default, pastikan untuk menentukannya saat mengonfigurasi Genkit.

Menjalankan perintah dari kode

Untuk menggunakan perintah, muat terlebih dahulu menggunakan metode prompt('file_name'):

const helloPrompt = ai.prompt('hello');

Setelah dimuat, Anda dapat memanggil perintah seperti fungsi:

const response = await helloPrompt();

// Alternatively, use destructuring assignments to get only the properties
// you're interested in:
const { text } = await helloPrompt();

Perintah yang dapat dipanggil memerlukan dua parameter opsional: input ke perintah (lihat bagian di bawah tentang menentukan skema input), dan objek konfigurasi, mirip dengan metode generate(). Contoh:

const response2 = await helloPrompt(
  // Prompt input:
  { name: 'Ted' },

  // Generation options:
  {
    config: {
      temperature: 0.4,
    },
  }
);

Setiap parameter yang Anda teruskan ke panggilan perintah akan menggantikan parameter yang sama yang ditentukan dalam file perintah.

Lihat Membuat konten dengan model AI untuk mengetahui deskripsi opsi yang tersedia.

Menggunakan UI developer

Saat Anda meningkatkan kualitas perintah aplikasi, Anda dapat menjalankannya di UI developer Genkit untuk melakukan iterasi pada perintah dan konfigurasi model dengan cepat, secara independen dari kode aplikasi Anda.

Muat UI developer dari direktori project Anda:

genkit start -- tsx --watch src/your-code.ts

Runner perintah UI developer Genkit

Setelah memuat perintah ke UI developer, Anda dapat menjalankannya dengan parameter input yang berbeda, dan bereksperimen dengan pengaruh perubahan pada kata-kata perintah atau parameter konfigurasi terhadap output model. Jika sudah puas dengan hasilnya, Anda dapat mengklik tombol Export prompt untuk menyimpan perintah yang diubah kembali ke direktori project.

Konfigurasi model

Di blok materi awal file perintah, Anda dapat menentukan nilai konfigurasi model untuk perintah secara opsional:

---
model: googleai/gemini-1.5-flash
config:
  temperature: 1.4
  topK: 50
  topP: 0.4
  maxOutputTokens: 400
  stopSequences:
    -   "<end>"
    -   "<fin>"
---

Nilai ini dipetakan langsung ke parameter config yang diterima oleh perintah yang dapat dipanggil:

const response3 = await helloPrompt(
  {},
  {
    config: {
      temperature: 1.4,
      topK: 50,
      topP: 0.4,
      maxOutputTokens: 400,
      stopSequences: ['<end>', '<fin>'],
    },
  }
);

Lihat Membuat konten dengan model AI untuk mengetahui deskripsi opsi yang tersedia.

Skema input dan output

Anda dapat menentukan skema input dan output untuk perintah dengan menentukannya di bagian isi awal:

---
model: googleai/gemini-1.5-flash
input:
  schema:
    theme?: string
  default:
    theme: "pirate"
output:
  schema:
    dishname: string
    description: string
    calories: integer
    allergens(array): string
---
Invent a menu item for a {{theme}} themed restaurant.

Skema ini digunakan dengan cara yang sama seperti yang diteruskan ke permintaan generate() atau definisi alur. Misalnya, perintah yang ditentukan di atas menghasilkan output terstruktur:

const menuPrompt = ai.prompt('menu');
const { data } = await menuPrompt({ theme: 'medieval' });

const dishName = data['dishname'];
const description = data['description'];

Anda memiliki beberapa opsi untuk menentukan skema dalam file .prompt: format definisi skema Dotprompt sendiri, Picoschema; Skema JSON standar; atau, sebagai referensi ke skema yang ditentukan dalam kode aplikasi Anda. Bagian berikut menjelaskan setiap opsi ini secara lebih mendetail.

Picoschema

Skema dalam contoh di atas ditentukan dalam format yang disebut Picoschema. Picoschema adalah format definisi skema ringkas yang dioptimalkan untuk YAML yang memudahkan dalam menentukan atribut skema yang paling penting untuk penggunaan LLM. Berikut adalah contoh skema yang lebih panjang, yang menentukan informasi yang mungkin disimpan aplikasi tentang 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. Objek, array, dan enum dilambangkan dengan tanda kurung setelah nama kolom.

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

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

Skema JSON

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

Skema Zod yang ditentukan dalam kode

Selain menentukan skema secara langsung dalam file .prompt, Anda dapat mereferensikan skema yang terdaftar dengan defineSchema() berdasarkan namanya. Jika Anda menggunakan TypeScript, pendekatan ini akan memungkinkan Anda memanfaatkan fitur pemeriksaan jenis statis bahasa saat Anda menggunakan perintah.

Untuk mendaftarkan skema:

import { z } from 'genkit';

const MenuItemSchema = ai.defineSchema(
  'MenuItemSchema',
  z.object({
    dishname: z.string(),
    description: z.string(),
    calories: z.coerce.number(),
    allergens: z.array(z.string()),
  })
);

Dalam perintah Anda, berikan nama skema yang terdaftar:

---
model: googleai/gemini-1.5-flash-latest
output:
  schema: MenuItemSchema
---

Library Dotprompt akan otomatis me-resolve nama ke skema Zod yang terdaftar di bawahnya. Kemudian, Anda dapat menggunakan skema untuk mengetik output Dotprompt dengan tegas:

const menuPrompt = ai.prompt<
  z.ZodTypeAny, // Input schema
  typeof MenuItemSchema, // Output schema
  z.ZodTypeAny // Custom options schema
>('menu');
const { data } = await menuPrompt({ theme: 'medieval' });

// Now data is strongly typed as MenuItemSchema:
const dishName = data?.dishname;
const description = data?.description;

Template perintah

Bagian file .prompt yang mengikuti materi awal (jika ada) adalah perintah itu sendiri, yang akan diteruskan ke model. Meskipun perintah ini dapat berupa string teks sederhana, Anda sering kali ingin menyertakan input pengguna ke dalam perintah. Untuk melakukannya, Anda dapat menentukan perintah menggunakan bahasa template Handlebars. Template perintah dapat menyertakan placeholder yang merujuk ke nilai yang ditentukan oleh skema input perintah Anda.

Anda telah melihat cara kerjanya di bagian skema input dan output:

---
model: googleai/gemini-1.5-flash
config:
  temperature: 1.4
  topK: 50
  topP: 0.4
  maxOutputTokens: 400
  stopSequences:
    -   "<end>"
    -   "<fin>"
---

Dalam contoh ini, ekspresi Handlebars, {{theme}}, di-resolve ke nilai properti theme input saat Anda menjalankan perintah. Untuk meneruskan input ke perintah, panggil perintah seperti dalam contoh berikut:

const menuPrompt = ai.prompt('menu');
const { data } = await menuPrompt({ theme: 'medieval' });

Perhatikan bahwa karena skema input mendeklarasikan properti theme sebagai opsional dan memberikan default, Anda dapat menghilangkan properti tersebut, dan perintah akan di-resolve menggunakan nilai default.

Template Handlebars juga mendukung beberapa konstruksi logis terbatas. Misalnya, sebagai alternatif untuk memberikan default, Anda dapat menentukan perintah menggunakan helper #if Handlebars:

---
model: googleai/gemini-1.5-flash
input:
  schema:
    theme?: string
---
Invent a menu item for a {{#if theme}}{{theme}} themed{{/if}} restaurant.

Dalam contoh ini, perintah dirender sebagai "Buat item menu untuk restoran" saat properti theme tidak ditentukan.

Lihat dokumentasi Handlebars untuk mengetahui informasi tentang semua helper logis bawaan.

Selain properti yang ditentukan oleh skema input, template Anda juga dapat merujuk ke nilai yang ditentukan secara otomatis oleh Genkit. Beberapa bagian berikutnya menjelaskan nilai yang ditentukan secara otomatis ini dan cara menggunakannya.

Prompt multi-pesan

Secara default, Dotprompt membuat satu pesan dengan peran "pengguna". Namun, beberapa perintah sebaiknya dinyatakan sebagai kombinasi dari beberapa pesan, seperti perintah sistem.

Helper {{role}} menyediakan cara mudah untuk membuat perintah 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}}

Prompt multi-modal

Untuk model yang mendukung input multimodal, seperti gambar di samping teks, Anda dapat 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 multimodalPrompt = ai.prompt('multimodal');
const { text } = await multimodalPrompt({
  photoUrl: 'https://example.com/photo.jpg',
});

Lihat juga Input multimodal, di halaman Model, untuk mengetahui contoh pembuatan URL data:.

Parsial

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

Saat memuat direktori perintah, setiap file yang diawali dengan garis bawah (_) dianggap sebagai parsial. Jadi, file _personality.prompt mungkin berisi:

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

Kemudian, ini dapat disertakan dalam perintah lain:

---
model: googleai/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}}

Bagian disisipkan menggunakan sintaksis {{>NAME_OF_PARTIAL args...}}. Jika tidak ada argumen yang diberikan ke bagian, argumen akan dieksekusi dengan konteks yang sama dengan perintah induk.

Bagian menerima argumen bernama seperti di atas atau satu argumen posisional yang mewakili konteks. Hal ini dapat berguna untuk tugas seperti merender anggota daftar.

_destination.prompt

- {{name}} ({{country}})

chooseDestination.prompt

---
model: googleai/gemini-1.5-flash-latest
input:
  schema:
    destinations(array):
      name: string
      country: string
---
Help the user decide between these vacation destinations:

{{#each destinations}}
{{>destination this}}
{{/each}}

Menentukan bagian dalam kode

Anda juga dapat menentukan parsial dalam kode menggunakan definePartial:

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

Bagian yang ditentukan kode tersedia di semua perintah.

Menentukan Helper Kustom

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

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

Setelah helper ditentukan, Anda dapat menggunakannya di perintah apa pun:

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

HELLO, {{shout name}}!!!

Varian perintah

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 perintah yang sudah diutak-atik hanya bisa diuji sepenuhnya di lingkungan produksi secara berdampingan dengan versi yang sudah ada. Dotprompt mendukung hal ini melalui fitur variannya.

Untuk membuat varian, buat file [name].[variant].prompt. Misalnya, jika Anda menggunakan Gemini 1.5 Flash di prompt, tetapi ingin mengetahui 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 varian saat memuat:

const myPrompt = ai.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 perintah dalam kode

Semua contoh yang telah dibahas sejauh ini mengasumsikan bahwa perintah Anda ditentukan dalam setiap file .prompt dalam satu direktori (atau subdirektorinya), yang dapat diakses oleh aplikasi Anda saat runtime. Dotprompt dirancang berdasarkan penyiapan ini, dan penulisnya menganggapnya sebagai pengalaman developer terbaik secara keseluruhan.

Namun, jika Anda memiliki kasus penggunaan yang tidak didukung dengan baik oleh penyiapan ini, Anda juga dapat menentukan perintah dalam kode menggunakan fungsi definePrompt():

Parameter pertama untuk fungsi ini analog dengan blok materi awal file .prompt; parameter kedua dapat berupa string template Handlebars, seperti dalam file perintah, atau fungsi yang menampilkan GenerateRequest:

const myPrompt = ai.definePrompt(
  {
    name: 'myPrompt',
    model: 'googleai/gemini-1.5-flash',
    input: {
      schema: z.object({
        name: z.string(),
      }),
    },
  },
  'Hello, {{name}}. How are you today?'
);
const myPrompt = ai.definePrompt(
  {
    name: 'myPrompt',
    model: 'googleai/gemini-1.5-flash',
    input: {
      schema: z.object({
        name: z.string(),
      }),
    },
  },
  async (input): Promise<GenerateRequest> => {
    return {
      messages: [
        {
          role: 'user',
          content: [{ text: `Hello, ${input.name}. How are you today?` }],
        },
      ],
    };
  }
);