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 incodebase_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:
codebase_rag/services/llm.py: HousesCypherGeneratorandcreate_rag_orchestrator, which coordinate LLM interaction and agent assemblycodebase_rag/tools/codebase_query.py: Containscreate_query_tool, wrapping the query execution into a Tool interface for the orchestratorcodebase_rag/services/__init__.py: DefinesQueryProtocol, the abstract interface for graph database operationscodebase_rag/constants/core.py: Stores forbidden Cypher keywords and allowed procedure prefixes for validationcodebase_rag/cli.py: Entry point that parses arguments and invokes the query orchestrator
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
CypherGeneratorclass incodebase_rag/services/llm.pyhandles 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_toolwraps the entire pipeline into a Tool interface consumable by the RAG orchestrator built viacreate_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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →