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 prompt ini, instal plugin dotprompt:

go get github.com/firebase/genkit/go/plugins/dotprompt

Kemudian, muat prompt menggunakan Open:

import "github.com/firebase/genkit/go/plugins/dotprompt"
dotprompt.SetDirectory("prompts")
prompt, err := dotprompt.Open("greeting")

Anda dapat memanggil metode Generate prompt untuk merender template dan meneruskannya ke API model dalam satu langkah:

ctx := context.Background()

// Default to the project in GCLOUD_PROJECT and the location "us-central1".
vertexai.Init(ctx, nil)

// The .prompt file specifies vertexai/gemini-1.5-flash, which is
// automatically defined by Init(). However, if it specified a model that
// isn't automatically loaded (such as a specific version), you would need
// to define it here:
// vertexai.DefineModel("gemini-1.0-pro-002", &ai.ModelCapabilities{
// 	Multiturn:  true,
// 	Tools:      true,
// 	SystemRole: true,
// 	Media:      false,
// })

type GreetingPromptInput struct {
	Location string `json:"location"`
	Style    string `json:"style"`
	Name     string `json:"name"`
}
response, err := prompt.Generate(
	ctx,
	&dotprompt.PromptRequest{
		Variables: GreetingPromptInput{
			Location: "the beach",
			Style:    "a fancy pirate",
			Name:     "Ed",
		},
	},
	nil,
)
if err != nil {
	return err
}

fmt.Println(response.Text())

Atau cukup render template ke sebuah string:

renderedPrompt, err := prompt.RenderText(map[string]any{
	"location": "a restaurant",
	"style":    "a pirate",
})

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 dengan Picoschema

Dotprompt menyertakan format ringkas definisi skema berbasis YAML yang disebut Picoschema untuk memudahkan dalam menentukan atribut skema yang paling penting 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 skema JSON berikut:

{
  "properties": {
    "metadata": {
      "properties": {
        "updatedAt": {
          "type": "string",
          "description": "ISO timestamp of last update"
        },
        "approvedBy": {
          "type": "integer",
          "description": "id of approver"
        }
      },
      "type": "object"
    },
    "title": {
      "type": "string"
    },
    "subtitle": {
      "type": "string"
    },
    "draft": {
      "type": "boolean",
      "description": "true when in draft state"
    },
    "date": {
      "type": "string",
      "description": "the date of publication e.g. '2024-04-09'"
    },
    "tags": {
      "items": {
        "type": "string"
      },
      "type": "array",
      "description": "relevant tags for article"
    },
    "authors": {
      "items": {
        "properties": {
          "name": {
            "type": "string"
          },
          "email": {
            "type": "string"
          }
        },
        "type": "object",
        "required": ["name"]
      },
      "type": "array"
    }
  },
  "type": "object",
  "required": ["title", "date", "tags", "authors"]
}

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

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:

// Make sure you set up the model you're using.
vertexai.DefineModel("gemini-1.5-flash", nil)

response, err := prompt.Generate(
	context.Background(),
	&dotprompt.PromptRequest{
		Variables: GreetingPromptInput{
			Location: "the beach",
			Style:    "a fancy pirate",
			Name:     "Ed",
		},
		Model: "vertexai/gemini-1.5-flash",
		Config: &ai.GenerationCommonConfig{
			Temperature: 1.0,
		},
	},
	nil,
)

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

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:

dotprompt.SetDirectory("prompts")
describeImagePrompt, err := dotprompt.Open("describe_image")
if err != nil {
	return err
}

imageBytes, err := os.ReadFile("img.jpg")
if err != nil {
	return err
}
encodedImage := base64.StdEncoding.EncodeToString(imageBytes)
dataURI := "data:image/jpeg;base64," + encodedImage

type DescribeImagePromptInput struct {
	PhotoUrl string `json:"photo_url"`
}
response, err := describeImagePrompt.Generate(
	context.Background(),
	&dotprompt.PromptRequest{Variables: DescribeImagePromptInput{
		PhotoUrl: dataURI,
	}},
	nil,
)

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 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.geminipro.prompt: varian bernama "geminipro"

Untuk menggunakan varian prompt, tentukan varian saat memuat:

describeImagePrompt, err := dotprompt.OpenVariant("describe_image", "geminipro")

Loader prompt akan mencoba memuat varian dari nama tersebut, dan kembali ke prompt dasar jika tidak ada. Ini berarti Anda dapat menggunakan pemuatan bersyarat berdasarkan kriteria apa pun yang masuk akal untuk aplikasi Anda:

var myPrompt *dotprompt.Prompt
var err error
if isBetaTester(user) {
	myPrompt, err = dotprompt.OpenVariant("describe_image", "geminipro")
} else {
	myPrompt, err = dotprompt.Open("describe_image")
}

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