La ingeniería de instrucciones es la forma principal en la que tú, como desarrollador de apps, influyes en el resultado de los modelos de IA generativa. Por ejemplo, cuando usas los LLMs, puedes crear instrucciones que influyen en el tono, el formato, la longitud y otras características de las respuestas de los modelos.
La forma en que escribas estas instrucciones dependerá del modelo que uses. Es posible que una instrucción escrita para un modelo no tenga un buen rendimiento cuando se use con otro. Del mismo modo, los parámetros del modelo que establezcas (temperatura, Top-K, etcétera) también afectarán el resultado de manera diferente según el modelo.
Hacer que los tres factores (el modelo, los parámetros del modelo y la instrucción) funcionen juntos para producir el resultado que deseas rara vez es un proceso trivial y, a menudo, implica iteración y experimentación sustanciales. Genkit proporciona una biblioteca y un formato de archivo llamados Dotprompt, cuyo objetivo es hacer que esta iteración sea más rápida y conveniente.
Dotprompt está diseñado con la premisa de que las instrucciones son código. Define tus instrucciones junto con los modelos y los parámetros de los modelos para los que están destinados, de forma independiente del código de tu aplicación. Luego, tú (o tal vez alguien que ni siquiera esté involucrado en escribir código de la aplicación) puedes iterar rápidamente en las instrucciones y los parámetros del modelo con la IU de desarrollador de Genkit. Una vez que tus instrucciones funcionen como deseas, puedes importarlas a tu aplicación y ejecutarlas con Genkit.
Cada una de las definiciones de instrucciones se coloca en un archivo con la extensión .prompt
. A continuación, se muestra un ejemplo de cómo se ven estos archivos:
---
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 de los tres guiones es el material de referencia de YAML, similar al formato de material de referencia que usan GitHub Markdown y Jekyll. El resto del archivo es la instrucción, que puede usar plantillas de Handlebars de forma opcional. En las siguientes secciones, se analizarán con más detalle cada una de las partes que conforman un archivo .prompt
y cómo usarlas.
Antes de comenzar
Antes de leer esta página, debes familiarizarte con el contenido que se explica en la página Cómo generar contenido con modelos de IA.
Si quieres ejecutar los ejemplos de código de esta página, primero completa los pasos de la guía Cómo comenzar. En todos los ejemplos, se da por sentado que ya instalaste Genkit como dependencia en tu proyecto.
Cómo crear archivos de instrucciones
Aunque Dotprompt proporciona varias formas diferentes de crear y cargar instrucciones, está optimizado para proyectos que organizan sus instrucciones como archivos .prompt
dentro de un solo directorio (o subdirectorios de este). En esta sección, se muestra cómo crear y cargar instrucciones con esta configuración recomendada.
Cómo crear un directorio de instrucciones
La biblioteca de Dotprompt espera encontrar tus instrucciones en un directorio en la raíz del proyecto y carga automáticamente las instrucciones que encuentre allí. De forma predeterminada, este directorio se llama prompts
. Por ejemplo, si usas el nombre de directorio predeterminado, la estructura de tu proyecto podría verse de la siguiente manera:
your-project/
├── prompts/
│ └── hello.prompt
├── main.go
├── go.mod
└── go.sum
Si deseas usar un directorio diferente, puedes especificarlo cuando configures Genkit:
g, err := genkit.Init(ctx.Background(), ai.WithPromptDir("./llm_prompts"))
Crea un archivo de instrucciones
Existen dos maneras de crear un archivo .prompt
: con un editor de texto o con la IU para desarrolladores.
Usa un editor de texto
Si quieres crear un archivo de instrucciones con un editor de texto, crea un archivo de texto con la extensión .prompt
en el directorio de instrucciones: por ejemplo, prompts/hello.prompt
.
A continuación, se muestra un ejemplo mínimo de un archivo de instrucciones:
---
model: vertexai/gemini-1.5-flash
---
You are the world's most welcoming AI assistant. Greet the user and offer your
assistance.
La parte entre guiones es el material de referencia de YAML, similar al formato de material de referencia que usan GitHub Markdown y Jekyll. El resto del archivo es la instrucción, que puede usar plantillas de Handlebars de forma opcional. La sección del material preliminar es opcional, pero la mayoría de los archivos de instrucciones al menos contendrán metadatos que especifiquen un modelo. En el resto de esta página, se muestra cómo ir más allá y usar las funciones de Dotprompt en tus archivos de instrucciones.
Usa la IU para desarrolladores
También puedes crear un archivo de instrucciones con el ejecutor de modelos en la IU para desarrolladores. Comienza con el código de la aplicación que importa la biblioteca de Genkit y la configura para usar el complemento de modelo que te interesa. Por ejemplo:
import (
"context"
"github.com/firebase/genkit/go/ai"
"github.com/firebase/genkit/go/genkit"
"github.com/firebase/genkit/go/plugins/googlegenai"
)
func main() {
g, err := genkit.Init(context.Background(), ai.WithPlugins(&googlegenai.GoogleAI{}))
if err != nil {
log.Fatal(err)
}
// Blocks end of program execution to use the developer UI.
select {}
}
Carga la IU para desarrolladores en el mismo proyecto:
genkit start -- go run .
En la sección Modelo, elige el modelo que deseas usar de la lista de modelos que proporciona el complemento.
Luego, experimenta con la instrucción y la configuración hasta que obtengas los resultados que te satisfagan. Cuando esté todo listo, presiona el botón Exportar y guarda el archivo en el directorio de instrucciones.
Ejecución de instrucciones
Después de crear los archivos de instrucciones, puedes ejecutarlos desde el código de tu aplicación o con las herramientas que proporciona Genkit. Independientemente de cómo quieras ejecutar tus instrucciones, primero comienza con el código de la aplicación que importa la biblioteca de Genkit y los complementos de modelos que te interesan. Por ejemplo:
import (
"context"
"github.com/firebase/genkit/go/ai"
"github.com/firebase/genkit/go/genkit"
"github.com/firebase/genkit/go/plugins/googlegenai"
)
func main() {
g, err := genkit.Init(context.Background(), ai.WithPlugins(&googlegenai.GoogleAI{}))
if err != nil {
log.Fatal(err)
}
// Blocks end of program execution to use the developer UI.
select {}
}
Si almacenas tus instrucciones en un directorio que no es el predeterminado, asegúrate de especificarlo cuando configures Genkit.
Ejecuta instrucciones desde el código
Para usar una instrucción, primero cárgala con la función genkit.LookupPrompt()
:
helloPrompt := genkit.LookupPrompt(g, "hello")
Una instrucción ejecutable tiene opciones similares a las de genkit.Generate()
, y muchas de ellas se pueden anular en el momento de la ejecución, como la entrada (consulta la sección sobre cómo especificar esquemas de entrada), la configuración y mucho más:
resp, err := helloPrompt.Execute(context.Background(),
ai.WithModelName("googleai/gemini-2.0-flash"),
ai.WithInput(map[string]any{"name": "John"}),
ai.WithConfig(&googlegenai.GeminiConfig{Temperature: 0.5})
)
Cualquier parámetro que pases a la llamada de instrucción anulará los mismos parámetros especificados en el archivo de instrucciones.
Consulta Genera contenido con modelos de IA para obtener descripciones de las opciones disponibles.
Usa la IU para desarrolladores
A medida que definas mejor las instrucciones de tu app, podrás ejecutarlas en la IU para desarrolladores de Genkit para iterar rápidamente sobre las instrucciones y las configuraciones de modelos, independientemente de el código de la aplicación.
Carga la IU para desarrolladores desde el directorio de tu proyecto:
genkit start -- go run .
Una vez que hayas cargado los prompts en la IU para desarrolladores, podrás ejecutarlos con valores de entrada diferentes y experimentar cómo los cambios en el texto del prompt o los parámetros de configuración afectan el resultado del modelo. Cuando estés conforme con el resultado, puedes hacer clic en el botón Exportar instrucción para guardar la instrucción modificada en el directorio de tu proyecto.
Configuración del modelo
En el bloque de material preliminar de tus archivos de instrucciones, puedes especificar valores de configuración del modelo para tu instrucción de forma opcional:
---
model: googleai/gemini-2.0-flash
config:
temperature: 1.4
topK: 50
topP: 0.4
maxOutputTokens: 400
stopSequences:
- "<end>"
- "<fin>"
---
Estos valores se asignan directamente a la opción WithConfig()
que acepta la instrucción ejecutable:
resp, err := helloPrompt.Execute(context.Background(),
ai.WithConfig(&googlegenai.GeminiConfig{
Temperature: 1.4,
TopK: 50,
TopP: 0.4,
MaxOutputTokens: 400,
StopSequences: []string{"<end>", "<fin>"},
}))
Consulta Genera contenido con modelos de IA para obtener descripciones de las opciones disponibles.
Esquemas de entrada y salida
Puedes especificar los esquemas de entrada y salida de tu instrucción definiéndolos en la sección de material preliminar. Estos esquemas se usan de la misma manera que los que se pasan a una solicitud genkit.Generate()
o a una definición de flujo:
---
model: googleai/gemini-2.0-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.
Este código produce el siguiente resultado estructurado:
menuPrompt = genkit.LookupPrompt(g, "menu")
if menuPrompt == nil {
log.Fatal("no prompt named 'menu' found")
}
resp, err := menuPrompt.Execute(context.Background(),
ai.WithInput(map[string]any{"theme": "medieval"}),
)
if err != nil {
log.Fatal(err)
}
var output map[string]any
if err := resp.Output(&output); err != nil {
log.Fatal(err)
}
log.Println(output["dishname"])
log.Println(output["description"])
Tienes varias opciones para definir esquemas en un archivo .prompt
: el formato de definición de esquemas de Dotprompt, Picoschema; el esquema JSON estándar o como referencias a esquemas definidos en el código de la aplicación. En las siguientes secciones, se describe cada una de estas opciones con más detalle.
Picoschema
Los esquemas del ejemplo anterior se definen en un formato llamado Picoschema. Picoschema es un formato de definición de esquema compacto y optimizado para YAML que simplifica la definición de los atributos más importantes de un esquema para el uso de LLM. A continuación, se muestra un ejemplo más extenso de un esquema, que especifica la información que una app podría almacenar sobre 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 al siguiente tipo de Go:
type Article struct {
Title string `json:"title"`
Subtitle string `json:"subtitle,omitempty" jsonschema:"required=false"`
Draft bool `json:"draft,omitempty"` // True when in draft state
Status string `json:"status,omitempty" jsonschema:"enum=PENDING,enum=APPROVED"` // Approval status
Date string `json:"date"` // The date of publication e.g. '2025-04-07'
Tags []string `json:"tags"` // Relevant tags for article
Authors []struct {
Name string `json:"name"`
Email string `json:"email,omitempty"`
} `json:"authors"`
Metadata struct {
UpdatedAt string `json:"updatedAt,omitempty"` // ISO timestamp of last update
ApprovedBy int `json:"approvedBy,omitempty"` // ID of approver
} `json:"metadata,omitempty"`
Extra any `json:"extra"` // Arbitrary extra data
}
Picoschema admite los tipos escalares string
, integer
, number
, boolean
y any
. 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 con ?
, 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.
Esquema de JSON
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
Plantillas de instrucciones
La parte de un archivo .prompt
que sigue al material preliminar (si está presente) es la instrucción en sí, que se pasará al modelo. Si bien esta instrucción podría ser una cadena de texto simple, a menudo querrás incorporar la entrada del usuario en la instrucción. Para ello, puedes especificar tu instrucción con el lenguaje de plantillas Handlebars.
Las plantillas de instrucciones pueden incluir marcadores de posición que hacen referencia a los valores definidos por el esquema de entrada de tu instrucción.
Ya viste esto en acción en la sección sobre esquemas de entrada y salida:
---
model: googleai/gemini-2.0-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.
En este ejemplo, la expresión Handlebars, {{theme}}
, se resuelve en el valor de la propiedad theme
de la entrada cuando ejecutas la instrucción. Para pasar la entrada a la instrucción, llámala como en el siguiente ejemplo:
menuPrompt = genkit.LookupPrompt(g, "menu")
resp, err := menuPrompt.Execute(context.Background(),
ai.WithInput(map[string]any{"theme": "medieval"}),
)
Ten en cuenta que, como el esquema de entrada declaró que la propiedad theme
es opcional y proporcionó un valor predeterminado, podrías haber omitido la propiedad, y la instrucción se habría resuelto con el valor predeterminado.
Las plantillas de Handlebars también admiten algunas construcciones lógicas limitadas. Por ejemplo, como alternativa a proporcionar un valor predeterminado, puedes definir la instrucción con el ayudante #if
de Handlebars:
---
model: googleai/gemini-2.0-flash
input:
schema:
theme?: string
---
Invent a menu item for a {{#if theme}}{{theme}}{else}themed{{/else}} restaurant.
En este ejemplo, la instrucción se renderiza como "Inventa un elemento de menú para un restaurante" cuando no se especifica la propiedad theme
.
Consulta la documentación de Handlebars para obtener información sobre todos los ayudantes lógicos integrados.
Además de las propiedades definidas por tu esquema de entrada, tus plantillas también pueden hacer referencia a valores definidos automáticamente por Genkit. En las siguientes secciones, se describen estos valores definidos automáticamente y cómo puedes usarlos.
Instrucciones de varios mensajes
De forma predeterminada, Dotprompt construye un solo mensaje con un rol de "usuario". Sin embargo, algunas instrucciones, como una instrucción del sistema, se expresan mejor como combinaciones de varios mensajes.
El ayudante {{role}}
proporciona una forma sencilla de crear instrucciones de varios mensajes:
---
model: vertexai/gemini-2.0-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}}
Instrucciones multimodales
Para los modelos que admiten entradas multimodales, como imágenes junto con texto, puedes usar el ayudante {{media}}
:
---
model: vertexai/gemini-2.0-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í:
multimodalPrompt = genkit.LookupPrompt(g, "multimodal")
resp, err := multimodalPrompt.Execute(context.Background(),
ai.WithInput(map[string]any{"photoUrl": "https://example.com/photo.jpg"}),
)
Consulta también Entrada multimodal en la página Cómo generar contenido con modelos de IA para ver un ejemplo de cómo crear una URL de data:
.
Partes
Los parciales son plantillas reutilizables que se pueden incluir en cualquier instrucción. Los parciales pueden ser especialmente útiles para las instrucciones relacionadas que comparten un comportamiento común.
Cuando se carga un directorio de instrucciones, cualquier archivo con un prefijo de guion bajo (_
) se considera parcial. 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: googleai/gemini-2.0-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 no se proporcionan argumentos a la parte, se ejecuta con el mismo contexto que la instrucción superior.
Los parciales aceptan argumentos nombrados o un solo argumento posicional que representa el contexto. Esto puede ser útil para tareas como renderizar miembros de una lista.
_destination.prompt
- {{name}} ({{country}})
chooseDestination.prompt
---
model: googleai/gemini-2.0-flash
input:
schema:
destinations(array):
name: string
country: string
---
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 genkit.DefinePartial()
:
genkit.DefinePartial(g, "personality", "Talk like a {{#if style}}{{style}}{{else}}helpful assistant{{/if}}.")
Los fragmentos definidos por código están disponibles en todas las instrucciones.
Cómo definir ayudantes personalizados
Puedes definir ayudantes personalizados para procesar y administrar datos dentro de una instrucción.
Los ayudantes se registran de forma global con genkit.DefineHelper()
:
genkit.DefineHelper(g, "shout", func(input string) string {
return strings.ToUpper(input)
})
Una vez que se define un ayudante, puedes usarlo en cualquier instrucción:
---
model: googleai/gemini-2.0-flash
input:
schema:
name: string
---
HELLO, {{shout name}}!!!
Variantes de instrucciones
Como los archivos de instrucciones son solo texto, puedes (y debes) confirmarlos en tu sistema de control de versiones, lo que simplifica el proceso de comparación de 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 2.0 Flash en tu instrucción, pero querías ver si Gemini 2.5 Pro tendría un mejor rendimiento, podrías crear dos archivos:
myPrompt.prompt
: La instrucción de "modelo de referencia"myPrompt.gemini25pro.prompt
: Una variante llamadagemini25pro
Para usar una variante de instrucción, especifícala cuando realices la carga:
myPrompt := genkit.LookupPrompt(g, "myPrompt.gemini25Pro")
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 instrucciones en el código
En todos los ejemplos que se analizaron hasta ahora, se asumió que tus instrucciones se definen en archivos .prompt
individuales en un solo directorio (o subdirectorios de este), al que tu app puede acceder durante el tiempo de ejecución. Dotprompt se diseñó en torno a esta configuración y sus autores consideran que es la mejor experiencia para desarrolladores en general.
Sin embargo, si tienes casos de uso que no son compatibles con esta configuración, también puedes definir instrucciones en el código con la función genkit.DefinePrompt()
:
type GeoQuery struct {
CountryCount int `json:"countryCount"`
}
type CountryList struct {
Countries []string `json:"countries"`
}
geographyPrompt, err := genkit.DefinePrompt(
g, "GeographyPrompt",
ai.WithSystem("You are a geography teacher. Respond only when the user asks about geography."),
ai.WithPrompt("Give me the {{countryCount}} biggest countries in the world by inhabitants."),
ai.WithConfig(&googlegenai.GeminiConfig{Temperature: 0.5}),
ai.WithInputType(GeoQuery{CountryCount: 10}) // Defaults to 10.
ai.WithOutputType(CountryList{}),
)
if err != nil {
log.Fatal(err)
}
resp, err := geographyPrompt.Execute(context.Background(), ai.WithInput(GeoQuery{CountryCount: 15}))
if err != nil {
log.Fatal(err)
}
var list CountryList
if err := resp.Output(&list); err != nil {
log.Fatal(err)
}
log.Println("Countries: %s", list.Countries)
Las instrucciones también se pueden renderizar en un GenerateActionOptions
, que luego se puede procesar y pasar a genkit.GenerateWithRequest()
:
actionOpts, err := geographyPrompt.Render(ctx, ai.WithInput(GeoQuery{CountryCount: 15}))
if err != nil {
log.Fatal(err)
}
// Do something with the value...
actionOpts.Config = &googlegenai.GeminiConfig{Temperature: 0.8}
resp, err := genkit.GenerateWithRequest(ctx, g, actionOpts, nil, nil) // No middleware or streaming
Ten en cuenta que todas las opciones de instrucciones se transfieren a GenerateActionOptions
, a excepción de WithMiddleware()
, que se debe pasar por separado si se usa Prompt.Render()
en lugar de Prompt.Execute()
.