# Generating Cypher Queries from Natural Language for Code-Graph-RAG: A Complete Implementation Guide

> Generate Cypher queries from natural language for Code-Graph-RAG. Discover how the vitali87/code-graph-rag repository uses LLMs and multi-layer validation for safe, bounded query generation.

- Repository: [Vitali Avagyan/code-graph-rag](https://github.com/vitali87/code-graph-rag)
- Tags: how-to-guide
- Published: 2026-08-15

---

**Code-Graph-RAG converts natural language questions about codebases into safe, bounded Cypher queries through an LLM-driven `CypherGenerator` class with multi-layer validation, as implemented in the `vitali87/code-graph-rag` repository.**

The `code-graph-rag` project bridges natural language and graph databases by translating user questions into executable Cypher queries against a Memgraph knowledge graph. This article examines the complete pipeline—from prompt construction through safety validation—based on the actual source code implementation.

## Architecture Overview

The system contains three tightly-coupled components responsible for the NL-to-Cypher transformation:

| Component | Role | Core Source |
|---|---|---|
| **Multi-language parser** | Extracts symbols (functions, classes, modules, imports) using Tree-sitter | `cgr` package |
| **CypherGenerator** | LLM-driven translator with sanitization and validation | [`codebase_rag/services/llm.py`](https://github.com/vitali87/code-graph-rag/blob/main/codebase_rag/services/llm.py) |
| **RAG orchestrator** | Combines semantic search, graph queries, and file reads | [`codebase_rag/services/llm.py`](https://github.com/vitali87/code-graph-rag/blob/main/codebase_rag/services/llm.py) (`create_rag_orchestrator`) |

The following sections trace how a natural language query flows through these components to become an executable Cypher statement.

## Step 1: Prompt Construction with Embedded Schema

Every NL-to-Cypher translation begins with `build_cypher_system_prompt` in [`codebase_rag/prompts.py`](https://github.com/vitali87/code-graph-rag/blob/main/codebase_rag/prompts.py) (lines 54-84). This function injects two critical artifacts into the system prompt:

- **`GRAPH_SCHEMA_DEFINITION`** — The complete graph schema describing nodes (`Function`, `Class`, `Module`, etc.) and relationships (`DEFINES`, `CALLS`, `INHERITS_FROM`, etc.)
- **`CYPHER_QUERY_RULES`** — Strict construction guidelines preventing common LLM errors

```python
from codebase_rag.prompts import build_cypher_system_prompt

# Scope to specific projects for smaller, more accurate schemas

system_prompt = build_cypher_system_prompt(active_projects=["myproject", "shared-lib"])

```

The resulting prompt constrains the LLM to valid graph traversals only, eliminating hallucinated node labels or relationship types.

## Step 2: Provider Selection and Agent Initialization

The `CypherGenerator` class dynamically instantiates the configured LLM provider. In [`codebase_rag/services/llm.py`](https://github.com/vitali87/code-graph-rag/blob/main/codebase_rag/services/llm.py) (lines 26-29):

```python
config = settings.active_cypher_config  # OpenAI, Ollama, etc.

llm = _create_provider_model(config)

```

The `Agent` is then constructed with the system prompt and bound to this model. The generator supports provider switching without code changes—configuration drives the selection.

## Step 3: Async Generation and Response Cleaning

The `generate` method ([`services/llm.py`](https://github.com/vitali87/code-graph-rag/blob/main/services/llm.py), lines 34-38) executes the agent:

```python
result = await self.agent.run(natural_language_query)
raw_output = result.output

```

Before validation, `_clean_cypher_response` (lines 46-53) strips formatting artifacts:

```python
def _clean_cypher_response(response: str) -> str:
    # Remove markdown fences, backticks, and 'cypher' prefix

    cleaned = response.strip()
    if cleaned.startswith("```"):
        cleaned = cleaned.split("```")[1]
    if cleaned.startswith("cypher"):
        cleaned = cleaned[6:]
    return cleaned.strip().strip("`")

```

This guarantees a plain query string regardless of how the LLM formats its output.

## Step 4: Multi-Layer Safety Validation

Three validation guards protect against harmful or expensive queries:

### Read-Only Enforcement

`_validate_cypher_read_only` (lines 80-86) blocks DML operations:

```python
forbidden = ["DELETE", "SET", "REMOVE", "CREATE", "MERGE"]
if any(keyword in query.upper() for keyword in forbidden):
    raise LLMGenerationError(f"Query contains forbidden keyword: {keyword}")

```

### Unbounded Path Prevention

`_validate_no_unbounded_paths` (lines 89-98) rejects variable-length paths without numeric limits:

```python

# Rejects: [:CALLS*]

# Allows:  [:CALLS*..5]

import re
if re.search(r"\*\s*[)\]]", query) and not re.search(r"\*\d+\.\.\d+", query):
    raise LLMGenerationError("Unbounded variable-length path detected")

```

### Procedure Whitelisting

`_validate_call_procedures` (lines 100-108) restricts `CALL` statements to approved prefixes:

```python
allowed_prefixes = settings.CYPHER_ALLOWED_PROCEDURE_PREFIXES  # e.g., ["gds.", "apoc."]

if not any(proc.startswith(prefix) for prefix in allowed_prefixes):
    raise LLMGenerationError(f"Procedure {proc} not in whitelist")

```

All validators raise `LLMGenerationError` from [`codebase_rag/exceptions.py`](https://github.com/vitali87/code-graph-rag/blob/main/codebase_rag/exceptions.py) with actionable messages for debugging.

## Step 5: RAG Orchestration Integration

The `create_rag_orchestrator` function builds a higher-level agent that coordinates multiple tools. From [`codebase_rag/prompts.py`](https://github.com/vitali87/code-graph-rag/blob/main/codebase_rag/prompts.py) (lines 31-41), the orchestrator prompt includes:

- The same schema/rules block as the generator
- Tool descriptions for `query_graph`, `semantic_search`, `read_file`, etc.
- Project-specific instructions from [`.cgr.md`](https://github.com/vitali87/code-graph-rag/blob/main/.cgr.md) files

```python
from codebase_rag.services.llm import create_rag_orchestrator
from codebase_rag.tools import QueryGraphTool, SemanticSearchTool, ReadFileTool

tools = [QueryGraphTool(), SemanticSearchTool(), ReadFileTool()]
agent, _ = create_rag_orchestrator(
    tools,
    project_root=Path("/path/to/repo"),
    active_projects=["myproject"],
)

```

The orchestrator decides execution strategy—semantic search for intent classification, direct graph queries for structural questions, or combined approaches for complex requests.

## End-to-End Execution Flow

```

User NL Query
    │
    ▼
┌─────────────────┐
│  RAG Orchestrator│ ← Builds plan using available tools
│  (Agent + Tools) │
└────────┬────────┘
         │
    ┌────┴────┐
    ▼         ▼
Semantic   query_graph tool
Search     │
(optional) ▼
         ┌─────────────────┐
         │  CypherGenerator │ ← build_cypher_system_prompt()
         │    ───────────── │
         │    _create_provider_model()
         │    agent.run()   │
         │    _clean_cypher_response()
         │    _validate_*() │
         └────────┬─────────┘
                  ▼
              Memgraph
                  │
                  ▼
              Results → File reads → Final answer

```

## Practical Usage Examples

### Direct CypherGenerator Invocation

For programmatic access without the full orchestrator:

```python
from codebase_rag.services.llm import CypherGenerator
import asyncio

async def main():
    gen = CypherGenerator(active_projects=["myproject"])
    
    query = await gen.generate(
        "List all public functions in the utils module that have a @task decorator"
    )
    print(query)
    # Output:

    # MATCH (f:Function)-[:DEFINES]->(m:Module)

    # WHERE m.name = 'utils' AND 'task' IN f.decorators

    # RETURN f.name, f.path, f.qualified_name

    # LIMIT 50;

asyncio.run(main())

```

The generator automatically appends semicolons and guarantees all safety constraints are satisfied.

### CLI-Based Workflow

```bash

# Start infrastructure (Memgraph + Qdrant)

cgr daemon up

# Parse repository and populate graph

cgr start --repo-path /path/to/myproject --update-graph

# Interactive natural language queries

cgr query
> "Which classes inherit from BaseHandler and override the handle method?"

```

The CLI internally instantiates the same `CypherGenerator` and orchestrator described above.

## Key Source Files

| File | Purpose |
|------|---------|
| [`codebase_rag/services/llm.py`](https://github.com/vitali87/code-graph-rag/blob/main/codebase_rag/services/llm.py) | Core implementation: `CypherGenerator`, validators, orchestrator builder |
| [`codebase_rag/prompts.py`](https://github.com/vitali87/code-graph-rag/blob/main/codebase_rag/prompts.py) | System prompt construction with schema embedding |
| [`codebase_rag/exceptions.py`](https://github.com/vitali87/code-graph-rag/blob/main/codebase_rag/exceptions.py) | `LLMGenerationError` and custom exception types |
| [`codebase_rag/main.py`](https://github.com/vitali87/code-graph-rag/blob/main/codebase_rag/main.py) | CLI entry point (`cgr start`, `cgr query`) |
| [`docs/sdk/cypher-generator.md`](https://github.com/vitali87/code-graph-rag/blob/main/docs/sdk/cypher-generator.md) | Official SDK documentation |

## Summary

- **Prompt engineering drives accuracy**: The `build_cypher_system_prompt` function embeds the complete graph schema and strict Cypher rules, preventing hallucinated queries
- **Safety is layered, not optional**: Three validators (read-only, bounded paths, procedure whitelist) execute before any query reaches Memgraph
- **Async-first architecture**: The `generate()` method is `async` for production throughput
- **Orchestrator enables complex workflows**: The `create_rag_orchestrator` function combines NL-to-Cypher with semantic search and file operations
- **Configuration-driven providers**: OpenAI, Ollama, and other backends swap via `settings.active_cypher_config` without code changes

## Frequently Asked Questions

### What LLM providers does Code-Graph-RAG support for Cypher generation?

The system supports any provider compatible with the internal provider model interface, including OpenAI (GPT-4, GPT-3.5) and Ollama (local models). Provider selection occurs through `settings.active_cypher_config` in [`codebase_rag/services/llm.py`](https://github.com/vitali87/code-graph-rag/blob/main/codebase_rag/services/llm.py) (lines 26-29), with instantiation handled by `_create_provider_model`.

### How does the system prevent LLMs from generating destructive Cypher queries?

Three sequential validators enforce safety: `_validate_cypher_read_only` blocks DML keywords like `DELETE` and `SET`; `_validate_no_unbounded_paths` rejects variable-length paths without numeric upper bounds; and `_validate_call_procedures` whitelists allowed procedure prefixes. Any failure raises `LLMGenerationError` from [`codebase_rag/exceptions.py`](https://github.com/vitali87/code-graph-rag/blob/main/codebase_rag/exceptions.py).

### Can I restrict Cypher generation to specific projects or codebases?

Yes. Pass `active_projects=["project-name"]` to `CypherGenerator` or `create_rag_orchestrator`. This scopes both the embedded schema (reducing token usage and hallucination risk) and the resulting queries (limiting to relevant subgraphs).

### What happens when the LLM returns Cypher wrapped in markdown fences?

The `_clean_cypher_response` function in [`codebase_rag/services/llm.py`](https://github.com/vitali87/code-graph-rag/blob/main/codebase_rag/services/llm.py) (lines 46-53) automatically strips markdown code blocks, backticks, and the `cypher` language identifier prefix. This ensures validators receive plain query strings regardless of LLM output formatting.

### How does the RAG orchestrator decide between semantic search and graph queries?

The orchestrator prompt in [`codebase_rag/prompts.py`](https://github.com/vitali87/code-graph-rag/blob/main/codebase_rag/prompts.py) (lines 31-41) equips the LLM with tool descriptions for both `semantic_search` and `query_graph`. The model plans its approach based on the natural language question—using semantic search for vague intent ("find authentication code"), graph queries for precise structural relationships ("what calls `validate_token`"), or combinations thereof.