The Big Picture
Step-by-Step Explanation
Step 1 — Keyword Extraction
What it does: Pulls out the important words from your question. How: Two methods run together:- Simple filter: Removes common words (“the”, “is”, “what”) and keeps meaningful terms
- LLM extraction: Asks the language model to identify proper nouns, names, places, and specific terms
Question: “What did Professor Harmon discover at the lighthouse?” Simple keywords:Code:["professor", "harmon", "discover", "lighthouse"]LLM keywords:["Professor Harmon", "lighthouse"]
MultiPathRetrieval._extract_keywords() in multi_path.py
Step 2 — Embed the Question
What it does: Converts the question text into a numerical vector (a list of numbers) that captures its meaning. Why: This vector is used later to find chunks and facts that are semantically similar to the question — even if they don’t share the exact same words. Code:Embedder.aembed_query() in providers/base.py
Step 3a — RELATES Vector Search (Knowledge Graph Facts)
What it does: Searches the relationship edges in the graph by meaning similarity. Every relationship between entities (like “Alice WORKS_AT Acme Corp”) has been embedded as a vector during ingestion. This step finds the relationships most relevant to the question. Returns: Fact strings like:search_relates_edges() in entity_discovery.py
Step 3b — Text-to-Cypher (Graph Queries)
What it does: Asks the LLM to write a database query (in the Cypher language) that can directly answer the question from the graph structure. Why it matters: Some questions need structural information that text search can’t provide:- “How many organizations are in the story?” → needs
COUNT - “What connects Alice and the castle?” → needs graph path traversal
- “List all locations mentioned” → needs
MATCH (l:Location) RETURN l.name
- The LLM receives a description of the graph schema (what node types exist, how edges work)
- It generates a Cypher query tailored to the question
- The query is validated (read-only, valid labels) and sanitized (adds LIMIT, removes unsupported FalkorDB patterns)
- If the query executes successfully, the results go directly to the final LLM context — they are NOT filtered by the reranker
execute_cypher_retrieval() in cypher_generation.py
Step 4 — Entity Discovery
What it does: Finds entities (people, places, organizations, etc.) in the graph that match the question’s keywords. Two paths:- Path A — Name matching: Searches entity names using
CONTAINS(e.g., “lighthouse” matches “The Old Lighthouse”). Runs as a single batched database query for efficiency. - Path B — Fulltext search: Uses the text search index on entity names and descriptions. Good for partial matches and stemming.
discover_entities() in entity_discovery.py
Step 5 — Relationship Expansion
What it does: Starting from the discovered entities, traverses the graph to find their relationships. Two depths:- 1-hop: Direct relationships (Alice → WORKS_AT → Acme Corp)
- 2-hop: Indirect connections through an intermediate entity (Alice → WORKS_AT → Acme → LOCATED_IN → New York)
expand_relationships() in relationship_expansion.py
Step 6 — Chunk Retrieval (4 Paths)
What it does: Finds the actual text passages (chunks) from the original documents that are most relevant to the question. This is the core of passage-based retrieval. Four independent paths ensure we don’t miss relevant passages:
All four paths contribute to a single pool of candidate chunks.
Code:
retrieve_chunks() in chunk_retrieval.py
Step 7 — Document Mapping
What it does: Looks up which source document each chunk came from, so the final answer can reference the source. Code:fetch_chunk_documents() in chunk_retrieval.py
Step 8 — Reranking (Differentiated)
Facts and passages are ranked by different criteria because they have different characteristics:8a — Passage Reranking (Stored Embeddings)
What it does: Ranks the candidate chunks by how similar their meaning is to the question, keeping only the top 15. How: Each chunk already has an embedding vector stored in the graph from ingestion. Instead of re-computing embeddings (which would require an expensive API call), we fetch the stored vectors and compute cosine similarity locally. This makes reranking instant instead of taking 2-3 seconds. Code:rerank_chunks() in result_assembly.py
8b — Fact Filtering (Score Threshold)
What it does: Filters knowledge graph facts by their vector similarity score from step 3a. Why separate? Facts are short structured strings (“Alice —[WORKS_AT]→ Acme”) while passages are long prose paragraphs. Short text has higher cosine similarity variance — a threshold that works for passages would let too many irrelevant facts through. Facts use a higher threshold (0.25) and always keep at least 3 top facts. Code:filter_facts_by_relevance() in result_assembly.py
Step 9 — Context Assembly
What it does: Combines everything into a structured context document that the LLM uses to generate the final answer. Sections (in order):- Answer format hint — e.g., “This is a yes/no question” for yes/no questions
- Graph Query Results — Direct results from text-to-cypher (bypasses reranking)
- Key Entities — Names and descriptions of relevant entities
- Entity Relationships — How entities connect to each other
- Knowledge Graph Facts — Evidence from relationship embeddings
- Source Document Passages — Ranked text passages with source attribution
assemble_raw_result() in result_assembly.py
Benchmark Results
All experiments were run on the same pre-built graph (graphrag_sdk_v2_retrieval_benchmark) with 100 questions scored by an LLM judge (0-10 scale).
Enhancement Comparison
Accuracy by Question Type
Text-to-Cypher helps most with Contextual Summarize questions where structured graph relationships provide the context the LLM needs to produce comprehensive summaries.
Isolated Path Performance
Each retrieval path was tested in isolation (only that path, no others) to measure its individual contribution:
Key insight: No single path matches the combined pipeline (83.8%). The value is in multi-path fusion — each path finds information the others miss, and together they cover more ground than any individual approach.