Founders

How to Eval Whether Your Pipeline Honors Exclusions

Antonio Evans9 min read

A diagram shows a search box at the top, a list of queries, and a results panel.
Key points

Key points

  1. 1An exclusion eval plants a document you have deliberately removed, runs realistic queries that would otherwise pull it back, and asserts the document's identifier appears zero times in the top-k you actually serve.
  2. 2Pass/fail is binary, not a score: one appearance anywhere in the served window is a failure, because a single leaked chunk can be paraphrased into a generated answer.
  3. 3The most common cause is filtering applied after ranking or only in one code path, so the leak shows up in a reranker, a sibling endpoint, or a cached response rather than in the main index.
  4. 4Run the eval in CI on every change to the exclusion list, the index build, and the retrieval configuration, and keep the planted documents versioned so the check is reproducible.

Why an exclusion list is a different problem from relevance

Most retrieval evaluation asks "did we find the right thing?" Relevance metrics such as recall@k, nDCG@k and MRR@k reward the presence of useful documents and are largely indifferent to a hostile document that ranks just below the cut. The Deepchecks RAG evaluation paper describes classic retrieval metrics this way, scoring ranking effectiveness against a relevance signal. None of them fail when an excluded document is retrieved. A pipeline can post excellent recall numbers and still leak a sealed document on every query.

Exclusions are a constraint, not a quality target. That changes the shape of the test. You are not measuring how often the pipeline is right, you are proving the absence of a specific identifier. Evidently's RAG evaluation guide separates retrieval from generation evaluation precisely because a fault in one stage is invisible when you only look at the final answer, and that separation matters here: a leak is a retrieval-stage fact and should be asserted at the retrieval layer, not inferred from prose.

The practical consequence is a different pass condition. For relevance you accept a rate ("recall@5 above some threshold"). For exclusions the only defensible threshold is zero. This is closer to a security test than a quality test, and it should live where security tests live: in the blocking path of continuous integration, not in a dashboard someone checks on Fridays.

What to plant, and why one document is not enough

A planted excluded document needs three properties to be a useful probe.

It must be semantically dense relative to the queries. If the planted document is a near-duplicate of the query, it proves only that the filter works in the easiest case. Write the planted text so it would rank in the top three on embedding similarity alone: use the same vocabulary, the same product names, the same phrasing a real user would type.

It must have a unique, greppable identifier that cannot appear by accident. Use a distinct token in the document body and in its metadata, for example a field such as doc_id: exclusion-probe-0007 plus a nonsense phrase like "zebra protocol annex". Grepping for a generic phrase such as "internal memo" will produce false failures.

It must be representative of the exclusion classes you actually maintain. Most teams hold several kinds of exclusion at once, and they are enforced by different mechanisms, which fail in different ways. The table below is the checklist I use.

Exclusion classExampleWhere it is usually enforcedTypical leak path
Legal or contractual removalDocument subject to a takedown or a customer deletion requestFilter at query time, or physical delete at index buildDelete applied to the primary index but not to a derived or cached copy
Confidentiality tierInternal pricing sheet, unreleased roadmapPre-filter on a permission metadata fieldFilter applied after top-k, so the item is ranked and then dropped only sometimes
Stale or superseded contentOld policy replaced by a revised versionIndex rebuild with a version fieldOld chunks remain in a shard that the rebuild did not cover
Tenant boundaryOne customer's documents in a shared indexNamespace or tenant ID filterA query path that omits the tenant predicate
Prompt-injection or spamText known to hijack generationBlocklist checked in the prompt constructorRetriever returns it; blocklist only inspects the final assembled prompt for some templates

Plant at least one document per row, because the enforcement points differ. A test suite that plants only a takedown case will never catch a tenant-boundary leak, and a suite that plants only one per class will miss the sibling-endpoint problem described later.

Building the query set that would otherwise surface the document

The query set is where most exclusion evals are too weak. A query that would never have retrieved the document proves nothing. You need queries where the planted document is a strong match, plus queries where it is a marginal match, because a marginal match is the case a post-ranking filter is most likely to mishandle.

Build the set in four slices.

First, direct hit queries. Take sentences from the planted document itself and paraphrase them lightly. These should return the document with the filter switched off. Verify that: before you trust the eval, run it in a deliberately unfiltered mode and confirm the planted identifier appears. A test that cannot fail is worthless, and this is the step that separates a real exclusion eval from a green tick.

Second, neighbor queries. Take real user queries from your logs that are adjacent in topic. If the excluded document is an internal pricing sheet, use the queries that currently return the public pricing page. These catch filters that are scoped too narrowly, for example one keyed on an exact product name.

Third, adversarial phrasing. Rephrase the same information request in the way a curious or careless user would, including misspellings and ambiguous acronyms. Evidently's guide treats stress and adversarial testing as a distinct layer for exactly this reason: normal traffic does not exercise the edge of the retriever's behavior.

Fourth, cross-path queries. For each query, call every endpoint that serves retrieval, not just the main one. Chat, autocomplete, "related documents" panels, summary tools and any agent tool that performs a search are separate code paths, and each needs its own assertion. In practice this is where the leak lives.

A workable size is small. Twenty to forty queries across the four slices, with at least one planted document per exclusion class, runs in seconds and fits in a pre-merge check. Generated question-answer pairs can bootstrap the set, but Evidently's guide frames synthetic data as a starting point rather than a substitute, so hand-check the direct-hit slice yourself.

Running the check and defining pass or fail

The mechanics are simple and deterministic: no LLM judge is needed for the core assertion, which is a string search over returned identifiers.

PLANTED = "doc_id:exclusion-probe-0007"
K_SERVED = 8   # the largest k any endpoint returns

def run_exclusion_eval(retrievers, queries):
    failures = []
    for name, retrieve in retrievers.items():
        for q in queries:
            hits = retrieve(q, k=K_SERVED)
            ids = [h.doc_id for h in hits]
            if PLANTED in ids:
                failures.append({
                    "path": name,
                    "query": q,
                    "rank": ids.index(PLANTED) + 1,
                })
    return failures

Three details in that snippet are load-bearing. K_SERVED must be the largest k your product exposes, not the k of the endpoint under test, because a document at rank 40 is still reachable by a user who asks for more or by an upstream reranker that expands the candidate set. The check reads the doc_id field rather than the text, so chunking and reflow cannot hide a leak. And the function returns the rank, which turns a failure into a diagnosis: rank one means no filter at all, rank seven means the filter is applied after ranking with a small window, and a rank that varies between identical runs means the filter is applied to a subset of shards.

The pass threshold is worth stating formally, since it is the part teams get wrong.

ResultVerdictWhat it implies
Zero appearances across all queries, all paths, all k up to the served maximumPassFilter is applied before ranking, or the document is absent from every index
Appears once, on any path, at any rankFailExclusion is not enforced on that code path
Appears only at ranks beyond the default k but within the served maximumFailFilter applied after ranking; will leak when k grows or a reranker expands candidates
Appears only on an endpoint not covered by a planted queryUnknownMissing coverage, not a pass

That last row is the one that protects you. Treat uncovered paths as failures of the eval, not passes of the pipeline, and add a planted document and query for each.

To keep the identifier check honest, store the mapping from probe to expected result in version control alongside the query set. CASRAI's guide to audit trails makes the general point well for research records: a trail of decisions is what lets someone else follow how a conclusion was reached. The same discipline applies to an eval suite, where the "why" behind each planted document is the thing a new engineer needs when the test starts failing.

Diagnosing a leak: the four failure modes

When the check fails, the rank and the path usually identify the cause before you read any code.

Filter after rank. The retriever returns the nearest N vectors and the application removes excluded identifiers from the list afterwards. This passes when the document sits at rank 30 and fails when it climbs to rank 3, which is why marginal queries matter. KX Systems' write-up on metadata filtering makes the architectural distinction clear: metadata attached to each vector can be used as a filter during the similarity search, rather than as a cleanup step applied to the results. The fix is to push the exclusion into the query itself, as a metadata predicate, so the candidate set never contains the document.

Sibling path. One service enforces the exclusion and another does not. This is the most common cause in mature systems and the one a single-endpoint test never catches. Compare the retrieval code between paths; the leaking one is usually missing the predicate, or has it and drops it when a request includes an override flag.

Stale index. The exclusion was applied at ingestion, but the identifier name changed or the rebuild covered only part of the corpus. The failure signature is sporadic: the document appears for some queries and not others, correlating with which shard served the request. Rebuilding is the fix, but the durable fix is a check that runs after every index build and compares the count of excluded identifiers in the index against the count in the exclusion source of truth.

Answer-level mention without retrieval. Occasionally the document identifier is absent from retrieval but the generated answer mentions the sealed fact anyway, because the model memorized it or because a different chunk restates it. This is why the exclusion eval belongs at the retrieval layer, and why the generation-layer check, if you add one, should use a judge calibrated against human labels rather than a keyword rule. Arize's guide to human-LLM judge alignment sets out the calibration approach: compare the judge against multiple human annotations, report agreement alongside a chance-adjusted statistic, and treat the judge as a classifier whose precision and recall you can state. Keep that judge out of the blocking gate and let the deterministic identifier check decide pass or fail.

Keeping the eval honest over time

An exclusion eval decays in three predictable ways, and each has a maintenance rule.

The corpus drifts and the planted documents stop being plausible matches. Queries that once ranked the probe at position two now rank it at forty, so the test passes for the wrong reason. Re-run the unfiltered verification monthly, or whenever the embedding model or chunking changes, and confirm each probe still ranks highly without the filter. If it no longer does, rewrite the probe text or the query.

New exclusion classes appear and no probe covers them. Every time the product team adds a new kind of exclusion, the eval suite gains a row. Make that part of the definition of done for the feature, not a follow-up ticket.

The plugin of exclusion grows and nobody audits it. Exclusion lists accumulate entries and each one is a chance for a typo that silently excludes nothing. Periodically reconcile the list against the systems of record. Snorkel's write-up on rubrics makes the broader argument that structured, written criteria beat ad hoc checks because they can be audited and improved. Your exclusion list is the same kind of artifact: if it is not written down in a form a reviewer can check, it is not enforced.

One more maintenance point is worth more than it looks. Keep a short decision log next to the suite recording why each probe exists, who owns it, and when it was last verified. Atlas's example of a decision log shows the useful shape: date, decision, reason, affected artifact, and a review point. Six lines per probe is enough to stop the next engineer from deleting a test they do not understand.

Where this fits in your stack, and how to get help

The eval itself is cheap once you commit to the binary threshold. What tends to be expensive is the surrounding work: standing up a reproducible index, running the same suite against every retrieval path, and wiring the result into the merge gate. If you would rather not build that plumbing from scratch, Quanta is a place to hand the job over: describe the pipeline and the exclusion behavior you need, and a specialist builds it with you.

If the retrieval layer is not the only part you are testing, note that the same discipline extends upward. A pipeline can pass the retrieval assertion and still leak through a generated summary, which is a generation-layer problem with its own eval design. Keeping the two layers separate from the start saves a painful refactor later.

FAQ

FAQ

Does the exclusion eval need an LLM judge?

No, not for the core assertion. The check is a string search for a document identifier in the returned results, which is deterministic and cheap. Reserve judged checks for the adjacent question of whether a generated answer restates excluded information, and calibrate any such judge against human labels as described in Arize's alignment guide.

How many planted documents do I need?

Start with one per exclusion class in your table, which is usually four to six, and add one per retrieval endpoint. The number of documents matters less than the coverage: a suite with six probes that span every enforcement mechanism beats a suite with fifty probes that all test the same takedown path.

Why is a single appearance a failure rather than a small error rate?

Because the failure is unbounded downstream. A chunk that reaches the context window can be quoted, paraphrased, or summarized, and Toloka's guide to RAG evaluation notes that retrieval and generation errors must be disentangled to understand a pipeline's behavior. One leaked chunk produces an unknown number of leaked sentences, and the exclusion was presumably not negotiable.

Should I test ranks beyond the default k?

Yes. Test up to the largest k any endpoint exposes, because a document at rank 40 is reachable by a user who expands results or by a reranker that widens the candidate set. A filter that only trims rank 8 and below is a filter that will leak the moment the window changes, and KX Systems' discussion of pushing metadata filters into the similarity search is the right model for fixing it.

What is the first thing to verify when the eval starts failing?

Run the suite with filtering disabled and confirm every probe still ranks highly. If it does, the leak is in enforcement and you can use the rank and the failing path to localize it. If it does not, your probes have gone stale and the suite needs new query text before you trust any result from it.

Sources

Sources

  1. Evaluating Retrieval-Augmented Generation (RAG)
  2. A complete guide to RAG evaluation: metrics, testing and ...
  3. Qualitative Audit Trail: What to Keep
  4. Optimizing Vector Search with Metadata Filtering
  5. How to Measure Human-LLM Judge Alignment - Arize AI
  6. Data quality and rubrics: how to build trust in your models | Snorkel AI
  7. Audit Trail in Qualitative Research: A Decision Log Example
  8. RAG evaluation: a technical guide to measuring retrieval- ...
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