Cómo administrar instrucciones con Dotprompt

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 los mensajes son código. Escribes y mantén 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 tu e implementar modelos automáticamente.

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-pro
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, haz lo siguiente:

Go

Instala el complemento dotprompt:

go get github.com/firebase/genkit/go/plugins/dotprompt

Luego, carga la instrucción con Open:

import "github.com/firebase/genkit/go/plugins/dotprompt"
dotprompt.SetDirectory("prompts")
prompt, err := dotprompt.Open("greeting")

Puedes llamar al método Generate de la instrucción para renderizar la plantilla y pasarla a la API del modelo en un solo paso:

ctx := context.Background()

// The .prompt file specifies vertexai/gemini-1.5-pro, so make sure it's set
// up.
// Default to the project in GCLOUD_PROJECT and the location "us-central1".
vertexai.Init(ctx, nil)
vertexai.DefineModel("gemini-1.5-pro", nil)

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
}

if responseText, err := response.Text(); err == nil {
  fmt.Println(responseText)
}

O solo renderiza la plantilla en una string:

Go

renderedPrompt, err := prompt.RenderText(map[string]any{
  "location": "a restaurant",
  "style":    "a pirate",
})

La sintaxis de Dotprompt se basa en los Handlebars. el lenguaje de las plantillas. 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 usa el frontend YAML para proporcionar metadatos para una instrucción intercalada con la plantilla.

Cómo definir esquemas de entrada y salida con Picoschema

Dotprompt incluye un formato de definición de esquema compacto basado en 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 al siguiente esquema de 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"]
}

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 ?, ni tampoco permitir propiedades adicionales. Cuando una propiedad se marca como opcional, también se hace anulable para proporcionar más legibilidad para que los LLM devuelvan un valor nulo en lugar de omitir un campo.

En una definición de objeto, se puede usar la clave especial (*) para declarar un “comodín” definición del campo. Esto coincidirá con cualquier propiedad adicional que no proporcione un explícita.

Picoschema no es compatible con muchas de las capacidades del esquema JSON completo. Si requieren esquemas más sólidos, puedes proporcionar un esquema JSON en su lugar:

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

Anula metadatos de instrucciones

Mientras que los archivos .prompt te permiten incorporar metadatos, como la configuración del modelo en el archivo, también puedes anular estos valores por llamada:

Go

// 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,
)

Mensajes de varios mensajes

De forma predeterminada, Dotprompt construye un solo mensaje con un rol "user". Algunos Las instrucciones se expresan mejor como una combinación de varios mensajes, como un mensaje del sistema.

El ayudante {{role}} proporciona una forma sencilla de crear instrucciones de varios mensajes:

---
model: vertexai/gemini-1.0-pro
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 usa el ayudante {{media}}:

---
model: vertexai/gemini-1.0-pro-vision
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 "intercalado" imagen de uso de la nube. En el código, sería así:

Go

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 instrucciones

Como los archivos de instrucciones son solo texto, puedes (y debes) confirmarlos a tu de versiones, que te permite comparar fácilmente los cambios en el tiempo. A menudo, las versiones modificadas de instrucciones solo se pueden probar completamente en un de producción en paralelo con versiones existentes. Dotprompt admite esto a través de la función variantes.

Para crear una variante, crea un archivo [name].[variant].prompt. Por ejemplo, si estabas usando Gemini 1.0 Pro 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: El "modelo de referencia" instrucción
  • my_prompt.gemini15.prompt: Una variante llamada "gemini"

Para usar una variante de instrucción, especifícala cuando realices la carga:

Go

describeImagePrompt, err := dotprompt.OpenVariant("describe_image", "gemini15")

El cargador de mensajes intentará cargar la variante de ese nombre y recurriremos con respecto al modelo de referencia si no hay ninguno. Esto significa que puedes usar la carga condicional basada según los criterios que se apliquen a tu aplicación:

Go

var myPrompt *dotprompt.Prompt
var err error
if isBetaTester(user) {
  myPrompt, err = dotprompt.OpenVariant("describe_image", "gemini15")
} else {
  myPrompt, err = dotprompt.Open("describe_image")
}

El nombre de la variante se incluye en los metadatos de los seguimientos de generación, por lo que puede comparar y contrastar el rendimiento real entre las variantes en el seguimiento de Genkit con el inspector de registros.