ChanlChanl
Learning AI

Como evaluar agentes de IA: construye un framework de evaluacion desde cero

Construye un framework funcional de evaluacion de agentes de IA en TypeScript y Python. Cubre LLM-as-judge, puntuacion por rubrica, pruebas de regresion e integracion con CI.

DGDean GroverCo-founderFollow
March 6, 2026
20 min read
Ilustracion de dos personas revisando un grafico de mejoras juntas en un escritorio de pie

Un equipo que conozco lanzo un agente de soporte al cliente despues de tres dias de pruebas manuales: unas cuarenta preguntas, les gustaron las respuestas y lo dieron por listo. En menos de una semana, el agente estaba citando con seguridad una politica de reembolsos obsoleta. Le dijo a tres clientes que eran elegibles para reembolsos completos en articulos de venta final. El agente no alucino ni se cayo. Simplemente dio respuestas incorrectas que sonaban plausibles, y nadie lo detecto durante cinco dias.

Esta guia te lleva paso a paso a construir un framework real de evaluacion, uno que puedas ejecutar antes de cada despliegue, detectar regresiones automaticamente y en el que realmente puedas confiar. Construiremos un harness funcional en TypeScript, cubriremos las seis estrategias de evaluacion y te dejaremos con patrones que escalan desde un proyecto personal hasta produccion.

Lo que aprenderasPor que importa
Puntuacion con LLM-as-judgeAutomatiza la evaluacion de calidad con rubricas estructuradas en lugar de revision manual
Rubricas multi-criterioEvalua precision, tono y completitud de forma independiente para identificar fallas con precision
Lineas base de regresionDetecta cambios en prompts y modelos que degradan silenciosamente la calidad
Integracion con CIBloquea despliegues automaticamente cuando las puntuaciones de eval caen por debajo del umbral
Comparacion A/B de promptsCompara versiones de prompts con numeros, no con intuicion
Evals de conversaciones multi-turnoPrueba flujos de dialogo completos, no solo pares individuales de pregunta y respuesta

Por que los agentes de IA necesitan evals en lugar de pruebas tradicionales

Las pruebas tradicionales son binarias: una funcion devuelve el valor correcto o no. Los agentes de IA no funcionan asi. Hazle la misma pregunta al mismo agente dos veces y obtendras dos respuestas diferentes, ambas potencialmente correctas, ambas redactadas de forma distinta. "El agente hizo un buen trabajo?" no es un booleano. Es un espectro que abarca precision, tono, completitud y adherencia a politicas.

Sin evals, te encontraras con problemas predecibles: ajustes en prompts que arreglan preguntas de facturacion pero degradan silenciosamente las respuestas de envios en un 15%, actualizaciones de modelos que rompen el formato del que dependen tus sistemas downstream, y ninguna forma de comparar enfoques con algo mejor que la intuicion. Los evals te dan numeros donde antes tenias solo sensaciones.

La evaluacion es el sistema inmunologico de una aplicacion de IA. Sin ella, cada cambio es una infeccion potencial que no detectaras hasta que los sintomas sean obvios.
Observacion de la industriaSabiduria comun en equipos de IA en produccion

Cuales son los seis tipos de evals para agentes de IA?

No todas las evaluaciones funcionan de la misma manera. Cada tipo apunta a un aspecto diferente de la calidad del agente, y los sistemas en produccion tipicamente combinan varios.

1. Evals de coincidencia exacta y heuristicos

El tipo mas simple. La salida contiene una cadena especifica? Es un JSON valido? Esta por debajo de cierta longitud?

typescript
function evalFormatting(output: string): boolean {
  // Must not contain internal system tags
  if (output.includes("[INTERNAL]") || output.includes("{{")) return false;
 
  // Must stay under 500 words
  if (output.split(/\s+/).length > 500) return false;
 
  // Must not quote a dollar amount without a disclaimer
  const hasDollar = /\$\d+/.test(output);
  const hasDisclaimer = /subject to change|may vary|contact.*for.*pricing/i.test(output);
  if (hasDollar && !hasDisclaimer) return false;
 
  return true;
}

Los evals heuristicos son rapidos, deterministicos y economicos. Usalos como un primer filtro para detectar fallas estructurales obvias antes de gastar dinero en puntuacion con LLM-as-judge.

2. LLM-as-judge

La pieza central de los frameworks modernos de evaluacion. Usas un LLM para evaluar la salida de otro. El judge recibe la pregunta original, la respuesta del agente y una rubrica de puntuacion, y luego devuelve puntuaciones estructuradas con razonamiento.

El prompt del judge importa enormemente. Un prompt vago produce puntuaciones inconsistentes. Uno preciso con rubricas y ejemplos produce puntuaciones que correlacionan fuertemente con el juicio humano.

text
Input: "What's your return policy?"
 
Agent output: "You can return any item within 30 days for a
full refund, no questions asked!"
 
Judge prompt:
  - Score ACCURACY (1-5): Is the information factually correct
    given the reference policy?
  - Score COMPLETENESS (1-5): Did the agent cover all relevant
    details (timeframe, conditions, exceptions)?
  - Score TONE (1-5): Was the response appropriately helpful
    without being misleading?
 
Judge output:
  accuracy: 3 (correct timeframe but omitted the
    "original packaging required" condition)
  completeness: 2 (missing restocking fee, packaging
    requirement, and gift card exception)
  tone: 4 (friendly and clear, slightly overpromises
    with "no questions asked")

3. Evals basados en referencia

Proporcionas una respuesta "estandar de oro" y mides que tan cercana es la respuesta del agente, no como coincidencia exacta de cadenas, sino como similitud semantica o comparacion de significado juzgada por un LLM. Excelente para preguntas factuales con respuestas claramente correctas. Menos util para conversaciones abiertas donde muchas respuestas diferentes podrian ser igualmente buenas.

4. Evals basados en rubrica

En lugar de comparar contra una respuesta de referencia, defines una rubrica, un conjunto estructurado de criterios con niveles de puntuacion. Esto es lo que usaras mas en la practica. Una rubrica para un agente de soporte al cliente podria evaluar precision, empatia, adherencia a politicas y efectividad en la resolucion como dimensiones separadas.

El poder aqui esta en la descomposicion. Un agente puede obtener 5/5 en precision mientras obtiene 2/5 en empatia. Eso te dice exactamente que arreglar, algo que una puntuacion general unica nunca hara. Este es el mismo principio detras de los sistemas estructurados de scorecards.

5. Evals de preferencia humana

Muestras a un humano dos respuestas del agente y preguntas cual es mejor. Con suficientes preferencias agregadas obtienes rankings confiables usando calificaciones Elo o modelos Bradley-Terry.

Los evals de preferencia humana son costosos y lentos, pero son el estandar de oro para la calidad subjetiva. Usalos para calibrar tus evals automatizados: si tu LLM judge constantemente discrepa con las preferencias humanas, tu prompt del judge necesita trabajo.

6. Completitud de tareas de extremo a extremo

El agente realmente logro el objetivo? Se resolvio el problema? Se hizo la reservacion correctamente? Se completaron los campos correctos?

Los evals de completitud de tareas frecuentemente requieren integracion con tus sistemas reales, verificando que se creo un ticket o que se hizo la llamada API correcta. Son el tipo de eval mas realista, pero tambien el mas complejo de configurar. Para agentes que manejan flujos de trabajo de multiples pasos, las pruebas basadas en escenarios te permiten simular conversaciones completas con personas y validar el estado final.

Como construir un harness de evaluacion en TypeScript?

Aqui tienes una implementacion completa y ejecutable. Cuatro componentes: definiciones de casos de prueba, un ejecutor del agente, un evaluador LLM-as-judge y un generador de reportes.

typescript
import Anthropic from "@anthropic-ai/sdk";
 
// — Types ---
 
interface TestCase {
  id: string;
  input: string;
  context?: string; // optional reference info for the judge
  criteria: string[]; // what the judge should evaluate
  expectedBehavior: string; // natural language description
}
 
interface EvalScore {
  criterion: string;
  score: number; // 1-5
  reasoning: string;
}
 
interface EvalResult {
  testCase: TestCase;
  agentOutput: string;
  scores: EvalScore[];
  averageScore: number;
  pass: boolean;
  latencyMs: number;
}
 
// — Agent Under Test ---
 
async function runAgent(
  client: Anthropic,
  systemPrompt: string,
  userMessage: string
): Promise<{ output: string; latencyMs: number }> {
  const start = Date.now();
  const response = await client.messages.create({
    model: "claude-sonnet-4-20250514",
    max_tokens: 1024,
    system: systemPrompt,
    messages: [{ role: "user", content: userMessage }],
  });
  const output =
    response.content[0].type === "text" ? response.content[0].text : "";
  return { output, latencyMs: Date.now() - start };
}
 
// — LLM-as-Judge ---
 
const JUDGE_PROMPT = `You are an expert evaluator for AI agent responses.
You will be given:
1. The user's input message
2. The agent's response
3. Context about what the correct behavior should be
4. A list of criteria to evaluate
 
For each criterion, provide:
- A score from 1-5 (1=terrible, 2=poor, 3=adequate, 4=good, 5=excellent)
- A brief reasoning explaining the score
 
Think step-by-step before scoring. Consider edge cases and subtle issues.
 
Respond in this exact JSON format:
{
  "scores": [
    {
      "criterion": "<criterion name>",
      "score": <1-5>,
      "reasoning": "<1-2 sentence explanation>"
    }
  ]
}`;
 
async function judgeResponse(
  client: Anthropic,
  testCase: TestCase,
  agentOutput: string
): Promise<EvalScore[]> {
  const message = await client.messages.create({
    model: "claude-sonnet-4-20250514",
    max_tokens: 1024,
    system: JUDGE_PROMPT,
    messages: [
      {
        role: "user",
        content: `## User Input
${testCase.input}
 
## Agent Response
${agentOutput}
 
## Expected Behavior
${testCase.expectedBehavior}
 
${testCase.context ? "## Reference Context\n" + testCase.context : ""}
 
## Criteria to Evaluate
${testCase.criteria.map((c, i) => `${i + 1}. ${c}`).join("\n")}`,
      },
    ],
  });
 
  const text =
    message.content[0].type === "text" ? message.content[0].text : "{}";
  const jsonMatch = text.match(/\{[\s\S]*\}/);
  if (!jsonMatch) throw new Error("Judge did not return valid JSON");
  const parsed = JSON.parse(jsonMatch[0]);
  return parsed.scores;
}
 
// — Test Runner ---
 
async function runEvals(
  testCases: TestCase[],
  systemPrompt: string,
  passThreshold: number = 3.5
): Promise<EvalResult[]> {
  const client = new Anthropic();
  const results: EvalResult[] = [];
 
  for (const testCase of testCases) {
    console.log(`Running: ${testCase.id}...`);
    const { output, latencyMs } = await runAgent(
      client, systemPrompt, testCase.input
    );
    const scores = await judgeResponse(client, testCase, output);
    const avg =
      scores.reduce((sum, s) => sum + s.score, 0) / scores.length;
 
    results.push({
      testCase,
      agentOutput: output,
      scores,
      averageScore: Math.round(avg * 100) / 100,
      pass: avg >= passThreshold,
      latencyMs,
    });
  }
 
  return results;
}
 
// — Report ---
 
function printReport(results: EvalResult[]): void {
  console.log("\n" + "=".repeat(60));
  console.log("EVALUATION REPORT");
  console.log("=".repeat(60));
 
  const passed = results.filter((r) => r.pass).length;
  console.log(`\nOverall: ${passed}/${results.length} passed\n`);
 
  for (const r of results) {
    const icon = r.pass ? "PASS" : "FAIL";
    console.log(`[${icon}] ${r.testCase.id} — avg: ${r.averageScore} (${r.latencyMs}ms)`);
    for (const s of r.scores) {
      console.log(`       ${s.criterion}: ${s.score}/5 — ${s.reasoning}`);
    }
    console.log();
  }
}
 
// — Test Cases ---
 
const SUPPORT_AGENT_PROMPT = `You are a customer support agent for TechCo.
Our return policy: 30-day returns with original packaging. Restocking
fee of 15% for opened electronics. Gift cards are final sale.
Business hours: Mon-Fri 9am-6pm EST.
Always be helpful, accurate, and empathetic.`;
 
const testCases: TestCase[] = [
  {
    id: "eval-001",
    input: "I bought a laptop 3 weeks ago and want to return it. I opened the box though.",
    context: "30-day return window. Opened electronics have 15% restocking fee.",
    criteria: ["Accuracy", "Completeness", "Empathy"],
    expectedBehavior:
      "Should confirm the return is within the 30-day window, mention the " +
      "15% restocking fee for opened electronics, and be empathetic.",
  },
  {
    id: "eval-002",
    input: "Can I return a gift card?",
    context: "Gift cards are final sale and cannot be returned.",
    criteria: ["Accuracy", "Tone", "Policy adherence"],
    expectedBehavior:
      "Should clearly state that gift cards are final sale and cannot be " +
      "returned. Should be empathetic but firm. Must not offer alternatives " +
      "that contradict the policy.",
  },
  {
    id: "eval-003",
    input: "Your product broke after 2 days! This is unacceptable!",
    context: "Defective items within 30 days get full refund, no restocking fee.",
    criteria: ["Empathy", "Accuracy", "De-escalation", "Resolution"],
    expectedBehavior:
      "Should acknowledge frustration, apologize, explain that defective items " +
      "qualify for full refund without restocking fee, and offer clear next steps.",
  },
  {
    id: "eval-004",
    input: "What are your hours? Also can I return something I bought 45 days ago?",
    context: "Hours: Mon-Fri 9-6 EST. Returns within 30 days only.",
    criteria: ["Accuracy", "Completeness", "Clarity"],
    expectedBehavior:
      "Should answer BOTH questions. State business hours correctly AND explain " +
      "that the 45-day return is outside the 30-day window. Must not skip either question.",
  },
];
 
// — Run ---
 
runEvals(testCases, SUPPORT_AGENT_PROMPT).then(printReport);

Instala el SDK (npm install @anthropic-ai/sdk), configura tu variable de entorno ANTHROPIC_API_KEY y ejecutalo con npx tsx eval-harness.ts.

Esto es lo que hace cada pieza:

Los casos de prueba definen la entrada, el comportamiento esperado, el contexto de referencia y criterios especificos. Cada criterio obtiene su propia puntuacion; no estas colapsando todo en un solo numero.

El ejecutor del agente llama a tu LLM y captura tanto la salida como la latencia. En produccion, reemplazarias esto por una llamada a la API real de tu agente.

El LLM judge recibe el caso de prueba, la respuesta y una rubrica. Usa razonamiento de cadena de pensamiento antes de puntuar, lo que mejora significativamente la consistencia. Devuelve JSON estructurado con puntuaciones por criterio.

El reporte muestra aprobado/reprobado con un desglose detallado para que puedas ver que criterios fallaron y por que.

Una nota sobre el diseno del prompt del judge

El prompt del judge es la pieza mas importante de tu framework de evaluacion. Tres principios:

Se especifico sobre lo que significa cada nivel de puntuacion. "Puntua del 1 al 5" es demasiado vago. Agrega ejemplos anclados: "Una puntuacion de 3 significa que la respuesta es factualmente correcta pero incompleta. Una puntuacion de 5 significa que la respuesta es correcta, completa y aborda proactivamente las preguntas de seguimiento probables."

Pide razonamiento antes de la puntuacion. Cuando el judge explica su pensamiento primero, las puntuaciones son mas consistentes. Esto es prompting de cadena de pensamiento aplicado a la evaluacion.

Usa un modelo fuerte para juzgar. Tu judge debe ser al menos tan capaz como el modelo que estas evaluando. Un judge mas debil produce resultados poco confiables.

Que metricas de eval realmente importan?

Metricas de calidad

Precision -- Es la respuesta factualmente correcta? No negociable para agentes en produccion. Midela por respuesta con puntuacion LLM-as-judge contra hechos conocidos o documentos de referencia.

Fidelidad -- La respuesta se mantiene fundamentada en el contexto proporcionado? Un agente que es "preciso" pero se basa en datos de entrenamiento en lugar de tu base de conocimiento es un riesgo. La fidelidad mide si las afirmaciones estan respaldadas por el contexto recuperado, no solo si resultan ser verdaderas.

Relevancia -- El agente respondio lo que el usuario pregunto? Una respuesta precisa y fiel que no contesta la pregunta sigue siendo un fallo.

Completitud -- La respuesta cubrio todo lo que debia? Omitir la tarifa de reposicion al explicar la politica de devoluciones no es impreciso, es incompleto. Modo de falla diferente, puntuacion diferente.

Metricas operacionales

Latencia -- Rastrea tanto p50 como p95. Para agentes conversacionales, cualquier cosa por encima de 3 segundos en p95 se siente roto.

Costo por evaluacion -- Si tu suite completa de evals cuesta $50, no la ejecutaras con suficiente frecuencia. Optimiza para centavos por ejecucion para poder ejecutar en cada PR.

Uso de tokens -- Rastrea tokens de entrada y salida por separado. Los agentes verbosos cuestan mas y frecuentemente ofrecen peores experiencias.

Metricas agregadas

Tasa de aprobacion -- Porcentaje de casos de prueba que pasan tu umbral. Rastreala en el tiempo. Una tasa de aprobacion decreciente es una senal de alerta temprana.

Puntuacion promedio por criterio -- Precision promedio en todos los casos, empatia promedio, y asi sucesivamente. Muestra que dimensiones son fuertes y cuales necesitan trabajo.

Varianza de puntuacion -- Alta varianza significa comportamiento inconsistente. Tu agente podria aprobar 8 de 10 pruebas de empatia pero fallar completamente las otras 2. Promedios bajos son un problema sistematico; alta varianza es un problema de robustez.

Promedios por criterio a lo largo de una suite de pruebas. De-escalation y completitud necesitan trabajo, aunque el promedio general se ve aceptable.

Como deberias disenar tu conjunto de evals?

Tu conjunto de evals es la coleccion de casos de prueba que ejecutas contra tu agente. La calidad de la cobertura importa mucho mas que la cantidad.

Cobertura sobre volumen

Veinte casos de prueba bien disenados que cubran tus escenarios clave superan a doscientos aleatorios. Estructura por categorias de conversacion:

CategoriaCasos de prueba de ejemplo
Camino felizPreguntas estandar con respuestas claras
Casos limiteCondiciones de frontera (dia 30 de una ventana de devolucion de 30 dias)
Conflictos de politicaEl usuario quiere algo que la politica no permite
Preguntas de multiples partesDos o tres preguntas en un solo mensaje
Usuarios emocionalesLlamadores frustrados, confundidos o molestos
Entradas ambiguasPreguntas que podrian significar multiples cosas
Fuera de alcancePreguntas que el agente no deberia intentar responder
AdversarialesIntentos de hacer que el agente rompa sus reglas

El conjunto de pruebas dorado

Mantiene un conjunto curado de 20 a 50 casos de prueba como tu suite de regresion. Estos no cambian a menos que la politica subyacente lo haga. Cada edicion de prompt, cambio de modelo y actualizacion de configuracion se ejecuta contra este conjunto antes del despliegue.

Cuando un bug de produccion aparece, agrega un caso de prueba para el. Tu conjunto dorado debe crecer con el tiempo, acumulando conocimiento ganado con esfuerzo de cada falla.

Versionado y seguimiento

Versiona tu conjunto de evals como codigo. Cuando cambies un caso de prueba, deberias saber por que. Cuando las puntuaciones cambien entre ejecuciones, necesitas poder distinguir si cambio el agente o la prueba.

Almacena los resultados de eval con metadatos: version del prompt, modelo, version del conjunto de evals, marca de tiempo. Esto crea la pista de auditoria que necesitas para depurar regresiones. El monitoreo en produccion complementa esto detectando problemas que tu conjunto de evals no anticipo.

Como ejecutar evals en CI en cada pull request?

Aqui tienes un workflow de GitHub Actions que ejecuta tu suite de evals y bloquea el merge si las puntuaciones caen:

yaml
name: Agent Evals
 
on:
  pull_request:
    paths:
      - "prompts/**"
      - "src/agent/**"
      - "eval/**"
 
jobs:
  evaluate:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
 
      - uses: actions/setup-node@v4
        with:
          node-version: "20"
 
      - name: Install dependencies
        run: npm ci
 
      - name: Run eval suite
        env:
          ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
        run: npx tsx eval/run.ts --output eval-results.json
 
      - name: Check thresholds
        run: |
          node -e "
            const r = require('./eval-results.json');
            const failed = r.results.filter(t => !t.pass);
            if (failed.length > 0) {
              console.log('FAILED EVALS:');
              failed.forEach(f => console.log('  ' + f.testCase.id + ': ' + f.averageScore));
              process.exit(1);
            }
            const avgScore = r.results.reduce((s,t) => s + t.averageScore, 0) / r.results.length;
            if (avgScore < 4.0) {
              console.log('Average score ' + avgScore + ' below threshold 4.0');
              process.exit(1);
            }
            console.log('All evals passed. Average: ' + avgScore.toFixed(2));
          "
 
      - name: Comment results on PR
        if: always()
        uses: actions/github-script@v7
        with:
          script: |
            const fs = require('fs');
            const results = JSON.parse(fs.readFileSync('eval-results.json', 'utf8'));
            const passed = results.results.filter(r => r.pass).length;
            const total = results.results.length;
            const avg = (results.results.reduce((s,r) => s + r.averageScore, 0) / total).toFixed(2);
 
            let body = '## Agent Eval Results\n\n';
            body += '| Test | Score | Status |\n|------|-------|--------|\n';
            results.results.forEach(r => {
              const status = r.pass ? 'Pass' : 'Fail';
              body += '| ' + r.testCase.id + ' | ' + r.averageScore + ' | ' + status + ' |\n';
            });
            body += '\n**Average: ' + avg + '** | **' + passed + '/' + total + ' passed**';
 
            github.rest.issues.createComment({
              issue_number: context.issue.number,
              owner: context.repo.owner,
              repo: context.repo.repo,
              body: body
            });

El workflow se activa con cambios en prompts o codigo del agente. Ejecuta la suite completa de evals, verifica los umbrales y publica un resumen en el PR. Las puntuaciones fallidas bloquean el merge.

Control de costos. Cada ejecucion llama a tu LLM dos veces por caso de prueba (agente + judge). Con 30 casos, son 60 llamadas, tipicamente entre $0.50 y $2.00 en total.

Inestabilidad. Las puntuaciones del LLM-as-judge tienen varianza natural. Una prueba que obtiene 3.8 en una ejecucion podria dar 3.4 la proxima vez. Establece tu umbral de aprobacion con margen, o ejecuta cada caso tres veces y toma la mediana.

Velocidad. Ejecuta los casos de prueba en paralelo cuando sea posible. Una suite de 30 casos ejecutandose secuencialmente tarda alrededor de 3 minutos. Lotes de 10 la reducen a menos de un minuto.

Como detectar regresiones con lineas base de eval?

Almacena una linea base de puntuaciones aprobatorias en un archivo JSON commiteado en tu repositorio. Despues de cada ejecucion, compara las puntuaciones actuales contra la linea base y marca cualquier criterio que caiga mas alla de tu umbral.

typescript
interface Baseline {
  [testCaseId: string]: {
    [criterion: string]: number; // baseline score
  };
}
 
function checkRegressions(
  results: EvalResult[],
  baseline: Baseline,
  regressionThreshold: number = 1.0
): { testId: string; criterion: string; drop: number }[] {
  const regressions: { testId: string; criterion: string; drop: number }[] = [];
 
  for (const result of results) {
    const baselineScores = baseline[result.testCase.id];
    if (!baselineScores) continue;
 
    for (const score of result.scores) {
      const baseScore = baselineScores[score.criterion];
      if (baseScore === undefined) continue;
 
      const drop = baseScore - score.score;
      if (drop >= regressionThreshold) {
        regressions.push({
          testId: result.testCase.id,
          criterion: score.criterion,
          drop,
        });
      }
    }
  }
 
  return regressions;
}
 
// Usage: after running evals, check for regressions
const regressions = checkRegressions(results, previousBaseline);
if (regressions.length > 0) {
  console.error("REGRESSIONS DETECTED:");
  regressions.forEach((r) =>
    console.error(`  ${r.testId} / ${r.criterion}: dropped ${r.drop} points`)
  );
  process.exit(1);
}

Actualiza tu linea base despues de cada ejecucion exitosa de eval con la que estes conforme. Esto crea un mecanismo de calidad tipo trinquete: las puntuaciones solo pueden subir, nunca degradarse silenciosamente.

Patrones avanzados

Comparacion A/B de evals

Cuando pruebes una nueva version de prompt, ejecuta los mismos casos contra ambos prompts y compara:

typescript
async function comparePrompts(
  testCases: TestCase[],
  promptA: string,
  promptB: string
): Promise<void> {
  const resultsA = await runEvals(testCases, promptA);
  const resultsB = await runEvals(testCases, promptB);
 
  console.log("\nA/B COMPARISON");
  console.log("=" .repeat(50));
  console.log("Test ID           | Prompt A | Prompt B | Delta");
  console.log("-".repeat(50));
 
  let totalA = 0, totalB = 0;
  for (let i = 0; i < testCases.length; i++) {
    const a = resultsA[i].averageScore;
    const b = resultsB[i].averageScore;
    const delta = b - a;
    const arrow = delta > 0 ? "+" : "";
    totalA += a;
    totalB += b;
    console.log(
      `${testCases[i].id.padEnd(18)}| ${a.toFixed(2).padEnd(9)}| ${b.toFixed(2).padEnd(9)}| ${arrow}${delta.toFixed(2)}`
    );
  }
 
  const avgA = totalA / testCases.length;
  const avgB = totalB / testCases.length;
  console.log("-".repeat(50));
  console.log(
    `Average           | ${avgA.toFixed(2).padEnd(9)}| ${avgB.toFixed(2).padEnd(9)}| ${(avgB - avgA > 0 ? "+" : "")}${(avgB - avgA).toFixed(2)}`
  );
}

Esto es esencial para flujos de trabajo de ingenieria de prompts. En lugar de adivinar si un cambio ayudo, obtienes una tabla de comparacion clara.

Evals de conversaciones multi-turno

Los agentes reales manejan conversaciones completas, no preguntas aisladas. Evaluar interacciones multi-turno requiere rastrear el contexto a traves de los turnos:

typescript
interface ConversationTestCase {
  id: string;
  turns: { role: "user" | "assistant"; content: string }[];
  // The last turn is the one we evaluate; earlier turns are context
  criteria: string[];
  expectedBehavior: string;
}
 
async function runConversationEval(
  client: Anthropic,
  systemPrompt: string,
  testCase: ConversationTestCase
): Promise<EvalResult> {
  // Build message history from all turns except the last user message
  const messages = testCase.turns.slice(0, -1).map((t) => ({
    role: t.role as "user" | "assistant",
    content: t.content,
  }));
 
  // Add the final user message
  const lastTurn = testCase.turns[testCase.turns.length - 1];
  messages.push({ role: "user", content: lastTurn.content });
 
  const start = Date.now();
  const response = await client.messages.create({
    model: "claude-sonnet-4-20250514",
    max_tokens: 1024,
    system: systemPrompt,
    messages,
  });
 
  const output = response.content[0].type === "text"
    ? response.content[0].text : "";
 
  // Judge with full conversation context
  const scores = await judgeResponse(client, {
    id: testCase.id,
    input: testCase.turns.map(
      (t) => `${t.role}: ${t.content}`
    ).join("\n"),
    criteria: testCase.criteria,
    expectedBehavior: testCase.expectedBehavior,
  }, output);
 
  const avg = scores.reduce((s, sc) => s + sc.score, 0) / scores.length;
 
  return {
    testCase: {
      id: testCase.id,
      input: lastTurn.content,
      criteria: testCase.criteria,
      expectedBehavior: testCase.expectedBehavior,
    },
    agentOutput: output,
    scores,
    averageScore: Math.round(avg * 100) / 100,
    pass: avg >= 3.5,
    latencyMs: Date.now() - start,
  };
}

Los evals multi-turno detectan perdida de contexto: un agente que maneja bien preguntas individuales podria olvidar detalles de antes en la conversacion. Los analytics en produccion te diran donde ocurren estos quiebres con mayor frecuencia.

Evaluacion consciente de costos

Rastrea los costos de eval para poder optimizarlos. Aqui tienes un estimador rapido:

typescript
function estimateCost(
  results: EvalResult[],
  pricePerKInput: number = 0.003,
  pricePerKOutput: number = 0.015
): { totalCost: number; costPerCase: number } {
  // ~200 input tokens per agent call, ~300 per judge call, ~200 output each
  const totalInputTokens = results.length * 500;
  const totalOutputTokens = results.length * 400;
 
  const inputCost = (totalInputTokens / 1000) * pricePerKInput;
  const outputCost = (totalOutputTokens / 1000) * pricePerKOutput;
  const totalCost = Math.round((inputCost + outputCost) * 10000) / 10000;
 
  return {
    totalCost,
    costPerCase: Math.round(totalCost / results.length * 10000) / 10000,
  };
}

Que frameworks de eval deberias conocer?

No tienes que construir todo tu mismo. El ecosistema ha madurado.

Braintrust conecta la puntuacion de eval con el rastreo en produccion, la gestion de datasets y la aplicacion de reglas de lanzamiento basadas en CI. Buena opcion si quieres una plataforma gestionada que cubra todo el ciclo de vida de las evaluaciones.

DeepEval es open-source con metricas plug-and-play e integracion con pytest. Se integra directamente en tu suite de pruebas sin necesidad de una plataforma separada.

RAGAS se enfoca en la evaluacion de RAG con metricas de recuperacion y generacion respaldadas por investigacion. Si tu agente depende de la generacion aumentada por recuperacion, las metricas de RAGAS como fidelidad y relevancia de respuesta valen la pena agregarlas.

Langfuse ofrece observabilidad open-source con evaluacion integrada. Buena para equipos que quieren auto-hospedar.

Promptfoo se enfoca en red-teaming y validacion de seguridad junto con evals estandar.

El harness que construiste antes te da los patrones fundamentales. Estas plataformas agregan infraestructura gestionada, metricas preconstruidas y dashboards sobre las mismas ideas.

Lista de verificacion de mejores practicas

Progress0/12
  • Comienza con 20-30 casos de prueba bien disenados que cubran caminos felices, casos limite y entradas adversariales
  • Usa LLM-as-judge con una rubrica detallada, no un prompt vago de "califica esto del 1 al 5"
  • Evalua multiples criterios de forma independiente (precision, completitud, tono, adherencia a politicas)
  • Ejecuta evals en CI en cada PR que modifique prompts o codigo del agente
  • Mantiene un conjunto de pruebas dorado que crezca con cada bug de produccion
  • Almacena lineas base y verifica regresiones: la calidad debe subir como trinquete, nunca degradarse silenciosamente
  • Rastrea costo y latencia junto con las puntuaciones de calidad
  • Ejecuta comparaciones A/B cuando pruebes cambios en prompts, nunca adivines
  • Usa un modelo fuerte como judge (al menos tan capaz como el modelo que estas evaluando)
  • Agrega evals de conversaciones multi-turno, no solo Q&A de un solo turno
  • Versiona tu conjunto de evals y rastrea los cambios junto con los cambios de codigo
  • Revisa las puntuaciones del judge contra el juicio humano trimestralmente para verificar la calibracion

A donde ir desde aqui

Tienes los bloques de construccion: diseno de casos de prueba, puntuacion con LLM-as-judge, deteccion de regresiones, integracion con CI y codigo funcional en TypeScript que puedes copiar directamente. Eso es suficiente para detectar la mayoria de los problemas antes de que lleguen a produccion.

Si estas empezando, pon a funcionar el harness y escribe casos de prueba para tus diez interacciones con clientes mas comunes. Ya ejecutas evals? Enfocate en la integracion con CI y las lineas base de regresion. Ya tienes todo eso? Explora la evaluacion multi-turno y la comparacion A/B para optimizacion de prompts.

Si prefieres no construir desde cero, los sistemas de scorecard y pruebas de escenarios de Chanl proporcionan flujos de trabajo de evaluacion listos para produccion, pero los principios aqui aplican independientemente de las herramientas que uses.

Empieza a medir. Deja de adivinar.

DG

Co-founder

Building the platform for AI agents at Chanl — tools, testing, and observability for customer experience.

Aprende IA Agéntica

Una lección por semana: técnicas prácticas para construir, probar y lanzar agentes IA. Desde ingeniería de prompts hasta monitoreo en producción. Aprende haciendo.

500+ ingenieros suscritos

Frequently Asked Questions