AI coding

Costing a Retrieval Eval: Golden Queries and Judge Calls

Antonio Evans9 min read

A flat lay of a workbench split into two halves.
Key points

Key points

  1. 1Start with roughly 50 to 200 golden queries and size the set by the decision you need to make, not by a stock number.
  2. 2Recall@k, MRR, nDCG, exact match and latency all run on your own hardware for free once you have labeled query/document pairs.
  3. 3Only the judge-based checks cost money, and the bill is driven by retrieved context length times rubric size per call.
  4. 4Estimate spend before you run anything by multiplying a per-call token figure you measure once by the number of queries and checks.

Split the eval into three cost buckets

A retrieval eval looks like one activity but bills like three. Separating them is what makes the budget tractable, because one bucket costs people, one costs hardware, and only one costs a paid model.

The three buckets are:

  1. Golden query creation: human time, or model time if you draft queries with a generator and then edit them.
  2. Offline deterministic checks: free at the marginal level. They run on your own CPU or GPU as part of your test suite and produce a number per query.
  3. Judge calls: every call sends a prompt and a chunk of retrieved context to a paid model and bills for input and output tokens.

The work of pulling these apart is itself a reliability practice, not just an accounting one. Contract testing treats the interface as a machine-checked artifact that every commit must prove still conforms, which is exactly the discipline an eval suite needs to catch drift between the retrieval contract and what the system actually does contract testing reference. The same logic applies to a retrieval eval: fix the expected shape of the answer, then prove it on every change.

A useful decision rule: if a check can be expressed as "did document X appear in the top k" or "is the answer string equal to Y", it belongs in bucket two and should never cost a token. Reserve bucket three for the judgments a string comparison genuinely cannot make, such as whether a generated answer stays faithful to the retrieved context.

The documentation for the standard consumer tests in Pact makes a related point that transfers directly here: write each test to catch a specific misunderstanding, and if you cannot name the bug a scenario would catch, do not write it Pact consumer tests. A golden query that cannot fail has no business in the suite.

How many golden queries you actually need

The honest answer is a range, because the number tracks the size of your decision. For a first eval, 50 to 200 golden queries is the right window. Fewer than 50 makes a single bad query move the aggregate by more than two percentage points, which is too noisy to act on. More than a few hundred before you have shipped a first pass means you are burning time on data you cannot yet interpret.

Use the margin of error to choose. If you have N queries, one failing query changes your recall@5 by 1/N. At 50 queries that is 2 points per query; at 200 it is 0.5 points. Write a table of that relationship before you write the queries:

QueriesOne query changes a rate byTypical use
254.0 pointsSmoke test, never for a ship decision
502.0 pointsFirst eval, catches gross breakage
1001.0 pointRoutine regression gate
2000.5 pointsComparing two retriever configs

The split within those queries matters as much as the total. A practical composition is 60 percent realistic user phrasings pulled from support tickets or search logs, 25 percent edge cases (ambiguous questions, questions that should return nothing, questions with a single correct document), and 15 percent multi-hop questions where the answer needs two retrieved documents joined together.

Coverage across the corpus is the second constraint. Retrieval evaluation depends on a ground truth set of queries paired with known relevant documents, often called qrels in information retrieval, and the metrics mean little if those pairs only touch a tenth of your corpus evaluating the retrieval pipeline. Count distinct source documents your queries touch and add queries until you cover the top of the access distribution.

If you draft queries with a model, treat the generated set as a first pass and edit it. A community thread on building a golden dataset with DeepEval's Synthesizer describes generating QA pairs from source documents and then building test cases from the real pipeline output golden dataset thread. That two-step flow (generate, then run the real system to capture what it actually returned) is the right shape, and it keeps the human review focused on the questions rather than on writing them from scratch.

Everything you can score without paying a model

Once the qrels exist, most of the eval is arithmetic. All of the following run offline and cost nothing beyond compute you already have.

Rank-unaware metrics count relevant items in the top k. Precision@k is the number of relevant items in the top k divided by k. Recall@k is the number of relevant items retrieved divided by the total number of relevant items for that query. Both fall between 0 and 1, and both are cheap to compute with a loop over your test cases retrieval evaluation metrics.

Rank-aware metrics care about position. MRR takes the reciprocal of the rank of the first relevant document and averages it across queries. NDCG discounts relevance by log of the position, so a relevant document at rank 1 counts far more than the same document at rank 9 retrieval evaluation metrics.

You do not need to implement these. The pytrec_eval package is a Python interface to TREC's trec_eval tool and computes precision, recall, MAP, MRR and NDCG from a qrels dictionary and a run dictionary retrieval evaluation metrics. A minimal offline check looks like this:

import pytrec_eval

qrels = {"return policy electronics": {"doc_return_301": 1, "doc_return_302": 1}}
run = {
    "return policy electronics": {
        "doc_return_301": 0.94,
        "doc_shipping_12": 0.88,
        "doc_return_302": 0.71,
    },
}

evaluator = pytrec_eval.RelevanceEvaluator(qrels, {"recall.5", "recip_rank", "ndcg_cut.5"})
print(evaluator.evaluate(run))

Alongside those, add deterministic checks that need no judgment at all:

  • Exact match or normalized match against a short reference answer, for factual lookups where one string is right.
  • Retrieval latency at p50 and p95, measured against an explicit budget.
  • Payload and response shape checks: required fields present, no unexpected fields, response size under a cap. These are the same class of checks that catch latency creep and field bloat in an API contract performance contract tests.
  • Whether retrieval returned zero results when the golden query was labeled "should return nothing", which is the check most teams forget and the one that catches an over-eager threshold.

One caveat worth knowing before you trust a single number: classic IR metrics assume a human reads results in order, but an LLM consumes all retrieved passages at once and can be distracted by irrelevant ones. A recent paper proposes a utility-based metric that accounts for this and reports better correlation with end-to-end answer accuracy retrieval evaluation for LLMs. For a costing exercise the takeaway is practical: NDCG and MRR are fine as regression signals, but do not assume a higher nDCG always means a better final answer. That is part of why the judge bucket exists.

What a judge call costs in tokens

Every judge-based check is one prompt that contains three growing pieces: the instructions and rubric, the question, and the retrieved context (plus the generated answer if you are grading faithfulness). Input tokens dominate, and they are easy to estimate before you spend anything.

The formula for one call:

input_tokens  = rubric_tokens + question_tokens + context_tokens + answer_tokens
output_tokens = expected_verdict_tokens
total_tokens  = input_tokens + output_tokens

Worked example with round numbers you can adjust to your own measurements. Suppose your rubric is a 250-token instruction block, the question averages 30 tokens, the retrieved context is 6 chunks of 400 tokens each (2,400 tokens), and the generated answer averages 300 tokens. That is 2,980 input tokens. If the judge returns a short verdict plus a one-sentence reason, budget about 80 output tokens. Call it 3,060 tokens per faithfulness judgment.

Now scale it. At 100 golden queries and two judge checks per query (faithfulness and answer relevance), you are at 200 calls, which is roughly 612,000 tokens per run if every call carries the same context. Drop the context to 4 chunks of 300 tokens and the same 200 calls come to about 400,000. The lever is context length, not the number of queries, which is why trimming retrieved chunks before judging saves more than shrinking the golden set.

This is the same measurement-before-enforcement discipline that token budgeting for AI products relies on: log prompt tokens, completion tokens, model, feature and environment for every request before you set hard limits, because the distribution tells you which workflow drives the spend setting token budgets. Instrument the judge calls the same way and you will learn within one run whether a full eval is a five-dollar exercise or a fifty-dollar one, without guessing.

Two practical ways to cut the bill without weakening the eval:

LeverWhat changesEffect on tokens
Judge a sample, not the full setRun judge checks on 30 of 100 queries70 percent fewer judge calls
Trim retrieved contextJudge on top 3 chunks, not top 8Proportional cut in input tokens
Shorter rubricTighter verdict format, no long prefaceSmall but free cut
Offline-first gateFail the build on recall@k before any judge runsZero judge spend on broken runs

The last row is the most valuable. Order your pipeline so the offline checks run first and the judge calls only fire when the deterministic metrics pass. A run that breaks recall@5 should never spend a token on grading answers.

Run the pieces in parallel and compare results

The comparison most teams skip is running the offline metrics and the judge scores side by side on the same queries. When they disagree, you learn something concrete: a query where recall@5 is 1.0 but the faithfulness judge flags an unsupported claim points at a generation prompt problem, not a retrieval one, and vice versa. Braintrust frames RAG evaluation as measuring retrieval quality, context utilization and answer grounding as separate components precisely because each fails independently RAG evaluation metrics.

You can get that side-by-side comparison cheaply by reusing one harness for both. The harness that runs the retrieval returns the top-k documents, the offline scorer computes recall and MRR against the qrels, and the judge scorer receives the same retrieved context. Keeping the inputs identical is what makes the two numbers comparable.

A parallel pattern from large refactors applies here too. When migrating a component, you can route a subset of traffic to the new implementation, compare outputs against the old one, and only cut over once accuracy crosses a threshold branch by abstraction walkthrough. Do the same across retriever versions: run both on the same golden set, diff the retrieved document IDs per query, and store the diff. The queries where the two versions disagree are the only ones worth judging with a model, which cuts judge spend to the disagreements rather than the whole set.

This is also where a single API for every model type helps. If your retriever uses an embedding model and your judge uses a chat model, running both through Quanta Inference removes the per-provider accounting and makes the per-call token logging above a single stream rather than a reconciliation job.

A checklist before you spend anything

Use this as a gate. Each unchecked line is a reason not to run judge calls yet.

CheckWhy it saves money
50 or more queries with qrels written downThe eval can produce an actionable number
Offline metrics run in under a minuteThe regression gate fires before any judge call
Per-call token count measured on one queryThe full-run estimate is real, not assumed
Judge rubric fixed and versionedYou are not re-running judges after every tweak
Judge checks limited to faithfulness and relevanceNo model is used where a string compare would do
Sampling plan chosenYou decide which 30 queries get judged, not the run
Token usage logged per call with model and featureThe next budget is based on data

The discipline that keeps the suite honest over time is the same one that keeps a contract test suite from rotting: never add expectations you cannot justify, and fail the build when a new unlabeled query slips in without a qrels entry Pact consumer tests. A golden set that grows without labels drifts into an unmeasured set.

If writing the harness itself is the bottleneck rather than the budget, Quanta Skills lets you install a skill into your coding agent so the offline scorer, the token estimator and the judge wrapper arrive as working code you then adapt, rather than a week of scaffolding.

FAQ

FAQ

How many golden queries should a first retrieval eval have?

Budget 50 to 200. Fifty is enough to catch gross breakage and keeps the set small enough to label by hand, while 200 gets the resolution down to half a point per query so you can compare two retriever configurations. Choose by the decision: 100 is a sensible routine regression gate for most teams.

Which retrieval checks can I run without paying a model?

Precision@k, recall@k, MRR, MAP, NDCG, exact match, retrieval latency and response shape checks all run on your own hardware once the qrels exist, and pytrec_eval computes the rank-aware metrics from a qrels and run dictionary. These should be your first gate. Keep paid judge calls for faithfulness and answer relevance, which a string comparison cannot evaluate.

How do I estimate tokens for a judge call before running it?

Measure one call, then multiply. Add the rubric, question, retrieved context and answer tokens to get input, add the verdict length for output, and multiply by the number of queries and judge checks. Trimming retrieved context from 8 chunks to 3 cuts input tokens roughly in proportion, and sampling 30 of 100 queries for judging cuts the bill by about 70 percent.

Why not judge every query with a model?

Because the judge step is the only one that costs money and the offline metrics answer most retrieval questions already. Run the judge only on queries where the offline metrics pass but you still suspect a groundedness problem, or on queries where two retriever versions disagree. That focuses the spend on the cases you cannot settle by arithmetic.

What is the difference between offline retrieval metrics and a judge?

Offline metrics compare retrieved document IDs against known relevant documents and produce a number with no model involved. A judge reads the question, retrieved context and generated answer and scores whether the answer is supported by that context. The first is free and deterministic, the second costs tokens and carries some variance, so the offline metrics should gate the judge run.

Sources

Sources

  1. API Contract Testing: What It Is, How It Works and Tools
  2. Writing Consumer tests
  3. Evaluating the Retrieval Pipeline | by Debraj Bhattacharya
  4. How to Evaluate a Self-Evaluating RAG System using DeepEval / RAGAS ...
  5. Evaluation Metrics for Search and Recommendation Systems
  6. Top 9 API Contract Tests That Catch Perf Regressions
  7. Redefining Retrieval Evaluation in the Era of LLMs
  8. Set Token Budgets for an AI Product
  9. How to evaluate your RAG pipeline with Braintrust - Articles
  10. Branch by Abstraction | Designing Event-Driven Microservices
About the author

About the author

Antonio Evans
Antonio Evans

Antonio Evans is the founder of Quanta Intelligence and a fractional CTO who builds HIPAA-ready AI systems for health and life-science companies. Before that he was technical co-founder of FriendsEAT for twenty years, CEO of Socialdraft, and a data engineer at IBM. Antonio lives with his dog Santino in Lisbon, Portugal.

All articles