Phase 3: Agentic RAG · 60 min · Python · Anthropic SDK · ChromaDB
Self-Querying & Recursive Retrieval
Self-querying separates what you're looking for from the hard constraints on what qualifies — and metadata is the difference between 8 right results and 200 wrong ones.
Hiring signal: Self-querying is the feature that makes enterprise search applications actually usable — when a legal team asks for 'high-risk contracts signed before 2023 in the APAC region,' they expect hard-filtered results, not 500 semantically similar documents. This skill transitions engineers from demo builders to enterprise product builders.
What you will learn
- Implement self-querying retrieval: use an LLM to generate structured queries with metadata filters from natural language
- Design document metadata schemas that enable powerful filtered retrieval across categorical, numeric, and date dimensions
- Build recursive retrieval that follows document references up to a configurable depth with cycle detection
The Problem
A user types: "Show me the critical unresolved support tickets about login issues on iOS filed in the last two weeks." Your semantic search embeds this and returns the 200 closest matches by cosine similarity to the word "login issues." Some are resolved. Many are on Android. Several are from 2022. A handful are feature requests, not bugs. The user wanted 8 tickets; they got 200.
The problem is that semantic similarity cannot enforce hard constraints. Cosine similarity measures conceptual proximity — "iOS login crash" is semantically close to "Android login crash" — but your user wants iOS only, and an Android result is not an acceptable substitute regardless of how semantically similar it is.
Self-querying retrieval solves this by having the LLM translate the natural language query into a structured query object: a semantic search string for the conceptual part ("login issues") plus metadata filters for the constraint parts (severity = "critical", status = "unresolved", platform = "iOS", date > 2 weeks ago). The retriever applies semantic search AND metadata filtering simultaneously, returning only results that are both semantically relevant AND satisfy every hard constraint.
This is the feature that makes enterprise search actually deployable. A legal team asking for "high-risk vendor contracts from APAC signed before 2023" is not asking for documents that are vaguely contract-like — they're specifying hard constraints that must hold. A security team asking for "critical unpatched CVEs in our authentication components" cannot afford to get medium-severity results mixed in. Self-querying handles these cases where pure semantic search fails.
Metadata schema design is the real work
Self-querying is only as powerful as your document metadata. An ideal schema covers every dimension a user might want to filter on: dates, categorical fields, severity levels, status flags, regions, versions. Retrofitting metadata onto a corpus after the fact is painful — design the schema before ingestion, and include typed fields with controlled vocabularies (enums) wherever possible.
Designing Document Metadata for Self-Querying
The metadata schema is the foundation. For a support ticket corpus:
TICKET_METADATA_SCHEMA = {
"ticket_id": "string", # unique identifier
"date_filed": "ISO 8601 date", # filter: date_after, date_before
"category": Literal[ # filter: eq, in
"technical", "billing", "shipping", "account"
],
"subcategory": "string", # filter: eq
"platform": Literal[ # filter: eq, in
"iOS", "Android", "web", "api"
],
"severity": Literal[ # filter: eq, gte
"low", "medium", "high", "critical"
],
"status": Literal[ # filter: eq, in
"open", "in_progress", "resolved", "closed"
],
"product_version": "string" # filter: eq, gte
}
Controlled vocabularies (Literals) are important: they let the LLM generate exactly-valid filter values rather than free-text strings that might not match your database. When defining the schema in your self-querying prompt, include the allowed values for each enum field explicitly.
The difference between good and bad metadata shows up in a concrete test. Query: "recent high-severity billing issues." With rich metadata: {semantic_query: "billing issues", filters: {category: "billing", severity: "high", date_after: "30_days_ago"}, sort: "date_desc"} → 12 precise results. With just document text and no metadata: semantic search on "billing issues" → 180 results, all relevance-ranked, severity mixed throughout.
Unlock the full lesson
You've read the first 2 sections. The rest of this lesson covers Implementing Self-Querying Retrieval, Recursive Retrieval — Following the Graph, Build It, What to Practice — plus a hands-on lab, quiz, and project artifact.
Create a free account to unlock Phase 0 and Phase 1 of every course — no credit card.
Browse all courses · View pricing · DeVenture Academy