Genera contenido con modelos de IA

En el centro de la IA generativa, se encuentran los modelos de IA. Actualmente, los dos ejemplos más destacados de modelos generativos son los modelos grandes de lenguaje (LLM) y los modelos de generación de imágenes. Estos modelos toman entradas, llamadas indicaciones (por lo general, texto, una imagen o una combinación de ambas), y a partir de ellas producen como salida texto, una imagen o incluso audio o video.

El resultado de estos modelos puede ser sorprendentemente convincente: los LLM generan texto que parece haber sido escrito por un ser humano, y los modelos de generación de imágenes pueden producir imágenes muy similares a fotografías reales o obras de arte creadas por humanos.

Además, los LLM demostraron ser capaces de realizar tareas más allá de la generación de texto simple:

  • Escribir programas informáticos
  • Planificar subtareas que se requieren para completar una tarea más grande
  • Cómo organizar datos no organizados
  • Comprender y extraer datos de información de un corpus de texto
  • Seguir y realizar actividades automatizadas según una descripción de texto de la actividad

Hay muchos modelos disponibles de varios proveedores diferentes. Cada modelo tiene sus propias fortalezas y debilidades, y uno puede destacarse en una tarea, pero no rendir tan bien en otras. A menudo, las apps que usan IA generativa pueden beneficiarse del uso de varios modelos diferentes según la tarea en cuestión.

Como desarrollador de apps, por lo general, no interactúas directamente con los modelos de IA generativa, sino a través de servicios disponibles como APIs web. Si bien estos servicios suelen tener una funcionalidad similar, todos los proporcionan a través de APIs diferentes e incompatibles. Si deseas usar varios servicios de modelos, debes usar cada uno de sus SDKs propietarios, que pueden ser incompatibles entre sí. Y si quieres actualizar de un modelo al más nuevo y capaz, es posible que debas volver a compilar esa integración.

Genkit aborda este desafío proporcionando una interfaz única que abstrae los detalles de acceso a cualquier servicio de modelo de IA generativa, con varias implementaciones precompiladas ya disponibles. Compilar tu app potenciada por IA con Genkit simplifica el proceso de realizar tu primera llamada a la IA generativa y facilita combinar varios modelos o cambiar uno por otro a medida que surgen nuevos modelos.

Antes de comenzar

Si quieres ejecutar los ejemplos de código de esta página, primero completa los pasos de la guía de Cómo comenzar. En todos los ejemplos, se da por sentado que ya instalaste Genkit como dependencia en tu proyecto.

Modelos compatibles con Genkit

Genkit está diseñado para ser lo suficientemente flexible como para usar potencialmente cualquier servicio de modelos de IA generativa. Sus bibliotecas principales definen la interfaz común para trabajar con modelos, y los complementos de modelos definen los detalles de la implementación para trabajar con un modelo específico y su API.

El equipo de Genkit mantiene complementos para trabajar con modelos proporcionados por Vertex AI, Google Generative AI y Ollama:

  • La familia de LLM de Gemini, a través del complemento de Google Cloud Vertex AI
  • La familia de LLM de Gemini, a través del complemento de Google AI
  • Modelos de generación de imágenes Imagen2 e Imagen3, a través de Google Cloud Vertex AI
  • La familia de LLM Claude 3 de Anthropic, a través del jardín de modelos de Google Cloud Vertex AI
  • Gemma 2, Llama 3 y muchos más modelos abiertos a través del complemento Ollama (debes alojar el servidor de Ollama por tu cuenta)

Además, también hay varios complementos compatibles con la comunidad que proporcionan interfaces a estos modelos:

Para obtener más información, busca paquetes etiquetados con genkit-model en npmjs.org.

Carga y configura complementos de modelos

Antes de usar Genkit para comenzar a generar contenido, debes cargar y configurar un complemento de modelo. Si vienes de la guía de introducción, ya lo hiciste. De lo contrario, consulta la guía de Introducción o la documentación del complemento individual y sigue los pasos que se indican antes de continuar.

El método generate()

En Genkit, la interfaz principal a través de la cual interactúas con los modelos de IA generativa es el método generate().

La llamada generate() más simple especifica el modelo que deseas usar y una instrucción de texto:

import { gemini15Flash, googleAI } from '@genkit-ai/googleai';
import { genkit } from 'genkit';

const ai = genkit({
  plugins: [googleAI()],
  model: gemini15Flash,
});

(async () => {
  const { text } = await ai.generate(
    'Invent a menu item for a pirate themed restaurant.'
  );
  console.log(text);
})();

Cuando ejecutes este breve ejemplo, se imprimirá cierta información de depuración, seguida del resultado de la llamada a generate(), que suele ser texto Markdown, como en el siguiente ejemplo:

## The Blackheart's Bounty

**A hearty stew of slow-cooked beef, spiced with rum and molasses, served in a
hollowed-out cannonball with a side of crusty bread and a dollop of tangy
pineapple salsa.**

**Description:** This dish is a tribute to the hearty meals enjoyed by pirates
on the high seas. The beef is tender and flavorful, infused with the warm spices
of rum and molasses. The pineapple salsa adds a touch of sweetness and acidity,
balancing the richness of the stew. The cannonball serving vessel adds a fun and
thematic touch, making this dish a perfect choice for any pirate-themed
adventure.

Vuelve a ejecutar la secuencia de comandos y obtendrás un resultado diferente.

La muestra de código anterior envió la solicitud de generación al modelo predeterminado, que especificaste cuando configuraste la instancia de Genkit.

También puedes especificar un modelo para una sola llamada a generate():

const { text } = await ai.generate({
  model: gemini15Pro,
  prompt: 'Invent a menu item for a pirate themed restaurant.',
});

En este ejemplo, se usa una referencia de modelo exportada por el complemento de modelos. Otra opción es especificar el modelo con un identificador de cadena:

const { text } = await ai.generate({
  model: 'googleai/gemini-1.5-pro-latest',
  prompt: 'Invent a menu item for a pirate themed restaurant.',
});

Un identificador de cadena de modelo se ve como providerid/modelid, en el que el ID del proveedor (en este caso, googleai) identifica el complemento, y el ID del modelo es un identificador de cadena específico del complemento para una versión específica de un modelo.

Algunos complementos de modelos, como el complemento Ollama, proporcionan acceso a decenas de modelos diferentes y, por lo tanto, no exportan referencias de modelos individuales. En estos casos, solo puedes especificar un modelo para generate() con su identificador de cadena.

Estos ejemplos también ilustran un punto importante: cuando usas generate() para realizar llamadas a modelos de IA generativa, cambiar el modelo que deseas usar es simplemente cuestión de pasar un valor diferente al parámetro del modelo. Si usas generate() en lugar de los SDKs de modelos nativos, te brindas la flexibilidad de usar varios modelos diferentes en tu app y cambiarlos en el futuro con mayor facilidad.

Hasta ahora, solo viste ejemplos de las llamadas generate() más simples. Sin embargo, generate() también proporciona una interfaz para interacciones más avanzadas con modelos generativos, que verás en las siguientes secciones.

Mensajes del sistema

Algunos modelos admiten proporcionar una instrucción del sistema, que le brinda al modelo instrucciones sobre cómo deseas que responda a los mensajes del usuario. Puedes usar la instrucción del sistema para especificar un arquetipo que deseas que adopte el modelo, el tono de sus respuestas, el formato de sus respuestas, etcétera.

Si el modelo que usas admite instrucciones del sistema, puedes proporcionar una con el parámetro system:

const { text } = await ai.generate({
  system: 'You are a food industry marketing consultant.',
  prompt: 'Invent a menu item for a pirate themed restaurant.',
});

Parámetros del modelo

La función generate() toma un parámetro config, a través del cual puedes especificar parámetros de configuración opcionales que controlan cómo el modelo genera contenido:

const { text } = await ai.generate({
  prompt: 'Invent a menu item for a pirate themed restaurant.',
  config: {
    maxOutputTokens: 400,
    stopSequences: ['<end>', '<fin>'],
    temperature: 1.2,
    topP: 0.4,
    topK: 50,
  },
});

Los parámetros exactos que se admiten dependen del modelo individual y de la API del modelo. Sin embargo, los parámetros del ejemplo anterior son comunes a casi todos los modelos. A continuación, se explica cada uno de estos parámetros:

Parámetros que controlan la longitud de la salida

maxOutputTokens

Los LLM operan en unidades llamadas tokens. Por lo general, un token se asigna a una secuencia específica de caracteres, pero no es necesariamente así. Cuando pasas una instrucción a un modelo, uno de los primeros pasos que realiza es tokenizar la cadena de instrucciones en una secuencia de tokens. Luego, el LLM genera una secuencia de tokens a partir de la entrada tokenizada. Por último, la secuencia de tokens se vuelve a convertir en texto, que es tu resultado.

El parámetro de tokens de salida máximos simplemente establece un límite en la cantidad de tokens que se deben generar con el LLM. Cada modelo puede usar un analizador de tokens diferente, pero una buena regla general es considerar que una sola palabra en inglés está compuesta de 2 a 4 tokens.

Como se indicó anteriormente, es posible que algunos tokens no se asignan a secuencias de caracteres. Un ejemplo de esto es que, a menudo, hay un token que indica el final de la secuencia: cuando un LLM genera este token, deja de generar más. Por lo tanto, es posible y, a menudo, es el caso que un LLM genere menos tokens que el máximo porque generó el token "stop".

stopSequences

Puedes usar este parámetro para establecer los tokens o las secuencias de tokens que, cuando se generan, indican el final de la salida de LLM. Los valores correctos que se deben usar aquí generalmente dependen de cómo se entrenó el modelo y, por lo general, los establece el complemento del modelo. Sin embargo, si le pediste al modelo que genere otra secuencia de detención, puedes especificarla aquí.

Ten en cuenta que estás especificando secuencias de caracteres, no tokens en sí. En la mayoría de los casos, especificarás una secuencia de caracteres que el analizador del modelo asignará a un solo token.

Parámetros que controlan la "creatividad"

Los parámetros temperatura, top-p y top-k juntos controlan qué tan “creativo” quieres que sea el modelo. A continuación, se incluyen explicaciones muy breves sobre el significado de estos parámetros, pero lo más importante que debes tener en cuenta es que estos parámetros se usan para ajustar el carácter del resultado de un LLM. Los valores óptimos para ellos dependen de tus objetivos y preferencias, y es probable que solo se encuentren a través de la experimentación.

temperatura

Los LLM son, en esencia, máquinas que predicen tokens. Para una secuencia determinada de tokens (como la instrucción), un LLM predice, para cada token de su vocabulario, la probabilidad de que el token sea el siguiente en la secuencia. La temperatura es un factor de escalamiento por el que se dividen estas predicciones antes de normalizarse a una probabilidad entre 0 y 1.

Los valores de temperatura baja (entre 0.0 y 1.0) amplifican la diferencia en las probabilidades entre los tokens, lo que hace que sea aún menos probable que el modelo produzca un token que ya evaluó como poco probable. Esto a menudo se percibe como un resultado menos creativo. Aunque, técnicamente, 0.0 no es un valor válido, muchos modelos lo consideran como un indicador de que el modelo debe comportarse de manera determinista y solo considerar el token más probable.

Los valores de temperatura alta (aquellos superiores a 1.0) comprimen las diferencias en las probabilidades entre los tokens, lo que aumenta la probabilidad de que el modelo produzca tokens que antes evaluó como poco probables. Esto suele percibirse como un resultado más creativo. Algunas APIs de modelos imponen una temperatura máxima, a menudo de 2.0.

topP

Top-p es un valor entre 0.0 y 1.0 que controla la cantidad de tokens posibles que deseas que el modelo considere, ya que especifica la probabilidad acumulativa de los tokens. Por ejemplo, un valor de 1.0 significa considerar todos los tokens posibles (pero aún tener en cuenta la probabilidad de cada token). Un valor de 0.4 significa que solo se deben considerar los tokens más probables, cuyas probabilidades suman 0.4, y excluir los tokens restantes.

topK

Top-K es un valor entero que también controla la cantidad de tokens posibles que quieres que el modelo considere, pero esta vez especificando explícitamente la cantidad máxima de tokens. Especificar un valor de 1 significa que el modelo debe comportarse de manera determinista.

Experimenta con los parámetros del modelo

Puedes experimentar con el efecto de estos parámetros en el resultado que generan diferentes combinaciones de modelos y mensajes con la IU para desarrolladores. Inicia la IU del desarrollador con el comando genkit start y se cargarán automáticamente todos los modelos definidos por los complementos configurados en tu proyecto. Puedes probar rápidamente diferentes instrucciones y valores de configuración sin tener que realizar estos cambios en el código de forma reiterada.

Resultados estructurados

Cuando usas la IA generativa como componente en tu aplicación, a menudo quieres que el resultado sea un formato diferente al texto sin formato. Incluso si solo generas contenido para mostrarlo al usuario, puedes beneficiarte de los resultados estructurados solo con el objetivo de presentarlo de forma más atractiva al usuario. Sin embargo, para aplicaciones más avanzadas de la IA generativa, como el uso programático del resultado del modelo o la alimentación del resultado de un modelo a otro, el resultado estructurado es imprescindible.

En Genkit, puedes solicitar un resultado estructurado de un modelo especificando un esquema cuando llamas a generate():

import { z } from 'genkit'; // Import Zod, which is re-exported by Genkit.
const MenuItemSchema = z.object({
  name: z.string(),
  description: z.string(),
  calories: z.number(),
  allergens: z.array(z.string()),
});

const { output } = await ai.generate({
  prompt: 'Invent a menu item for a pirate themed restaurant.',
  output: { schema: MenuItemSchema },
});

Los esquemas de salida del modelo se especifican con la biblioteca Zod. Además de un lenguaje de definición de esquemas, Zod también proporciona una verificación de tipos de tiempo de ejecución, que cierra la brecha entre los tipos estáticos de TypeScript y el resultado impredecible de los modelos de IA generativa. Zod te permite escribir código que puede confiar en el hecho de que una llamada de generación correcta siempre mostrará un resultado que se ajusta a tus tipos de TypeScript.

Cuando especificas un esquema en generate(), Genkit realiza varias acciones en segundo plano:

  • Aumenta la instrucción con orientación adicional sobre el formato de salida deseado. Esto también tiene el efecto secundario de especificarle al modelo qué contenido quieres generar exactamente (por ejemplo, no solo sugerir un elemento de menú, sino también generar una descripción, una lista de alérgenos, etcétera).
  • Analiza el resultado del modelo en un objeto JavaScript.
  • Verifica que el resultado cumpla con el esquema.

Para obtener un resultado estructurado de una llamada de generación correcta, usa la propiedad output del objeto de respuesta:

if (output) {
  const { name, description, calories, allergens } = output;
}

Maneja los errores

En el ejemplo anterior, ten en cuenta que la propiedad output puede ser null. Esto puede suceder cuando el modelo no genera resultados que se ajusten al esquema. La mejor estrategia para abordar estos errores dependerá de tu caso de uso exacto, pero aquí tienes algunas sugerencias generales:

  • Prueba con otro modelo. Para que el resultado estructurado tenga éxito, el modelo debe ser capaz de generar resultados en JSON. Los LLM más potentes, como Gemini y Claude, son lo suficientemente versátiles como para hacerlo. Sin embargo, los modelos más pequeños, como algunos de los modelos locales que usarías con Ollama, podrían no ser capaces de generar resultados estructurados de forma confiable, a menos que se hayan entrenado específicamente para hacerlo.

  • Usa las capacidades de coerción de Zod: Puedes especificar en tus esquemas que Zod debe intentar forzar los tipos que no cumplen con el esquema al tipo que especifica el esquema. Si tu esquema incluye tipos primitivos distintos de cadenas, usar la coerción de Zod puede reducir la cantidad de fallas de generate() que experimentas. La siguiente versión de MenuItemSchema usa la coerción de tipos para corregir automáticamente situaciones en las que el modelo genera información de calorías como una cadena en lugar de un número:

    const MenuItemSchema = z.object({
      name: z.string(),
      description: z.string(),
      calories: z.coerce.number(),
      allergens: z.array(z.string()),
    });
    
  • Vuelve a intentar la llamada a generate(). Si el modelo que elegiste solo falla en ocasiones para generar un resultado conforme, puedes tratar el error como lo harías con un error de red y simplemente volver a intentar la solicitud con algún tipo de estrategia de retirada incremental.

Transmisión

Cuando generas grandes cantidades de texto, puedes mejorar la experiencia de los usuarios presentando el resultado a medida que se genera, es decir, transmitiéndolo. Un ejemplo familiar de transmisión en acción se puede ver en la mayoría de las apps de chat de LLM: los usuarios pueden leer la respuesta del modelo a su mensaje a medida que se genera, lo que mejora la capacidad de respuesta percibida de la aplicación y mejora la ilusión de chatear con una contraparte inteligente.

En Genkit, puedes transmitir la salida con el método generateStream(). Su sintaxis es similar al método generate():

const { response, stream } = await ai.generateStream(
  'Suggest a complete menu for a pirate themed restaurant.'
);

El objeto de respuesta tiene una propiedad stream, que puedes usar para iterar sobre el resultado de transmisión de la solicitud a medida que se genera:

for await (const chunk of stream) {
  console.log(chunk.text);
}

También puedes obtener el resultado completo de la solicitud, como lo haces con una solicitud que no es de transmisión:

const completeText = (await response).text;

La transmisión también funciona con resultados estructurados:

const MenuSchema = z.object({
  starters: z.array(MenuItemSchema),
  mains: z.array(MenuItemSchema),
  desserts: z.array(MenuItemSchema),
});

const { response, stream } = await ai.generateStream({
  prompt: 'Suggest a complete menu for a pirate themed restaurant.',
  output: { schema: MenuSchema },
});

for await (const chunk of stream) {
  // `output` is an object representing the entire output so far.
  console.log(chunk.output);
}

// Get the completed output.
const { output } = await response;

La transmisión de resultados estructurados funciona de manera un poco diferente a la transmisión de texto: la propiedad output de un fragmento de respuesta es un objeto construido a partir de la acumulación de los fragmentos que se produjeron hasta el momento, en lugar de un objeto que representa un solo fragmento (que podría no ser válido por sí solo). Cada fragmento de salida estructurada, en cierto sentido, reemplaza al fragmento anterior.

Por ejemplo, este es el aspecto que podrían tener los primeros cinco resultados del ejemplo anterior:

null

{ starters: [ {} ] }

{
  starters: [ { name: "Captain's Treasure Chest", description: 'A' } ]
}

{
  starters: [
    {
      name: "Captain's Treasure Chest",
      description: 'A mix of spiced nuts, olives, and marinated cheese served in a treasure chest.',
      calories: 350
    }
  ]
}

{
  starters: [
    {
      name: "Captain's Treasure Chest",
      description: 'A mix of spiced nuts, olives, and marinated cheese served in a treasure chest.',
      calories: 350,
      allergens: [Array]
    },
    { name: 'Shipwreck Salad', description: 'Fresh' }
  ]
}

Entrada multimodal

En los ejemplos que viste hasta ahora, se usaron cadenas de texto como instrucciones del modelo. Si bien esta sigue siendo la forma más común de solicitar modelos de IA generativa, muchos modelos también pueden aceptar otros medios como instrucciones. Las instrucciones multimedia se usan con mayor frecuencia junto con instrucciones de texto que le indican al modelo que realice alguna operación en el contenido multimedia, como agregar una leyenda a una imagen o transcribir una grabación de audio.

La capacidad de aceptar entradas multimedia y los tipos de contenido multimedia que puedes usar dependen por completo del modelo y su API. Por ejemplo, la serie de modelos Gemini 1.5 puede aceptar imágenes, videos y audio como instrucciones.

Para proporcionar una instrucción multimedia a un modelo que la admita, en lugar de pasar una instrucción de texto simple a generate, pasa un array que consta de una parte multimedia y una parte de texto:

const { text } = await ai.generate([
  { media: { url: 'https://example.com/photo.jpg' } },
  { text: 'Compose a poem about this image.' },
]);

En el ejemplo anterior, especificaste una imagen con una URL HTTPS de acceso público. También puedes pasar datos multimedia directamente codificándolos como una URL de datos. Por ejemplo:

import { readFile } from 'node:fs/promises';
const b64Data = await readFile('photo.jpg', { encoding: 'base64url' });
const dataUrl = `data:image/jpeg;base64,${b64Data}`;

const { text } = await ai.generate([
  { media: { url: dataUrl } },
  { text: 'Compose a poem about this image.' },
]);

Todos los modelos que admiten entradas multimedia admiten URLs de datos y URLs HTTPS. Algunos complementos de modelos agregan compatibilidad con otras fuentes de contenido multimedia. Por ejemplo, el complemento de Vertex AI también te permite usar URLs de Cloud Storage (gs://).

Genera contenido multimedia

Hasta ahora, la mayoría de los ejemplos de esta página se han ocupado de generar texto con LLM. Sin embargo, Genkit también se puede usar con modelos de generación de imágenes. El uso de generate() con un modelo de generación de imágenes es similar al uso de un LLM. Por ejemplo, para generar una imagen con el modelo Imagen2 a través de Vertex AI, haz lo siguiente:

  1. Genkit usa URLs de data: como el formato de salida estándar para el contenido multimedia generado. Este es un formato estándar con muchas bibliotecas disponibles para controlarlo. En este ejemplo, se usa el paquete data-urls de jsdom:

    npm i --save data-urls
    npm i --save-dev @types/data-urls
  2. Para generar una imagen y guardarla en un archivo, llama a generate() y especifica un modelo de generación de imágenes y el tipo de medio del formato de salida:

    import { imagen3Fast, vertexAI } from '@genkit-ai/vertexai';
    import parseDataURL from 'data-urls';
    import { genkit } from 'genkit';
    
    import { writeFile } from 'node:fs/promises';
    
    const ai = genkit({
      plugins: [vertexAI({ location: 'us-central1' })],
    });
    
    (async () => {
      const { media } = await ai.generate({
        model: imagen3Fast,
        prompt: 'photo of a meal fit for a pirate',
        output: { format: 'media' },
      });
    
      if (media === null) throw new Error('No media generated.');
    
      const data = parseDataURL(media.url);
      if (data === null) throw new Error('Invalid "data:" URL.');
    
      await writeFile(`output.${data.mimeType.subtype}`, data.body);
    })();
    

Próximos pasos

Más información sobre Genkit

  • Como desarrollador de apps, la forma principal en la que influyes en el resultado de los modelos de IA generativa es a través de instrucciones. Lee Administración de instrucciones para descubrir cómo Genkit te ayuda a desarrollar instrucciones eficaces y administrarlas en tu base de código.
  • Si bien generate() es el núcleo de cada aplicación potenciada por IA generativa, las aplicaciones del mundo real suelen requerir trabajo adicional antes y después de invocar un modelo de IA generativa. Para reflejar esto, Genkit presenta el concepto de flujos, que se definen como funciones, pero agregan funciones adicionales, como la observabilidad y la implementación simplificada. Para obtener más información, consulta Cómo definir flujos de trabajo.

Uso avanzado de LLM

  • Una forma de mejorar las capacidades de los LLM es proporcionarles una lista de formas en las que pueden solicitarte más información o pedirte que realices alguna acción. Esto se conoce como llamada a herramientas o llamada a función. Los modelos que se entrenan para admitir esta función pueden responder a una instrucción con una respuesta con formato especial, que le indica a la aplicación que realiza la llamada que debe realizar alguna acción y enviar el resultado al LLM junto con la instrucción original. Genkit tiene funciones de biblioteca que automatizan la generación de instrucciones y los elementos del bucle de llamada y respuesta de una implementación de llamada a herramienta. Consulta Llamadas a herramientas para obtener más información.
  • La generación aumentada de recuperación (RAG) es una técnica que se usa para introducir información específica del dominio en el resultado de un modelo. Para ello, se inserta información relevante en una instrucción antes de pasarla al modelo de lenguaje. Una implementación completa de la RAG requiere que combines varias tecnologías: modelos de generación de incorporaciones de texto, bases de datos de vectores y modelos de lenguaje extensos. Consulta Generación aumentada de recuperación (RAG) para aprender cómo Genkit simplifica el proceso de coordinación de estos diversos elementos.

Prueba la salida del modelo

Como ingeniero de software, estás acostumbrado a los sistemas deterministas en los que la misma entrada siempre produce el mismo resultado. Sin embargo, como los modelos de IA son probabilísticos, el resultado puede variar según los matices sutiles de la entrada, los datos de entrenamiento del modelo y hasta la aleatoriedad que introducen deliberadamente parámetros como la temperatura.

Los evaluadores de Genkit son formas estructuradas de evaluar la calidad de las respuestas de tu LLM con una variedad de estrategias. Obtén más información en la página Evaluación.