Ir al contenido

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.

Ventana de terminal
# 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 eval

El harness corre 12 preguntas × 4 frameworks = 48 cells. Por cada cell:

  1. 1 query (embed + retrieve + generate) ≈ 2 segundos.
  2. 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.

Antes de correr nada, el harness chequea:

  • Ollama reachable en OLLAMA_URL (default http://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 ingest

Mensaje literal: te dice el comando exacto que tenés que correr para arreglarlo. Pequeño detalle de DX que ahorra debugging.

Conocidas del Nivel 2, pero acá medidas en los 4 frameworks paralelo:

MétricaMideCuándo te alerta
Latencyquery() round-trip end-to-endUn framework es lento
Retrieval@k|expected ∩ returned| / |expected| (filenames)Un framework no llega a chunks correctos
FaithfulnessLLM-as-judge YES/PARTIAL/NO → 1.0/0.5/0.0Un framework alucina más
Answer relevanceLLM-as-judge 1-5 → score/5Un framework se va por las ramas

El harness genera un markdown report + JSON en packages/06-evals/reports/<timestamp>.md. Forma típica:

reports/2026-05-XXTXX-XX-XXZ.md (estructura)
# 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.

Sobre el corpus del curso (4 markdowns en español, 12 preguntas curadas), corrida típica:

  • 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.

  • 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.

  • 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.

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.

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:

Ventana de terminal
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.)

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.

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.