Searching a subset of the index
Every retrieval so far has searched the whole index: every stored Node is a candidate, similarity search ranks all of them, and the top matches win. MetadataFilters narrows that search space before ranking even happens, restricting retrieval to only the Nodes whose metadata satisfies a condition, e.g. only Nodes from one specific source file.
This matters once an index holds more than a handful of documents. If you already know a question belongs to one document (a user picked a file in a UI, or a router step upstream already decided "this is a vacation question"), searching the entire index anyway wastes both similarity search accuracy (results from irrelevant documents can outscore the right document's less-central chunks) and, at scale, latency.
| Unfiltered retrieval | MetadataFilters retrieval | |
|---|---|---|
| Candidate Nodes | Every Node in the index | Only Nodes matching the filter |
| When applied | N/A | Before similarity ranking, not after |
| Good for | "Search everything I have" | "Search only this document / category" |
| LangChain equivalent | retriever.invoke(query) | vectorstore.as_retriever(search_kwargs={"filter": ...}) |
The pieces
MetadataFilters(llama_index.core.vector_stores): a container for one or more filter conditions, combined with AND/OR.ExactMatchFilter: an alias forMetadataFilterusing the defaultFilterOperator.EQ, checks a metadata key equals a given value exactly. Every Node's metadata already carriesfile_name(Lesson 2), which is what this lesson filters on.- Filters attach to either
index.as_retriever(filters=...)(raw retrieval, no LLM call) orindex.as_query_engine(filters=...)(Lesson 5's full retrieve-then-synthesize pipeline).
The code, piece by piece
vacation_filter = MetadataFilters( filters=[ExactMatchFilter(key="file_name", value="vacation_policy.txt")])One condition: node.metadata["file_name"] == "vacation_policy.txt". MetadataFilters takes a list so multiple conditions can be combined (default: AND).
filtered_retriever = index.as_retriever(similarity_top_k=3, filters=vacation_filter)filtered_nodes = filtered_retriever.retrieve(question)The filter is applied before the top-k similarity search runs, not afterward, so it's not "search everything, then throw away the mismatches," it's "only ever consider these Nodes in the first place."
filtered_query_engine = index.as_query_engine(filters=vacation_filter)response = filtered_query_engine.query(question)sources = sorted({Path(n.metadata["file_name"]).name for n in response.source_nodes})Same filters= keyword works on a query engine. response.source_nodes lists every Node the LLM actually used to synthesize its answer, checking their file_name metadata is how you confirm the filter did what you expect: every source should be vacation_policy.txt, nothing else.
Checkpoint
MetadataFilters: restricts which Nodes a retriever or query engine can even consider, applied before similarity ranking.ExactMatchFilter: the common case,metadata[key] == value, an alias forMetadataFilterwithFilterOperator.EQ.- Attach filters via
index.as_retriever(filters=...)orindex.as_query_engine(filters=...). response.source_nodes[i].metadata["file_name"]is how you verify which document(s) actually backed an answer, filtered or not.- Filtering is most useful once you have more documents than you want to search on every query, e.g. after a routing step already narrowed down which document a question belongs to.
If anything here still feels unclear, ask before moving to Lesson 12.