Skip to content

Guías

Evaluación RAG

La versión en inglés de esta página ha cambiado desde que se tradujo. La página en inglés es la vigente. Leerla en inglés

Una respuesta aumentada con recuperación puede ser incorrecta por cuatro motivos distintos: el pasaje correcto nunca se recuperó, se recuperó pero quedó demasiado abajo en el ranking, quedó lo bastante arriba y luego se eliminó del contexto, o llegó al modelo y el modelo se equivocó de todos modos. Una única cifra de exactitud no puede distinguirlos. Oloproof ejecuta un sistema RAG como dos etapas que puede observar, mide cada una y vuelve a ejecutar los casos fallidos bajo cambios controlados para averiguar qué motivo se aplica.

examples/support_rag/ es el proyecto que ejecuta esta página. No necesita credenciales de proveedor.

Un sistema por etapas

El sistema es una clase con una etapa de recuperación y una etapa de generación, decorada con @rag_system:

from oloproof import Passage, Retrieval, rag_system


@rag_system(
    name="support-rag",
    version="slice-b-example",
    depth=6,
    top_k=2,
    token_budget=40,
    index_version="kb-2026-09-16",
)
class SupportRag:
    def retrieve(self, input, depth):
        ...
        return Retrieval(query=input["question"], depth=depth, candidates=tuple(passages))

    def generate(self, input, context):
        ...
        return {"answer": answer, "citations": [best.doc_id]}

    def count_tokens(self, passage):
        return len((passage.text or "").split())

retrieve(input, depth) devuelve hasta depth candidatos, como Passage(doc_id=..., score=..., text=...), en el orden en que los produjo su recuperador. Oloproof registra las posiciones y nunca reordena. Después conserva los primeros top_k, elimina los pasajes que exceden token_budget y pasa lo que queda a generate(input, context). count_tokens(passage) solo se necesita con un token_budget; Oloproof nunca estima tokens.

oloproof.yaml apunta a la clase y puede sobrescribir cualquiera de sus parámetros:

system:
  name: support-rag
  version: slice-b-example
  rag:
    object: app:SupportRag
    depth: 6
    top_k: 2
    token_budget: 40
    index_version: kb-2026-09-16

index_version forma parte de la identidad de la recuperación. Cámbielo cuando cambie el índice, o las recuperaciones en caché se reutilizarán contra un índice que ya no las devuelve.

Qué declara un caso

Un caso RAG lleva dos campos bajo expected que ningún otro tipo de caso necesita:

{"id":"refund_annual","input":{"question":"How long do refunds take for annual plans?"},"expected":{"answer":"14 days","relevant":[{"doc_id":"kb-01"}],"gold_context":[{"doc_id":"kb-01","text":"Refunds for annual plans are issued within 14 days of an approved request. Support reviews each request on the same business day."}]},"metadata":{"topic":"billing"}}
  • expected.relevant enumera los documentos que responden a la pregunta. Las métricas de recuperación lo

leen, y un caso sin él queda excluido de ellas con no_relevance_labels.

  • expected.gold_context es el propio texto del pasaje. El diagnóstico lo sustituye por el contexto

recuperado, y un caso fallido sin él no puede diagnosticarse.

Los evaluadores

evaluators:
  - {type: contains, criterion: answer_correct, field: answer, expected_field: answer}
  - {type: hit_rate, k: 2}
  - {type: recall, k: 2}
  - {type: ndcg, k: 6}
  - {type: citation_validity}
slices: [metadata.topic, relevant_position, context_truncated]
min_slice_support: 4

hit_rate, recall, mrr y ndcg admiten k y derivan de él el nombre de su criterio: hit_rate_at_2. Una entrada de expected.relevant también puede llevar un chunk_id y un grade, que por defecto es 1; relevance_unit: chunk cuenta entonces cada fragmento como unidad propia, en lugar de documentos completos. citation_validity comprueba que cada id que cita una respuesta nombre un pasaje del contexto que recibió, y con require_citations: true una respuesta que no cita nada falla. groundedness_judge y citation_support_judge son jueces LLM que leen el contexto ensamblado, y admiten un provider y un model como cualquier otro juez.

oloproof run

Tres filas de la tabla de métricas:

│ answer_correct  │ 69.2%    │ [38.5%, 91.0%]  │ 9 / 13 observed · 0 missing · 0 excluded     │
│ ndcg_at_6       │ 0.866    │ [0.506, 0.990]  │ mean of 13 observed · 0 missing · 0 excluded │
│ citations_valid │ 100.0%   │ [75.2%, 100.0%] │ 13 / 13 observed · 0 missing · 0 excluded    │

Entre ellas, hit_rate_at_2 y recall_at_2 marcan ambas 92.3%, 12 de 13 observados, con una cota inferior de 63.9%: el pasaje relevante de una pregunta nunca se recuperó.

Cache: execution 0 hit/13 miss; judgment 0 hit/65 miss
Stages: retrieve 0 hit/13 miss; generate 0 hit/13 miss

La línea Stages es la caché propia del sistema por etapas. La recuperación y la generación se almacenan en caché por separado, de modo que un cambio en la generación nunca vuelve a ejecutar la recuperación.

Averiguar el porqué

Cuatro preguntas fallaron. diagnose las vuelve a ejecutar con el pasaje de referencia en lugar del contexto recuperado, junto a un control que las vuelve a ejecutar sin cambios:

oloproof diagnose RUN_ID --intervention gold-context --criterion answer_correct
Selected: 4 failed cases with gold context (observed; no population claim)
Control: 0 of 4 passed when re-executed without the intervention
Recovered under gold context: 3 of 4
RETRIEVAL_MISS: 1 of 4, recovered; no relevant evidence was retrieved
CONTEXT_ASSEMBLY_LOSS: 2 of 4, recovered; relevant evidence within top-k was left out of the context
GENERATION_FAILURE: 1 of 4, still failed with the gold context
Implicated: context budget, in 2 of the 3 recovered failures.
Candidate experiment: a larger token budget. This is a hypothesis to test, not an established cause.
Candidate experiment: smaller chunks. This is a hypothesis to test, not an established cause.
Diagnosis sha256:b2846cbe04099225bda4bf21beb1738f54d6cc0e9a7ce210bb3eb467619cbd69
Child runs: gold context run_01M3C3REAAZDF9FD8N2C8BG17P, control run_01M3C3REAFVXK2VM4MYAQYH6KE
Cases: oloproof inspect sha256:b2846cbe04099225bda4bf21beb1738f54d6cc0e9a7ce210bb3eb467619cbd69

Un caso que pasa con el pasaje de referencia y falla sin él falló antes de llegar al modelo. Un caso que falla teniendo el pasaje de referencia es responsabilidad del modelo. El control es lo que hace segura esa lectura: un caso que se recupera en una simple reejecución era inestable, no quedó diagnosticado.

El id del diagnóstico enumera el caso detrás de cada recuento:

oloproof inspect DIAGNOSIS_ID
money_back: RETRIEVAL_MISS, relevant_not_retrieved, strength intervention_recovery, best relevant position none
refund_review: CONTEXT_ASSEMBLY_LOSS, relevant_dropped_from_context, strength intervention_recovery, best relevant position 2
seat_count: GENERATION_FAILURE, fails_with_gold_context, strength intervention_non_recovery, best relevant position 1
security_review: CONTEXT_ASSEMBLY_LOSS, relevant_dropped_from_context, strength intervention_recovery, best relevant position 2

Nombra un factor, nunca una causa. "Implicado" y "experimento candidato" son las palabras más fuertes que usa, porque que cuatro casos se recuperen bajo una intervención no establece por qué fallaron.

Probar una corrección antes de hacerla

Otras dos intervenciones reproducen la recuperación registrada con un parámetro distinto, de modo que no se hace ninguna llamada al recuperador. top-k amplía el corte:

oloproof diagnose RUN_ID --intervention top-k --top-k 4 --criterion answer_correct
Selected: 4 failed cases with gold context (observed; no population claim)
Control: 0 of 4 passed when re-executed without the intervention
Recovered under top-k 4: 0 of 4
Confirmed under top-k 4: 0 of 0 RANKED_OUT cases also recovered
Labels from gold context (diagnosis sha256:b2846cbe04099225bda4bf21beb1738f54d6cc0e9a7ce210bb3eb467619cbd69): 3 of 4 recovered
RETRIEVAL_MISS: 1 of 4, recovered; no relevant evidence was retrieved
CONTEXT_ASSEMBLY_LOSS: 2 of 4, recovered; relevant evidence within top-k was left out of the context
GENERATION_FAILURE: 1 of 4, still failed with the gold context
Diagnosis sha256:668084c3e99df84f7b63f138792aa96f48e5cb8251fee3acae8e1cc0500ba085
Child runs: gold context run_01M3C3REAAZDF9FD8N2C8BG17P, control run_01M3C3REAFVXK2VM4MYAQYH6KE, top-k 4 run_01M3C3RKHTGGV37079V11JJ78M, replay control run_01M3C3RKJ076K7MSVQ94J4ZE2P
Cases: oloproof inspect sha256:668084c3e99df84f7b63f138792aa96f48e5cb8251fee3acae8e1cc0500ba085

Nada se recuperó, que es lo que predecían las etiquetas: ningún fallo aquí era un pasaje situado justo por debajo del corte. La reproducción arrastra las etiquetas de contexto de referencia, de modo que los dos diagnósticos se leen juntos. reranker admite --reranker con un module:function y reproduce la recuperación pasándola por su reordenador.

Ejecutar el experimento

El diagnóstico señaló un presupuesto de tokens mayor. Suba token_budget a 120 y ejecute de nuevo:

Cache: execution 0 hit/13 miss; judgment 0 hit/65 miss
Stages: retrieve 13 hit/0 miss; generate 7 hit/6 miss

Todas las recuperaciones se reutilizaron, porque top_k y el presupuesto de tokens quedan fuera de la identidad de la recuperación; solo se volvieron a generar los seis casos cuyo contexto cambió. Después compare, con la política de comparación del ejemplo:

oloproof compare CANDIDATE_RUN_ID BASELINE_RUN_ID --policy compare.yaml
Comparison sha256:d801ed897f1fa1b196ca1e3f73a1ef4ffe6845d6ee8bddaea8a44da94d27a75b of run_01M3C3RYJZ4PAJ8P4P9NT71360 against run_01M3C3R5Y9D18WGSZQFA7N4XRT · 13 paired cases
answer_correct: +0.0 points [-33.6, +33.6] · 13 paired · 0 missing · 0 excluded
hit_rate_at_2: +0.0 points [-33.6, +33.6] · 13 paired · 0 missing · 0 excluded
recall_at_2: +0.0 points [-33.6, +33.6] · 13 paired · 0 missing · 0 excluded
ndcg_at_6: +0.0 points [-33.6, +33.6] · 13 paired · 0 missing · 0 excluded
citations_valid: +0.0 points [-33.6, +33.6] · 13 paired · 0 missing · 0 excluded

A continuación aparece una línea exploratoria por cada segmento y métrica, y después las decisiones:

Decisions
  answers-not-worse  answer_correct  non-inferiority, margin 5.0 points  INSUFFICIENT_EVIDENCE  interval_overlaps_margin
  citations-not-worse  citations_valid  non-inferiority, margin 2.0 points  INSUFFICIENT_EVIDENCE  interval_overlaps_margin
Gate: BLOCK (exit 3)

El experimento no ayudó: ninguno de los trece casos cambió su veredicto. Y trece casos emparejados no podrían haber establecido un cambio de ningún tamaño que importe para una publicación, que es lo otro que dice el intervalo.

Siguientes pasos

  • Segmentos trata relevant_position y context_truncated.
  • Reglas de comparación trata las reglas con las que se decidió el último paso.
  • Jueces trata lo que debe superar un juez de fundamentación antes de poder actuar como gate.