Skip to main content
This page is the vocabulary map for evaluating GraphRAG SDK as a trustworthy, citable RAG system. Each term below is tied to a concrete class, parameter or returned field, with the honest answer about what the SDK enforces and what it leaves to you.
Everything here describes shipped behavior. Where a capability is not built in — abstention enforcement and declarative metadata filters are the two cases — the page says so and shows the supported workaround.
Looking for the narrative version — why RAG systems produce unsupported answers and how to design around it? Start with Reducing LLM Hallucinations. This page is the API-level reference behind it.

Vocabulary map


Grounded retrieval

Grounded retrieval means the generated answer is produced only from context retrieved out of the knowledge graph — never from the model’s parametric memory. GraphRAG.completion() retrieves first, assembles the retrieved items into a single context string, wraps it in a <context> block and sends it with a system prompt that opens with “Answer questions using ONLY the context provided in the user message”. The numbered rules that follow require the model to base the answer strictly on that context, to preserve exact names, dates and negations, and — rule 6 — to state that the context is insufficient rather than invent details.
Two grounding safeguards run automatically when you use the default prompt template:
  • Retrieved document text is scanned for a forged </context> closing tag and neutralized, so untrusted source text cannot break out of the context block and issue instructions.
  • The delimited system prompt explicitly tells the model that everything inside <context> is data, not instructions.
Passing your own prompt_template= disables both safeguards — the SDK assumes a custom template owns its escaping rules, and it applies the shorter, non-delimited system prompt. If you supply a template, keep the “answer only from the context” instruction in it.

Source provenance

Every ingested document produces a mandatory provenance chain in the graph. It is not optional and not a post-processing step — the ingestion pipeline builds it before extraction results are written.
Two provenance details are worth calling out:
  • RELATES.source_chunk_ids records which chunks produced each extracted relationship, so a graph fact traces back to the passages that justify it.
  • Retrieved passages are tagged inline. MultiPathRetrieval batch-resolves each candidate chunk’s Document.path through PART_OF and prefixes the passage with [Source: <path>] before it reaches the LLM. The path is stored exactly as it was at ingestion time (typically relative to the ingestion root), because bare filenames are ambiguous across directories.
Pass an explicit document_id at ingestion time when you need a stable citation key — a content hash, a repo-relative path, or a slug:

Evidence trail

The evidence trail is the chain from an answer back to the source document. Ask for the context alongside the answer and walk it:
MultiPathRetrieval returns one item per context section, and item.metadata["section"] is one of hint, cypher_results, entities, relationships, facts or passages. The passages section carries the document attribution; facts and relationships carry the graph-level evidence. A chunk_id in item.metadata is what lets you close the loop from a retrieved item back to its document. Only LocalRetrieval populates it — MultiPathRetrieval sets metadata to {"section": ...} and rerankers copy that metadata through unchanged, so under the default strategy the list below is empty and you should use the [Source: <path>] prefix on passages entries instead:
And from an entity back to the passages that mention it:

Relationship-aware retrieval

Top-k vector search over chunks answers “which passages look like this question”. It cannot answer “what is connected to this entity”. The default strategy, MultiPathRetrieval, runs graph traversal alongside vector search so connected evidence is retrieved even when it is not lexically or semantically close to the question:
Measured in isolation, no single path matches the fused pipeline — see the per-path numbers in Retrieval. For the simpler alternative (vector search plus optional 1-hop entity context), use LocalRetrieval from graphrag_sdk.retrieval.strategies.local — see Strategies.

Metadata filtering

retrieve() and completion() do not accept a metadata filter argument. Metadata is persisted on the graph, and filtering is done with Cypher or by scoping the graph. Do not expect declarative filter= semantics.
Loader-supplied document metadata is flattened onto the Document node at ingestion time, and chunk metadata onto each Chunk node. The built-in loaders contribute keys such as loader, size_bytes, suffix (text and markdown) and page_count, pdf_backend (PDF). A custom LoaderStrategy that returns a DocumentInfo with your own metadata dictionary makes those keys queryable properties:
Feed the resulting passages to your own prompt, or post-filter the output of retrieve() against the allowed document set.
The strongest filter is a separate graph. ConnectionConfig(graph_name=...) scopes an entire GraphRAG instance, so a tenant’s retrieval can never reach another tenant’s chunks. This is the layout used for the benchmark run, which indexes one graph per corpus document.

Confidence thresholds

Every similarity score returned by the storage layer is a similarity, not a distance — higher means closer. Chunk, entity and relationship vector searches all return (1 - score) from the FalkorDB cosine index and order by that value descending.
This was corrected recently: earlier releases ordered by raw distance, which ranked the least similar results first. If you pinned a threshold against the old behavior, invert it.
Facts use a higher threshold than passages on purpose: short structured strings (Alice —[WORKS_AT]→ Acme) have much higher cosine variance than prose paragraphs, so a passage-tuned threshold would let noise through. The min_keep floor guarantees the pipeline never returns an empty fact section purely because of the threshold — which is exactly why a threshold alone is not an abstention mechanism. To obtain explicit per-item scores, apply the reranker — it is the component that populates RetrieverResultItem.score:
Without a reranker, MultiPathRetrieval returns section items with score = None — scoring happens internally during fact filtering and passage reranking and is not surfaced per section. LocalRetrieval items do carry the chunk’s vector similarity in score.Attaching a reranker populates score, but note what it scores: a MultiPathRetrieval item is a whole concatenated section, so CosineReranker embeds that entire block against the question. One highly relevant passage is diluted by everything else in the same section, and the resulting number is systematically lower than a per-chunk similarity. Calibrate section-level thresholds against your own corpus rather than reusing a chunk-level value — a LocalRetrieval-derived threshold will over-abstain here.

Abstention behavior

Abstention is prompt-level, not enforced. The SDK ships no confidence score on the answer, no “insufficient context” marker on RagResult, and no threshold that rejects a generation. What exists is rule 6 of the built-in system prompt: “If the context lacks sufficient information, say so briefly rather than inventing details.” A model may ignore it. If you need enforced abstention, gate the call yourself: retrieve first, decide, and only then generate. The runnable pattern and its trade-offs are covered in the guide; the score-based variant looks like this:
Tighten the prompt side as well, with a template that makes abstention the required output for weak context:
A custom prompt_template must keep both {context} and {question} placeholders, and it opts out of the </context> neutralization and the delimited system prompt described under Grounded retrieval.
The retrieval-side threshold is a partial gate on its own: min_keep=3 in the fact filter means at least some facts always survive, so an empty or weak graph still yields a non-empty context. The score check above is what turns that into a real refusal.

Unsupported claims

An unsupported claim is a statement in the answer that the retrieved evidence does not back. The SDK does not detect these for you — it gives you the exact evidence the answer was allowed to use, which is what makes detection possible.
Practices that reduce unsupported claims:
  • Keep the default template. Its system prompt already forbids outside knowledge, preambles and verbatim quoting, and requires negations to be preserved.
  • Log the evidence with the answer. return_context=True plus result.metadata["retrieval_query"] reproduces exactly what the model saw, including after a history rewrite.
  • Raise the bar for context quality rather than the amount: a higher min_score on fact filtering and a reranker top_k that fits your model’s attention budget beat sending more marginal passages.
  • Prefer explicit entity/relationship evidence. The facts and relationships sections carry the fact text extracted from source chunks, and each RELATES edge keeps source_chunk_ids.

Retrieval validation

Answer quality problems are usually retrieval problems. GraphRAG.retrieve() runs the full retrieval and reranking pipeline and returns the context without calling the generation model — no generation cost, no generation variance. It is not free, though: the default MultiPathRetrieval still issues an LLM keyword-extraction call, plus a text-to-Cypher call when enable_cypher=True.
A validation checklist that works well before shipping a graph:
Check that a passages item carries the [Source: <path>] tag of the document you expect. If not, the chunk never made it through any of the four chunk paths — inspect chunking and the entity extraction for that document.
An empty entities section means keyword extraction and fulltext matching both missed. Verify the document was ingested, and that GraphRAG.finalize() ran — without it, entity- and edge-level vector search returns nothing and cross-document duplicates remain.
Attach a CosineReranker and read item.score. Values clustered near zero mean the question and corpus are semantically far apart — the threshold gate from Abstention behavior belongs in front of generation.
Query it directly with GraphStore.query_raw() and compare counts against GraphRAG.get_statistics(). See Graph Schema for the node and relationship reference.

Factual accuracy

Grounding claims are only credible if they are measured. GraphRAG SDK is evaluated on GraphRAG-Bench (Xiang et al., ICLR 2026) across both subsets and all four task categories, with the SDK’s own defaults preserved rather than tuned to the benchmark, and the benchmark’s unmodified evaluation script as judge. Reported averages are 76.87 (Medical) and 66.09 (Novel), the unweighted mean of the four ACC values, which is the convention the GraphRAG-Bench leaderboard uses. The Benchmark page publishes the per-level breakdown, the full configuration, the two declared deviations from SDK defaults, and the hand-authored ontologies so the run is checkable.
These numbers measure answer accuracy end to end. For per-retrieval-path contribution — including how each path scores in isolation — see Retrieval.