GraphCypherQAChain

GraphCypherQAChain is the core of Graph RAG. It takes a natural-language question, asks an LLM to write Cypher against your schema, runs the Cypher against Neo4j, and asks the LLM again to turn the raw result into a human-readable answer. Two LLM calls bookending one database query, wrapped in one chain.

GraphCypherQAChain lifecycle

The path from a natural-language question to a grounded answer.

graph_rag.py
python
from langchain_neo4j import GraphCypherQAChain

class GraphRAG:
    def __init__(self, graph_store, vector_store=None):
        self.graph_store = graph_store
        self.vector_store = vector_store
        self._chain = None  # lazy GraphCypherQAChain

    def _chain_or_raise(self):
        if self._chain is not None:
            return self._chain
        graph = self.graph_store.connect()
        self._chain = GraphCypherQAChain.from_llm(
            graph=graph,
            llm=_build_chat_model(),
            verbose=False,
            return_intermediate_steps=True,
            allow_dangerous_requests=True,
        )
        return self._chain

return_intermediate_steps=True is essential. Without it, you get only the final answer and have no way to see or debug the Cypher. allow_dangerous_requests=True acknowledges that generated Cypher can DELETE; in production you would sandbox or restrict the chain to read-only queries.

graph_rag.py
python
def ask(self, question):
    chain = self._chain_or_raise()
    result = chain.invoke({"query": question})
    cypher, graph_context = self._extract_steps(result)
    return {
        "question": question,
        "answer": result.get("result", ""),
        "cypher": cypher,
        "graph_context": graph_context,
    }

def _extract_steps(self, chain_result):
    cypher = None
    graph_context = None
    for step in chain_result.get("intermediate_steps", []) or []:
        if not isinstance(step, dict):
            continue
        if "query" in step and cypher is None:
            cypher = step["query"]
        if "context" in step:
            graph_context = step["context"]
    return cypher, graph_context

Pulling the generated Cypher and the raw context out of intermediate_steps is what makes the /ask response debuggable. When an answer looks wrong, you can paste the Cypher into the Neo4j browser and see exactly what the chain retrieved.

It tells LangChain that you accept the risk of the chain running Cypher that mutates or deletes data. By default the chain refuses to run queries it judges potentially destructive. For a workshop against a dev database this is fine. In production you either constrain the LLM with a tighter prompt, use a read-only Neo4j user, or both. The flag is an informed opt-in, not a magic switch.

Quiz: Quiz

Loading practice…