🇺🇸 EN
AI
·13 min read·↗·🇺🇸 EN

Cómo probar y evaluar agentes de IA con TypeScript y Genkit

Portada principal del artículo: Cómo probar y evaluar agentes de IA con TypeScript y Genkit

Cuando desarrollamos aplicaciones frontend o backend habituales, las pruebas forman parte de nuestra rutina. Escribimos tests unitarios con Vitest o Jest, comprobamos los resultados y desplegamos con tranquilidad.

Cuando empecé a construir agentes de IA, hice lo que casi cualquier desarrollador hace al principio: una prueba manual "a ojo" (vibe check).

Abría mi terminal, enviaba un par de prompts al agente, veía lo que respondía y pensaba: "¡Tiene buena pinta!"

Y entonces llegaron los fallos inesperados. Unos días después, cambié una sola frase en mi system prompt para mejorar una funcionalidad. Justo después de eso, el agente empezó a fallar en tareas que ayer resolvía sin problemas: llamaba a herramientas equivocadas, se quedaba atascado en bucles infinitos y malgastaba tokens.

En mi artículo anterior, ¿Qué es la Observabilidad de Agentes?, expliqué cómo trazar e inspeccionar lo que hace un agente en producción. Pero antes de mandar un agente a producción, necesitas una forma reproducible de medir su calidad: necesitas Evals (evaluaciones).

En este artículo te mostraré cómo probar agentes de IA paso a paso en TypeScript utilizando el framework open source de Google, Firebase Genkit. Veremos validación de prompts, pruebas de selección de herramientas y ejecución de evaluaciones automáticas.

📦 Todo el código de este artículo está disponible en GitHub: danywalls/genkit-agent-evals-demo. Clónalo y ejecuta pnpm run evals para ver las evaluaciones funcionando en menos de 5 minutos.

Antes de escribir código o instalar paquetes, necesitamos un modelo mental claro: ¿qué significa realmente evaluar un agente?


Las 3 preguntas que debes hacerle a tu agente

Al evaluar un agente, fijarse únicamente en el mensaje final es un error. Un agente puede sonar muy educado mientras llama a la API equivocada o malgasta veinte peticiones innecesarias.

Para probar un agente correctamente, medimos tres áreas principales:

ÁreaPreguntaQué comprobamos
Selección de herramientas¿Eligió la herramienta correcta?Nombre de la tool y argumentos
Trayectoria¿Trabajó de forma eficiente?Número de pasos, bucles y tokens consumidos
Resultado final¿Resolvió el problema del usuario?Precisión y veracidad de la respuesta

Primero, comprobamos la Selección de herramientas. Si un usuario pide buscar fotos, el agente debe invocar la herramienta de búsqueda, no una consulta a base de datos sin relación.

Segundo, evaluamos la Trayectoria. Si un agente necesita ocho intentos para ejecutar una sola herramienta, algo falla en la descripción o en el esquema de esa tool.

Tercero, medimos el Resultado final. La respuesta debe ser precisa y resolver directamente la petición del usuario.

Con este modelo mental claro, configuremos nuestro proyecto para evaluar estas tres áreas paso a paso.


Configuración del entorno de pruebas

Para construir nuestro agente y escribir las pruebas, utilizaremos Firebase Genkit con TypeScript.

Ejecuta el siguiente comando para instalar las dependencias necesarias:

pnpm add @genkit-ai/ai @genkit-ai/flow @genkit-ai/dotprompt @genkit-ai/googleai zod

A continuación, añade tu API key de Gemini en tu archivo .env:

GOOGLE_GENAI_API_KEY=tu_api_key_aqui

Puedes obtener una clave gratuita en Google AI Studio.

Con el entorno listo, pasemos al primer punto donde suelen tropezar los agentes: los inputs inválidos en los prompts.


Paso 1: Validar prompts con Dotprompt y Zod

Muchos fallos en agentes ocurren antes de que el modelo empiece siquiera a generar texto. Suceden porque una variable de plantilla llegó como undefined, vacía o con un formato incorrecto.

¿Qué es Dotprompt?

La mayoría de desarrolladores escriben prompts como simples strings en su código:

const prompt = `Ayuda al usuario: ${userQuery}`;

El problema es que no hay ninguna validación. Si userQuery llega como undefined, el modelo recibe un prompt roto y solo te enteras cuando produce una respuesta errónea.

Dotprompt es una funcionalidad de Firebase Genkit que te permite escribir prompts en archivos .prompt dedicados con una cabecera YAML. En esa cabecera declaras qué variables requiere el prompt, sus tipos y qué modelo utilizar.

Piénsalo como la firma tipada de una función: si lo llamas con argumentos erróneos, Genkit lo frena antes de consultar al modelo.

En Firebase Genkit combinamos Dotprompt (archivos .prompt) con Zod para interceptar estos errores de inmediato:

---
# prompts/assistant.prompt
model: googleai/gemini-2.5-flash
input:
  schema:
    userQuery: string
    preferredOrientation: string
    maxResults: number
---
 
Eres un asistente de fotografía. Ayuda al usuario a encontrar fotos de calidad.
Petición del usuario: {{userQuery}}
Orientación preferida: {{preferredOrientation}}
Máximo de resultados: {{maxResults}}

Este archivo .prompt actúa como un contrato que define qué variables son obligatorias antes de renderizar.

En nuestro código TypeScript, Genkit valida los datos contra este esquema antes de hacer la llamada:

// promptValidator.ts
import { dotprompt } from '@genkit-ai/dotprompt';
import { z } from 'zod';
 
const SearchInputSchema = z.object({
  userQuery: z.string().min(2, "La consulta es demasiado corta"),
  preferredOrientation: z.enum(["landscape", "portrait", "squarish"]),
  maxResults: z.number().min(1).max(10),
});
 
export async function renderValidatedPrompt(input: unknown) {
  // 1. Validar entradas antes de llamar al modelo
  const validated = SearchInputSchema.parse(input);
  const prompt = await dotprompt('assistant');
  return prompt.render(validated);
}

La llamada SearchInputSchema.parse valida los datos en tiempo de ejecución con Zod. Si falta una variable o llega con un tipo incorrecto, la ejecución se detiene al momento con un error claro antes de llamar a la API de Gemini. Esto ahorra costes y previene ejecuciones rotas.

Una vez validados los inputs, nuestro agente necesita herramientas para actuar. Creemos nuestra tool de búsqueda y el flujo principal.


Paso 2: Definir el flujo y las herramientas del agente

En Genkit, tu agente se construye a partir de dos piezas fundamentales: Tools (Herramientas) y Flows (Flujos).

¿Qué es una Tool?

Una Tool es una función tipada que el modelo puede decidir ejecutar de forma autónoma. Describes qué hace la herramienta en texto claro, defines sus entradas y salidas con esquemas de Zod, y Genkit expone esa herramienta al modelo.

El modelo lee tu descripción y decide cuándo utilizarla. Si la descripción es precisa, elige la herramienta adecuada. Si la descripción es confusa, el modelo adivina, y ahí es donde las evaluaciones detectan el fallo.

¿Qué es un Flow?

Un Flow une todas las piezas. Es una función tipada y observable que envuelve toda la lógica de tu agente: recibe la entrada del usuario, invoca al modelo (con las herramientas disponibles) y devuelve el resultado.

Al estar registrados en Genkit, puedes ejecutar flujos desde la terminal, la Developer UI o tus scripts de evaluación con el mismo comportamiento.

Definamos una herramienta de búsqueda de fotos inspirada en mi proyecto unsplashx:

// tools.ts
import { defineTool } from '@genkit-ai/ai';
import { z } from 'zod';
 
export const searchPhotosTool = defineTool(
  {
    name: 'searchPhotos',
    description: 'Busca fotos de alta calidad según palabra clave y orientación.',
    inputSchema: z.object({
      query: z.string().describe('Palabra clave de búsqueda'),
      orientation: z.enum(['landscape', 'portrait', 'squarish']),
    }),
    outputSchema: z.object({
      count: z.number(),
      results: z.array(z.string()),
    }),
  },
  async (input) => {
    // Lógica de ejecución de la herramienta
    return {
      count: 2,
      results: [`https://images.unsplash.com/photo-1?${input.orientation}`],
    };
  }
);

La propiedad description es fundamental porque el LLM la lee para decidir cuándo llamar a la tool. El inputSchema utiliza Zod para restringir los argumentos, garantizando que el modelo nunca pase parámetros inesperados.

Ahora creamos el flujo del agente utilizando esta tool y reutilizando renderValidatedPrompt:

// agentFlow.ts
import { configureGenkit } from '@genkit-ai/core';
import { googleAI } from '@genkit-ai/googleai';
import { defineFlow } from '@genkit-ai/flow';
import { generate } from '@genkit-ai/ai';
import { dotprompt } from '@genkit-ai/dotprompt';
import { searchPhotosTool } from './tools.js';
import { renderValidatedPrompt } from './promptValidator.js';
 
configureGenkit({
  plugins: [googleAI(), dotprompt()],
  enableTracingAndMetrics: true,
});
 
export const photoAgentFlow = defineFlow(
  { name: 'photoAgent' },
  async (input: { userQuery: string; preferredOrientation: string; maxResults: number }) => {
    const messages = await renderValidatedPrompt(input);
 
    const response = await generate({
      model: 'googleai/gemini-2.5-flash',
      messages,
      tools: [searchPhotosTool],
    });
 
    return {
      text: response.text(),
      toolCalls: response.toolCalls(),
    };
  }
);

Fíjate en que photoAgentFlow devuelve response.toolCalls() junto al texto. Esto permite a nuestro runner de pruebas verificar qué herramientas y argumentos seleccionó el modelo sin tener que parsear texto plano con expresiones regulares complejas.

Ahora que el flujo funciona, probarlo una sola vez en la terminal sigue siendo una prueba superficial. Para asegurarnos de que no se rompe con futuros cambios, creamos un Golden Dataset.


Paso 3: Construir un Golden Dataset

Un Golden Dataset es una colección de escenarios de prueba reales con sus comportamientos esperados.

¿Qué es un Golden Dataset?

El concepto hace referencia a un "estándar de oro": casos de prueba contrastados que sabemos que son correctos y que nuestro agente siempre debe superar.

Funciona como una suite de regresión para tu agente. En software tradicional compruebas que suma(2, 3) devuelva 5. Al probar agentes, compruebas que ante la petición "Busca una foto horizontal de un escritorio" el agente llame a la herramienta adecuada con los argumentos correctos.

Sin un Golden Dataset, cada ajuste de prompt es una ruleta rusa: cambias una frase, lo pruebas una vez, parece que responde bien y lo publicas. Dos días más tarde un usuario avisa de que el agente usa herramientas equivocadas. Con un dataset de evaluación, ejecutas todos los casos tras cada cambio y detectas el problema al instante.

Cada caso de prueba consta de cuatro partes:

CampoPropósito
idIdentificador único del escenario para los informes
inputEl mensaje real que enviamos al agente
expectedToolLa herramienta exacta que debe invocar
expectedParamsLos argumentos que debe recibir la herramienta

Definamos nuestro dataset de evaluación en evalDataset.json:

[
  {
    "id": "search-minimal-desk",
    "input": {
      "userQuery": "Busca una foto horizontal de un escritorio minimalista.",
      "preferredOrientation": "landscape",
      "maxResults": 3
    },
    "expectedTool": "searchPhotos",
    "expectedParams": { "orientation": "landscape" }
  },
  {
    "id": "search-vertical-nature",
    "input": {
      "userQuery": "Necesito fotos verticales de árboles en la montaña para fondo de pantalla.",
      "preferredOrientation": "portrait",
      "maxResults": 5
    },
    "expectedTool": "searchPhotos",
    "expectedParams": { "orientation": "portrait" }
  }
]

Cada entrada define un mensaje real y la herramienta y parámetros esperados. Si mañana modificamos las instrucciones del prompt, ejecutar este dataset nos avisará de inmediato si el modelo escoge la herramienta incorrecta.

Con nuestros casos listos, el paso final es escribir un runner de evaluación automatizado en TypeScript para comprobar los resultados.


Paso 4: Ejecutar evaluaciones con aserciones en código

Para probar nuestro agente en local, ejecutamos cada caso de prueba y comprobamos dos aspectos:

  1. ¿Llamó el agente a la herramienta esperada?
  2. ¿Pasó los argumentos correctos?

Veamos el runner de evaluación escrito en TypeScript:

// runEvals.ts
import fs from 'fs';
import { photoAgentFlow } from './agentFlow.js';
 
interface TestCase {
  id: string;
  input: { userQuery: string; preferredOrientation: string; maxResults: number };
  expectedTool: string;
  expectedParams: Record<string, string>;
}
 
function checkParams(
  actual: Record<string, unknown>,
  expected: Record<string, string>
): boolean {
  return Object.entries(expected).every(([key, val]) => actual[key] === val);
}
 
async function runEvaluation() {
  const cases: TestCase[] = JSON.parse(fs.readFileSync('evalDataset.json', 'utf8'));
  let passed = 0;
 
  console.log('Ejecutando evaluaciones de agentes...\n');
 
  for (const test of cases) {
    const output = await photoAgentFlow(test.input);
    const firstCall = output.toolCalls?.[0];
    const calledTool = firstCall?.name;
    const calledParams = (firstCall?.input ?? {}) as Record<string, unknown>;
 
    const toolOk = calledTool === test.expectedTool;
    const paramsOk = checkParams(calledParams, test.expectedParams);
 
    if (toolOk && paramsOk) {
      console.log(`✅ [PASS] ${test.id}: ${calledTool} con parámetros correctos`);
      passed++;
    } else if (!toolOk) {
      console.log(`❌ [FAIL] ${test.id}: Herramienta esperada ${test.expectedTool}, recibida ${calledTool}`);
    } else {
      console.log(`❌ [FAIL] ${test.id}: Herramienta correcta, pero parámetros incorrectos:`, test.expectedParams);
    }
  }
 
  const passRate = Math.round((passed / cases.length) * 100);
  console.log(`\nResultados: ${passed}/${cases.length} superados (${passRate}%)\n`);
}
 
runEvaluation();

La función runEvaluation recorre cada escenario de evalDataset.json, ejecuta photoAgentFlow y valida tanto la tool invocada como los argumentos enviados. Muestra el resultado de cada caso y calcula la tasa de acierto final.

Si estás usando el repositorio del proyecto, ejecuta:

pnpm run evals

O en tu propio proyecto desde cero:

npx tsx src/runEvals.ts

Verás una salida similar a esta:

🧪 Ejecutando evaluaciones de agentes...
 
  Ejecutando: search-minimal-desk
  ✅ [PASS] search-minimal-desk: searchPhotos con parámetros correctos
 
  Ejecutando: search-vertical-nature
  ✅ [PASS] search-vertical-nature: searchPhotos con parámetros correctos
 
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Resultados: 2/2 superados (100%)

También puedes iniciar la Developer UI local de Genkit ejecutando:

npx genkit start

Esto abrirá un panel en http://localhost:4000 donde podrás inspeccionar cada traza, revisar costes de tokens y probar flujos de forma interactiva sin coste.

Las evaluaciones locales ofrecen feedback inmediato mientras desarrollas en tu máquina. Pero cuando construyes agentes en equipo, los scripts locales empiezan a mostrar ciertas limitaciones.


Cuándo dar el salto a plataformas empresariales de evaluación

Construir tus propios scripts de prueba en TypeScript con Genkit es la mejor forma de entender cómo funcionan las evaluaciones: es gratuito, rápido y se ejecuta en tu propio equipo.

Sin embargo, a medida que el proyecto y el equipo crecen, surgen retos habituales:

Gestión de Golden Datasets: Un archivo JSON con 10 casos es manejable. Con 200 casos y 5 desarrolladores editándolo a la vez, los conflictos en Git son constantes. Falta una interfaz para añadir o filtrar escenarios sin abrir un editor de código.

Rastreo de regresiones históricas: La salida de tu terminal te dice si la ejecución de hoy ha pasado. No te dice si la tasa de acierto ha caído del 95% al 78% en las últimas dos semanas a raíz de un cambio de prompt.

Pruebas comparativas entre modelos: Para comparar Gemini 2.5 Flash con Claude 3.5 Sonnet sobre el mismo dataset, necesitas programar runners a medida para cada proveedor, lo que añade código de mantenimiento.

Visibilidad para todo el equipo: Los responsables de producto no leen logs de terminal. El equipo necesita dashboards visuales con tendencias de precisión, costes por ejecución y análisis de fallos por categoría.

Dar el salto de pruebas locales a una plataforma centralizada de evaluación es el paso natural para equipos en crecimiento. En mi próximo artículo profundizaremos en cómo las plataformas empresariales resuelven esta escala.

Tanto si ejecutas pruebas mediante scripts locales como a través de una plataforma centralizada, abandonar las pruebas manuales es lo que hace que tus agentes sean realmente fiables en producción.


Resumen

Probar agentes de IA requiere superar las pruebas manuales a ojo:

  1. Valida prompts primero: Usa Genkit Dotprompt y Zod para verificar variables antes de consultar al modelo.
  2. Evalúa la trayectoria completa: Comprueba la selección de herramientas y el número de pasos, no solo la respuesta final.
  3. Construye un golden dataset: Prueba escenarios reales para detectar regresiones antes de publicar.
  4. Aprovecha herramientas open source en local: Firebase Genkit proporciona flujos tipados, trazas y una Developer UI gratuita.

Si ya estás recolectando trazas con Observabilidad de Agentes, ya dispones de los datos necesarios para construir tu primer dataset de evaluación. ¡Empieza a probar tus agentes hoy mismo!

Parte de la serie AI & Engineering

Bridging the gap between AI demos and real-world production applications.

Ver serie completa

Preguntas Frecuentes

¿Qué es una evaluación (eval) en agentes de IA?

Un eval es una prueba automatizada que mide la eficacia con la que un agente de IA realiza una tarea, verificando inputs del prompt, selección de herramientas, número de pasos y precisión de la respuesta final.

¿Cómo se validan los prompts antes de llamar a un LLM?

Puedes usar Genkit Dotprompt junto con Zod para validar variables de plantilla, tipos de datos y límites antes de enviar la petición al modelo de lenguaje.

¿Por qué usar Firebase Genkit para probar agentes de IA en TypeScript?

Genkit es un framework open source para TypeScript con soporte nativo de herramientas tipadas, flujos observables, plantillas de prompts y una Developer UI local gratuita para inspeccionar trazas y ejecutar evaluaciones.

¿Cuáles son las limitaciones de los scripts de evaluación locales?

Las evaluaciones locales son ideales en etapas iniciales, pero gestionar grandes conjuntos de datos entre varios miembros del equipo, rastrear regresiones históricas y disponer de dashboards requiere plataformas dedicadas.

Artículos Relacionados

Compartir este artículo

Si esta guía te resultó útil, compártela con tu equipo o con otros desarrolladores.


Software real. Lecciones reales.

Comparto las lecciones que aprendí por las malas, para que puedas evitarlas o estar preparado cuando ocurran.

User avatar
User avatar
User avatar
User avatar
+13K

Únete a 13,800+ desarrolladores y lectores.

Cero spam. Cancela tu suscripción en cualquier momento.