This repository contains a local workspace for experimenting with knowledge-graph workflows, Neo4j, and graph-oriented retrieval for RAG-style use cases.
The current focus is the kg4rag demo, which generates a synthetic media-operations dataset, loads it into Neo4j, and provides a practical graph structure for multi-hop retrieval experiments.
docker-compose.ymlRuns Neo4j locally, plus optional Ollama and Weaviate services.kg4rag/kg-data-gen.pyGenerates synthetic CSV data for titles, versions, rights, localization, delivery specs, and delivery requests.kg4rag/kg-data-loader.pyLoads the generated CSV files into Neo4j using the Python driver.kg4rag/kg_demo_data/Output folder for generated CSV files and supporting demo artifacts.app/FastAPI web service: natural-language question → LLM-generated Cypher → Neo4j → synthesized answer.tests/Unit and integration test suite, including a gold-query evaluation dataset.notebooks/Notebooks for graph-related exploration.
The Docker setup provides these services:
Neo4j- Bolt:
bolt://localhost:7687 - Browser:
http://localhost:7474 - Default credentials:
neo4j/testpass
- Bolt:
Ollama- API:
http://localhost:11434
- API:
Weaviate- HTTP API:
http://localhost:8080
- HTTP API:
If you only need the graph database for the kg4rag workflow, Neo4j is the main required service.
This project uses uv and defines dependencies in pyproject.toml.
Create or sync the environment with:
uv syncIf you prefer pip, the dependency list is mirrored in requirements.txt.
To start Neo4j only:
docker compose up -d neo4jTo start all services:
docker compose up -dThe main generator builds a synthetic media supply-chain dataset with entities such as:
TitleVersionClientRegionLanguageDeliveryPointRightsLocalizationJobDeliverySpecDeliveryRequest
Run the generator with:
uv run python kg4rag/kg-data-gen.pyBy default, the generated files are written to:
kg4rag/kg_demo_data/
The generator creates CSV files such as:
titles.csvversions.csvclients.csvregions.csvlanguages.csvrights.csvlocalization.csvdelivery_points.csvdelivery_specs.csvdelivery_requests.csv
After generating the CSV files, load them into Neo4j with:
uv run python kg4rag/kg-data-loader.pyThe loader:
- reads CSV files directly from
kg4rag/kg_demo_data - creates Neo4j constraints for the main node identifiers
- loads nodes in batches
- creates the main graph relationships after node import
By default, the loader connects with:
NEO4J_URI=bolt://localhost:7687NEO4J_USER=neo4jNEO4J_PASSWORD=testpass
You can override them with environment variables:
NEO4J_URI=bolt://localhost:7687 \
NEO4J_USER=neo4j \
NEO4J_PASSWORD=testpass \
KG_DATA_DIR=kg4rag/kg_demo_data \
uv run python kg4rag/kg-data-loader.pyImportant:
- the loader currently starts with
MATCH (n) DETACH DELETE n - this clears the entire Neo4j database before reloading the demo data
The repository now includes a FastAPI web service that lets a user submit a natural-language question, translates that question into read-only Cypher, executes the query against Neo4j, and sends the retrieved records to an LLM for a natural-language answer.
The service code lives under:
The QA flow is intentionally agentic in a narrow, practical sense:
- plan a Cypher query from the user question
- execute the query against Neo4j
- repair and retry if Neo4j rejects the generated Cypher
- synthesize a grounded answer from the retrieved rows
- Neo4j running locally
- Ollama running locally
- a local Ollama model pulled, for example:
ollama pull llama3.2If you use Docker Compose from this repo, start Neo4j and Ollama with:
docker compose up -d neo4j ollamaIf you need the default model locally outside Compose, pull it with:
ollama pull llama3.2To run the API in Docker as well:
docker compose up -d api neo4j ollamaIn Docker Compose, the API container connects to Neo4j and Ollama over the internal service network using:
NEO4J_URI=bolt://neo4j:7687OLLAMA_BASE_URL=http://ollama:11434
Compose also starts a one-shot ollama-pull service that ensures the configured model is available before the API starts. By default it pulls llama3.2, and you can override that with OLLAMA_MODEL.
uv run uvicorn app.main:app --reload --host 0.0.0.0 --port 8000After the server starts, open the browser UI at:
http://localhost:8000/
The page lets you submit a question, inspect the generated Cypher, and review the returned rows and agent trace.
The service uses these defaults:
NEO4J_URI=bolt://localhost:7687NEO4J_USER=neo4jNEO4J_PASSWORD=testpassNEO4J_DATABASE=neo4jOLLAMA_BASE_URL=http://localhost:11434OLLAMA_MODEL=llama3.2
Optional tuning:
LOG_LEVEL=INFOTRACING_ENABLED=falseTRACING_SERVICE_NAME=knowledge-graph-apiOTEL_TRACES_EXPORTER_ENDPOINT=http://localhost:4318/v1/tracesQA_MAX_CONCURRENCY=64QA_QUEUE_TIMEOUT_MS=250MAX_QUERY_RETRIES=2NEO4J_MAX_CONNECTION_POOL_SIZE=100OLLAMA_TIMEOUT_SECONDS=60OLLAMA_MAX_CONNECTIONS=200OLLAMA_MAX_KEEPALIVE_CONNECTIONS=50RESULT_ROW_LIMIT=25CYPHER_TIMEOUT_MS=15000
GET /healthGET /api/v1/metricsGET /api/v1/metrics/prometheusGET /api/v1/schemaPOST /api/v1/ask
Compatibility routes are also kept for the current browser UI and existing callers:
GET /metricsGET /metrics/prometheusGET /schemaPOST /api/ask
The metrics middleware tracks in-memory request counts, status codes, in-flight requests, exceptions, and per-route timing summaries. Responses also include an X-Response-Time header. The Prometheus endpoint exposes scrapeable counters, gauges, and histograms for production monitoring.
For higher-concurrency environments, the API now uses:
- async Neo4j access via the async driver
- bounded request admission control for expensive QA flows
- configurable Neo4j and Ollama connection pools
- a Prometheus scrape endpoint at
/api/v1/metrics/prometheus - OpenTelemetry tracing exported to Jaeger
Prometheus does not ingest logs directly. This repository now includes a small observability stack where:
- Prometheus scrapes FastAPI metrics
- Loki stores logs
- Promtail ships container logs from Docker to Loki
- Grafana provides a single UI for metrics, logs, and traces
- Jaeger stores and visualizes distributed traces from FastAPI requests
To start the app together with observability services:
docker compose --profile all up -d api prometheus loki promtail jaeger grafanaEndpoints:
- Grafana UI:
http://localhost:3000 - FastAPI metrics:
http://localhost:8000/api/v1/metrics/prometheus - Prometheus UI:
http://localhost:9090 - Loki API:
http://localhost:3100 - Jaeger UI:
http://localhost:16686
Grafana notes:
- Grafana is provisioned automatically with Prometheus, Loki, and Jaeger datasources.
- Default credentials are
admin/adminunless overridden withGRAFANA_ADMIN_USERandGRAFANA_ADMIN_PASSWORD. - If you prefer browser access without login for local demos, set
GRAFANA_ANONYMOUS_ENABLED=true. - FastAPI logs now include
trace_id=andspan_id=fields so Loki log lines can link directly into Jaeger traces from Grafana Explore. - The default Grafana dashboard includes HTTP, QA, Neo4j, and Ollama panels plus a trace-correlated log stream.
Tracing notes:
- The API exports traces to Jaeger over OTLP HTTP.
- In Docker Compose, tracing is enabled by default and points to
http://jaeger:4318/v1/traces. - Successful responses include an
X-Trace-Idheader so the request can be found quickly in Jaeger.
Example request:
curl -X POST http://localhost:8000/api/v1/ask \
-H 'Content-Type: application/json' \
-d '{
"question": "Which Tier 1 clients have active rights for localized versions?",
"include_rows": true
}'Example response shape:
{
"question": "Which Tier 1 clients have active rights for localized versions?",
"answer": "...",
"cypher": "MATCH ... RETURN ... LIMIT 25",
"rows": [
{
"title_name": "...",
"client_name": "..."
}
],
"row_count": 12,
"agent_trace": [
"Received user question.",
"Planned Cypher on attempt 1.",
"Executed Cypher and retrieved 12 rows.",
"Synthesized natural-language answer."
]
}The test suite is split into unit tests (no external services required) and integration tests (require the demo stack).
uv run pytest tests/unit/ -vCovers the Cypher normalization pipeline in graph_store.py, the QA service retry and concurrency logic, the Ollama client, and the metrics middleware. All tests mock external dependencies.
# Start the demo stack first
docker compose --profile all up -d
# Run integration tests
uv run pytest tests/integration/ -vIntegration tests that cannot reach Neo4j skip gracefully so the suite can still run in CI without the database.
tests/fixtures/gold_queries.yaml contains hand-verified Cypher for all eight built-in sample questions. Each entry includes:
| Field | Purpose |
|---|---|
id |
Stable identifier used in test parametrisation |
question |
Exact natural-language question shown in the UI |
cypher |
Hand-verified Cypher that returns the correct answer |
expected_columns |
Column names the query must return |
min_rows |
Minimum acceptable row count against the demo data |
tests/integration/test_gold_queries.py runs every gold query against the live Neo4j instance and checks that:
- each query executes without error
- each result has the expected columns
- each query returns at least
min_rowsrows
The gold dataset provides a stable baseline for catching regressions after prompt changes, schema changes, or model upgrades — the LLM planner can produce structurally different Cypher across runs, and the gold queries confirm that the intended question is answered correctly regardless of which path the LLM takes.
uv run pytest tests/ -vdocker compose up -d neo4j
uv sync
uv run python kg4rag/kg-data-gen.py
uv run python kg4rag/kg-data-loader.pyThen open Neo4j Browser at:
http://localhost:7474
- The
kg4ragdataset is synthetic and intended for demos, graph experiments, and article examples. - The current graph model keeps operational records such as rights, localization jobs, delivery specs, and delivery requests as explicit nodes.
- Some technical format fields are still stored inline in the generated CSV data rather than being fully normalized.