Zarządzanie promptami za pomocą Dotprompt

Firebase Genkit udostępnia wtyczkę Dotprompt i format tekstowy, aby ułatwić Ci pisanie i porządkowanie promptów generatywnej AI.

Dotprompt opiera się na założeniu, że prompty są kodem. Ty piszesz i przechowywać prompty w specjalnie sformatowanych plikach zwanych plikami kropkaprompt, śledzić przy użyciu tego samego systemu kontroli wersji, którego używasz w przypadku w kodzie, w którym wdrażasz je razem z kodem wywołującym generatywną AI modeli ML.

Aby używać Dotprompt, najpierw utwórz katalog prompts w katalogu głównym projektu i a następnie utwórz w nim plik .prompt. Oto prosty przykład, może zadzwonić do użytkownika 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}}.

Aby użyć tego promptu, zainstaluj wtyczkę dotprompt:

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

Następnie wczytaj prompt za pomocą polecenia Open:

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

Możesz wywołać metodę Generate promptu, aby wyrenderować szablon i go przekazać do interfejsu API modelu w jednym kroku:

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())

Lub po prostu wyrenderuj szablon w ciągu znaków:

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

Składnia Dotprompt opiera się na kierunkach obsługi. język szablonowy. Możesz użyć pomocników if, unless i each, aby dodać albo części warunkowe promptu lub powtarzaj to, wykorzystując uporządkowane treści. format pliku korzysta z interfejsu YAML do dostarczania metadanych dla wbudowanego promptu zgodnie z szablonem.

Definiowanie schematów wejściowych/wyjściowych za pomocą Picoschema

Dotprompt zawiera kompaktowy, oparty na YAML format definicji schematu o nazwie schemat Picoschema ułatwiający zdefiniowanie najważniejszych atrybutów schematu do wykorzystania LLM. Oto przykładowy schemat artykułu:

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

Powyższy schemat jest odpowiednikiem tego schematu JSON:

{
  "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 obsługuje typy skalarne string, integer, number, boolean i any. Obiekty, tablice i wyliczenia są oznaczane w nawiasach po nazwie pola.

Obiekty zdefiniowane przez Picoschema mają wszystkie wymagane właściwości, chyba że są oznaczone jako opcjonalne do ? i nie zezwalaj na dodatkowe właściwości. Jeśli właściwość jest oznaczona jako opcjonalna, ma też wartość null, aby ułatwić LLM zwracanie wartości null zamiast z pominięciem pola.

W definicji obiektu można użyć klucza specjalnego (*) do zadeklarowania symbolu wieloznacznego definicji pola. Spowoduje to dopasowanie wszystkich dodatkowych właściwości, które nie zostały podane przez jawny klucz.

Picoschema nie obsługuje wielu możliwości pełnego schematu JSON. Jeśli wymagają bardziej zaawansowanych schematów, możesz zamiast tego podać schemat JSON:

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

Zastępowanie metadanych promptu

Z kolei pliki .prompt umożliwiają umieszczanie metadanych, takich jak konfiguracja modelu, w w samym pliku, możesz też zastąpić te wartości dla poszczególnych wywołań:

// 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,
)

Prompty zawierające wiele wiadomości

Domyślnie Dotprompt tworzy jedną wiadomość z przypisaną rolą "user". Niektóre prompty najlepiej wyrażać jako połączenie kilku wiadomości, np. komunikatora systemowego.

Pomocnik {{role}} pozwala w prosty sposób tworzyć prompty zawierające wiele wiadomości:

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

Prompty multimodalne

W przypadku modeli obsługujących multimodalne dane wejściowe, np. obrazy obok tekstu, możesz użyj aplikacji pomocniczej {{media}}:

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

Describe this image in a detailed paragraph:

{{media url=photoUrl}}

Adres URL może być identyfikatorem URI data: zakodowanym w formacie https:// lub w standardzie base64. obraz i ich wykorzystaniu. W kodzie będzie to:

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,
)

Warianty promptu

Ponieważ pliki promptów mają postać tekstową, możesz (– a powinien!) system kontroli wersji, który pozwala łatwo porównywać zmiany w czasie. Często dopracowane wersje promptów można w pełni przetestować środowiska produkcyjnego i istniejących wersji. Obsługiwany jest kropka to za pomocą funkcji wariantów.

Aby utworzyć wariant, utwórz plik [name].[variant].prompt. Jeśli na przykład używasz w prompcie Gemini 1.5 Flash, ale chcesz sprawdzić, czy Gemini 1.5 Wersja Pro działa lepiej, ale możesz utworzyć 2 pliki:

  • my_prompt.prompt: wartość bazowa prompt
  • my_prompt.geminipro.prompt: wariant o nazwie „geminipro”.

Aby użyć wariantu promptu, określ go podczas wczytywania:

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

Moduł wczytywania promptu spróbuje wczytać wariant tej nazwy i wykona działanie zastępcze do punktu odniesienia. Oznacza to, że możesz używać wczytywania warunkowego opartego na niezależnie od kryteriów, które mają sens w przypadku Twojego zgłoszenia:

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

Nazwa wariantu jest zawarta w metadanych logów czasu generowania, więc może porównywać rzeczywistą skuteczność różnych wariantów w zrzucie danych Genkit i inspektor.