Skip to content

Repository files navigation

Knowledge Graph Demo Workspace

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.

What Is Included

  • docker-compose.yml Runs Neo4j locally, plus optional Ollama and Weaviate services.
  • kg4rag/kg-data-gen.py Generates synthetic CSV data for titles, versions, rights, localization, delivery specs, and delivery requests.
  • kg4rag/kg-data-loader.py Loads 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.

Services

The Docker setup provides these services:

  • Neo4j
    • Bolt: bolt://localhost:7687
    • Browser: http://localhost:7474
    • Default credentials: neo4j / testpass
  • Ollama
    • API: http://localhost:11434
  • Weaviate
    • HTTP API: http://localhost:8080

If you only need the graph database for the kg4rag workflow, Neo4j is the main required service.

Python Environment

This project uses uv and defines dependencies in pyproject.toml.

Create or sync the environment with:

uv sync

If you prefer pip, the dependency list is mirrored in requirements.txt.

Start the Local Services

To start Neo4j only:

docker compose up -d neo4j

To start all services:

docker compose up -d

Generate the KG Demo Data

The main generator builds a synthetic media supply-chain dataset with entities such as:

  • Title
  • Version
  • Client
  • Region
  • Language
  • DeliveryPoint
  • Rights
  • LocalizationJob
  • DeliverySpec
  • DeliveryRequest

Run the generator with:

uv run python kg4rag/kg-data-gen.py

By default, the generated files are written to:

kg4rag/kg_demo_data/

The generator creates CSV files such as:

  • titles.csv
  • versions.csv
  • clients.csv
  • regions.csv
  • languages.csv
  • rights.csv
  • localization.csv
  • delivery_points.csv
  • delivery_specs.csv
  • delivery_requests.csv

Load the Data into Neo4j

After generating the CSV files, load them into Neo4j with:

uv run python kg4rag/kg-data-loader.py

The 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:7687
  • NEO4J_USER=neo4j
  • NEO4J_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.py

Important:

  • the loader currently starts with MATCH (n) DETACH DELETE n
  • this clears the entire Neo4j database before reloading the demo data

FastAPI Question-Answering Service

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

Prerequisites

  • Neo4j running locally
  • Ollama running locally
  • a local Ollama model pulled, for example:
ollama pull llama3.2

If you use Docker Compose from this repo, start Neo4j and Ollama with:

docker compose up -d neo4j ollama

If you need the default model locally outside Compose, pull it with:

ollama pull llama3.2

To run the API in Docker as well:

docker compose up -d api neo4j ollama

In Docker Compose, the API container connects to Neo4j and Ollama over the internal service network using:

  • NEO4J_URI=bolt://neo4j:7687
  • OLLAMA_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.

Start the API

uv run uvicorn app.main:app --reload --host 0.0.0.0 --port 8000

After 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.

Environment Variables

The service uses these defaults:

  • NEO4J_URI=bolt://localhost:7687
  • NEO4J_USER=neo4j
  • NEO4J_PASSWORD=testpass
  • NEO4J_DATABASE=neo4j
  • OLLAMA_BASE_URL=http://localhost:11434
  • OLLAMA_MODEL=llama3.2

Optional tuning:

  • LOG_LEVEL=INFO
  • TRACING_ENABLED=false
  • TRACING_SERVICE_NAME=knowledge-graph-api
  • OTEL_TRACES_EXPORTER_ENDPOINT=http://localhost:4318/v1/traces
  • QA_MAX_CONCURRENCY=64
  • QA_QUEUE_TIMEOUT_MS=250
  • MAX_QUERY_RETRIES=2
  • NEO4J_MAX_CONNECTION_POOL_SIZE=100
  • OLLAMA_TIMEOUT_SECONDS=60
  • OLLAMA_MAX_CONNECTIONS=200
  • OLLAMA_MAX_KEEPALIVE_CONNECTIONS=50
  • RESULT_ROW_LIMIT=25
  • CYPHER_TIMEOUT_MS=15000

API Endpoints

  • GET /health
  • GET /api/v1/metrics
  • GET /api/v1/metrics/prometheus
  • GET /api/v1/schema
  • POST /api/v1/ask

Compatibility routes are also kept for the current browser UI and existing callers:

  • GET /metrics
  • GET /metrics/prometheus
  • GET /schema
  • POST /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.

Production Notes

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 grafana

Endpoints:

  • 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 / admin unless overridden with GRAFANA_ADMIN_USER and GRAFANA_ADMIN_PASSWORD.
  • If you prefer browser access without login for local demos, set GRAFANA_ANONYMOUS_ENABLED=true.
  • FastAPI logs now include trace_id= and span_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-Id header 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."
	]
}

Testing

The test suite is split into unit tests (no external services required) and integration tests (require the demo stack).

Unit Tests

uv run pytest tests/unit/ -v

Covers 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.

Integration Tests

# Start the demo stack first
docker compose --profile all up -d

# Run integration tests
uv run pytest tests/integration/ -v

Integration tests that cannot reach Neo4j skip gracefully so the suite can still run in CI without the database.

Gold Query Evaluation

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_rows rows

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.

Run the Full Suite

uv run pytest tests/ -v

Example Workflow

docker compose up -d neo4j
uv sync
uv run python kg4rag/kg-data-gen.py
uv run python kg4rag/kg-data-loader.py

Then open Neo4j Browser at:

http://localhost:7474

Notes

  • The kg4rag dataset 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.

Related Files

About

No description, website, or topics provided.

Resources

Stars

3 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages