Spaces:
Sleeping
Eval Runbook
How to read, run, and triage the eval harness when a metric drops.
What runs where
| Where | What | When |
|---|---|---|
| Local dev | python -m eval.smoke_eval (10 questions) |
Iterating on retrieval changes |
| Local dev | python -m eval.run_full --out eval/baselines/<name>.json |
Before opening a PR |
| Local dev | python -m eval.run_full --with-llm --judge --out ... |
Pre-release; uses LLM quota |
| GitHub Actions | .github/workflows/eval.yml |
Every PR touching app/, ingest/, eval/ |
CI runs retrieval metrics only — no LLM calls, no rate limits, fully deterministic. Generation faithfulness is graded offline before each release.
CI gate
The job fails if either:
recall_at_5 < 0.85(answerable questions where any top-5 chunk is relevant)ooc_refusal_rate < 0.90(out-of-corpus questions where the gate fired)
Set in eval/run_full.py via the EVAL_CI_GATE=1 env var, so the same script
runs locally without gating.
Triage: a metric just dropped. What now?
Pull the artifact from the failed PR's "eval-results" upload, or rerun locally:
python -m eval.run_full --out eval/baselines/diag.json.Diff against the last good baseline (
eval/baselines/v0.4.0-rerank-full.json). Look atresults[*].verdict_passflips andresults[*].retrieved_top5_urlsshifts.Categorise the regression by
category:Category that dropped First suspects vocab-mismatchReranker config, query expansion synonyms entity-lookupEmbedder change, index rebuild gap, chunk boundaries fact-lookupChunking strategy, embed model, top-k multi-hopTop-k too small, chunk granularity, relevance threshold ooc-negativerelevance_thresholdlowered too farReproduce in isolation: pick one failing question and run
python -m app.ask --no-llm "<question>"to see what dense retrieval gave. Ifgate=PASSbut the right chunk isn't in top-K, the issue is rank order (reranker / query expansion). Ifgate=FAIL, retrieval missed entirely.Knobs in order of safety to twist (safest first):
- Add a synonym to
_EXPANSIONSinapp/providers/reranker.py - Bump
RERANK_TOP_N(currently 20) - Re-tune
RELEVANCE_THRESHOLDagainsteval/questions_v2.jsonl - Re-chunk and re-index (last resort; rebuilds 12k vectors)
- Add a synonym to
Never silently lower the CI thresholds to make the gate pass. If a threshold is wrong, change it in a dedicated PR with an explanation, not in the same PR that introduced the regression.
Updating the eval set
eval/questions_v2.jsonl is the source of truth for retrieval metrics.
When you add a question:
- Pick a
categoryfrom the five existing ones; don't invent new ones unless you also update this runbook. relevant_text_anyshould be the smallest substrings that uniquely identify the right answer in the corpus (e.g."Désirée Finnegan", not"is the").answer_must_contain_anyis checked only in--with-llmmode; it's looser than the retrieval needle (lets the LLM paraphrase).
Re-run the baseline and commit the new eval/baselines/*.json so PR reviewers
can see whether your question changed the aggregate metrics.
Faithfulness scoring
Faithfulness is judged by an LLM (NVIDIA Llama 70B by default) against the retrieved chunks the answerer was actually shown. It's NOT part of the CI gate because:
- It's non-deterministic (judge LLM sampling).
- It costs quota — running it on every PR would exhaust free-tier limits.
- A faithfulness miss is a generator bug, not a retrieval bug — different triage path.
Run it before tagging a release:
python -m eval.run_full --with-llm --judge \
--out eval/baselines/v$(date +%Y%m%d)-prerelease.json
If faithfulness_avg < 0.85, investigate the system prompt or grounding
discipline before shipping.