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
:
go get github.com/firebase/genkit/go/plugins/dotprompt
Em seguida, carregue o comando usando Open
:
import "github.com/firebase/genkit/go/plugins/dotprompt"
dotprompt.SetDirectory("prompts")
prompt, err := dotprompt.Open("greeting")
É possível chamar o método Generate
do comando para renderizar o modelo e passá-lo para a API do modelo em uma única etapa:
ctx := context.Background()
// Default to the project in GCLOUD_PROJECT and the location "us-central1".
vertexai.Init(ctx, nil)
// The .prompt file specifies vertexai/gemini-1.5-flash, which is
// automatically defined by Init(). However, if it specified a model that
// isn't automatically loaded (such as a specific version), you would need
// to define it here:
// vertexai.DefineModel("gemini-1.0-pro-002", &ai.ModelCapabilities{
// Multiturn: true,
// Tools: true,
// SystemRole: true,
// Media: false,
// })
type GreetingPromptInput struct {
Location string `json:"location"`
Style string `json:"style"`
Name string `json:"name"`
}
response, err := prompt.Generate(
ctx,
&dotprompt.PromptRequest{
Variables: GreetingPromptInput{
Location: "the beach",
Style: "a fancy pirate",
Name: "Ed",
},
},
nil,
)
if err != nil {
return err
}
fmt.Println(response.Text())
Ou simplesmente renderize o modelo em uma string:
renderedPrompt, err := prompt.RenderText(map[string]any{
"location": "a restaurant",
"style": "a pirate",
})
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 com o Picoschema
Dotprompt inclui um formato de definição de esquema compacto baseado em YAML chamado Picoschema para facilitar a definição dos atributos mais importantes de um esquema para uso do LLM. 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 ao seguinte esquema JSON:
{
"properties": {
"metadata": {
"properties": {
"updatedAt": {
"type": "string",
"description": "ISO timestamp of last update"
},
"approvedBy": {
"type": "integer",
"description": "id of approver"
}
},
"type": "object"
},
"title": {
"type": "string"
},
"subtitle": {
"type": "string"
},
"draft": {
"type": "boolean",
"description": "true when in draft state"
},
"date": {
"type": "string",
"description": "the date of publication e.g. '2024-04-09'"
},
"tags": {
"items": {
"type": "string"
},
"type": "array",
"description": "relevant tags for article"
},
"authors": {
"items": {
"properties": {
"name": {
"type": "string"
},
"email": {
"type": "string"
}
},
"type": "object",
"required": ["name"]
},
"type": "array"
}
},
"type": "object",
"required": ["title", "date", "tags", "authors"]
}
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
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:
// Make sure you set up the model you're using.
vertexai.DefineModel("gemini-1.5-flash", nil)
response, err := prompt.Generate(
context.Background(),
&dotprompt.PromptRequest{
Variables: GreetingPromptInput{
Location: "the beach",
Style: "a fancy pirate",
Name: "Ed",
},
Model: "vertexai/gemini-1.5-flash",
Config: &ai.GenerationCommonConfig{
Temperature: 1.0,
},
},
nil,
)
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 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:
dotprompt.SetDirectory("prompts")
describeImagePrompt, err := dotprompt.Open("describe_image")
if err != nil {
return err
}
imageBytes, err := os.ReadFile("img.jpg")
if err != nil {
return err
}
encodedImage := base64.StdEncoding.EncodeToString(imageBytes)
dataURI := "data:image/jpeg;base64," + encodedImage
type DescribeImagePromptInput struct {
PhotoUrl string `json:"photo_url"`
}
response, err := describeImagePrompt.Generate(
context.Background(),
&dotprompt.PromptRequest{Variables: DescribeImagePromptInput{
PhotoUrl: dataURI,
}},
nil,
)
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.geminipro.prompt
: uma variante chamada "geminipro"
Para usar uma variante de comando, especifique-a ao carregar:
describeImagePrompt, err := dotprompt.OpenVariant("describe_image", "geminipro")
O carregador de comandos tenta carregar a variante desse nome e retorna à linha de base se não existir nenhuma. Isso significa que é possível usar o carregamento condicional com base em qualquer critério que faça sentido para seu aplicativo:
var myPrompt *dotprompt.Prompt
var err error
if isBetaTester(user) {
myPrompt, err = dotprompt.OpenVariant("describe_image", "geminipro")
} else {
myPrompt, err = dotprompt.Open("describe_image")
}
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.