OntoRAG is an ontology-first alternative to traditional Retrieval-Augmented Generation (RAG).
Instead of retrieving text fragments and hoping the LLM reasons correctly, OntoRAG:
- extracts explicit structure from documents,
- builds a governed knowledge graph (RDF),
- and uses LLMs only where they add value: proposal, extraction, interpretation.
The result is a system that is inspectable, auditable, evolvable, and usable beyond chat.
Traditional RAG systems suffer from structural weaknesses:
- No explicit domain model
- No traceability from answers to sources
- No governance or evolution of knowledge
- Hidden schema inside prompts and embeddings
OntoRAG flips the model:
Baselines --> Documents --> DTOs --> Ontology --> Instances --> SPARQL --> MCP tools --> LLM reasoning
LLMs propose. Code decides. Humans govern.
Baseline Ontologies (OWL/TTL)
|
+-- Ontology Catalog (register, browse, compose)
|
v
Schema Card (initial or evolved)
|
Documents --> DTOs (Document / Chunk)
|
+-- Ontology Extraction (LLM -> proposals)
| |
| v
+-- Schema Card (deterministic merge, origin-tracked)
|
+-- Instance Extraction (LLM -> RDF with provenance)
|
v
Knowledge Graph (TTL / SPARQL)
|
+-- SPARQL endpoint (local rdflib or Blazegraph)
+-- Knowledge MCP Server (graph tools for agents)
+-- Ontology MCP Server (catalog tools for agents)
Before processing any documents, you can seed OntoRAG with baseline ontologies -- existing OWL/RDFS vocabularies (FOAF, Schema.org, PROV-O, domain-specific schemas, etc.).
Baselines are registered in a catalog (a directory of TTL files with a JSON manifest). You can:
- register standard or custom ontologies,
- browse and search across all baselines,
- compose multiple baselines into an initial schema card.
Each class and property from a baseline carries an origin field (e.g., "foaf", "schema_org") so you always know where a term came from.
Documents are content-hashed (SHA-256) before any processing occurs. The document ID is derived from the hash, making ingestion content-addressable: the same file ingested from different paths or at different times produces the same document_id. If a document has already been ingested, the pipeline skips re-chunking automatically (--force to override).
Documents are then parsed using PageIndex (for PDFs and Markdown — hierarchical, reasoning-based section detection) with fallback text extraction for other formats. The result is stable DocumentDTO / ChunkDTO objects.
DTOs are:
- content-addressable (same content = same document ID, no re-processing),
- format-agnostic (PDF, Markdown, CSV, DOCX, HTML, EPUB, ...),
- persistent (stored as JSON + JSONL),
- replayable,
- provenance-aware (page, section, text snippet, source path).
They are the semantic checkpoint of the pipeline.
LLMs analyze DTO chunks and propose:
- candidate classes,
- datatype properties,
- object properties,
- events,
- merge/alias suggestions.
These are proposals, not production schema. The LLM sees the current schema card and is instructed to reuse existing terms before inventing new ones.
The Schema Card is a compact, deterministic JSON description of the current ontology:
{
"version": "2026-02-12T10:00:00Z",
"namespace": "http://my.org/ns/",
"classes": [
{"name": "Person", "description": "A human being.", "origin": "foaf"},
{"name": "Invoice", "description": "A commercial invoice.", "origin": "induced"}
],
"datatype_properties": [
{"name": "email", "domain": "Person", "range": "string", "description": "...", "origin": "foaf"}
],
"object_properties": [
{"name": "knows", "domain": "Person", "range": "Person", "description": "...", "origin": "foaf"}
],
"events": [],
"aliases": [
{"names": ["Person", "Agent"], "rationale": "FOAF uses both interchangeably"}
],
"warnings": []
}It is:
- versioned (ISO timestamp),
- human-reviewable,
- origin-tracked (
"foaf","schema_org","induced", etc.), - used to guide all downstream extraction.
The merge is deterministic: classes and properties are deduplicated by normalized name, descriptions are merged (longer wins), and baseline origins are preserved.
Given a stable schema card, OntoRAG extracts instances from documents:
- RDF instances typed to schema card classes
- datatype properties as literals
- object properties linking instances
- every fact linked to its source chunk via PROV-style mention nodes (quote, page, section)
No hallucinated facts, no orphan triples.
OntoRAG supports two modes:
- Local inspection: in-memory RDF via rdflib, served as a FastAPI SPARQL endpoint
- Production-grade: external SPARQL engines (Blazegraph, QLever, others)
Both are exposed via standard SPARQL (GET/POST /sparql).
OntoRAG provides two MCP servers:
Knowledge MCP (default port 9010) -- query the knowledge graph:
sparql_select/sparql_construct-- raw SPARQL queriesdescribe-- describe a resource by IRIlist_by_class-- find instances of a classoutgoing/incoming-- graph traversal
Ontology Catalog MCP (default port 9020) -- browse and compose baselines:
list_ontologies-- list registered baselinesinspect_ontology-- view classes/properties of a baselinesearch_classes/search_properties-- search across all baselinescompose-- merge selected baselines into a schema cardadd_ontology-- register a new baseline from TTL content
This allows LLM agents to both select their starting ontology and query the resulting knowledge graph.
The Hub is a GitHub-like infrastructure for ontology-driven RAG. It exposes the full pipeline as a web API with a clear data-sovereignty model:
User (browser / agent)
│
▼
OntoRAG Hub API (FastAPI)
│ GitHub OAuth login → JWT session
│
├── Ingest / Extract / Instances
│ │
│ ▼
│ User's private GitHub repo: {user}/ontorag-data
│ └── data/dto/ data/proposals/ data/instances/
│
└── Ontology Registry (central, shared)
└── schema cards → dynamic onto-mcp (near-zero storage)
Key principles:
-
User data stays in the user's GitHub account. DTOs, chunks, proposals, and instance TTLs are stored in a private repo (
ontorag-data) created automatically via the GitHub API. OntoRAG Hub never holds user documents on its own servers. -
Ontologies are centrally shared. Published schema cards live on the Hub server and can be referenced by any user for extraction or composition.
-
MCP servers are generated dynamically from the ontology structure alone. Since a schema card is just a small JSON file describing classes and properties, the resulting onto-mcp is nearly volume-less — it needs no user data, only the schema structure and SPARQL templates.
-
Content-addressable dedup applies at the Hub level too. Uploading the same file content twice (even from different users) produces the same
document_id, and the second ingest is skipped.
pip install -e .Core dependencies (declared in pyproject.toml):
typer, requests, pydantic, rdflib, pageindex, pymupdf, python-dotenv, fastapi, uvicorn, fastmcp, EbookLib, html2text, httpx, PyJWT, python-multipart.
Copy the example environment file and fill in your API key:
cp .example.env .envOPENROUTER_API_KEY=...
OPENROUTER_MODEL=openai/gpt-4o-mini
OPENROUTER_BASE_URL=https://openrouter.ai/api/v1
OPENROUTER_APP_NAME=OntoRAG
OPENROUTER_SITE_URL=https://ontorag.github.io
# Optional: only needed for load-ttl / sparql-update commands
BLAZEGRAPH_ENDPOINT=http://localhost:9999/blazegraph/namespace/ontorag/sparqlAll commands are available via ontorag <command> --help.
Register a baseline ontology:
ontorag register-ontology foaf ./ontologies/foaf.ttl \
--label "Friend of a Friend" \
--description "People, social networks, and their connections" \
--tags "social,people"Copies the TTL file into the catalog directory, auto-detects the namespace, and registers it in catalog.json.
Create an initial schema card from baselines:
ontorag init-schema-card \
--baselines foaf,prov \
--out data/schema/schema_card.json \
--namespace http://my.org/ns/Parses the selected OWL/TTL baselines, extracts classes and properties, and merges them into a single schema card with origin tracking.
Start the ontology catalog MCP server:
ontorag ontology-mcp --catalog ./data/ontologies --port 9020Ingest a document:
ontorag ingest data/raw/manual.pdf --out data/dto
ontorag ingest data/raw/handbook.epub --out data/dto
# Re-ingesting the same file is a no-op (content-hashed):
ontorag ingest data/raw/manual.pdf --out data/dto
# → SKIP ingest: already ingested (document_id=doc_..., hash=...)
# Force re-ingest:
ontorag ingest data/raw/manual.pdf --out data/dto --forceThe file is content-hashed (SHA-256) before chunking. If the same content was already ingested, the command skips processing and reports the existing document ID. Use --force to re-ingest anyway.
Uses PageIndex for PDFs and Markdown (hierarchical section tree) with fallback text extraction for other formats (DOCX, HTML, CSV, EPUB, ...). Stores DocumentDTO + ChunkDTOs as JSON + JSONL.
Extract ontology proposals:
ontorag extract-schema \
--chunks data/dto/chunks/doc_x.jsonl \
--schema-card data/schema/schema_card.json \
--out data/proposals/doc_x.schema.jsonSends each chunk + the current schema card to the LLM. The LLM proposes new classes, properties, events, and merge suggestions. Per-chunk proposals are aggregated into a single document-level proposal.
Build schema card (deterministic merge):
ontorag build-schema-card \
--previous data/schema/schema_card.json \
--proposal data/proposals/doc_x.schema.json \
--out data/schema/schema_card.next.jsonDeterministically merges the proposal into the existing schema card. Deduplicates by normalized name, normalizes datatype ranges, validates domain/range references, and accumulates aliases and warnings. New items get "origin": "induced".
Export schema to Turtle:
ontorag export-schema-ttl \
--proposal data/proposals/doc_x.schema.json \
--out data/schema/staging_schema.ttl \
--namespace http://my.org/ns/Extract instances:
ontorag extract-instances \
--chunks data/dto/chunks/doc_x.jsonl \
--schema-card data/schema/schema_card.json \
--out-ttl data/instances/doc_x.instances.ttlExtracts structured instances constrained to the schema card, then converts to RDF with PROV-style provenance (quote, page, section for every fact).
Upload TTL to Blazegraph:
ontorag load-ttl \
--file data/schema/staging_schema.ttl \
--graph urn:staging:schemaExecute a SPARQL UPDATE:
ontorag sparql-update --query-file queries/promote_schema.rqStart the local SPARQL server:
ontorag sparql-server \
--onto data/schema/staging_schema.ttl \
--inst data/instances/doc_x.instances.ttl \
--port 8890Endpoints:
GET/POST /sparql-- SPARQL queries (SELECT, ASK, CONSTRUCT, DESCRIBE)GET /health-- health check with triple countGET /stats-- SPARQL-based statisticsPOST /reload-- reload graph from files
Supports content negotiation: JSON, CSV, TSV, XML, Turtle, N-Triples, JSON-LD.
Start the knowledge MCP server:
# Local TTL backend
ontorag mcp-server \
--onto data/schema/staging_schema.ttl \
--inst data/instances/doc_x.instances.ttl
# Remote SPARQL backend
ontorag mcp-server \
--sparql-endpoint http://localhost:9999/blazegraph/namespace/ontorag/sparqlStart the Hub API server:
ontorag hub --port 8000Required env vars: GITHUB_CLIENT_ID, GITHUB_CLIENT_SECRET, HUB_JWT_SECRET.
Hub API endpoints:
| Method | Path | Auth | Description |
|---|---|---|---|
| GET | /auth/login |
-- | Redirect to GitHub OAuth |
| GET | /auth/callback?code=... |
-- | Exchange code for JWT |
| GET | /auth/me |
JWT | Current user profile |
| POST | /api/ingest |
JWT | Upload & chunk a file (multipart) |
| POST | /api/extract-schema |
JWT | Run ontology induction |
| POST | /api/extract-instances |
JWT | Extract instances |
| GET | /api/documents |
JWT | List user's ingested documents |
| GET | /api/ontologies |
-- | List centrally registered ontologies |
| POST | /api/ontologies |
JWT | Publish a schema card as shared ontology |
| GET | /api/ontologies/{slug} |
-- | Get a schema card |
| GET | /api/mcp/{slug} |
-- | Dynamic MCP endpoint info |
User artifacts (DTOs, chunks, proposals, instances) are stored in the user's private ontorag-data GitHub repo. Ontologies are stored centrally on the Hub server.
# 1. Register baseline ontologies
ontorag register-ontology foaf ./ontologies/foaf.ttl --label "FOAF"
ontorag register-ontology prov ./ontologies/prov-o.ttl --label "PROV-O"
# 2. Compose baselines into an initial schema card
ontorag init-schema-card --baselines foaf,prov \
--out data/schema/schema_card.json
# 3. Ingest a document
ontorag ingest data/raw/report.pdf --out data/dto
# 4. Extract ontology proposals (LLM sees FOAF/PROV terms, reuses them)
ontorag extract-schema \
--chunks data/dto/chunks/doc_*.jsonl \
--schema-card data/schema/schema_card.json \
--out data/proposals/report.schema.json
# 5. Review and merge proposals into the schema card
ontorag build-schema-card \
--previous data/schema/schema_card.json \
--proposal data/proposals/report.schema.json \
--out data/schema/schema_card.json
# 6. Export schema to Turtle
ontorag export-schema-ttl \
--proposal data/proposals/report.schema.json \
--out data/schema/staging_schema.ttl
# 7. Extract instances with provenance
ontorag extract-instances \
--chunks data/dto/chunks/doc_*.jsonl \
--schema-card data/schema/schema_card.json \
--out-ttl data/instances/report.instances.ttl
# 8. Inspect the graph locally
ontorag sparql-server \
--onto data/schema/staging_schema.ttl \
--inst data/instances/report.instances.ttl
# 9. Expose to LLM agents
ontorag mcp-server \
--onto data/schema/staging_schema.ttl \
--inst data/instances/report.instances.ttlEvery class, property, and event in the schema card carries an origin field:
| Origin value | Meaning |
|---|---|
"foaf", "schema_org", ... |
Came from a registered baseline ontology |
"induced" |
Proposed by the LLM during ontology extraction |
"" (empty) |
Pre-existing item with unknown origin |
Origin is set when an item first enters the schema card and is preserved across merges. If a baseline defines Person and the LLM later proposes Person again, the baseline origin is kept.
ontorag/
__init__.py
cli.py # Typer CLI (13 commands, incl. hub)
dto.py # DocumentDTO, ChunkDTO, ProvenanceDTO + content hashing
extractor_ingest.py # PageIndex doc parsing + fallback chunking
storage_jsonl.py # JSONL persistence for DTOs
ontology_extractor_openrouter.py # LLM schema proposal extraction
instance_extractor_openrouter.py # LLM instance extraction
proposal_aggregator.py # Merge per-chunk proposals into one
schema_card.py # Deterministic schema card merge (with origin)
proposal_to_ttl.py # Schema proposal -> OWL/RDFS Turtle
instances_to_ttl.py # Instance proposals -> RDF with provenance
blazegraph.py # Blazegraph REST API integration
sparql_server.py # FastAPI in-memory SPARQL endpoint
mcp_backend.py # SparqlBackend ABC + Local/Remote impls
mcp_server.py # Knowledge graph MCP server
mcp_client.py # Async SSE client for remote MCP
ontology_catalog.py # Baseline catalog + OWL/TTL converter
ontology_mcp.py # Ontology catalog MCP server
hub/
__init__.py
app.py # Hub FastAPI app (all routes)
auth.py # GitHub OAuth + JWT sessions
github_storage.py # Read/write artifacts to user's GitHub repos
models.py # Pydantic request/response models
app.py # Vercel-deployed ontology catalog API
data/
ontologies/
catalog.json # Ontology catalog manifest
*.ttl # Registered baseline ontologies
hub_ontologies/ # Central ontology registry (Hub)
- Not a vector-only RAG
- Not a black-box "AI magic" system
- Not a chatbot framework
OntoRAG is a knowledge engineering system with LLM assistance.
This project is:
- experimental but functional,
- architecture-first,
- designed for research, enterprise prototyping, and public-sector semantics.
APIs may evolve, concepts will stabilize.
Apache 2.0
If the system cannot explain what it knows, where it comes from, and why it changed, it is not a knowledge system.
OntoRAG is built to make that explanation unavoidable.