Qwen3-1.7B · Text2Cypher
Given a Neo4j graph schema and a question in plain English, it writes the read-only Cypher query that answers it. It is generic: the schema is part of every prompt, so it works with any graph database without retraining. Small enough to run locally with Ollama (1.8 GB).
Schema:
Node labels:
(:Service {name: STRING, environment: STRING, version: STRING, criticality: STRING})
(:Database {name: STRING, engine: STRING})
(:Team {name: STRING})
Relationships:
(:Service)-[:CALLS]->(:Service)
(:Service)-[:USES]->(:Database)
(:Team)-[:OWNS]->(:Service)
Notes:
- Service.environment: 'prd' = production, 'pre' = staging, 'dev' = development
- Service.criticality: 'high', 'medium', 'low'
- CALLS: the source service calls the target service
- Glossary: 'the core' = services owned by the team 'Platform'
Question: What breaks if auth fails?
→ MATCH (x:Service {name: 'auth'})<-[:CALLS*1..10]-(n:Service) RETURN DISTINCT n.name
Quick start
ollama pull hf.co/Ernes14AG/qwen3-1.7b-text2cypher-GGUF:Q8_0
This repository also includes ready-to-use scripts (pip install neo4j):
schema_neo4j.py: reads your database schema in the format below, with category values and your notes.ask_cypher.py: question → Cypher →EXPLAIN→ read-only execution → table.mcp_tool_example.py: the same as an MCP tool (FastMCP), so an agent can query the graph.load_example.py+notes_example.json: a small service topology to try it on an empty Neo4j.
export NEO4J_URI=bolt://localhost:7687 NEO4J_USER=neo4j NEO4J_PASSWORD=...
python ask_cypher.py --notes notes_example.json
Prompt format
Schema:
Node labels:
(:Label {prop: TYPE, ...})
Relationships:
(:A)-[:TYPE]->(:B)
Notes:
- Label.prop: 'code' = meaning, ...
- TYPE: what the relationship means
- Glossary: 'term' = what it refers to
Question: your question
The notes are optional but valuable: coded values ('prd' = production), possible values,
relationship meaning and internal jargon. The model is trained to use them. Other schema
formats (such as the ones in the Neo4j text2cypher dataset) also work.
Always run the generated query in a read-only transaction (session.execute_read) and check it
with EXPLAIN first: safety comes from the database, not from the model. The included scripts do both.
Results
Correct = running the generated query returns the same result as the reference query.
| Test set | Qwen3-1.7B base | Fine-tuned |
|---|---|---|
| synthetic (300 questions) | 14% | 94% |
| neo4j official (1 questions) | 0% | 0% |
| topology (15 questions) | 13% | 87% |
| synthetic · multi hop | 6% | 86% |
| synthetic · one hop | 23% | 95% |
| synthetic · properties | 13% | 99% |
| synthetic · domains seen in training | 15% | 100% |
| synthetic · domains never seen | 13% | 87% |
- synthetic: new schemas and data; half of them from domains never seen in training (banking, healthcare, software packages).
- neo4j official: questions from the test split of neo4j/text2cypher-2025v1, run on the public Neo4j Labs demo databases.
- topology: 15 hand-written questions on a service topology, with natural phrasings such as "What breaks if auth fails?" or "Which services have no owner?".
Training data
- 3000 synthetic examples, every reference
query verified by running it in Neo4j: 24 question types, from filters and aggregations to impact
analysis (
<-[:R*1..10]-), shortest paths, missing relationships and glossary terms. - 11 examples from neo4j/text2cypher-2025v1 (Apache-2.0), filtered to valid read-only queries, without the CySpider subset.
LoRA (r=32) on Qwen/Qwen3-1.7B, 1 epoch(s).
Limitations
- English only.
- Very complex queries (long
UNIONs, nested subqueries, procedures) may fail. - For large schemas, pass only the labels and relationships relevant to the question.
- Cypher for Neo4j 5.
Versions: Ernes14AG/qwen3-1.7b-text2cypher (full weights) · Ernes14AG/qwen3-1.7b-text2cypher-GGUF (GGUF for Ollama + scripts)