Why Graph Search Needed a Natural Language Interface
Netflix’s Graph Search platform was built to handle search across federated enterprise data sets, but it has always required users to express themselves in the Graph Search Filter DSL—a structured query language. Applications in the Content and Business Products suite typically act as intermediaries: users toggle facets or fill out query builders, and the application programmatically translates those UI interactions into valid DSL statements.
That arrangement creates friction in several ways. Each application has its own bespoke input components, so support for the DSL varies and users must learn each UI individually. Some domains have hundreds of index fields that could be filtered, and a subject matter expert who knows exactly what they want still has to translate their question into a form the UI and the DSL can accept. The core problem is that users think in natural language, not in technical query constructs.
With LLMs now readily available, Netflix decided to generate Graph Search Filter statements directly from natural language. The goal is to augment existing applications with retrieval augmented generation (RAG) tooling rather than replace them, giving teams in the ecosystem new ways to process and present their data. All of this work has direct application to building a full RAG system on top of Graph Search in the future.
Defining a Correct Text-to-Query Conversion
The conversion task comes down to having an LLM produce a Graph Search Filter DSL statement that is correct on three levels.
Syntactic correctness is the simplest bar: the generated statement must parse according to the DSL grammar.
Semantic correctness requires knowledge of the index itself. The statement must:
- respect field types, using only comparisons that make sense for the underlying type;
- reference only fields that actually exist in the index, avoiding hallucinated fields;
- use only permitted values when a field's values come from a controlled vocabulary.
Pragmatic correctness is the hardest requirement: the generated filter must actually capture the intent of the user's question.
Context Engineering
Most of the preparation work is engineering the right context for the LLM. Because each Graph Search index is defined by a GraphQL query, the GraphQL schema provides the field metadata needed to construct semantically correct filters. Each field is associated with:
- field: derived from the document path in the GraphQL query;
- description: the comment from the GraphQL schema;
- type: from the GraphQL schema (e.g.
Boolean,String,enum) plus a custom controlled vocabulary type; - valid values: from enum values or from a controlled vocabulary.
A controlled vocabulary is a field type with a finite set of allowed values defined by subject matter experts or domain owners. Index fields can be attached to one—countries is an example—and any generated statement must reference values from that vocabulary.
Naively supplying all of this metadata as context worked for simple cases but did not scale. Some indices have hundreds of fields, and some controlled vocabularies have thousands of possible values. Expanding the context proportionally increases latency and degrades the correctness of generated statements. Omitting the values was not an option either, because without grounding the LLM would frequently invent values that do not exist. The solution was to curate context with a RAG pattern.
Field RAG
Most user questions touch only a handful of fields, even on indices with hundreds. Including everything has a real cost in query-generation latency and correctness due to the needle-in-a-haystack problem. To pick a relevant subset, Netflix matches candidate fields against the user's question:
- Embeddings are created for each index field and its metadata (name, description, type) and stored in a vector index.
- At query time, the user's question is chunked with an overlapping strategy. For each chunk, a vector search retrieves the top K most relevant fields.
- The top K fields across all chunks are consolidated and deduplicated before being added to the system instructions.
Controlled Vocabulary RAG
For fields of the controlled vocabulary type, the system infers from the user's question whether a particular vocabulary is relevant. Knowing which controlled vocabulary values appear in the question also helps identify additional related index fields that the field RAG step may have missed, so those can be included in the context as well.
Controlled Vocabulary Matching
Beyond fields, graph search also matches controlled vocabulary values against the user’s question. Each vocabulary value carries a unique identifier, a display name, a description, and AKA names (for example, “romcom” for “Romantic Comedy”). All of these are embedded and indexed in a vector store, populated regularly from GraphQL so the system stays current with the domain.
At filter generation time, the question is chunked and each chunk runs a vector search to find the top K most relevant values across the vocabularies tied to fields in the index. The results are deduplicated by vocabulary type, and the matching value metadata is injected into the context alongside the associated field definitions.
Press enter or click to view image in full size
The quality of the RAG output depends heavily on several tuning parameters, including reranking strategies, chunking approaches, and the embedding model in use. These “levers” and their systematic evaluation are the subject of later parts of this series.
Instructions and Generation
Given the assembled context, the LLM receives the user’s question alongside instructions to produce “a syntactically, semantically, and pragmatically correct filter statement” using only the provided fields and metadata. To meet those three criteria, the prompt includes the DSL syntax rules, an instruction to ground the output in the context, and ideally the most relevant context values.
Press enter or click to view image in full size
Pragmatic correctness — generating a filter that actually reflects user intent — is the hardest part. So far, better context engineering has proven more effective than any prompt phrasing for steering the LLM’s choices.
Deterministic Validation
Before the generated filter statement reaches the user, it passes through two deterministic checks built on the AST parser for the DSL.
Syntactic Checks
If the string cannot be parsed into a valid AST, the query is malformed. Structured output modes from some LLM providers are a possible alternative, but initial evaluations were inconclusive because the custom DSL is not natively supported.
Semantic Checks
Despite careful RAG-based context engineering, the LLM will occasionally invent fields or values. Validation compares the AST against the available index metadata, which is already in memory from the context stage, so this adds no perceptible latency. Each detected hallucination can either surface to the user as an error or feed back into the LLM for self-correction; the latter increases generation time and should be limited to a small number of retries.
That leaves pragmatic correctness unvalidated. The gap is real — the same word may map to different intents. A search for “Dark” could target the German series or the mood. Even worse, natural language implies compression: “German time-travel mystery with the missing boy and the cave” must map onto discrete fields like releaseYear, genreTags, and synopsisKeywords.
Showing the User the Query
One practical mitigation is to make the generated filter visible to the user. Displaying a raw DSL string such as origin.country == ‘Germany’ AND genre.tags CONTAINS ‘Time Travel’ AND synopsisKeywords LIKE ‘*cave*’ would be confusing. Instead, the generated AST is translated into existing UI components like Chips and Facets. When the LLM generates origin.country == ‘Germany’, the user sees the Country dropdown pre-selected to “Germany,” giving them immediate, editable feedback.
Press enter or click to view image in full size
Explicit Entity Mentions
Ambiguity can also be reduced at query entry time. Users can type an “@mention” to explicitly select a known entity from a specialized Graph Search UI component, which covers multiple controlled vocabularies and their context — launch year, for example. With “When was @dark produced,” the system resolves the mention directly to the Series vocabulary, bypassing RAG inference entirely and avoiding the dark mood/title ambiguity.
Press enter or click to view image in full size
Putting It Together
The overall flow is divided into pre-processing (context building and retrieval), filter generation, and post-processing (validation and UI rendering). The end-to-end sequence:
- The user’s question — with optional @mentions — arrives with the search index context.
- The RAG pattern narrows the context to relevant fields and controlled vocabulary values.
- The context and question are sent to the LLM with the instruction to produce a valid filter statement.
- The resulting DSL string is parsed and checked against index metadata for hallucinations.
- The final answer includes the AST so the UI can build Chips and Facets for user review.
Press enter or click to view image in full size
This design shifts the burden from users learning the DSL to the system understanding natural language, while retaining deterministic validation where it matters most. The project still has significant open work — named entity recognition, intent detection for routing questions to the right indices, and query rewriting are all on the roadmap. Future installments will cover evaluating the system in production and expanding toward GraphQL-first interfaces.



