Zarządzanie promptami za pomocą Dotprompt

Firebase Genkit udostępnia wtyczkę Dotprompt i format tekstowy, które ułatwiają pisanie i organizowanie promptów generatywnej AI.

Działanie Dotprompt opiera się na założeniu, że potwierdzenia to kod. Możesz zapisywać i obsługiwać prompty w specjalnie sformatowanych plikach zwanych plikami dotprompt, śledzisz ich zmiany za pomocą tego samego systemu kontroli wersji, którego używasz w przypadku kodu, oraz wdrażasz je razem z kodem, który wywołuje Twoje modele generatywnej AI.

Aby używać Dotprompt, najpierw utwórz katalog prompts w katalogu głównym projektu, a potem utwórz w nim plik .prompt. Oto prosty przykład, który możesz wywołać greeting.prompt:

---
model: vertexai/gemini-1.0-pro
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 biblioteki @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 instrukcji Dotprompt opiera się na języku szablonów Handlebars. Aby dodać części warunkowe do promptu lub powtórzyć uporządkowane treści, możesz użyć pomocników if, unless i each. Format pliku wykorzystuje interfejs YAML do przekazywania metadanych promptu wbudowanego w szablon.

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

Dotprompt zawiera kompaktowy, zoptymalizowany pod kątem YAML format definicji schematu o nazwie Piicoschema, który ułatwia definiowanie najważniejszych atrybutów schematu na potrzeby wykorzystania LLM. Oto przykład schematu 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

Powyższy schemat jest odpowiednikiem tego interfejsu TypeScript:

interface Article {
  title: string;
  subtitle?: string;
  /** true when in draft state */
  draft?: boolean;
  /** approval status */
  status?: 'PENDING' | 'APPROVED';
  /** the date of publication e.g. '2024-04-09' */
  date: string;
  /** relevant tags for article */
  tags: string[];
  authors: {
    name: string;
    email?: string;
  }[];
  metadata?: {
    /** ISO timestamp of last update */
    updatedAt?: string;
    /** id of approver */
    approvedBy?: number;
  };
}

Schemat Picoschema obsługuje typy skalarne string, integer, number i boolean. W przypadku obiektów, tablic i wyliczeń po nazwie pola oznacza je nawias.

Obiekty zdefiniowane przez Picoschema mają wszystkie wymagane właściwości, chyba że są oznaczone jako opcjonalne w elemencie ?, i nie zezwalają na dodatkowe właściwości.

Schemat Picoschema nie obsługuje wielu funkcji pełnego schematu JSON. Jeśli potrzebujesz bardziej zaawansowanych schematów, możesz podać schemat JSON:

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

Zastępowanie metadanych promptu

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

const result = await greetingPrompt.generate({
  model: 'google-genai/gemini-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, aby przekształcić je w format JSON:

---
model: vertexai/gemini-1.0-pro
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 go pobrać i zweryfikować:

const createMenuPrompt = await prompt('create_menu');

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

console.log(menu.output());

Prompty z wieloma wiadomościami

Domyślnie Dotprompt tworzy pojedynczą wiadomość z rolą "user". Niektóre prompty najlepiej wyrażać jako kombinację różnych komunikatów, np. promptów systemowych.

Asystent {{role}} pozwala w prosty sposób tworzyć prompty z wieloma wiadomościami:

---
model: vertexai/gemini-1.0-pro
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 wielomodalne

W przypadku modeli obsługujących multimodalne dane wejściowe, takich jak obrazy obok tekstu, możesz użyć elementu pomocniczego {{media}}:

---
model: vertexai/gemini-1.0-pro-vision
input:
  schema:
    photoUrl: string
---

Describe this image in a detailed paragraph:

{{media url=photoUrl}}

Adres URL może być identyfikatorem URI data: zakodowanym w https:// lub base64 na potrzeby wykorzystania obrazu w treści. W kodzie będzie to wyglądać tak:

const describeImagePrompt = await prompt('describe_image');

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

console.log(result.text());

Warianty promptów

Pliki promptów są tylko tekstem, więc możesz (i powinien!) zatwierdzić je w swoim systemie kontroli wersji, co umożliwi łatwe porównywanie zmian z upływem czasu. Często zmienione wersje promptów można w pełni przetestować tylko w środowisku produkcyjnym obok istniejących wersji. Dotprompt obsługuje to za pomocą funkcji wariantów.

Aby utworzyć wariant, utwórz plik [name].[variant].prompt. Jeśli np. w prompcie używasz Gemini 1.0 Pro, ale chcesz sprawdzić, czy Gemini 1.5 Pro będzie skuteczniejsze, możesz utworzyć 2 pliki:

  • my_prompt.prompt: prompt „odniesienie”
  • my_prompt.gemini15.prompt: odmiana o nazwie „gemini”

Aby użyć wariantu promptu, podczas wczytywania wybierz opcję variant:

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

Narzędzie ładujące prompty podejmie próbę wczytania wariantu o tej nazwie i powrót do wersji bazowej, jeśli nie istnieje. Oznacza to, że możesz używać wczytywania warunkowego na podstawie dowolnych kryteriów pasujących do Twojej aplikacji:

const myPrompt = await prompt('my_prompt', {
  variant: isBetaTester(user) ? 'gemini15' : null,
});

Nazwa wariantu jest zawarta w metadanych logów czasu generowania, dzięki czemu możesz porównywać rzeczywistą wydajność między wariantami w inspektorze logu czasu Genkit.

Alternatywne sposoby wczytywania i definiowania promptów

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

  • loadPromptFile: wczytaj prompt z pliku w katalogu promptów.
  • loadPromptUrl: wczytanie promptu 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.0-pro',
    input: {
      schema: z.object({
        name: z.string(),
      }),
    },
  },
  `Hello {{name}}, how are you today?`
);