Mengelola prompt dengan Dotprompt

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

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

Membuat ketiga faktor ini—model, parameter model, dan prompt—bekerja sama untuk menghasilkan output yang Anda inginkan bukanlah 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 prompt beserta model dan parameter model yang dimaksudkan secara terpisah dari kode aplikasi Anda. Kemudian, Anda (atau, mungkin seseorang yang bahkan tidak terlibat dalam penulisan kode aplikasi) dapat melakukan iterasi dengan cepat pada prompt dan parameter model menggunakan UI Developer Genkit. Setelah prompt berfungsi seperti yang diinginkan, Anda dapat mengimpornya ke aplikasi dan menjalankannya menggunakan Genkit.

Setiap definisi prompt 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 pisah adalah frontmatter YAML, mirip dengan format frontmatter yang digunakan oleh GitHub Markdown dan Jekyll. Bagian lain dari file adalah prompt, yang dapat menggunakan template Handlebars secara opsional. Bagian berikut akan membahas lebih detail setiap bagian dalam pembuatan file .prompt beserta cara menggunakannya.

Sebelum memulai

Sebelum membaca halaman ini, Anda harus 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 prompt

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

Membuat direktori prompt

Library Dotprompt diharapkan menemukan prompt Anda pada direktori di root project dan otomatis memuat prompt 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/
├── prompts/
│   └── hello.prompt
├── main.go
├── go.mod
└── go.sum

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

g, err := genkit.Init(ctx.Background(), ai.WithPromptDir("./llm_prompts"))

Membuat satu file prompt

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

Menggunakan editor teks

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

Berikut adalah contoh sederhana file prompt:

---
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 pisah adalah frontmatter YAML, mirip dengan format frontmatter yang digunakan oleh Markdown GitHub dan Jekyll. Bagian lain dari file adalah prompt, yang secara opsional dapat menggunakan template Handlebars. Bagian frontmatter bersifat opsional, tetapi sebagian besar file prompt setidaknya akan berisi metadata yang menentukan model. Bagian lain dari halaman ini menunjukkan cara melakukan tindakan lainnya, dan menggunakan fitur Dotprompt dalam file prompt Anda.

Menggunakan UI developer

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

import (
    "context"

    "github.com/firebase/genkit/go/ai"
    "github.com/firebase/genkit/go/genkit"
    "github.com/firebase/genkit/go/plugins/googlegenai"
)

func main() {
    g, err := genkit.Init(context.Background(), ai.WithPlugins(&googlegenai.GoogleAI{}))
    if err != nil {
        log.Fatal(err)
    }

    // Blocks end of program execution to use the developer UI.
    select {}
}

Muat UI developer di project yang sama:

genkit start -- go run .

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

Runner model UI developer Genkit

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

Menjalankan prompt

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

import (
    "context"

      "github.com/firebase/genkit/go/ai"
    "github.com/firebase/genkit/go/genkit"
    "github.com/firebase/genkit/go/plugins/googlegenai"
)

func main() {
    g, err := genkit.Init(context.Background(), ai.WithPlugins(&googlegenai.GoogleAI{}))
    if err != nil {
        log.Fatal(err)
    }

    // Blocks end of program execution to use the developer UI.
    select {}
}

Jika Anda menyimpan prompt di direktori selain direktori default, pastikan untuk menentukannya saat mengonfigurasi Genkit.

Menjalankan prompt dari kode

Untuk menggunakan prompt, muat prompt terlebih dahulu menggunakan fungsi genkit.LookupPrompt():

helloPrompt := genkit.LookupPrompt(g, "hello")

Prompt yang dapat dieksekusi memiliki opsi yang mirip dengan genkit.Generate() dan banyak di antaranya dapat diganti pada waktu eksekusi, termasuk hal-hal seperti input (lihat bagian tentang menentukan skema input), konfigurasi, dan sebagainya:

resp, err := helloPrompt.Execute(context.Background(),
    ai.WithModelName("googleai/gemini-2.0-flash"),
    ai.WithInput(map[string]any{"name": "John"}),
    ai.WithConfig(&googlegenai.GeminiConfig{Temperature: 0.5})
)

Setiap parameter yang diteruskan ke panggilan prompt akan mengganti parameter yang sama dengan yang ditentukan dalam file prompt.

Baca Membuat konten dengan model AI untuk deskripsi opsi yang tersedia.

Menggunakan UI developer

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

Muat UI developer dari direktori project Anda:

genkit start -- go run .

Runner prompt UI developer Genkit

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

Konfigurasi model

Di blok frontmatter pada file prompt, Anda dapat menentukan nilai konfigurasi model untuk prompt secara opsional:

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

Nilai ini dipetakan secara langsung ke opsi WithConfig() yang diterima oleh prompt yang dapat dieksekusi:

resp, err := helloPrompt.Execute(context.Background(),
    ai.WithConfig(&googlegenai.GeminiConfig{
        Temperature:     1.4,
        TopK:            50,
        TopP:            0.4,
        MaxOutputTokens: 400,
        StopSequences:   []string{"<end>", "<fin>"},
    }))

Baca Membuat konten dengan model AI untuk deskripsi opsi yang tersedia.

Skema input dan output

Anda dapat menentukan skema input dan output prompt dengan menentukan skema tersebut di bagian frontmatter. Skema ini digunakan dengan cara yang sama seperti yang diteruskan ke permintaan genkit.Generate() atau definisi flow:

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

Kode ini menghasilkan output terstruktur berikut:

menuPrompt = genkit.LookupPrompt(g, "menu")
if menuPrompt == nil {
    log.Fatal("no prompt named 'menu' found")
}

resp, err := menuPrompt.Execute(context.Background(),
    ai.WithInput(map[string]any{"theme": "medieval"}),
)
if err != nil {
    log.Fatal(err)
}

var output map[string]any
if err := resp.Output(&output); err != nil {
    log.Fatal(err)
}

log.Println(output["dishname"])
log.Println(output["description"])

Anda memiliki beberapa opsi untuk menentukan skema dalam file .prompt: format definisi skema Dotprompt-nya 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 pada contoh di atas ditentukan dalam format yang disebut Picoschema. Picoschema adalah format definisi skema ringkas yang dioptimalkan untuk YAML yang menyederhanakan penentuan atribut skema paling penting untuk penggunaan LLM. Berikut adalah contoh skema yang lebih panjang, yang menentukan informasi yang mungkin disimpan oleh aplikasi tentang sebuah 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 jenis Go berikut:

type Article struct {
    Title    string   `json:"title"`
    Subtitle string   `json:"subtitle,omitempty" jsonschema:"required=false"`
    Draft    bool     `json:"draft,omitempty"`  // True when in draft state
    Status   string   `json:"status,omitempty" jsonschema:"enum=PENDING,enum=APPROVED"` // Approval status
    Date     string   `json:"date"`   // The date of publication e.g. '2025-04-07'
    Tags     []string `json:"tags"`   // Relevant tags for article
    Authors  []struct {
      Name  string `json:"name"`
      Email string `json:"email,omitempty"`
    } `json:"authors"`
    Metadata struct {
      UpdatedAt  string `json:"updatedAt,omitempty"`  // ISO timestamp of last update
      ApprovedBy int    `json:"approvedBy,omitempty"` // ID of approver
    } `json:"metadata,omitempty"`
    Extra any `json:"extra"` // Arbitrary extra data
}

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 jika dilambangkan sebagai properti opsional dengan ?, dan tidak mengizinkan properti tambahan. Ketika sebuah properti ditandai sebagai opsional, properti tersebut juga dibuat nullable, sehingga 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.

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

Template prompt

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

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

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

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

menuPrompt = genkit.LookupPrompt(g, "menu")

resp, err := menuPrompt.Execute(context.Background(),
    ai.WithInput(map[string]any{"theme": "medieval"}),
)

Perlu diperhatikan, karena skema input mendeklarasikan properti theme sebagai opsional dan memberikan nilai default, Anda dapat menghilangkan properti, dan prompt akan di-resolve menggunakan nilai default.

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

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

Dalam contoh ini, prompt 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 dan cara menggunakannya.

Prompt multi-pesan

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

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

---
model: vertexai/gemini-2.0-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 bisa menggunakan helper {{media}}:

---
model: vertexai/gemini-2.0-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:

multimodalPrompt = genkit.LookupPrompt(g, "multimodal")

resp, err := multimodalPrompt.Execute(context.Background(),
    ai.WithInput(map[string]any{"photoUrl": "https://example.com/photo.jpg"}),
)

Lihat juga Input multimodal, di halaman Membuat konten dengan model AI, untuk contoh pembuatan URL data:.

Parsial

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

Saat memuat direktori prompt, 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, hal ini dapat disertakan dalam prompt lain:

---
model: googleai/gemini-2.0-flash
input:
  schema:
    name: string
    style?: string
---
{{ role "system" }}
{{>personality style=style}}

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

User's Name: {{name}}

Parsial disisipkan menggunakan sintaksis {{>NAME_OF_PARTIAL args...}}. Jika tidak ada argumen yang disediakan ke template parsial, argumen tersebut akan dijalankan menggunakan konteks yang sama dengan prompt induk.

Parsial menerima argumen bernama 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-2.0-flash
input:
  schema:
    destinations(array):
      name: string
      country: string
---
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 genkit.DefinePartial():

genkit.DefinePartial(g, "personality", "Talk like a {{#if style}}{{style}}{{else}}helpful assistant{{/if}}.")

Parsial yang ditentukan oleh kode tersedia di semua prompt.

Menentukan Helper Kustom

Anda dapat menentukan helper kustom untuk memproses dan mengelola data di dalam prompt. Helper didaftarkan secara global menggunakan genkit.DefineHelper():

genkit.DefineHelper(g, "shout", func(input string) string {
    return strings.ToUpper(input)
})

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

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

HELLO, {{shout name}}!!!

Varian prompt

Karena file prompt berupa teks, Anda bisa (dan harus) melakukan commit ke sistem kontrol versi, yang menyederhanakan proses membandingkan perubahan dari waktu ke waktu. 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 2.0 Flash dalam prompt, tetapi ingin mengetahui apakah Gemini 2.5 Pro akan berperforma lebih baik, Anda dapat membuat dua file:

  • myPrompt.prompt: prompt "dasar"
  • myPrompt.gemini25pro.prompt: varian bernama gemini25pro

Untuk menggunakan varian prompt, tentukan opsi varian saat memuat:

myPrompt := genkit.LookupPrompt(g, "myPrompt.gemini25Pro")

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

Menentukan prompt dalam kode

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

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

type GeoQuery struct {
    CountryCount int `json:"countryCount"`
}

type CountryList struct {
    Countries []string `json:"countries"`
}

geographyPrompt, err := genkit.DefinePrompt(
    g, "GeographyPrompt",
    ai.WithSystem("You are a geography teacher. Respond only when the user asks about geography."),
    ai.WithPrompt("Give me the {{countryCount}} biggest countries in the world by inhabitants."),
    ai.WithConfig(&googlegenai.GeminiConfig{Temperature: 0.5}),
    ai.WithInputType(GeoQuery{CountryCount: 10}) // Defaults to 10.
    ai.WithOutputType(CountryList{}),
)
if err != nil {
    log.Fatal(err)
}

resp, err := geographyPrompt.Execute(context.Background(), ai.WithInput(GeoQuery{CountryCount: 15}))
if err != nil {
    log.Fatal(err)
}

var list CountryList
if err := resp.Output(&list); err != nil {
    log.Fatal(err)
}

log.Println("Countries: %s", list.Countries)

Prompt juga dapat dirender menjadi GenerateActionOptions yang kemudian dapat diproses dan diteruskan ke genkit.GenerateWithRequest():

actionOpts, err := geographyPrompt.Render(ctx, ai.WithInput(GeoQuery{CountryCount: 15}))
if err != nil {
    log.Fatal(err)
}

// Do something with the value...
actionOpts.Config = &googlegenai.GeminiConfig{Temperature: 0.8}

resp, err := genkit.GenerateWithRequest(ctx, g, actionOpts, nil, nil) // No middleware or streaming

Perhatikan bahwa semua opsi prompt akan diterapkan ke GenerateActionOptions dengan pengecualian WithMiddleware(), yang harus diteruskan secara terpisah jika menggunakan Prompt.Render(), bukan Prompt.Execute().