Ir al contenido

Faithfulness: LLM-as-judge

De las 4 métricas del harness, faithfulness es la que más directamente captura “¿podés confiar en esta respuesta?”.

Definición operativa: faithfulness mide si cada afirmación factual de la respuesta está soportada por al menos un chunk del contexto recuperado. Si el LLM dice “Clean Architecture fue inventada en 2012” y eso no está en ningún chunk, faithfulness baja.

Es la diferencia entre una respuesta defendible y una alucinación bien escrita.

No hay forma fácil de hacer esto con reglas — necesitarías parsear la respuesta, identificar afirmaciones, buscar cada una en los chunks, decidir si están soportadas o no. Demasiado.

La solución pragmática (que usan Ragas y otros frameworks de eval): otro LLM call mira la respuesta y los chunks, y dice “sí, esto está soportado / no, hay claims sin respaldo”.

En judge.ts el harness usa este prompt:

packages/06-evals/src/judge.ts: faithfulness
const prompt = `You are a strict evaluator. Your job is to decide whether an ANSWER is supported
by the provided CONTEXT. Use ONLY the context — do not use outside knowledge.
CONTEXT:
${contextText}
ANSWER:
${answer}
Decide which one of these three labels best applies:
- YES — every factual claim in the answer is directly supported by the context.
- PARTIAL — most of the answer is supported, but at least one claim is not in the context.
- NO — the answer contains information that is not in the context, or contradicts it.
Reply with EXACTLY one word, in uppercase: YES, PARTIAL, or NO. No explanation.`;

Tres detalles importantes del prompt:

  • Use ONLY the context — do not use outside knowledge. Sin esto, el judge puede decir “YES, esto está bien” porque el judge mismo “sabe” que la afirmación es verdadera por su entrenamiento. Querés que se restrinja a lo que dicen los chunks.
  • Tres labels, no cinco. Más granularidad parece mejor pero el LLM se confunde más. YES/PARTIAL/NO es lo más estable que probamos.
  • Reply with EXACTLY one word. El parser después busca la palabra con un regex. Si no la encuentra, default es NO (penalizamos respuestas mal formateadas — un judge que no sigue instrucciones no debería ganar puntos).
judge.ts: parser
const raw = await ollamaGenerate(opts.ollamaUrl, opts.model, prompt, opts.timeoutMs);
const match = raw.match(/\b(YES|PARTIAL|NO)\b/i);
const matchedStr = match?.[1];
const label: FaithfulnessLabel = matchedStr
? (matchedStr.toUpperCase() as FaithfulnessLabel)
: 'NO';
return { label, score: labelToScore[label], raw };

Y la tabla:

const labelToScore = { YES: 1, PARTIAL: 0.5, NO: 0 };

Ese score (0, 0.5 o 1) es lo que aparece en el reporte. Cuando lo promedias sobre N preguntas, te da un número entre 0 y 1 — que se lee como porcentaje de respuestas confiables.

El harness corre, en paralelo con faithfulness, una segunda métrica: answer relevance. Mide algo distinto: ¿la respuesta responde la pregunta, sin importar si es factualmente correcta?

Una respuesta puede ser:

  • Faithful pero irrelevante: “Sí, en el contexto se menciona Clean Architecture” cuando la pregunta era “¿qué es la regla de dependencia?”. Anclada en el chunk pero no contesta.
  • Relevante pero no faithful: el LLM contesta exactamente lo que se le preguntó pero inventa partes. Riesgo de producción.
  • Faithful y relevante: lo que querés.
  • Ni una ni otra: descarte directo.

El judge prompt para answer-relevance vive en el mismo judge.ts, escala 1-5. No vamos a deep-divearlo en este capítulo — el patrón es el mismo (LLM-as-judge, prompt cerrado, parser permisivo). Mirá el código si te interesa.

Ventana de terminal
pnpm --filter @rag-lab/06-evals run eval -- --only vercel-ai-sdk --limit 5

Esto va a tardar entre 1 y 3 minutos. Mientras corre, mirá el stderr:

[1/5] Case: clean-arch-01
vercel-ai-sdk: OK
[2/5] Case: clean-arch-02
vercel-ai-sdk: OK
...

Cada OK cubre: 1 query (con embedding + retrieve + generate) + 2 calls al judge (faithfulness + answer-relevance). Eso es por qué un eval de 5 preguntas son ~15 LLM calls.

Cuando termine, abrí el .md más reciente en packages/06-evals/reports/. Buscá una pregunta donde faithfulness sea 0.5 o 0.0.

Cuando la encuentres, leé:

  1. La pregunta.
  2. La respuesta del LLM.
  3. Los chunks recuperados (sus filenames y previews).

Vas a ver, casi siempre, una de dos cosas:

  • El LLM agregó una afirmación que no está en los chunks (alucinación parcial). Típico: una fecha, un autor, una atribución. La respuesta “suena bien” pero algún detalle no está soportado.
  • El judge se equivocó y penalizó algo que sí estaba en los chunks. Esto pasa — llama3.2:3b no es un judge perfecto.

Si la pregunta del set #2 te da el judge equivocándose, no te frustres. Eso confirma el bias del self-judge — y es por qué corremos múltiples preguntas: la señal aparece en el promedio, no en una corrida aislada.

Después de un eval con 12 preguntas, vas a ver un summary tipo:

faithfulness_mean: 0.792

Léelo así:

RangoLectura
≥ 0.85Tu RAG anda bien. Las alucinaciones son raras.
0.65 — 0.84Hay margen. Probá ajustar el prompt anti-alucinación o el TOP_K.
0.50 — 0.64El LLM está inventando con frecuencia. Empezá por los #4 y #6 del catálogo de errores comunes.
< 0.50Algo está roto de fondo. El judge mismo puede estar fallando, o tu prompt no restringe nada.

Faithfulness mide si la respuesta usa los chunks. Pero hay otra dimensión clave: ¿el chunk correcto está entre los recuperados? Para medir eso necesitás saber cuál es el chunk correcto de antemano. Eso es un golden set, y es lo que viene en el siguiente capítulo.