RAG metadata prefiltering limits which documents are eligible before ranking. It can enforce a declared source or date policy, but a bad filter can remove the relevant document before similarity search sees it. Compare that loss explicitly instead of promising a fixed accuracy improvement or token saving.
Separate authorization from a relevance preference
A permission rule is mandatory: an unauthorized document must not enter the answer context. A publication-date preference is task-dependent. Do not label extraction time as publication time, and do not treat a missing date as proof that a document is old. The example rejects unknown dates from this narrowly defined recent-source task; a production policy should record them for review.
| Metadata | Failure mode | Handling |
|---|---|---|
| Permission list | Stale or user-supplied labels allow a forbidden source | Enforce trusted authorization at the storage/retrieval boundary |
| Publication date | Missing, malformed or timezone-free value | Apply an explicit policy and log unknowns |
| Source identity | Repeated or changed URL | Keep a stable record and provenance |
| Retrieval time | Confused with publication time | Use a separate UTC field |
A synthetic comparison of filter order
The code sorts a small labeled fixture by a supplied score. It is not a vector-database implementation or a production retrieval benchmark. Use it to inspect the difference between filtering the candidate set before top-k and filtering only the already selected top-k. All dates in the fixture are timezone-aware.
from datetime import datetime, timezone
def eligible(document, principal, earliest):
raw = document.get("published_at")
if not isinstance(raw, str):
return False
try:
stamp = datetime.fromisoformat(raw.replace("Z", "+00:00"))
except ValueError:
return False
if stamp.tzinfo is None or earliest.tzinfo is None:
return False
return principal in document.get("allowed_principals", []) and stamp >= earliest
def compare_filter_order(documents, principal, earliest, k=2):
ranked = sorted(documents, key=lambda d:d["score"], reverse=True)
pre = [d for d in ranked if eligible(d, principal, earliest)][:k]
post = [d for d in ranked[:k] if eligible(d, principal, earliest)]
eligible_relevant = {d["id"] for d in documents if d["relevant"]
and eligible(d, principal, earliest)}
def report(rows):
kept = {d["id"] for d in rows}
return {"kept":sorted(kept), "missed_eligible_relevant":sorted(eligible_relevant-kept)}
return {"prefilter":report(pre), "postfilter":report(post),
"observed_at":datetime.now(timezone.utc).isoformat()}
What the failure example should reveal
Construct four records: a high-scoring unauthorized document, a recent permitted relevant document, an older permitted relevant document and a recent permitted irrelevant document. With top-k equal to two, postfiltering can lose a permitted result because an unauthorized candidate consumed a ranking slot. A strict recent-date rule also removes the older relevant document regardless of how good its score is.
Keep two denominators distinct. Recall among eligible relevant documents evaluates the retrieval pipeline under the chosen policy. Recall among all relevant documents reveals what the policy itself excluded. Reporting only the first can hide an over-restrictive date filter. Neither synthetic measure establishes a production precision gain.
Apply the filter where permissions are trusted
The Python predicate is a readable fixture, not an access-control boundary. Apply authorization in the database or service that returns candidates, using trusted identities and metadata. Do not send unauthorized bodies to the model and ask it to ignore them. Use a labeled permitted query set to test the real store’s filtering behavior, including missing and conflicting metadata.
The old SERP collection example is intentionally removed from this filtering article. the ingestion acceptance workflow owns source collection and validation; the search indexing workflow owns indexing mechanics. the live RAG architecture connects those stages. This page owns filter policy and its measurable false exclusions.