Firebase Genkit proporciona el complemento Dotprompt y el formato de texto para ayudarte a escribir y organizar tus instrucciones de IA generativa.
Dotprompt está diseñado con la premisa de que las instrucciones son código. Escribes y mantienes tus instrucciones en archivos con formato especial llamados archivos dotprompt, rastreas los cambios con el mismo sistema de control de versión que usas para tu código y, luego, los implementas junto con el código que llama a tus modelos de IA generativa.
Para usar Dotprompt, primero crea un directorio prompts
en la raíz del proyecto y, luego, crea un archivo .prompt
en ese directorio. Aquí hay un ejemplo sencillo podría llamar a 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}}.
Para usar esta instrucción, instala el complemento dotprompt
e importa la función promptRef
desde
la biblioteca @genkit-ai/dotprompt
:
import { dotprompt, promptRef } from '@genkit-ai/dotprompt';
configureGenkit({ plugins: [dotprompt()] });
Luego, carga la instrucción con promptRef('file_name')
:
const greetingPrompt = promptRef('greeting');
const result = await greetingPrompt.generate({
input: {
location: 'the beach',
style: 'a fancy pirate',
},
});
console.log(result.text());
La sintaxis de Dotprompt se basa en el lenguaje de plantillas Handlebars. Puedes usar los asistentes de if
, unless
y each
para agregar partes condicionales a tu instrucción o iterar a través del contenido estructurado. El formato de archivo usa YAML frontmatter para proporcionar metadatos para una instrucción intercalada con la plantilla.
Definición de esquemas de entrada y salida
Dotprompt incluye un formato de definición de esquema compacto y optimizado para YAML llamado Picoschema para facilitar la definición de los atributos más importantes de un esquema para el uso de LLM. Este es un ejemplo de un esquema para un artículo:
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
El esquema anterior es equivalente a la siguiente interfaz de 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 admite los tipos escalares string
, integer
, number
, boolean
y any
.
En el caso de los objetos, arrays y enums, se denotan con un paréntesis después del nombre del campo.
Los objetos definidos por Picoschema tienen todas las propiedades necesarias, a menos que se indique que son opcionales por ?
, y no permiten propiedades adicionales. Cuando una propiedad se marca como opcional, también se hace anulable para proporcionar más legibilidad para que los LLMs devuelvan un valor nulo en lugar de omitir un campo.
En una definición de objeto, se puede usar la clave especial (*)
para declarar una definición de campo “comodín”. Esto coincidirá con cualquier propiedad adicional que no proporcione una clave explícita.
Picoschema no es compatible con muchas de las capacidades del esquema JSON completo. Si necesitas esquemas más sólidos, puedes proporcionar un esquema JSON en su lugar:
output:
schema:
type: object
properties:
field1:
type: number
minimum: 20
Aprovecha esquemas reutilizables
Además de definir esquemas directamente en el archivo .prompt
, puedes hacer referencia a
un esquema registrado con defineSchema
por nombre. Sigue estos pasos para registrar un esquema:
import { defineSchema } from '@genkit-ai/core';
import { z } from 'zod';
const MySchema = defineSchema(
'MySchema',
z.object({
field1: z.string(),
field2: z.number(),
})
);
En la instrucción, puedes proporcionar el nombre del esquema registrado:
# myPrompt.prompt
---
model: vertexai/gemini-1.5-flash
output:
schema: MySchema
---
La biblioteca Dotprompt resolverá automáticamente el nombre del esquema de Zod registrado. Luego, puedes usar el esquema para escribir fuertemente Resultado de un mensaje de Dotprompt:
import { promptRef } from "@genkit-ai/dotprompt";
const myPrompt = promptRef("myPrompt");
const result = await myPrompt.generate<typeof MySchema>({...});
// now strongly typed as MySchema
result.output();
Anula metadatos de instrucciones
Mientras que los archivos .prompt
te permiten incorporar metadatos, como la configuración del modelo en el propio archivo, también puedes anular estos valores por llamada:
const result = await greetingPrompt.generate({
model: 'vertexai/gemini-1.5-pro',
config: {
temperature: 1.0,
},
input: {
location: 'the beach',
style: 'a fancy pirate',
},
});
Resultados estructurados
Puedes configurar el formato y el esquema de salida de una instrucción para convertirla en 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.
Cuando generes una instrucción con un resultado estructurado, usa el ayudante output()
para
recuperarlo y validarlo:
const createMenuPrompt = promptRef('create_menu');
const menu = await createMenuPrompt.generate({
input: {
theme: 'banana',
},
});
console.log(menu.output());
El cumplimiento de salida se logra insertando instrucciones adicionales en el
mensaje. De forma predeterminada, se agrega al final del último mensaje generado
por el mensaje. Puedes cambiar su posición manualmente con el {{section "output"}}
.
como un asistente de chat.
This is a prompt that manually positions output instructions.
== Output Instructions
{{section "output"}}
== Other Instructions
This will come after the output instructions.
Instrucciones de varios mensajes
De forma predeterminada, Dotprompt construye un solo mensaje con un rol "user"
. Algunas instrucciones se expresan mejor como una combinación de varios mensajes, como una instrucción del sistema.
El ayudante {{role}}
proporciona una forma sencilla de crear instrucciones de varios mensajes:
---
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}}
Historial y mensajes de varios turnos
Dotprompt admite instrucciones de varios turnos pasando la opción history
al
Método generate
:
const result = await multiTurnPrompt.generate({
history: [
{ role: 'user', content: [{ text: 'Hello.' }] },
{ role: 'model', content: [{ text: 'Hi there!' }] },
],
});
De forma predeterminada, el historial se insertará antes del mensaje final generado por
el mensaje. Sin embargo, puedes posicionar el historial manualmente usando el {{history}}
.
ayudante:
{{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.
Instrucciones multimodales
Para los modelos que admiten entradas multimodales, como imágenes junto con texto, puedes usar el ayudante {{media}}
:
---
model: vertexai/gemini-1.5-flash
input:
schema:
photoUrl: string
---
Describe this image in a detailed paragraph:
{{media url=photoUrl}}
La URL puede ser URI de https://
o data:
codificado en base64 para el uso de imagen "intercalada". En el código, sería así:
const describeImagePrompt = promptRef('describe_image');
const result = await describeImagePrompt.generate({
input: {
photoUrl: 'https://example.com/image.png',
},
});
console.log(result.text());
Parcial
Los parciales son plantillas reutilizables que se pueden incluir dentro de cualquier instrucción. Parcial puede ser especialmente útil para instrucciones relacionadas que comparten un comportamiento común.
Cuando se carga un directorio de instrucciones, cualquier archivo con el prefijo _
se considera un
parciales. Por lo tanto, un archivo _personality.prompt
podría contener lo siguiente:
You should speak like a {{#if style}}{{style}}{{else}}helpful assistant.{{/else}}.
Esto se puede incluir en otras instrucciones:
---
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}}
Los parciales se insertan con la sintaxis {{>NAME_OF_PARTIAL args...}}
. Si la respuesta es no
se proporcionan argumentos al parcial, esta se ejecuta con el mismo contexto que
mensaje principal.
Los parciales aceptan los argumentos con nombre como se indica más arriba o un solo argumento posicional que representan el contexto. Esto puede ser útil para las siguientes situaciones: renderizando miembros de una lista.
# _destination.prompt
- {{name}} ({{country}})
# chooseDestination.prompt
Help the user decide between these vacation destinations:
{{#each destinations}}
{{>destination this}}{{/each}}
Cómo definir parciales en el código
También puedes definir parciales en el código con definePartial
:
import { definePartial } from '@genkit-ai/dotprompt';
definePartial(
'personality',
'Talk like a {{#if style}}{{style}}{{else}}helpful assistant{{/if}}.'
);
Los parciales definidos por código están disponibles en todas las instrucciones.
Variantes de instrucciones
Como los archivos de instrucciones son solo texto, puedes (y debes) confirmarlos en tu sistema de control de versiones, lo que te permite comparar fácilmente los cambios a lo largo del tiempo. A menudo, las versiones modificadas de instrucciones solo se pueden probar completamente en un entorno de producción en paralelo con versiones existentes. Dotprompt admite esto a través de la característica variantes.
Para crear una variante, crea un archivo [name].[variant].prompt
. Por ejemplo, si estabas usando Gemini 1.5 Flash en tu instrucción, pero querías ver si Gemini 1.5 Pro tendría un mejor rendimiento, podrías crear dos archivos:
my_prompt.prompt
: La instrucción de "modelo de referencia"my_prompt.gemini15pro.prompt
: Una variante llamada "gemini15pro"
Para usar una variante de instrucción, especifica la opción variant
durante la carga:
const myPrompt = promptRef('my_prompt', { variant: 'gemini15pro' });
El nombre de la variante se incluye en los metadatos de los seguimientos de generación, por lo que puedes comparar y contrastar el rendimiento real entre las variantes en el seguimiento de Genkit con el inspector de registros.
Cómo definir asistentes personalizados
Puedes definir asistentes personalizados para procesar y administrar datos dentro de una instrucción. Ayudantes
se registran globalmente con defineHelper
:
import { defineHelper } from '@genkit-ai/dotprompt';
defineHelper('shout', (text: string) => text.toUpperCase());
Una vez que se define un asistente, puedes usarlo en cualquier instrucción:
---
model: vertexai/gemini-1.5-flash
input:
schema:
name: string
---
HELLO, {{shout name}}!!!
Para obtener más información sobre los argumentos que se pasaron a los asistentes, consulta el Documentación sobre Handlebars sobre cómo crear asistentes personalizados.
Alternativas para cargar y definir instrucciones
Dotprompt está optimizado para la organización en el directorio de instrucciones. Sin embargo, hay hay algunas otras formas de cargar y definir instrucciones:
loadPromptFile
: Carga un mensaje desde un archivo en el directorio del mensaje.loadPromptUrl
: Carga un mensaje desde una URL.defineDotprompt
: Define un mensaje en el código.
Ejemplos:
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?`
);