Gestione dei prompt con Dotprompt

La progettazione di prompt è il modo principale in cui, in qualità di sviluppatore di app, puoi influenzare gli output dei modelli di IA generativa. Ad esempio, quando utilizzi gli LLM, puoi creare prompt che influiscono sul tono, sul formato, sulla lunghezza e su altre caratteristiche delle risposte dei modelli.

Il modo in cui scrivi questi prompt dipende dal modello che utilizzi. Un prompt scritto per un modello potrebbe non funzionare bene se utilizzato con un altro modello. Analogamente, anche i parametri del modello impostati (temperatura, top-k e così via) influiscono sull'output in modo diverso a seconda del modello.

Far funzionare insieme tutti e tre questi fattori (il modello, i parametri del modello e il prompt) per produrre l'output desiderato è raramente un processo banale e spesso richiede iterazioni e sperimentazioni sostanziali. Genkit fornisce una libreria e un formato file chiamato Dotprompt, che mira a rendere questa iterazione più rapida e comoda.

Dotprompt è progettato sulla premessa che i prompt sono codice. Definisci i prompt insieme ai modelli e ai parametri del modello a cui sono destinati, separatamente dal codice dell'applicazione. Poi, tu (o forse qualcuno che non è nemmeno coinvolto nella scrittura del codice dell'applicazione) puoi eseguire rapidamente l'iterazione sui prompt e sui parametri del modello utilizzando l'interfaccia utente per sviluppatori di Genkit. Una volta che i prompt funzionano come preferisci, puoi importarli nella tua applicazione ed eseguirli utilizzando Genkit.

Le definizioni dei prompt vengono inserite in un file con estensione .prompt. Ecco un esempio di come si presentano questi file:

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

La parte tra i tre trattini è il front matter YAML, simile al formato del front matter utilizzato da GitHub Markdown e Jekyll. Il resto del file è il prompt, che può eventualmente utilizzare i modelli Handlebars. Le sezioni seguenti descrivono in dettaglio ciascuna delle parti che compongono un file .prompt e come utilizzarle.

Prima di iniziare

Prima di leggere questa pagina, devi conoscere i contenuti trattati nella pagina Generare contenuti con modelli di IA.

Se vuoi eseguire gli esempi di codice in questa pagina, completa prima i passaggi descritti nella guida Introduzione. Tutti gli esempi presuppongono che tu abbia già installato Genkit come dipendenza nel tuo progetto.

Creazione di file prompt

Sebbene Dotprompt fornisca diversi modi per creare e caricare i prompt, è ottimizzato per i progetti che organizzano i prompt come file .prompt all'interno di un'unica directory (o sottodirectory). Questa sezione mostra come creare e caricare i prompt utilizzando questa configurazione consigliata.

Creazione di una directory di prompt

La libreria Dotprompt si aspetta di trovare i prompt in una directory nella directory principale del progetto e carica automaticamente tutti i prompt che trova. Per impostazione predefinita, questa directory è denominata prompts. Ad esempio, utilizzando il nome della directory predefinito, la struttura del progetto potrebbe essere simile alla seguente:

your-project/
├── lib/
├── node_modules/
├── prompts/
│   └── hello.prompt
├── src/
├── package-lock.json
├── package.json
└── tsconfig.json

Se vuoi utilizzare una directory diversa, puoi specificarla durante la configurazione di Genkit:

const ai = genkit({
  promptDir: './llm_prompts',
  // (Other settings...)
});

Creazione di un file prompt

Esistono due modi per creare un file .prompt: utilizzando un editor di testo o con l'interfaccia utente per gli sviluppatori.

Utilizzo di un editor di testo

Se vuoi creare un file di prompt utilizzando un editor di testo, crea un file di testo con l'estensione .prompt nella directory dei prompt: ad esempio,prompts/hello.prompt.

Ecco un esempio minimo di file prompt:

---
model: vertexai/gemini-1.5-flash
---
You are the world's most welcoming AI assistant. Greet the user and offer your assistance.

La parte tra i trattini è il front matter YAML, simile al formato del front matter utilizzato da GitHub Markdown e Jekyll. Il resto del file è il prompt, che può eventualmente utilizzare i modelli Handlebars. La sezione di front matter è facoltativa, ma la maggior parte dei file prompt conterrà almeno i metadati che specificano un modello. Il resto di questa pagina spiega come andare oltre e utilizzare le funzionalità di Dotprompt nei file prompt.

Utilizzo dell'interfaccia utente per sviluppatori

Puoi anche creare un file prompt utilizzando il programma di esecuzione del modello nell'interfaccia utente per gli sviluppatori. Inizia con il codice dell'applicazione che importa la libreria Genkit e la configura per utilizzare il plug-in del modello che ti interessa. Ad esempio:

import { genkit } from 'genkit';

// Import the model plugins you want to use.
import { googleAI } from '@genkit-ai/googleai';

const ai = genkit({
  // Initialize and configure the model plugins.
  plugins: [
    googleAI({
      apiKey: 'your-api-key', // Or (preferred): export GOOGLE_GENAI_API_KEY=...
    }),
  ],
});

Non è un problema se il file contiene altro codice, ma quanto sopra è tutto ciò che è necessario.

Carica l'interfaccia utente per sviluppatori nello stesso progetto:

genkit start -- tsx --watch src/your-code.ts

Nella sezione Modelli, scegli il modello da utilizzare dall'elenco di modelli fornito dal plug-in.

Genkit developer UI model runner

Poi, sperimenta il prompt e la configurazione finché non ottieni risultati che ti soddisfano. Quando è tutto pronto, premi il pulsante Esporta e salva il file nella directory prompt.

Prompt in esecuzione

Dopo aver creato i file prompt, puoi eseguirli dal codice dell'applicazione o utilizzando gli strumenti forniti da Genkit. Indipendentemente da come vuoi eseguire i prompt, inizia con il codice dell'applicazione che importa la libreria Genkit e i plug-in dei modelli che ti interessano. Ad esempio:

import { genkit } from 'genkit';

// Import the model plugins you want to use.
import { googleAI } from '@genkit-ai/googleai';

const ai = genkit({
  // Initialize and configure the model plugins.
  plugins: [
    googleAI({
      apiKey: 'your-api-key', // Or (preferred): export GOOGLE_GENAI_API_KEY=...
    }),
  ],
});

Non è un problema se il file contiene altro codice, ma quanto sopra è tutto ciò che è necessario. Se memorizzi i prompt in una directory diversa da quella predefinita, assicurati di specificarla quando configuri Genkit.

Eseguire i prompt dal codice

Per utilizzare un prompt, caricalo prima utilizzando il metodo prompt('file_name'):

const helloPrompt = ai.prompt('hello');

Una volta caricato, puoi chiamare il prompt come una funzione:

const response = await helloPrompt();

// Alternatively, use destructuring assignments to get only the properties
// you're interested in:
const { text } = await helloPrompt();

Un prompt richiamabile accetta due parametri facoltativi: l'input del prompt (consulta la sezione di seguito su come specificare gli schemi di input) e un oggetto di configurazione simile a quello del metodo generate(). Ad esempio:

const response2 = await helloPrompt(
  // Prompt input:
  { name: 'Ted' },

  // Generation options:
  {
    config: {
      temperature: 0.4,
    },
  }
);

Tutti i parametri passati alla chiamata del prompt sostituiranno gli stessi parametri specificati nel file prompt.

Per descrizioni delle opzioni disponibili, consulta Generare contenuti con modelli di IA.

Utilizzo dell'interfaccia utente per sviluppatori

Mentre perfezioni i prompt della tua app, puoi eseguirli nell'UI sviluppatore di Genkit per eseguire rapidamente l'iterazione su prompt e configurazioni del modello, indipendentemente dal codice dell'applicazione.

Carica l'interfaccia utente per sviluppatori dalla directory del progetto:

genkit start -- tsx --watch src/your-code.ts

Genkit developer UI prompt runner

Dopo aver caricato i prompt nell'interfaccia utente per gli sviluppatori, puoi eseguirli con diversi valori di input e sperimentare in che modo le modifiche al testo del prompt o ai parametri di configurazione influiscono sull'output del modello. Quando il risultato ti soddisfa, puoi fare clic sul pulsante Esporta prompt per salvare il prompt modificato nella directory del progetto.

Configurazione modello

Nel blocco di front matter dei file prompt, puoi specificare facoltativamente i valori di configurazione del modello per il prompt:

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

Questi valori vengono mappati direttamente al parametro config accettato dal prompt callable:

const response3 = await helloPrompt(
  {},
  {
    config: {
      temperature: 1.4,
      topK: 50,
      topP: 0.4,
      maxOutputTokens: 400,
      stopSequences: ['<end>', '<fin>'],
    },
  }
);

Per descrizioni delle opzioni disponibili, consulta Generare contenuti con modelli di IA.

Schemi di input e output

Puoi specificare gli schemi di input e output per il prompt definendoli nella sezione preliminare:

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

Questi schemi vengono utilizzati in modo molto simile a quelli passati a una richiesta generate() o a una definizione di flusso. Ad esempio, il prompt definito sopra produce un'output strutturata:

const menuPrompt = ai.prompt('menu');
const { data } = await menuPrompt({ theme: 'medieval' });

const dishName = data['dishname'];
const description = data['description'];

Hai a disposizione diverse opzioni per definire gli schemi in un file .prompt: il formato di definizione dello schema di Dotprompt, Picoschema; lo schema JSON standard; o come riferimenti agli schemi definiti nel codice dell'applicazione. Le sezioni seguenti descrivono in dettaglio ciascuna di queste opzioni.

Picoschema

Gli schemi nell'esempio precedente sono definiti in un formato chiamato Picoschema. Picoschema è un formato di definizione dello schema compatto e ottimizzato per YAML che consente di definire facilmente gli attributi più importanti di uno schema per l'utilizzo di LLM. Ecco un esempio più lungo di uno schema, che specifica le informazioni che un'app potrebbe memorizzare su un articolo:

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

Lo schema riportato sopra è equivalente alla seguente interfaccia 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 supporta i tipi scalari string, integer, number, boolean e any. Gli oggetti, gli array e gli enum sono indicati tra parentesi dopo il nome del campo.

Gli oggetti definiti da Picoschema hanno tutte le proprietà richieste, a meno che non siano contrassegnate come facoltative da ? e non consentano proprietà aggiuntive. Quando una proprietà è contrassegnata come facoltativa, viene impostata anche come nullable per consentire ai modelli LLM di restituire null anziché omettere un campo.

In una definizione di oggetto, la chiave speciale (*) può essere utilizzata per dichiarare una definizione di campo "wildcard". Verranno associate eventuali proprietà aggiuntive non fornite da una chiave esplicita.

Schema JSON

Picoschema non supporta molte delle funzionalità dello schema JSON completo. Se hai bisogno di schemi più solidi, puoi fornire uno schema JSON:

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

Schemi Zod definiti nel codice

Oltre a definire direttamente gli schemi nel file .prompt, puoi fare riferimento a uno schema registrato con defineSchema() per nome. Se utilizzi TypeScript, questo approccio ti consente di sfruttare le funzionalità di controllo statico dei tipi del linguaggio quando lavori con i prompt.

Per registrare uno schema:

import { z } from 'genkit';

const MenuItemSchema = ai.defineSchema(
  'MenuItemSchema',
  z.object({
    dishname: z.string(),
    description: z.string(),
    calories: z.coerce.number(),
    allergens: z.array(z.string()),
  })
);

All'interno del prompt, fornisci il nome dello schema registrato:

---
model: googleai/gemini-1.5-flash-latest
output:
  schema: MenuItemSchema
---

La libreria Dotprompt risolverà automaticamente il nome nello schema Zod registrato sottostante. Puoi quindi utilizzare lo schema per specificare il tipo di output di un prompt di Dot:

const menuPrompt = ai.prompt<
  z.ZodTypeAny, // Input schema
  typeof MenuItemSchema, // Output schema
  z.ZodTypeAny // Custom options schema
>('menu');
const { data } = await menuPrompt({ theme: 'medieval' });

// Now data is strongly typed as MenuItemSchema:
const dishName = data?.dishname;
const description = data?.description;

Modelli di prompt

La parte di un file .prompt che segue la premessa (se presente) è il prompt stesso, che verrà passato al modello. Sebbene questo prompt possa essere una semplice stringa di testo, molto spesso è consigliabile incorporare l'input dell'utente. A tale scopo, puoi specificare il prompt utilizzando il linguaggio di creazione di modelli Handlebars. I modelli di prompt possono includere segnaposto che fanno riferimento ai valori definiti dallo schema di input del prompt.

Lo hai già visto in azione nella sezione sugli schemi di input e output:

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

In questo esempio, l'espressione Handlebars {{theme}} si risolve nel valore della proprietà theme dell'input quando esegui il prompt. Per passare l'input al prompt, chiamalo come nell'esempio seguente:

const menuPrompt = ai.prompt('menu');
const { data } = await menuPrompt({ theme: 'medieval' });

Tieni presente che, poiché lo schema di input ha dichiarato la proprietà theme come facoltativa e ha fornito un valore predefinito, potevi omettere la proprietà e la richiesta sarebbe stata risolta utilizzando il valore predefinito.

I modelli Handlebars supportano anche alcuni costrutti logici limitati. Ad esempio, come alternativa alla definizione di un valore predefinito, puoi definire il prompt utilizzando l'helper #if di Handlebars:

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

In questo esempio, il prompt viene visualizzato come "Inventa un articolo del menu per un ristorante" quando la proprietà theme non è specificata.

Consulta la documentazione di Handlebars per informazioni su tutti gli aiuti logici integrati.

Oltre alle proprietà definite dallo schema di input, i modelli possono anche fare riferimento ai valori definiti automaticamente da Genkit. Le sezioni successive descrivono questi valori definiti automaticamente e come utilizzarli.

Prompt con più messaggi

Per impostazione predefinita, Dotprompt crea un singolo messaggio con un ruolo "utente". Tuttavia, alcuni prompt sono meglio espressi come una combinazione di più messaggi, come un prompt di sistema.

L'helper {{role}} offre un modo semplice per costruire prompt di più messaggi:

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

Per i modelli che supportano input multimodali, come immagini insieme a testo, puoi utilizzare l'helper {{media}}:

---
model: vertexai/gemini-1.5-flash
input:
  schema:
    photoUrl: string
---
Describe this image in a detailed paragraph:

{{media url=photoUrl}}

L'URL può essere un URI https: o data: con codifica Base64 per l'utilizzo di immagini "in linea". In codice, sarà:

const multimodalPrompt = ai.prompt('multimodal');
const { text } = await multimodalPrompt({
  photoUrl: 'https://example.com/photo.jpg',
});

Consulta anche Input multimodale nella pagina Modelli per un esempio di creazione di un URL data:.

Particolari

I componenti parziali sono modelli riutilizzabili che possono essere inclusi in qualsiasi prompt. I prompt parziali possono essere particolarmente utili per i prompt correlati che condividono un comportamento comune.

Quando carichi una directory di prompt, qualsiasi file con un prefisso sottotraccia (_) è considerato parziale. Pertanto, un file _personality.prompt potrebbe contenere:

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

che può essere incluso in altri prompt:

---
model: googleai/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}}

I componenti parziali vengono inseriti utilizzando la sintassi {{>NAME_OF_PARTIAL args...}}. Se non vengono forniti argomenti al comando parziale, questo viene eseguito con lo stesso contesto del prompt principale.

I componenti parziali accettano sia gli argomenti denominati come sopra sia un singolo argomento posizionale che rappresenta il contesto. Questo può essere utile per attività come il rendering degli elementi di un elenco.

_destination.prompt

- {{name}} ({{country}})

chooseDestination.prompt

---
model: googleai/gemini-1.5-flash-latest
input:
  schema:
    destinations(array):
      name: string
      country: string
---
Help the user decide between these vacation destinations:

{{#each destinations}}
{{>destination this}}
{{/each}}

Definire le parti nel codice

Puoi anche definire i componenti parziali nel codice utilizzando definePartial:

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

I componenti parziali definiti tramite codice sono disponibili in tutti i prompt.

Definizione di helper personalizzati

Puoi definire helper personalizzati per elaborare e gestire i dati all'interno di un prompt. Gli aiutanti vengono registrati a livello globale utilizzando defineHelper:

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

Una volta definito un comando aggiuntivo, puoi utilizzarlo in qualsiasi prompt:

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

HELLO, {{shout name}}!!!

Varianti del prompt

Poiché i file prompt sono solo di testo, puoi (e devi) eseguirne il commit nel tuo sistema di controllo della versione, in modo da confrontare facilmente le modifiche nel tempo. Spesso, le versioni modificate dei prompt possono essere testate completamente solo in un ambiente di produzione insieme alle versioni esistenti. Dotprompt supporta questa funzionalità tramite la sua funzionalità Variants.

Per creare una variante, crea un file [name].[variant].prompt. Ad esempio, se utilizzi Gemini 1.5 Flash nel tuo prompt, ma vuoi verificare se Gemini 1.5 Pro ha un rendimento migliore, puoi creare due file:

  • my_prompt.prompt: il prompt "baseline"
  • my_prompt.gemini15pro.prompt: una variante denominata gemini15pro

Per utilizzare una variante del prompt, specifica l'opzione della variante al momento del caricamento:

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

Il nome della variante è incluso nei metadati delle tracce di generazione, pertanto puoi confrontare e mettere a confronto il rendimento effettivo tra le varianti nell'ispettore delle tracce Genkit.

Definire i prompt nel codice

Tutti gli esempi discussi finora hanno presupposto che i prompt siano definiti in singoli file .prompt in un'unica directory (o sottodirectory), accessibili all'app in fase di esecuzione. Dotprompt è progettato in base a questa configurazione e i suoi autori lo considerano la migliore esperienza per gli sviluppatori in assoluto.

Tuttavia, se hai casi d'uso non supportati bene da questa configurazione, puoi anche definire i prompt nel codice utilizzando la funzione definePrompt():

Il primo parametro di questa funzione è analogo al blocco di front matter di un file .prompt. Il secondo parametro può essere una stringa di modello Handlebars, come in un file prompt, o una funzione che restituisce un GenerateRequest:

const myPrompt = ai.definePrompt(
  {
    name: 'myPrompt',
    model: 'googleai/gemini-1.5-flash',
    input: {
      schema: z.object({
        name: z.string(),
      }),
    },
  },
  'Hello, {{name}}. How are you today?'
);
const myPrompt = ai.definePrompt(
  {
    name: 'myPrompt',
    model: 'googleai/gemini-1.5-flash',
    input: {
      schema: z.object({
        name: z.string(),
      }),
    },
  },
  async (input): Promise<GenerateRequest> => {
    return {
      messages: [
        {
          role: 'user',
          content: [{ text: `Hello, ${input.name}. How are you today?` }],
        },
      ],
    };
  }
);