How Code-Graph-RAG Performs Natural Language Querying: From English to Cypher

Code-Graph-RAG converts English questions into executable Cypher queries by orchestrating an LLM-driven generation pipeline, safety-validated execution against Memgraph, and rich console rendering.

Code-Graph-RAG (CG-RAG) is an open-source tool that enables natural language querying of software repositories by translating free-form questions into precise knowledge graph traversals. According to the vitali87/code-graph-rag source code, the system implements a three-stage pipeline that bridges the gap between human intent and structured graph data without requiring manual Cypher syntax knowledge.

The Natural Language Querying Pipeline

The natural language querying capability relies on three tightly integrated stages that transform user input into tabular results.

Stage 1: LLM-Driven Cypher Generation

When a user submits a question, the CypherGenerator class in codebase_rag/services/llm.py wraps a large language model and prompts it with repository context. The LLM receives either the local cypher system prompt or the generic cypher system prompt, instructing it to output a Cypher query that retrieves the requested information from the knowledge graph.

The raw LLM response undergoes cleaning via _clean_cypher_response, which strips markdown fences, stray asterisks, and ensures a trailing semicolon. This produces sanitized Cypher ready for validation.

Stage 2: Safety Validation and Query Execution

Before touching the database, CG-RAG enforces strict safety constraints. The system runs three validation checks:

  • _validate_cypher_read_only: Ensures no write operations (CREATE, DELETE, SET, etc.) are present
  • _validate_no_unbounded_paths: Prevents queries that could scan the entire graph without limits
  • _validate_call_procedures: Restricts procedure calls to allowed prefixes defined in codebase_rag/constants/core.py

After validation, the query executes against a QueryProtocol implementation (typically Memgraph) inside an asynchronous asyncio.wait_for block. The execution respects settings.QUERY_TIMEOUT_S and caps results at settings.QUERY_RESULT_ROW_CAP. Results are then token-truncated via truncate_results_by_tokens to maintain manageable LLM context windows.

Stage 3: Rich Console Rendering

Final results flow through create_query_tool in codebase_rag/tools/codebase_query.py, which formats the data into a rich.Table displayed inside a Panel titled QUERY_RESULTS_PANEL_TITLE. If truncation occurred, the system emits QUERY_SUMMARY_TRUNCATED detailing kept rows, total rows, and token usage. Successful queries display QUERY_SUMMARY_SUCCESS.

Key Implementation Files

Understanding the natural language querying architecture requires familiarity with these specific modules:

Practical Implementation Example

The following programmatic example demonstrates the complete natural language querying flow without the interactive CLI:

import asyncio
from pathlib import Path
from codebase_rag.services.llm import CypherGenerator, create_rag_orchestrator
from codebase_rag.tools.codebase_query import create_query_tool
from codebase_rag.services import QueryProtocol
from rich.console import Console

# 1️⃣ Initialise the Cypher‑generating LLM

cypher_gen = CypherGenerator(active_projects=["my‑project"])

# 2️⃣ Initialise a QueryProtocol (e.g. Memgraph client)

ingestor: QueryProtocol = ...  # obtain a concrete implementation

# 3️⃣ Build the query tool that the orchestrator can invoke

query_tool = create_query_tool(ingestor, cypher_gen, console=Console())

# 4️⃣ Assemble the RAG orchestrator with the tool

agent, _ = create_rag_orchestrator(tools=[query_tool])

# 5️⃣ Ask a natural‑language question

nl_query = "Which functions call `fetch_data` and are not used elsewhere?"
result = asyncio.run(agent.run(nl_query))

# `result` is a `QueryGraphData` instance containing the generated Cypher,

# the raw rows from Memgraph, and a human‑readable summary.

print(result.summary)

Running this pipeline sends the English question to the LLM, receives a Cypher query such as MATCH (f:Function)-[:CALLS]->(c:Function {name: "fetch_data"}) RETURN f.name, f.file, validates it for safety, executes against Memgraph, and renders a formatted table of matching functions.

Summary

  • Code-Graph-RAG enables natural language querying by chaining LLM-based Cypher generation, safety validation, and Memgraph execution.
  • The CypherGenerator class in codebase_rag/services/llm.py handles prompt management, response cleaning, and read-only validation.
  • Three safety checks (_validate_cypher_read_only, _validate_no_unbounded_paths, _validate_call_procedures) prevent dangerous queries before execution.
  • Results are automatically truncated by both row count (QUERY_RESULT_ROW_CAP) and token count to protect downstream context limits.
  • create_query_tool wraps the entire pipeline into a Tool interface consumable by the RAG orchestrator built via create_rag_orchestrator.

Frequently Asked Questions

How does Code-Graph-RAG prevent malicious Cypher queries?

The system implements multi-layer validation in CypherGenerator that scans for forbidden keywords (CREATE, DELETE, DROP, etc.), unbounded path patterns, and unauthorized procedure calls. Only queries passing all three checks (_validate_cypher_read_only, _validate_no_unbounded_paths, _validate_call_procedures) execute against the Memgraph instance, ensuring read-only, bounded operations.

What happens if a natural language query generates invalid Cypher?

If the LLM returns malformed Cypher or violates safety constraints, the validation layer rejects the query before execution. The _clean_cypher_response method handles formatting issues like markdown fences, but semantic errors or policy violations trigger early termination with an error message, preventing database errors or unsafe operations.

Can I use Code-Graph-RAG with graph databases other than Memgraph?

Yes, through the QueryProtocol abstraction defined in codebase_rag/services/__init__.py. While the reference implementation targets Memgraph, any class implementing this protocol can substitute the graph backend. The create_query_tool function accepts any QueryProtocol instance, decoupling the orchestration logic from specific database implementations.

How does the system handle large query results?

Results undergo dual truncation: first by row count using settings.QUERY_RESULT_ROW_CAP, then by token count via truncate_results_by_tokens. This keeps the result set manageable for both console display and subsequent LLM processing. When truncation occurs, the QUERY_SUMMARY_TRUNCATED message reports the final statistics to the user.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →