Eval suite para agentes IA en español chileno: tutorial técnico con código
Tutorial para construir un eval suite que valide la calidad de tu agente IA en español chileno antes de cada release: golden dataset, runner, scoring e integración CI/CD.
Sin eval suite no hay mejora continua de un agente IA en producción. Cada cambio de modelo, prompt o tool calling puede romper casos que antes funcionaban. Sin un set de casos golden representativos del negocio, las regresiones se descubren con el cliente final, lo que es caro y reputacionalmente costoso. Este tutorial entrega el código mínimo para construir un eval suite efectivo para agentes en español chileno: golden dataset estructurado, runner que ejecuta los casos contra el agente, scoring multi-criterio y integración con CI para no promover cambios sin pasar evals.
¿Qué evaluar? Las cuatro dimensiones
No todos los evals son iguales. Recomendamos cuatro dimensiones:
- Corrección factual: la respuesta es verdadera y completa.
- Adherencia a instrucciones: respeta el system prompt (tono, formato, restricciones).
- Tool calling correcto: invoca las tools correctas con argumentos válidos.
- Seguridad y compliance: no filtra información sensible, no inventa, no se desvía de la política.
Para agentes en español chileno, agregar una quinta dimensión: registro y léxico apropiado (sin chilenismos marcados, sin anglicismos jerga, formal-ejecutivo cuando corresponda).
Paso 1: Golden dataset estructurado
El golden dataset es el activo más importante. 30-50 casos representativos del negocio cubren el 80% del valor.
Estructura recomendada (JSON):
{
"version": "2026-06-22",
"cases": [
{
"id": "consulta-pedido-positivo-001",
"category": "consulta-pedido",
"input": "¿En qué estado está mi pedido 47821?",
"expected_tool": "consultarPedido",
"expected_tool_args": { "numero": "47821" },
"expected_behavior": [
"invoca consultarPedido con numero=47821",
"responde con el estado y fecha estimada",
"no inventa información"
],
"forbidden_phrases": ["disculpa el inconveniente", "lamentablemente"]
},
{
"id": "fuera-alcance-derivacion-001",
"category": "fuera-alcance",
"input": "Quiero devolver un pedido que ya recibí. ¿Cómo lo hago?",
"expected_behavior": [
"no inventa procedimiento",
"deriva a humano con contexto",
"mantiene tono formal"
],
"forbidden_tools": ["consultarPedido", "agendarRetiro"]
},
{
"id": "chileno-correcto-001",
"category": "registro-linguistico",
"input": "¿Tienen oferta de zapatos para mi pololo?",
"expected_behavior": [
"responde sin usar chilenismos marcados",
"registra el contexto coloquial sin reproducir"
],
"forbidden_phrases": ["pololo", "cachái", "po"]
}
]
}Categorías típicas para un agente WhatsApp: consultas válidas (5-10), casos fuera de alcance (3-5), intentos de prompt injection (3-5), registro lingüístico (2-3), errores de usuario (3-5), casos extremos del negocio (5-10).
Paso 2: Runner que ejecuta los casos
import { readFile } from 'fs/promises';
import { runAgent } from './tu-agente';
interface EvalCase {
id: string;
category: string;
input: string;
expected_tool?: string;
expected_tool_args?: Record<string, any>;
expected_behavior: string[];
forbidden_phrases?: string[];
forbidden_tools?: string[];
}
interface EvalResult {
case_id: string;
output: string;
tool_calls: { name: string; args: any }[];
duration_ms: number;
error?: string;
}
async function ejecutarEvals(): Promise<EvalResult[]> {
const raw = await readFile('./evals/golden.json', 'utf-8');
const dataset = JSON.parse(raw);
const resultados: EvalResult[] = [];
for (const caso of dataset.cases as EvalCase[]) {
const start = Date.now();
try {
const { text, toolCalls } = await runAgent(caso.input);
resultados.push({
case_id: caso.id,
output: text,
tool_calls: toolCalls,
duration_ms: Date.now() - start,
});
} catch (e: any) {
resultados.push({
case_id: caso.id,
output: '',
tool_calls: [],
duration_ms: Date.now() - start,
error: e.message,
});
}
}
return resultados;
}El runner ejecuta secuencial para no saturar el LLM. Para volumen alto, paralelizar con límite de concurrencia (p-limit u otros).
Paso 3: Scoring multi-criterio
Scoring tiene dos partes: validación determinista (programable) y validación con LLM judge (cuando el criterio es semántico).
Validación determinista
function scoreDeterminista(caso: EvalCase, resultado: EvalResult) {
const checks = {
tool_invocada_correctamente: caso.expected_tool
? resultado.tool_calls.some((tc) => tc.name === caso.expected_tool)
: null,
tool_args_correctos: caso.expected_tool_args
? deepEqual(
resultado.tool_calls.find((tc) => tc.name === caso.expected_tool)?.args,
caso.expected_tool_args
)
: null,
no_uso_tools_prohibidas: caso.forbidden_tools
? !resultado.tool_calls.some((tc) => caso.forbidden_tools!.includes(tc.name))
: true,
sin_frases_prohibidas: caso.forbidden_phrases
? !caso.forbidden_phrases.some((p) =>
resultado.output.toLowerCase().includes(p.toLowerCase())
)
: true,
latencia_ok: resultado.duration_ms < 5000,
};
return checks;
}LLM judge para criterios semánticos
Para evaluar "responde con tono formal" o "no inventa información", usamos Claude como juez:
import { anthropic } from '@ai-sdk/anthropic';
import { generateObject } from 'ai';
import { z } from 'zod';
async function scoreSemantico(caso: EvalCase, resultado: EvalResult) {
const { object } = await generateObject({
model: anthropic('claude-sonnet-4-6'),
system: `Eres juez de evaluacion. Eres estricto. Responde solo en el formato JSON solicitado.`,
prompt: `Caso: ${JSON.stringify(caso, null, 2)}
Output del agente:
"${resultado.output}"
Califica si el output cumple cada comportamiento esperado.`,
schema: z.object({
cumple: z.record(z.string(), z.boolean()),
razon: z.string(),
}),
});
return object;
}Usar Sonnet o Haiku como juez es más barato que Opus y suficiente para evaluación.
Paso 4: Reporte agregado
function reporteAgregado(casos: EvalCase[], resultados: EvalResult[], scores: any[]) {
const total = casos.length;
const ok = scores.filter((s) =>
Object.values(s).every((v) => v === true || v === null)
).length;
const pct = (ok / total) * 100;
const porCategoria: Record<string, { ok: number; total: number }> = {};
casos.forEach((c, i) => {
if (!porCategoria[c.category]) porCategoria[c.category] = { ok: 0, total: 0 };
porCategoria[c.category].total++;
if (Object.values(scores[i]).every((v) => v === true || v === null)) {
porCategoria[c.category].ok++;
}
});
return {
pct_global: pct,
aprobados: ok,
fallidos: total - ok,
por_categoria: porCategoria,
latencia_p95: percentil(resultados.map((r) => r.duration_ms), 95),
};
}Paso 5: Integración con CI/CD
El eval suite debe correr antes de cada merge a main:
# .github/workflows/eval.yml
name: Eval Suite
on:
pull_request:
branches: [main]
jobs:
eval:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
- run: npm ci
- run: npm run eval
env:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
- name: Falla si % aprobado < 85
run: |
PCT=$(cat eval-report.json | jq '.pct_global')
if (( $(echo "$PCT < 85" | bc -l) )); then
echo "Eval suite falla: $PCT% < 85%"
exit 1
fiEl umbral 85% es punto de partida. Subir gradualmente a 90-95% conforme el agente madura.
Mantenimiento del golden dataset
El golden dataset es vivo:
- Cada incidente real en producción debe sumarse como caso al dataset.
- Cada nueva feature del agente requiere casos representativos.
- Revisión trimestral: eliminar casos obsoletos, balancear categorías.
- Versionar el dataset (git) y trackear el % por versión para detectar regresiones.
Eval suite para agentes IA en español chileno
Cinco pasos para tener eval suite productivo en una semana.
- Días 1-2
Golden dataset
Estructurar 30-50 casos representativos por categoría.
- Recopilar conversaciones reales del agente
- Categorizar y estructurar en JSON
- Versionar en git
- Día 3
Runner
Implementar ejecutor que itere y registre resultados.
- Función ejecutarEvals con manejo de errores
- Capturar output, tool calls y latencia
- Día 4
Scoring
Combinar validación determinista + LLM judge.
- scoreDeterminista con checks programables
- scoreSemantico con Sonnet como juez
- Día 5
Reporte
Agregación global, por categoría y latencia.
- Función reporteAgregado
- Exportar a JSON y dashboard simple
- Días 6-7
Integración CI
GitHub Actions con umbral de aprobación.
- Workflow .github/workflows/eval.yml
- Falla el build si % < umbral
Preguntas frecuentes
Da el siguiente paso
Hablemos
Cuéntanos sobre tu negocio. Respondemos en 24 horas.
Otros canales
Escríbenos por WhatsApp o agenda una llamada de 30 minutos.