# graphrag-neo4j GraphRAG on Spring AI 2.0 and Neo4j, next to plain vector RAG over the same documents. Companion code for [GraphRAG in Java with Neo4j and Spring AI: Turning Documents into a Knowledge Graph](https://ankurm.com/graphrag-java-neo4j-spring-ai-knowledge-graph/). **Read this first: no language model is called anywhere in this module.** The extraction step runs through the real `ChatClient.entity(...)` against `RuleBasedExtractorModel`, a stand-in that finds eight fixed sentence patterns with regular expressions and answers with the JSON a good model would. Everything downstream of it (Neo4j, the vector indexes, MiniLM embeddings, the Cypher, the retrieval numbers) is real. Nothing here says how well a real model extracts from real prose. Answer generation is not exercised at all; the tests print the prompt that would be sent. ## Versions | | | |---|---| | Spring Boot | 4.1.2 | | Spring AI | 2.0.2 (`spring-ai-client-chat`, `spring-ai-neo4j-store`, `spring-ai-transformers`) | | Neo4j server | `neo4j:2026.10.1-community` (Docker); also run on `neo4j:5-community`, see `output/03b-graph-build-neo4j-5.txt` | | Neo4j Java driver | 6.1.0 (managed by Boot) | | Testcontainers | 2.0.5 (`testcontainers-neo4j`) | | Embeddings | all-MiniLM-L6-v2 (384 dims) in-process through ONNX Runtime, as in `rag-ingestion` | | Java | 25 (Temurin 25.0.5+8) | ## Run it ```bash ./scripts/run-all.sh # regenerates output/ (needs Docker; first run downloads MiniLM and the Neo4j image) NEO4J_IMAGE=neo4j:5-community ./scripts/run-all.sh ``` A full run takes a few minutes: the Neo4j container needs about a minute to start, and the sweep test runs a few hundred retrievals. ## Layout | File | What it does | |---|---| | `Extraction`, `EntityType`, `RelationType` | The record the model must fill; the enums are the closed vocabulary and the allow-list for relationship types put into Cypher | | `Extractor` | `ChatClient.entity(Extraction.class)` per chunk, then drops relations whose endpoints were never listed | | `stub/RuleBasedExtractorModel` | The stand-in for the model, with failure modes (fenced JSON, truncated JSON, unknown and hostile relation types, dangling relation) | | `EntityResolver` | Merges names: exact key, alias table, optional embedding-similarity threshold | | `GraphStore` | Every Cypher statement: schema, vector index, writes, variable-length paths | | `GraphRetriever` | Span linking through the entity vector index, path walk, path ranking | | `Pipeline` | Chunks into `Neo4jVectorStore`, extraction, resolution, graph write; the plain vector search used as the baseline | | `Answerer` | Builds the final prompt from selected paths and source passages | | `Question` | The 9 evaluation questions and, for each, the documents that together hold the facts | | `src/main/resources/corpus/` | 35 short invented documents: 21 with facts, 14 distractors that share the vocabulary | ## Captured output | File | Shows | |---|---| | `output/01-extraction.txt` | The exact user message (with the JSON schema Spring AI appends), the parsed record, call and size totals | | `output/02-extraction-failures.txt` | What each bad reply does | | `output/03-graph-build.txt` | Server version, the two vector indexes, the duplicate-entity problem and what each merge threshold does | | `output/03b-graph-build-neo4j-5.txt` | The same test against `neo4j:5-community` | | `output/04-vector-vs-graph.txt` | Plain vector RAG against GraphRAG, per question | | `output/05-graph-sweeps.txt` | Hops, paths kept and link threshold against the result; path counts by hop | | `output/06-prompt.txt` | The prompt GraphRAG builds for one question | | `output/07-fixed-shape-cypher.txt` | Hand-written Cypher for questions whose shape is known | ## What the numbers are and are not The corpus is small and invented, the nine questions were written by hand after the corpus, and the default retriever settings (3 hops, 6 paths, link threshold 0.80) were fixed before the first result. The sweep in `05` was run afterwards, on the same questions, so a best cell in that table is tuned on the test set. "Complete" means every supporting document is in the context; it does not mean a model answered correctly.