Métricas de Evaluación
Por qué la Evaluación Importa
Construir un pipeline RAG es una cosa. Saber si realmente funciona bien es otra. Sin evaluación rigurosa, estás volando a ciegas -- haciendo cambios a tu estrategia de chunking, modelo de embedding o parámetros de recuperación sin forma de medir si las cosas mejoraron o empeoraron.
La evaluación de RAG es más difícil que la evaluación tradicional de ML porque hay múltiples etapas que pueden fallar independientemente. El retriever podría encontrar los documentos correctos pero el LLM podría alucinar de todos modos. El LLM podría generar una respuesta perfecta pero desde contexto irrelevante. Necesitas métricas que evalúen cada etapa por separado y el pipeline como un todo.
Las Cuatro Métricas Clave
Fidelidad (Faithfulness)
¿La respuesta generada refleja con precisión lo que dice el contexto recuperado? Una respuesta fiel no agrega información que no está en el contexto y no contradice el contexto.
- Alta fidelidad: La respuesta solo afirma cosas que están en los documentos recuperados.
- Baja fidelidad: La respuesta incluye afirmaciones que no se encuentran en el contexto (alucinaciones).
Relevancia de la Respuesta
¿La respuesta generada realmente aborda la pregunta del usuario? Una respuesta puede ser fiel al contexto pero perder completamente el punto de la pregunta.
- Alta relevancia: La respuesta aborda directamente lo que se preguntó.
- Baja relevancia: La respuesta discute temas tangencialmente relacionados o malinterpreta la pregunta.
Precisión del Contexto
De los fragmentos recuperados, ¿cuántos son realmente relevantes para la pregunta? Una alta precisión de contexto significa que el retriever no está desperdiciando espacio de ventana de contexto en documentos irrelevantes.
- Alta precisión: La mayoría de los fragmentos recuperados son útiles para responder la pregunta.
- Baja precisión: El retriever retorna muchos fragmentos que no son relevantes.
Recall del Contexto
De toda la información relevante en la base de conocimiento, ¿cuánto encontró realmente el retriever? Un alto recall significa que el retriever no está perdiendo documentos importantes.
- Alto recall: El retriever encuentra toda (o la mayoría) de la información relevante.
- Bajo recall: El retriever pierde documentos clave que contienen la respuesta.
El Framework RAGAS
RAGAS (Retrieval Augmented Generation Assessment) es el framework más ampliamente usado para evaluar sistemas RAG. Proporciona métricas automatizadas para las cuatro dimensiones.
Configurando RAGAS
from ragas import evaluate
from ragas.metrics import (
faithfulness,
answer_relevancy,
context_precision,
context_recall,
)
from datasets import Dataset
# Preparar tu dataset de evaluacion
eval_data = {
"question": [
"Cual es la politica de vacaciones para nuevos empleados?",
"Como envio un reporte de gastos?",
"Cual es el codigo de vestimenta?",
],
"answer": [
# Respuestas generadas por tu pipeline RAG
"Los nuevos empleados reciben 15 dias de PTO en su primer ano.",
"Envia reportes de gastos a traves del portal de RRHH dentro de 30 dias.",
"El codigo de vestimenta es casual de negocios de lunes a jueves.",
],
"contexts": [
# Contextos recuperados (lista de strings por pregunta)
["Los nuevos empleados tienen derecho a 15 dias de tiempo libre pagado..."],
["Los reportes de gastos deben enviarse via el portal de RRHH..."],
["Nuestra politica de codigo de vestimenta requiere vestimenta casual de negocios..."],
],
"ground_truth": [
# Respuestas de referencia (para calculo de recall)
"Los nuevos empleados obtienen 15 dias de PTO, acumulando 1.25 dias por mes.",
"Usa el portal de RRHH para enviar reportes de gastos dentro de 30 dias del gasto.",
"Casual de negocios de lunes a jueves, casual los viernes.",
],
}
dataset = Dataset.from_dict(eval_data)
# Ejecutar evaluacion
results = evaluate(
dataset,
metrics=[
faithfulness,
answer_relevancy,
context_precision,
context_recall,
],
)
print(results)
# {'faithfulness': 0.92, 'answer_relevancy': 0.88,
# 'context_precision': 0.85, 'context_recall': 0.78}
Interpretando Puntuaciones RAGAS
- Faithfulness > 0.9: Tu LLM se mantiene fundamentado en el contexto. Bien.
- Answer Relevance > 0.85: Las respuestas abordan las preguntas correctamente.
- Context Precision > 0.8: Tu retriever está encontrando documentos relevantes.
- Context Recall > 0.75: Tu retriever no está perdiendo demasiado.
Puntuaciones por debajo de estos umbrales indican áreas específicas a mejorar. Baja fidelidad significa que necesitas mejor prompting o un LLM más confiable. Baja precisión de contexto significa que tu recuperación es demasiado amplia. Bajo recall significa que tu recuperación es demasiado estrecha.
Construyendo un Dataset de Evaluación
La calidad de tu evaluación depende enteramente de la calidad de tu conjunto de datos de prueba. Así es como construir uno:
Curación Manual (Estándar de Oro)
Haz que expertos del dominio creen pares de pregunta-respuesta de tus documentos reales. Consume tiempo pero produce los datos de evaluación más confiables.
# Estructura para un dataset de evaluacion curado manualmente
eval_questions = [
{
"question": "Cual es la duracion maxima del permiso parental?",
"ground_truth": "Los empleados pueden tomar hasta 16 semanas de permiso parental.",
"source_document": "politicas_rrhh/permiso_parental.md",
"difficulty": "easy", # facil, medio, dificil
},
{
"question": "Pueden los empleados combinar permiso parental con PTO?",
"ground_truth": "Si, los empleados pueden usar PTO acumulado para extender el permiso parental.",
"source_document": "politicas_rrhh/permiso_parental.md",
"difficulty": "medium",
},
]
Generación Sintética (Escalable)
Usa un LLM para generar preguntas de tus documentos. Esto escala mejor pero requiere validación.
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
llm = ChatOpenAI(model="gpt-4o", temperature=0.7)
def generate_eval_questions(document_text: str, n_questions: int = 5) -> list[dict]:
"""Generar preguntas de evaluacion de un documento."""
prompt = ChatPromptTemplate.from_messages([
("system", """Genera {n} pares de pregunta-respuesta de este documento.
Cada par debe:
- Probar un hecho especifico del documento
- Tener una respuesta clara y verificable
- Variar en dificultad
Retorna como arreglo JSON con campos "question" y "answer".
Documento:
{document}"""),
("human", "Genera los pares de pregunta-respuesta."),
])
response = (prompt | llm).invoke({
"n": n_questions,
"document": document_text,
})
return response.content
# Generar preguntas de cada documento en tu base de conocimiento
# Luego haz que un humano revise y corrija
Consejo: Siempre haz que un humano revise las preguntas de evaluación generadas sintéticamente. Los LLMs pueden generar preguntas ambiguas, demasiado fáciles o con respuestas incorrectas. Una revisión de 30 minutos atrapa la mayoría de los problemas.
Evaluación LLM-como-Juez
Cuando no tienes respuestas de referencia, puedes usar un LLM para evaluar la calidad de tus respuestas RAG. Esto se llama LLM-como-Juez.
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
judge_llm = ChatOpenAI(model="gpt-4o", temperature=0)
JUDGE_PROMPT = ChatPromptTemplate.from_messages([
("system", """Eres un evaluador imparcial. Califica la calidad de la
respuesta de un asistente de IA a una pregunta del usuario, dado el contexto fuente.
Evalua en tres dimensiones (1-5 cada una):
1. PRECISION: Es la respuesta factualmente correcta basada en el contexto?
2. COMPLETITUD: La respuesta aborda completamente la pregunta?
3. CONCISION: La respuesta es apropiadamente breve sin perder informacion?
Responde con JSON:
{{"accuracy": N, "completeness": N, "conciseness": N, "reasoning": "..."}}"""),
("human", """Contexto: {context}
Pregunta: {question}
Respuesta: {answer}
Evalua esta respuesta."""),
])
def evaluate_response(question: str, answer: str, context: str) -> dict:
"""Usar LLM-como-juez para evaluar una respuesta RAG."""
result = (JUDGE_PROMPT | judge_llm).invoke({
"context": context,
"question": question,
"answer": answer,
})
return result.content
Pruebas A/B de Configuraciones RAG
Cuando cambias un componente de tu sistema RAG (nuevo modelo de embedding, diferente tamaño de fragmento, re-ranking agregado), necesitas comparar la configuración vieja y nueva en el mismo conjunto de pruebas.
def ab_test_configs(eval_dataset: list, config_a, config_b) -> dict:
"""Comparar dos configuraciones RAG en el mismo conjunto de evaluacion."""
results_a = []
results_b = []
for item in eval_dataset:
question = item["question"]
# Ejecutar configuracion A
answer_a = config_a["chain"].invoke(question)
score_a = evaluate_response(question, answer_a, item.get("ground_truth", ""))
results_a.append(score_a)
# Ejecutar configuracion B
answer_b = config_b["chain"].invoke(question)
score_b = evaluate_response(question, answer_b, item.get("ground_truth", ""))
results_b.append(score_b)
return {
"config_a_avg": sum(results_a) / len(results_a),
"config_b_avg": sum(results_b) / len(results_b),
"config_a_details": results_a,
"config_b_details": results_b,
}
Protocolos de Evaluación Humana
Las métricas automatizadas son valiosas pero no son el cuadro completo. La evaluación humana captura matices que los LLM-jueces pierden: tono de la respuesta, calidad del formato, utilidad y si la respuesta realmente sería útil para el usuario final.
Protocolo Simple de Pulgar Arriba/Abajo
Para cada respuesta RAG, haz que los evaluadores respondan:
- ¿La respuesta es correcta? (Sí/No)
- ¿La respuesta es completa? (Sí/No)
- ¿Confiarías en esta respuesta? (Sí/No)
- ¿Falta alguna información? (Texto libre)
Protocolo Comparativo
Muestra a los evaluadores la misma pregunta respondida por dos configuraciones RAG diferentes. Pregúntales cuál respuesta es mejor y por qué. Esto es más confiable que la puntuación absoluta porque los humanos son mejores en comparaciones que en juicios absolutos.
Evaluación Continua en Producción
La evaluación no es una actividad de una sola vez. Configura evaluación automatizada que se ejecute en un horario:
import json
from datetime import datetime
def run_eval_suite(chain, eval_dataset: list, output_file: str):
"""Ejecutar suite de evaluacion y guardar resultados."""
results = []
for item in eval_dataset:
answer = chain.invoke(item["question"])
score = evaluate_response(
item["question"], answer, item.get("ground_truth", "")
)
results.append({
"question": item["question"],
"answer": answer,
"score": score,
"timestamp": datetime.now().isoformat(),
})
# Guardar resultados
with open(output_file, "w") as f:
json.dump(results, f, indent=2)
# Calcular promedios
avg_score = sum(r["score"] for r in results if isinstance(r["score"], (int, float))) / len(results)
print(f"Puntuacion promedio: {avg_score:.2f}")
return results
Consejos para Evaluación Efectiva
- Comienza con 50-100 preguntas curadas a mano. Esto es suficiente para obtener resultados estadísticamente significativos y atrapar problemas mayores.
- Incluye casos límite. Preguntas sin respuesta en la base de conocimiento, preguntas ambiguas, preguntas que abarcan múltiples documentos.
- Rastrea métricas a lo largo del tiempo. Un dashboard mostrando tendencias de fidelidad, relevancia, precisión y recall te ayuda a detectar regresiones antes de que los usuarios las noten.
- Separa la evaluación de recuperación de la evaluación de generación. Si la fidelidad baja, ¿es porque el retriever encontró los documentos equivocados o porque el LLM alucinó? Prueba cada etapa independientemente.
- Evalúa con consultas reales de usuarios. Una vez que tu sistema está en vivo, registra las consultas reales de usuarios e inclúyelas en tu conjunto de evaluación. Las consultas reales son a menudo más desordenadas y variadas que las sintéticas.
En la siguiente lección, aprenderás cómo endurecer tu sistema RAG para producción con caché, monitoreo, seguridad y optimización de costos.