Skip to content
RAG Explained Better

Self-Query Retrieval: Letting the Model Write the Filter

Translating a natural-language question into a structured filter plus a semantic query.

Self-query retrieval uses an LLM to split a natural-language question into a semantic search string plus a structured metadata filter, then runs filtered vector search so year, genre, and rating constraints become store filters — not embedding luck.

LangChain ships the pattern as SelfQueryRetriever: the model writes the filter; the index executes it. This page covers the mechanism, the schema it needs, a worked natural-language → filter trace, and the failure modes tutorials skip.

How does a self-query retriever work?

Self-query retrieval is a three-stage pipeline on every request, not a smarter embedding:

Three-step vertical flow. One, query constructor: an LLM reads the user's question together with a declared metadata schema and emits a structured object with a semantic query string and a filter tree. Two, query translator: a store-specific visitor turns that internal filter into the vector database's comparator language. Three, filtered vector search: the store applies the metadata filter and runs similarity search on what remains, filtering before similarity so constraints never compete with embedding neighbourhood.
The filter is built before the vector search runs — year or genre constraints remove documents from the pool instead of competing with embedding neighbourhood.
  1. Query constructor. An LLM reads the user’s question together with a declared metadata schema and emits a structured object — in LangChain’s terms a StructuredQuery with a semantic query string and a filter tree (LangChain classic SelfQueryRetriever reference, class documented as of v1.3.14).
  2. Query translator. A store-specific visitor turns that internal filter into the vector database’s comparator language — Weaviate, Pinecone, Elasticsearch, and others expose different operators — so the model is not free to invent unsupported syntax (Lore Van Oudenhove, 2024; Izaguirre / Towards Data Science, 2024).
  3. Filtered vector search. The store applies the metadata filter and runs similarity search on what remains. Guides framed around LangChain’s self-query diagram describe this as filtering before similarity so year or genre constraints never compete with embedding neighbourhood (Lore, 2024; Izaguirre, 2024). Pre-filter versus post-filter ANN trade-offs live on metadata filtering.

Amazon Bedrock’s “intelligent metadata filtering” post (AWS Machine Learning Blog) is the same idea without the LangChain class name: tool use / function calling extracts entities from the question, builds a filter, then calls retrieve-and-generate. Haystack’s extract-metadata path does the same split — manual filter from the app, or LLM-extracted filter from the query text (Haystack, May 2024).

What metadata schema does self-query need?

Self-query can only filter fields that already exist on indexed chunks. The schema is not optional decoration — it is the prompt the constructor LLM reads.

  • Named fields with type and description. LangChain’s AttributeInfo carries name, type, and a natural-language description; NVIDIA’s RAG Blueprint uses an analogous metadata_schema with typed fields. Vague descriptions produce wrong operators; precise ones (“release year as four-digit integer”) steer the model.
  • Values written at ingest. Filename parses (climate report year/country in Lore, 2024), API columns (TMDB genre/runtime/year in Izaguirre, 2024), or document meta dicts (Haystack) must land on every chunk before query time. A field the constructor invents that was never stored filters the corpus to empty. How those fields get extracted is owned by metadata extraction.
  • Allowed comparators matched to the store. Equality, range, and in / contains operators are not universal. Towards Data Science (2024) and Lore (2024) both warn that Weaviate, Pinecone, and other stores accept different comparator sets — pin the allowlist in the constructor prompt so the LLM cannot emit a forbidden operator.

What does a self-query look like on a real question?

The constructor’s job is to separate what should be a filter from what should stay a semantic query. The rows below restate published examples — not measured scores:

Natural-language question → semantic query + metadata filter (published examples)
Natural-language question Semantic query (approx.) Metadata filter (approx.) Source
Find science fiction movies released after 2000 with a rating above 8. science fiction movies genre = science fiction AND year > 2000 AND rating > 8 Elastic Search Labs (Feb 2025)
Recommend horror movies made after 1980 that feature lots of explosions. lots of explosions / vibe genre = horror AND year > 1980 Izaguirre / TDS (Apr 2024)
What was the revenue of Nvidia in 2022? revenue / causes of increase company = Nvidia AND year = 2022 Haystack (May 2024)
Films similar to Yorgos Lanthimos movies. similar vibe to Lanthimos NO_FILTER (do not force Directors = Lanthimos) Izaguirre / TDS few-shot (2024)

The last row is the trap. “Recommend some films by Yorgos Lanthimos” should filter Directors; “films similar to Lanthimos” should not. Izaguirre (2024) feeds both as few-shot pairs so the constructor generalises — without them, the LLM collapses both into the same director filter and kills recall on the similarity intent.

When does self-query retrieval fail?

Self-query fails when the filter is wrong, missing, or too expensive — not when the embedding model is weak. The common modes:

  • Wrong or over-tight filter. The gold chunk still embeds near the query, but the metadata predicate excludes it. The detection signature used elsewhere on this site is recall(no-filter) − recall(with-filter) > 0 on the failing question: drop the self-query filter and the gold returns. That pattern is documented on wrong chunk and missing document.
  • Ambiguous natural language. Elastic’s self-querying retrievers FAQ (Feb 2025) lists LLM interpretation accuracy as the first limitation: poorly defined metadata or vague questions produce incomplete or incorrect filters.
  • Empty candidate set → generator hallucination. Izaguirre (2024) reports that a query like horror + Matt Damon + Wes Anderson + before 1980 can retrieve zero films; without a system rule that forbids recommending titles outside the retrieved set, the chat model invents films from parametric memory.
  • Extra model call on every query. AWS’s Bedrock intelligent-metadata post states the structural cost plainly: dynamic filtering adds one foundation-model call to extract metadata, raising both latency and spend. Mitigations they name: a lighter extract model, caching for repeated questions. Elastic (2025) lists the same cost caveat for high-traffic apps.

Store-specific filter limits still apply

NVIDIA’s RAG Blueprint docs (as of the live teardown, July 2026) note unsupported operations such as IS NULL / empty-string comparisons and warn that removing schema fields can break existing filters. Pin your store’s filter DSL independently of whatever the constructor emits.

When should you use self-query retrieval?

Use self-query when user questions routinely encode structured constraints — year, product line, locale, rating, document type — that already live as metadata on the chunks in your index, and when you cannot or will not collect those filters from a trusted UI control.

Skip it when metadata was never populated at ingest; when questions are pure paraphrase with no filterable attributes; when the application already passes a verified filter (Haystack’s “specify it directly” path is cheaper and safer than LLM extraction); or when query volume makes an extra LLM call per request unaffordable without a light extract model and a cache (AWS Machine Learning Blog). Self-query also does not replace hybrid search — lexical+dense fusion fixes vocabulary mismatch; self-query fixes structured constraints the embedding cannot see as fields.

How is self-query different from query rewriting?

Query rewriting changes the text sent to the retriever — synonyms, expansions, hypothetical documents — but does not emit a structured metadata predicate the store executes. Self-query keeps (or lightly revises) a semantic query string and emits an explicit filter.

Reach for rewriting when the failure is vocabulary mismatch between the user’s words and the document’s. Reach for self-query when the constraint is a field value (year = 2022, company = Nvidia). The two can stack — fix the filter first, then rewrite the semantic half — but conflating them produces either a filter-free paraphrase that still retrieves the wrong year, or a filter with no synonym bridge. Strategy depth for rewriting lives on query rewriting.

How do you implement self-query retrieval?

Frameworks expose the same pattern under different names: LangChain’s SelfQueryRetriever.from_llm (vector store + AttributeInfo list + document contents description), Haystack’s query-metadata extractor in a pipeline, AWS Bedrock tool use that builds a Knowledge Bases filter, or NVIDIA’s filter_expression_generator flag. Pin three things before you trust it in production: field descriptions the constructor can read, the comparator allowlist for your store, and few-shot pairs for traps like “by director” versus “similar to director.”

Measure with the no-filter recall delta on a labelled set before and after enabling self-query — not with a demo that only shows happy-path movies. Full runnable wiring belongs with building the pipeline; this page stops at the mechanism. For the rest of the retrieval cluster, start from the retrieval hub.

What is self-query retrieval?

Self-query retrieval uses an LLM to split a natural-language question into a semantic search string plus a structured metadata filter, then runs filtered vector search on the remaining documents. Year, genre, rating, and similar constraints become store filters instead of hoping the embedding ranks them correctly. LangChain’s SelfQueryRetriever is the named implementation of this pattern.

How does the query constructor differ from the translator?

The query constructor is the LLM chain that reads the question and the AttributeInfo schema and emits a StructuredQuery with a query string and a filter tree. The translator is a store-specific visitor that turns that internal filter into Weaviate, Pinecone, Elasticsearch, or another database’s comparator language. Without the translator (and an allowlist of legal operators), the model can emit filters the store cannot execute.

What metadata do you need for self-query?

You need typed fields with clear natural-language descriptions on every indexed chunk — LangChain AttributeInfo or an equivalent schema — and those values must be written at ingest. A constructor cannot filter on year or company if those keys were never stored. How fields get extracted and enriched is covered on /ingestion/metadata; how the store applies pre- versus post-filters is on /indexing/filtering.

When does the LLM write a bad filter?

When the question is ambiguous, the schema descriptions are vague, or few-shot examples are missing for traps like “films by X” versus “films similar to X.” The detection signature is recall(no-filter) − recall(with-self-query-filter) > 0: drop the generated filter and the gold chunk reappears. Empty filters that match nothing can also push the generator to hallucinate answers unless the system prompt forbids citing documents outside the retrieved set.

How is self-query different from query rewriting?

Query rewriting changes the text sent to the retriever but does not emit a structured metadata predicate. Self-query emits an explicit filter the vector store executes, optionally alongside a revised semantic query. Use rewriting for vocabulary mismatch; use self-query for field-value constraints. Depth on rewrite strategies lives on /retrieval/query-rewriting.