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 retrievalMetadataFilters retrieval
Candidate NodesEvery Node in the indexOnly Nodes matching the filter
When appliedN/ABefore similarity ranking, not after
Good for"Search everything I have""Search only this document / category"
LangChain equivalentretriever.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 for MetadataFilter using the default FilterOperator.EQ, checks a metadata key equals a given value exactly. Every Node's metadata already carries file_name (Lesson 2), which is what this lesson filters on.
  • Filters attach to either index.as_retriever(filters=...) (raw retrieval, no LLM call) or index.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 for MetadataFilter with FilterOperator.EQ.
  • Attach filters via index.as_retriever(filters=...) or index.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.