Graph-RAG: The WITH CONTEXT Clause and Why RAG Belongs in SQL

A retrieval-augmented generation pipeline is conventionally four services. A vector store holds embeddings. A graph database holds relationships. An entity linker maps mentions to canonical nodes. A re-ranker boosts the top-k by some structural signal. Glue them with HTTP, queue work between them, keep their indices coordinated. The composition works. The composition is also expensive to operate.

HeliosDB Nano collapses that into one engine. The vector store is a column. The graph is two tables. The entity linker is a function call on insert. The re-ranker is part of the query planner. The whole thing fits behind a SQL clause: WITH CONTEXT.

The Two Tables That Carry the Whole Graph

  • _hdb_graph.nodes — (id, kind, source_ref, title, text, embedding VECTOR(n)). One row per node, regardless of source modality. Code symbols, doc paragraphs, email threads, and issue comments share this table.
  • _hdb_graph.edges — (src, dst, kind, weight, source_ref). One row per relationship. IMPORTS, REFERENCES, MENTIONS, CONTAINS, REPLIES_TO — one schema, many edge kinds.

Both are plain user tables. Queryable, joinable, branch-aware, time-travel-able, indexable.

Eight Ingestion Adapters

v3.17 shipped four adapters; v3.19 added four more (Docling-backed):

Adapter Modality Edge kinds
graph_rag_ingest_docsDocumentation pagesCONTAINS, MENTIONS
graph_rag_ingest_emailEmail threadsREPLIES_TO, MENTIONS
graph_rag_ingest_issuesIssue trackersMENTIONS, BLOCKS, RELATES_TO
graph_rag_ingest_qaQ&A pairsANSWERS, MENTIONS
graph_rag_ingest_pdfPDFs (Docling)CONTAINS
graph_rag_ingest_officeOffice docs (Docling)CONTAINS
graph_rag_ingest_audioAudio (transcribed)CONTAINS
graph_rag_ingest_imageImages (OCR'd)CONTAINS

Same SQL shape: one call per file, idempotent via source_ref keys, sections and chunks and tables project as nodes with CONTAINS edges. Re-ingest an updated file, only the diff lands.

The Entity Linker

v3.17 shipped a string-matching linker. v3.19 added graph_rag_link_vector: vector-similar entity linking with cosine top-k, threshold gating, and MENTIONS edges weighted by similarity. The hand-labelled fixture reports 100% precision; the bench is in tests/linker_precision.rs.

-- Vector-similar linker (v3.19)
SELECT graph_rag_link_vector(
  left_table  := '_hdb_graph.nodes',
  right_table := '_hdb_graph.nodes',
  left_kind   := 'symbol',
  right_kind  := 'doc_chunk',
  k           := 5,
  threshold   := 0.78
);
-- Emits MENTIONS edges with weight = similarity

The WITH CONTEXT Clause

The headline. WITH CONTEXT is a SQL clause attached to a query that takes a seed, expands the subgraph by BFS over selected edge kinds, and returns nodes ranked by a configurable scoring policy.

SELECT answer
FROM ask_llm('What changed in the auth flow last quarter?')
WITH CONTEXT (
    seed_text   = 'auth flow',
    seed_kinds  = ['function', 'doc', 'issue'],
    edge_kinds  = ['IMPORTS', 'REFERENCES', 'MENTIONS', 'BLOCKS'],
    hops        = 3,
    rerank      = 'centrality',
    alpha       = 0.3,
    limit       = 25
);

The clause is sugar over a pipeline: vector pre-filter on the seed, BFS expand respecting the edge kinds, centrality re-rank with the supplied alpha, top-limit. None of the steps are hidden — you can replace the clause with explicit SELECTs against the underlying tables and get the same answers, slower.

Centrality Re-Ranking and the Vector Prefilter

The default re-rank is centrality-biased. The wrapper over-fetches k × over_fetch_multiplier candidates, applies the row-level prefilter, then re-scores:

final_score = (1 − alpha) · vector_distance − alpha · centrality(node)

Higher-centrality nodes float to the top; vector similarity narrows the candidate set. alpha defaults to 0.3 — tuned on the acceptance fixture. The CHANGELOG ratifies a deliberate design choice: centrality is a post-rerank weighting, not an HNSW navigation bias. Forking hnsw_rs to inject centrality into descent would lift relevance further, but the cost is high. Descent-bias is a follow-up if the gap matters in the pilot.

Semantic-Merkle Invalidation

RAG indexes go stale. Re-embedding everything on every change is wasteful; embedding nothing leaves you with stale grounding. v3.19's answer is CREATE SEMANTIC HASH INDEX, which surfaces the underlying semantic-Merkle primitive as DDL:

CREATE SEMANTIC HASH INDEX IF NOT EXISTS
  graph_rag_chunks_hash ON _hdb_graph.nodes;

Subtree hashes detect what's stale at the document-subtree level rather than the row level. Re-ingest a 200-page PDF where one section changed; only that section's chunks re-embed.

Acceptance Numbers

v3.19 ships tests/with_context_bench.rs as the acceptance bench. 10k-node fixture, 100 queries, mean wall-time:

Workload Target Measured
WITH CONTEXT mean (10k-node fixture)≤ 500 ms62 ms
Linker precision (hand-labelled fixture)≥ 80 %100 %

When to Use This (and When Not)

Graph-RAG inside the database is right when you are already running HeliosDB and your graph is < ~10M nodes / ~50M edges in v3.19. Larger graphs are tracked for HeliosDB Lite and Full where storage scales out. It is the wrong choice when you are running a billion-node knowledge graph with a dedicated graph DB team and bespoke ranking.

What's Next

The next post covers the native MCP server — how the helios_graphrag_search tool surfaces over JSON-RPC, the streaming progress events that fire from inside WITH CONTEXT, and how Claude Code, Cursor, and Aider call the whole graph-RAG surface as tools.

← Back to Blog