Eval harness en acción
La diferencia entre cualitativo y cuantitativo
Sección titulada «La diferencia entre cualitativo y cuantitativo»El cap anterior te dio un retrato cualitativo: cómo se ven los outputs lado a lado. Útil para entender personalidades, no para defender una decisión técnica frente al equipo.
Ahora venimos con el harness — el mismo que usaste en el Nivel 2 sobre un solo framework. Acá lo corremos con su modo default: los 4 frameworks side-by-side, sobre las 12 preguntas del golden set, con las 4 métricas.
El comando
Sección titulada «El comando»# Smoke test: 1 pregunta × 4 frameworks (~1 min)pnpm --filter @rag-lab/06-evals run eval -- --limit 1
# Full run: 12 preguntas × 4 frameworks (~4 min)pnpm --filter @rag-lab/06-evals run evalEl harness corre 12 preguntas × 4 frameworks = 48 cells. Por cada cell:
- 1 query (embed + retrieve + generate) ≈ 2 segundos.
- 2 LLM-as-judge calls (faithfulness + answer-relevance) ≈ 1 segundo cada uno.
Total: ~4 segundos por cell × 48 cells ≈ ~3 minutos efectivos. Con preflight, warm-up calls e overhead, ~4 minutos.
Lo que el preflight verifica
Sección titulada «Lo que el preflight verifica»Antes de correr nada, el harness chequea:
- Ollama reachable en
OLLAMA_URL(defaulthttp://localhost:11434). - Las 4 collections existen y tienen points:
rag_vercel,rag_langchain,rag_llamaindex,rag_mastra.
Si falta algo, te tira un error accionable:
ERROR: Collection 'rag_mastra' is missing. Run: pnpm --filter @rag-lab/04-mastra run ingestMensaje literal: te dice el comando exacto que tenés que correr para arreglarlo. Pequeño detalle de DX que ahorra debugging.
Las 4 métricas (recap)
Sección titulada «Las 4 métricas (recap)»Conocidas del Nivel 2, pero acá medidas en los 4 frameworks paralelo:
| Métrica | Mide | Cuándo te alerta |
|---|---|---|
| Latency | query() round-trip end-to-end | Un framework es lento |
| Retrieval@k | |expected ∩ returned| / |expected| (filenames) | Un framework no llega a chunks correctos |
| Faithfulness | LLM-as-judge YES/PARTIAL/NO → 1.0/0.5/0.0 | Un framework alucina más |
| Answer relevance | LLM-as-judge 1-5 → score/5 | Un framework se va por las ramas |
Cómo se ve el reporte
Sección titulada «Cómo se ve el reporte»El harness genera un markdown report + JSON en packages/06-evals/reports/<timestamp>.md. Forma típica:
# Eval Run Report
**Started:** 2026-05-XX**Duration:** 3m 47s**Generator/Judge model:** llama3.2:3b (self-judge bias disclosed)
## Summary
| framework | questions | errors | latency_mean | retrieval@k | faithfulness | answer_relevance ||------------------|-----------|--------|--------------|-------------|--------------|------------------|| vercel-ai-sdk | 12 | 0 | 1842 ms | 0.917 | 0.792 | 0.733 || langchain | 12 | 0 | 1934 ms | 0.917 | 0.708 | 0.700 || llamaindex | 12 | 0 | 2208 ms | 0.833 | 0.750 | 0.733 || mastra | 12 | 0 | 2412 ms | 0.917 | 0.792 | 0.717 |
## Per-question detail
### Q1: ¿Qué establece la regla de dependencia en Clean Architecture?
| framework | latency | retrieval@k | faithfulness | answer_relevance ||------------------|---------|-------------|--------------|------------------|| vercel-ai-sdk | 1820 ms | 1.0 | 1.0 | 1.0 |...Los reportes están gitignorados por diseño (cada corrida tiene timestamp distinto y los números varían por el judge). Si querés conservar uno como referencia, copialo afuera.
Lectura del summary
Sección titulada «Lectura del summary»Sobre el corpus del curso (4 markdowns en español, 12 preguntas curadas), corrida típica:
Latency
Sección titulada «Latency»- Vercel y LangChain alrededor de ~1900 ms — los más rápidos.
- LlamaIndex ~2200 ms — paga el QueryEngine + response synthesizer.
- Mastra ~2400 ms — el más alto, por el bookkeeping del agent runtime.
Nota: estos son números chicos sobre un corpus chico. Para corpus de 100k chunks en producción, las diferencias se diluyen — el cuello de botella se vuelve el LLM, no el framework.
Retrieval@k
Sección titulada «Retrieval@k»- Vercel, LangChain y Mastra: 0.917 (~11/12 preguntas con todos los chunks correctos en el top-K).
- LlamaIndex: 0.833 (~10/12). La asimetría del normalizador de embeddings que mencionamos en cap 05 visible en números.
Diferencia chica pero estadísticamente real: si corrés varias veces, LlamaIndex consistentemente queda 5-10 puntos abajo. No es bug — es decisión de implementación de QdrantVectorStore de LlamaIndex que normaliza distinto.
Faithfulness
Sección titulada «Faithfulness»- Vercel y Mastra: 0.792 (más alto). Sus prompt templates simples + instrucción anti-alucinación clara funcionan bien.
- LlamaIndex: 0.750 — el response synthesizer interno tiende a parafrasear más, lo que hace que ocasionalmente meta detalles no soportados.
- LangChain: 0.708 — el más bajo. La instrucción “cite the source filename in brackets” del system prompt empuja al modelo a inventar fuentes (vimos el
[5]alucinado en cap 07). Esa es la métrica visible del problema.
Lección: el mismo modelo (llama3.2:3b) puede dar +/- 8 puntos de faithfulness solo cambiando el prompt template del framework. El prompt importa más que el framework.
Answer relevance
Sección titulada «Answer relevance»Los 4 quedan empatados ~0.70-0.73. Para preguntas conceptuales del corpus, todos responden algo razonable — la calidad relativa se notan más en faithfulness y retrieval que en relevance.
El bias del self-judge
Sección titulada «El bias del self-judge»Importante recordatorio del Nivel 2 / cap 02: el judge es el mismo modelo que genera. llama3.2:3b evalúa sus propias respuestas en los 4 frameworks. Eso introduce un sesgo conocido (el modelo es indulgente con su propio output).
Por eso los scores absolutos del harness no son comparables contra benchmarks publicados (Ragas, MTEB-RAG). Pero los scores relativos entre frameworks sí — todos sufren el mismo sesgo en la misma dirección.
Si querés escapar del bias, podés usar un judge más fuerte:
JUDGE_MODEL=qwen2.5:7b pnpm --filter @rag-lab/06-evals run eval(Asumiendo que tenés ese modelo en Ollama. El harness lee JUDGE_MODEL del config.)
Las dos asimetrías declaradas
Sección titulada «Las dos asimetrías declaradas»El header de cada reporte declara:
“LlamaIndex eval path uses asRetriever + manual LLM call to expose contexts uniformly. CLI behavior (asQueryEngine) is unchanged.”
“Mastra eval uses Naive RAG mode (directRetrievalQuery); Agent mode out of scope.”
Quien lea el reporte y vea Mastra en último puesto, pero no entienda que el modo Agent (donde Mastra brilla) no está medido, va a llegar a la conclusión equivocada. Por eso las asimetrías están al pie de cada reporte y en cap 01 de este nivel.
Lo que viene
Sección titulada «Lo que viene»Tenés:
- Análisis cualitativo (caps 03-06).
- Outputs reales lado a lado (cap 07).
- Métricas duras side-by-side (cap 08).
Falta el cierre — la pregunta del meta-curso. ¿Cuál elegir? El último capítulo te da el árbol de decisión con criterios concretos.