Natural-language queries (Text2Cypher)

Tip

On a JenaGraph use Natural-language queries in SPARQL (Text2SPARQL), or Natural-language queries on any backend (Text2Query) to pick the right translator for any backend.

Optional module

  • Install: only on Neo4jGraph: pip install "cvcdocdb[graphrag]" (neo4j-graphrag, Python >= 3.10). Nothing extra on NetworkXGraph or MemgraphGraph.

  • Import: from cvcdocdb import Text2Cypher

cvcdocdb.text2cypher lets you ask questions in natural language to a cvcdocdb graph. An LLM translates the question to Cypher and the query is run on the graph. The API is the same for every backend, so a script doesn’t change when you switch from NetworkXGraph to Neo4jGraph or back.

The LLM is passed as a handle that already carries its own configuration (API key, endpoint, model…):

  • a plain prompt: str -> str callable, e.g. a wrapper around an in-house LLM client, or

  • any object with an invoke(prompt) method returning an answer with a .content string (e.g. the neo4j_graphrag LLM classes).

Quick-start

from cvcdocdb import Text2Cypher, Text2CypherError

def ask(graph, question, llm):
    """Works unchanged with any backend."""
    try:
        result = Text2Cypher(graph, llm=llm).query(question)
    except Text2CypherError as exc:
        print("The LLM produced an unusable query:", exc)
        return None
    print(result.cypher)   # the Cypher generated by the LLM
    return result.records  # e.g. [{'n': 42}], shaped like graph.query(cypher)

ask(graph, "Quantes persones viuen a Caldes d'Estrac el 1905?",
    llm=lambda prompt: my_client.complete(prompt))

Backends

  • Neo4jGraph — delegates to neo4j_graphrag’s Text2CypherRetriever, on the graph’s own driver and database (no Neo4j credentials passed again). Needs the optional dependency (Python >= 3.10):

    pip install cvcdocdb[graphrag]
    

    retriever exposes the underlying retriever, e.g. to plug it into a neo4j_graphrag GraphRAG pipeline.

  • MemgraphGraph — handled by cvcdocdb itself, with no extra dependency (neo4j_graphrag doesn’t work with Memgraph: its retriever runs CALL dbms.components() without YIELD, which Memgraph rejects). The query is checked for write clauses and run with graph.query(cypher), and the schema is read with plain Cypher (Memgraph has no db.schema.*). retriever is None.

  • NetworkXGraph (or any backend whose query() accepts Cypher) — handled by cvcdocdb itself, with no extra dependency; the query is run with graph.query(cypher). NetworkXGraph evaluates the common read-only Cypher (node/relationship patterns, WHERE, WITH, aggregations, ORDER BY, SKIP/LIMIT…) with the same results as Neo4j — see cvcdocdb.nx_cypher for the supported subset. If the LLM generates syntax outside it (variable-length paths, UNWIND, CASE…), Text2CypherError is raised instead of returning wrong results.

Notes

  • Only read-only Cypher is executed; anything else raises Text2CypherError before running. On Neo4j the query is planned with EXPLAIN first; on other backends it is checked for write clauses (CREATE, MERGE, SET, DELETE…).

  • The schema shown to the LLM is introspected from the graph (on Neo4j with built-in db.schema.* procedures, so the APOC plugin is not required). It is the same text on every backend for the same graph: every label of a node (main and alternative), labels and properties in alphabetical order. Properties starting with _ and cvcdocdb bookkeeping attributes are left out. Pass schema=... to provide your own schema text, examples=[...] for few-shot examples, or custom_prompt=... (with {schema}, {examples} and {query_text} placeholders) to replace the prompt.

Natural-language questions over any cvcdocdb graph backend.

Text2Cypher gives the same API whatever the backend, so a script doesn’t change when the graph does:

from cvcdocdb import Text2Cypher

t2c = Text2Cypher(graph, llm=my_llm)     # graph: Neo4jGraph, NetworkXGraph...
result = t2c.query("Quantes persones viuen a Caldes d'Estrac?")
result.cypher    # "MATCH (i:IndividuPadro)-[:VIU_A]->(l:LlocPadro ...) ..."
result.records   # [{'n': 42}] — same shape as graph.query(cypher)

An LLM translates the question to Cypher, which is then run on the graph. Only read-only Cypher is ever executed; anything else raises Text2CypherError.

The LLM is a handle that already carries its own configuration (API key, endpoint, model…), so nothing else has to be passed:

  • a plain Python callable prompt: str -> str (wrapped in CallableLLM), e.g. a wrapper around any in-house LLM client, or

  • any object with an invoke(prompt) method returning something with a .content string — e.g. the neo4j_graphrag LLM classes.

Backends:

  • Neo4jGraph — delegates to neo4j_graphrag’s Text2CypherRetriever on the graph’s own driver and database (no Neo4j credentials passed again), which checks the query with EXPLAIN before running it. Requires the optional dependency: pip install cvcdocdb[graphrag].

  • MemgraphGraph — handled by cvcdocdb itself like the backends below (neo4j_graphrag’s retriever doesn’t work on Memgraph), with the schema read with plain Cypher. No extra dependency.

  • Any other backend with a Cypher-capable query() (e.g. NetworkXGraph) — handled by cvcdocdb itself, no extra dependency; the query is checked for write clauses and run with graph.query(). On NetworkXGraph that evaluates the common read-only Cypher (patterns, WHERE, WITH, aggregations, ORDER BY…) with Neo4j semantics (see cvcdocdb.nx_cypher); a query using syntax it doesn’t support (variable-length paths, UNWIND, CASE…) raises Text2CypherError instead of returning wrong results.

class cvcdocdb.text2cypher.CallableLLM(fn, model_name='callable')[source]

Bases: object

Wrap a plain prompt: str -> str callable as an LLM handle.

Accepts the call styles LLM clients (and neo4j_graphrag) use: a prompt string, optionally with system_instruction/ message_history, or a list of messages (dicts or objects with content); messages are flattened into a single prompt.

Parameters:
  • fn (Callable[[str], str]) – Function that sends a prompt to an LLM and returns its text answer.

  • model_name (str) – Informative name only.

__init__(fn, model_name='callable')[source]
Parameters:
Return type:

None

invoke(input, message_history=None, system_instruction=None, **kwargs)[source]
Parameters:
  • input (Any)

  • message_history (Any)

  • system_instruction (str | None)

  • kwargs (Any)

Return type:

LLMResponse

async ainvoke(input, message_history=None, system_instruction=None, **kwargs)[source]
Parameters:
  • input (Any)

  • message_history (Any)

  • system_instruction (str | None)

  • kwargs (Any)

Return type:

LLMResponse

class cvcdocdb.text2cypher.LLMResponse(content)[source]

Bases: object

Minimal LLM answer: just its text content.

Parameters:

content (str)

content: str
__init__(content)
Parameters:

content (str)

Return type:

None

class cvcdocdb.text2cypher.Text2Cypher(graph, llm, *, schema=None, examples=None, custom_prompt=None, database=None)[source]

Bases: object

Ask natural-language questions to a cvcdocdb graph, whatever its backend.

Unless schema is given, the graph schema shown to the LLM is introspected from the graph (on Neo4j with built-in db.schema.* procedures, so the APOC plugin is not needed). It is the same text on every backend for the same graph. Properties starting with _ and cvcdocdb bookkeeping attributes are left out of it.

Parameters:
  • graph (Any) – The graph to query: a Neo4jGraph, a MemgraphGraph, a NetworkXGraph, or any backend whose query() accepts a Cypher string.

  • llm (Any) – The LLM handle — a prompt: str -> str callable, or an object with invoke(prompt) returning an answer with .content.

  • schema (str | None) – Schema text for the prompt; introspected when omitted.

  • examples (List[str] | None) – Optional example question/Cypher pairs for the prompt.

  • custom_prompt (str | None) – Optional prompt template replacing DEFAULT_PROMPT, with {schema}, {examples} and {query_text} placeholders.

  • database (str | None) – Neo4j only — database to query; defaults to the graph’s.

retriever

On Neo4j, the underlying neo4j_graphrag Text2CypherRetriever (e.g. to plug it into a GraphRAG pipeline); None on other backends.

Raises:
  • TypeError – If graph has no query() method, or llm is neither a callable nor an object with invoke.

  • ImportError – On a Neo4jGraph, if neo4j-graphrag is missing.

Parameters:
  • graph (Any)

  • llm (Any)

  • schema (str | None)

  • examples (List[str] | None)

  • custom_prompt (str | None)

  • database (str | None)

__init__(graph, llm, *, schema=None, examples=None, custom_prompt=None, database=None)[source]
Parameters:
  • graph (Any)

  • llm (Any)

  • schema (str | None)

  • examples (List[str] | None)

  • custom_prompt (str | None)

  • database (str | None)

Return type:

None

query(question)[source]

Translate question to Cypher with the LLM, run it, return the rows.

Raises:

Text2CypherError – If the generated Cypher is invalid or not read-only (nothing is executed in that case).

Parameters:

question (str)

Return type:

Text2CypherResult

exception cvcdocdb.text2cypher.Text2CypherError[source]

Bases: Text2QueryError

The LLM’s answer could not be run: invalid or non-read-only Cypher.

class cvcdocdb.text2cypher.Text2CypherResult(cypher, records, metadata=<factory>)[source]

Bases: object

Answer to a natural-language question.

Parameters:
cypher

The Cypher query generated by the LLM (and executed).

Type:

str

records

Query results, one dict per row, shaped like graph.query(cypher)’s output.

Type:

List[Dict[str, Any]]

metadata

Extra metadata (cypher, and the retriever’s own on Neo4j).

Type:

Dict[str, Any]

__init__(cypher, records, metadata=<factory>)
Parameters:
Return type:

None

exception cvcdocdb.text2cypher.Text2QueryError[source]

Bases: Exception

Base class of the natural-language query errors (Text2Cypher, Text2SPARQL, Text2Query): the LLM’s answer could not be run.

cvcdocdb.text2cypher.collect_schema(graph)[source]

(node_props, rel_props, relationships) of a graph, read through the common GraphStore API (see format_schema() for their shape). Every label of a node (main and alternative) is included, as on Neo4j.

Parameters:

graph (Any)

Return type:

Tuple[Dict[str, List[Tuple[str, str]]], Dict[str, List[Tuple[str, str]]], List[Tuple[str, str, str]]]

cvcdocdb.text2cypher.extract_cypher(text)[source]

Return the Cypher in an LLM answer (inside a code fence if there is one).

Parameters:

text (str)

Return type:

str

cvcdocdb.text2cypher.format_schema(node_props, rel_props, relationships)[source]

Render a graph schema as the text given to the LLM (same layout as neo4j_graphrag.schema.get_schema).

Parameters:
Return type:

str

cvcdocdb.text2cypher.is_read_only_cypher(cypher)[source]

Whether cypher has no clause that could modify the graph (or call a procedure). String literals and backtick-quoted names are ignored.

Parameters:

cypher (str)

Return type:

bool