O Firebase Genkit fornece o plug-in Dotprompt e formato de texto para ajudar você a escrever e organizar seus comandos de IA generativa.
O Dotprompt foi desenvolvido com base na premissa de que comandos são código. Você escreve e mantém seus comandos em arquivos especialmente formatados chamados arquivos dotprompt, rastreia alterações neles usando o mesmo sistema de controle de versão usado para seu código e os implanta com o código que chama seus modelos de IA generativa.
Para usar o Dotprompt, primeiro crie um diretório prompts
na raiz do seu projeto
e, em seguida, criar um arquivo .prompt
nesse diretório. Aqui está um exemplo simples que você
pode chamar 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 esse comando, instale o plug-in dotprompt
e importe a função promptRef
do
biblioteca @genkit-ai/dotprompt
:
import { dotprompt, promptRef } from '@genkit-ai/dotprompt';
configureGenkit({ plugins: [dotprompt()] });
Em seguida, carregue o comando usando promptRef('file_name')
:
const greetingPrompt = promptRef('greeting');
const result = await greetingPrompt.generate({
input: {
location: 'the beach',
style: 'a fancy pirate',
},
});
console.log(result.text());
A sintaxe do Dotprompt é baseada na linguagem de modelagem Handlebars. Você pode usar os auxiliares if
, unless
e each
para adicionar
partes condicionais do seu comando ou iterar conteúdo estruturado. O
formato do arquivo utiliza um front-end YAML para fornecer metadados para um comando in-line
com o modelo.
Como definir esquemas de entrada/saída
O Dotprompt inclui um formato de definição de esquema compacto e otimizado para YAML chamado Picoschema para facilitar a definição dos atributos mais importantes de um esquema para o uso de LLMs. Veja um exemplo de esquema para um artigo:
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
O esquema acima é equivalente à seguinte interface do 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 */
}
O Picoschema oferece suporte aos tipos escalares string
, integer
, number
, boolean
e any
.
Para objetos, matrizes e tipos enumerados, eles são indicados por um parêntese após o nome do campo.
Os objetos definidos pelo Picoschema têm todas as propriedades conforme necessário, a menos que sejam indicadas como opcionais
por ?
e não permitam outras propriedades. Quando uma propriedade é marcada como opcional,
ela também é anulável, proporcionando mais tolerância para que os LLMs retornem um valor nulo em vez da omissão de um campo.
Em uma definição de objeto, a chave especial (*)
pode ser usada para declarar uma definição de campo "caractere curinga". Isso corresponderá a todas as propriedades adicionais não fornecidas por uma
chave explícita.
O Picoschema não é compatível com muitos dos recursos do esquema JSON completo. Se você precisar de esquemas mais robustos, poderá fornecer um esquema JSON:
output:
schema:
type: object
properties:
field1:
type: number
minimum: 20
Como aproveitar esquemas reutilizáveis
Além de definir esquemas diretamente no arquivo .prompt
, é possível referenciar
um esquema registrado com defineSchema
pelo nome. Para registrar um esquema, faça o seguinte:
import { defineSchema } from '@genkit-ai/core';
import { z } from 'zod';
const MySchema = defineSchema(
'MySchema',
z.object({
field1: z.string(),
field2: z.number(),
})
);
No comando, insira o nome do esquema registrado:
# myPrompt.prompt
---
model: vertexai/gemini-1.5-flash
output:
schema: MySchema
---
A biblioteca Dotprompt resolverá automaticamente o nome para o comando esquema Zod registrado. Depois, você pode usar o esquema para digitar fortemente saída de um 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();
Substituir metadados de comandos
Já os arquivos .prompt
permitem incorporar metadados, como configuração de modelo, no próprio arquivo, você também pode substituir esses valores por chamada:
const result = await greetingPrompt.generate({
model: 'vertexai/gemini-1.5-pro',
config: {
temperature: 1.0,
},
input: {
location: 'the beach',
style: 'a fancy pirate',
},
});
Saída estruturada
É possível definir o formato e o esquema de saída de um comando para forçar a conversão em 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.
Ao gerar um comando com saída estruturada, use o auxiliar output()
para
recuperá-los e validá-los:
const createMenuPrompt = promptRef('create_menu');
const menu = await createMenuPrompt.generate({
input: {
theme: 'banana',
},
});
console.log(menu.output());
A conformidade da saída é alcançada com a inserção de mais instruções
prompt de comando. Por padrão, ele é anexado ao final da última mensagem gerada.
ao comando. É possível reposicioná-la manualmente usando o {{section "output"}}
.
ajudante.
This is a prompt that manually positions output instructions.
== Output Instructions
{{section "output"}}
== Other Instructions
This will come after the output instructions.
Comandos com várias mensagens
Por padrão, o Dotprompt constrói uma única mensagem com o papel "user"
. Alguns
comandos são mais bem expressos como uma combinação de várias mensagens, como um
comando do sistema.
O auxiliar {{role}}
oferece uma maneira simples de criar comandos de várias mensagens:
---
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}}
Comandos e histórico de várias interações
O Dotprompt oferece suporte a comandos de várias interações transmitindo a opção history
para o
Método generate
:
const result = await multiTurnPrompt.generate({
history: [
{ role: 'user', content: [{ text: 'Hello.' }] },
{ role: 'model', content: [{ text: 'Hi there!' }] },
],
});
Por padrão, o histórico será inserido antes da mensagem final gerada pelo
ao comando. No entanto, você pode posicionar o histórico manualmente usando o {{history}}
ajudante:
{{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.
Comandos multimodais
Para modelos com suporte a entrada multimodal, como imagens e texto, é possível
usar o auxiliar {{media}}
:
---
model: vertexai/gemini-1.5-flash
input:
schema:
photoUrl: string
---
Describe this image in a detailed paragraph:
{{media url=photoUrl}}
O URL pode ser um URI data:
codificado em base64 ou https://
para uso de imagem "inline". No código, seria:
const describeImagePrompt = promptRef('describe_image');
const result = await describeImagePrompt.generate({
input: {
photoUrl: 'https://example.com/image.png',
},
});
console.log(result.text());
Parcial
Parcials são modelos reutilizáveis que podem ser incluídos em qualquer comando. Parcial pode ser especialmente útil para comandos relacionados que compartilham um comportamento comum.
Ao carregar um diretório de prompt, qualquer arquivo prefixado com _
é considerado um
parcial. Portanto, o arquivo _personality.prompt
pode conter:
You should speak like a {{#if style}}{{style}}{{else}}helpful assistant.{{/else}}.
Isso pode ser incluído em outros comandos:
---
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}}
As parciais são inseridas usando a sintaxe {{>NAME_OF_PARTIAL args...}}
. Em caso negativo
forem fornecidos à parcial, ela será executada com o mesmo contexto
comando principal.
As parciais aceitam ambos os argumentos nomeados como acima ou um único argumento posicional. que representam o contexto. Isso pode ser útil para, por exemplo, renderização de membros de uma lista.
# _destination.prompt
- {{name}} ({{country}})
# chooseDestination.prompt
Help the user decide between these vacation destinations:
{{#each destinations}}
{{>destination this}}{{/each}}
Definição de parciais no código
Você também pode definir parciais no código usando definePartial
:
import { definePartial } from '@genkit-ai/dotprompt';
definePartial(
'personality',
'Talk like a {{#if style}}{{style}}{{else}}helpful assistant{{/if}}.'
);
Dados parciais definidos por código estão disponíveis em todos os comandos.
Variantes de comando
Como os arquivos de comando são apenas textos, você pode (e deve) enviá-los ao seu sistema de controle de versões, o que facilita a comparação das mudanças ao longo do tempo. Muitas vezes, as versões ajustadas dos comandos só podem ser totalmente testadas em um ambiente de produção lado a lado com as versões atuais. O Dotprompt oferece suporte por meio do recurso de variantes.
Para criar uma variante, crie um arquivo [name].[variant].prompt
. Por exemplo, se
você estava usando o Gemini 1.5 Flash no comando, mas quer saber se o Gemini 1.5 Pro
teria um desempenho melhor, você pode criar dois arquivos:
my_prompt.prompt
: o comando de "valor de referência"my_prompt.gemini15pro.prompt
: uma variante chamada "gemini15pro"
Para usar uma variante de comando, especifique a opção variant
ao carregar:
const myPrompt = promptRef('my_prompt', { variant: 'gemini15pro' });
O nome da variante é incluído nos metadados dos traces de geração. Assim, você pode comparar e contrastar o desempenho real entre variantes no inspetor de trace do Genkit.
Definição de auxiliares personalizados
É possível definir auxiliares personalizados para processar e gerenciar dados em um prompt. Auxiliares
estão registrados globalmente usando defineHelper
:
import { defineHelper } from '@genkit-ai/dotprompt';
defineHelper('shout', (text: string) => text.toUpperCase());
Depois de definido, o assistente pode ser usado em qualquer prompt:
---
model: vertexai/gemini-1.5-flash
input:
schema:
name: string
---
HELLO, {{shout name}}!!!
Para obter mais informações sobre os argumentos passados para os auxiliares, consulte a seção Documentação do Handlebars sobre criação assistentes personalizados.
Formas alternativas de carregar e definir comandos
O Dotprompt é otimizado para organização no diretório do prompt. No entanto, não há há outras formas de carregar e definir comandos:
loadPromptFile
: carrega um prompt de um arquivo no diretório de comandos.loadPromptUrl
: carrega um prompt de um URL.defineDotprompt
: define um comando no código.
Exemplos:
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?`
);