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 i zaimportuj funkcję prompt z biblioteka @genkit-ai/dotprompt:

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

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

Następnie wczytaj prompt za pomocą polecenia 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());

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

Dotprompt zawiera kompaktowy, zoptymalizowany pod kątem 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 poniższego interfejsu TypeScript:

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

Wykorzystywanie schematów wielokrotnego użytku

Oprócz bezpośrednio definiowania schematów w pliku .prompt możesz też odwoływać się schemat zarejestrowany według nazwy w defineSchema. Aby zarejestrować schemat:

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

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

W prompcie możesz podać nazwę zarejestrowanego schematu:

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

Biblioteka Dotprompt automatycznie zidentyfikuje nazwę na podstawie i zarejestrowano schemat Zod. Możesz wtedy użyć tego schematu, aby dokładnie wpisać wynik działania prompta 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();

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ń:

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

Uporządkowane dane wyjściowe

Możesz ustawić format i schemat wyjściowy promptu do wymuszania na 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.

Podczas generowania promptu z uporządkowanymi danymi wyjściowymi użyj elementu pomocniczego output(), aby pobierz i sprawdź:

const createMenuPrompt = await prompt('create_menu');

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

console.log(menu.output());

Zgodność danych wyjściowych jest osiągana przez wstawienie dodatkowych instrukcji do . Domyślnie jest on dodawany na końcu ostatniej wygenerowanej wiadomości. za pomocą prompta. Możesz ręcznie zmienić jej położenie za pomocą {{section "output"}} jako pomoc.

This is a prompt that manually positions output instructions.

== Output Instructions

{{section "output"}}

== Other Instructions

This will come after the output instructions.

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 i historia wieloetapów

Dotprompt obsługuje prompty wieloetapowe, przekazując opcję history do funkcji Metoda generate:

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

Domyślnie historia jest wstawiana przed ostatnią wiadomością wygenerowaną przez pojawi się prośba. Możesz jednak ręcznie pozycjonować historię za pomocą funkcji {{history}} pomocnik:

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

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:

const describeImagePrompt = await prompt('describe_image');

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

console.log(result.text());

Częściowe

Częściowe części to szablony wielokrotnego użytku, które można uwzględnić w każdym prompcie. Częściowe może być szczególnie przydatne w przypadku powiązanych promptów o typowym działaniu.

Podczas wczytywania katalogu promptów każdy plik z prefiksem _ jest uważany za częściową. Plik _personality.prompt może więc zawierać:

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

Możesz to potem uwzględnić w innych promptach:

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

Fragmenty są wstawiane przy użyciu składni {{>NAME_OF_PARTIAL args...}}. Jeśli nie argumentów zostanie podana do części, będzie ona wykonywana z tym samym kontekstem na urządzeniu nadrzędnym.

Częściowe argumenty przyjmują oba argumenty nazwane jak powyżej lub jeden argument pozycjonujący co reprezentuje kontekst. Może to być pomocne np. dodawania członków listy do listy.

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

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

Definiowanie częściowych fragmentów w kodzie

Możesz również zdefiniować części kodu za pomocą parametru definePartial:

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

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

Części zdefiniowane za pomocą kodu są dostępne we wszystkich promptach.

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.gemini15pro.prompt: wariant o nazwie „gemini15pro”.

Aby użyć wariantu promptu, podczas wczytywania określ opcję variant:

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

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.

Definiowanie niestandardowych pomocników

Możesz zdefiniować niestandardowych pomocników do przetwarzania danych i zarządzania nimi w prompcie. Pomocnicy są zarejestrowane na całym świecie przy użyciu defineHelper:

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

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

Zdefiniowanego asystenta możesz używać w dowolnym prompcie:

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

HELLO, {{shout name}}!!!

Więcej informacji o argumentach przekazanych do pomocy znajdziesz w Dokumentacja tworzenia za pomocą kierownicy z niestandardowymi aplikacjami pomocniczymi.

Alternatywne sposoby wczytywania i definiowania promptów

Metoda Dotprompt jest zoptymalizowana pod kątem organizacji w katalogu promptów. Istnieje jednak Oto kilka innych sposobów wczytywania i definiowania promptów:

  • loadPromptFile: wczytaj prompt z pliku w katalogu promptów.
  • loadPromptUrl: wczytaj prompt z adresu URL.
  • defineDotprompt: zdefiniuj prompt w kodzie.

Przykłady:

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