Skip to main content

OntoRAG

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.


Why OntoRAG exists

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.


Architecture overview

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)

Core concepts

1. Ontology catalog and baselines

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.

2. DTO-first ingestion

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 parsed by a pluggable ingest engine (--engine). The default engine, builtin, has no external dependencies and no API keys: Markdown/text via a local recursive splitter, EPUB/HTML via ebooklib + html2text, and PDF via PyMuPDF (pip install 'ontorag[pdf]'). Additional engines are available as optional extras — pageindex (hosted hierarchical PDF), llamaindex, docling (IBM layout-aware), and unstructured (typed elements). Run ontorag doctor to see which engines are installed. The result is stable DocumentDTO / ChunkDTO objects, independent of the engine used.

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.

3. Ontology induction (proposal, not truth)

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.

4. Schema Card

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.

5. Instance extraction with provenance

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 the passage it came from by an orp:Mention — a W3C Web Annotation carrying the quote, the printed page and the section, following the OntoRAG Provenance Ontology

No hallucinated facts, no orphan triples.

6. Knowledge graph backends

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

7. MCP integration

OntoRAG provides two MCP servers:

Knowledge MCP (default port 9010) -- query the knowledge graph:

  • sparql_select / sparql_construct -- raw SPARQL queries
  • describe -- describe a resource by IRI
  • list_by_class -- find instances of a class
  • outgoing / incoming -- graph traversal

Ontology Catalog MCP (default port 9020) -- browse and compose baselines:

  • list_ontologies -- list registered baselines
  • inspect_ontology -- view classes/properties of a baseline
  • search_classes / search_properties -- search across all baselines
  • compose -- merge selected baselines into a schema card
  • add_ontology -- register a new baseline from TTL content

This allows LLM agents to both select their starting ontology and query the resulting knowledge graph.


Installation

OntoRAG is on PyPI (Python ≥ 3.12):

pip install ontorag                 # core — ingests Markdown/text/HTML/EPUB out of the box
pip install 'ontorag[pdf]'          # + PDF ingest via PyMuPDF (builtin engine)

Optional ingest engines are extras — install only what you need:

pip install 'ontorag[pageindex]'    # hosted hierarchical PDF (needs PAGEINDEX_API_KEY)
pip install 'ontorag[llamaindex]'   # LlamaIndex fixed-chunk ingest
pip install 'ontorag[docling]'      # IBM Docling, layout-aware PDF/DOCX/PPTX
pip install 'ontorag[unstructured]' # Unstructured typed elements

Core dependencies (always installed): typer, requests, pydantic, rdflib, python-dotenv, fastapi, uvicorn, fastmcp, mcp, EbookLib, html2text, httpx. Parsers (pymupdf, pageindex, llama-index, docling, unstructured) are optional extras.

After installing, check your environment and available engines:

ontorag doctor

For local development (editable install):

pip install -e '.[pdf,dev]'

Configuration

Copy the example environment file and fill in your API key:

cp .example.env .env
OPENROUTER_API_KEY=...
OPENROUTER_MODEL=~deepseek/deepseek-v4-flash-latest
OPENROUTER_BASE_URL=https://openrouter.ai/api/v1
OPENROUTER_APP_NAME=OntoRAG
OPENROUTER_SITE_URL=https://ontorag.org

# Optional: only needed for load-ttl / sparql-update commands
BLAZEGRAPH_ENDPOINT=http://localhost:9999/blazegraph/namespace/ontorag/sparql

Model choice matters. Every LLM step (extract-schema, align-schema, extract-instances) sends the full schema card in each prompt, so a fast, capable model is worth it. ~deepseek/deepseek-v4-flash-latest (the tilde ~ is part of the OpenRouter "latest" alias; it resolves to the newest deepseek/deepseek-v4-flash) is a good choice — validated end-to-end below. If OPENROUTER_MODEL is not set, the built-in fallback is openai/gpt-4o-mini. Avoid *:free slugs for real runs: they are frequently retired and the shared free router is slow enough to stall multi-chunk instance extraction.

Local / self-hosted models. Point OPENROUTER_BASE_URL at any OpenAI-compatible endpoint to run fully offline — e.g. a local Ollama:

OPENROUTER_BASE_URL=http://localhost:11434/v1
OPENROUTER_MODEL=qwen2.5:14b
OPENROUTER_API_KEY=ollama    # any non-empty value

Overriding per-invocation (CLI flags). Every OpenRouter setting is also a global flag, placed before the subcommand. Flags win over environment variables, which win over built-in defaults:

ontorag --model '~deepseek/deepseek-v4-flash-latest' --api-key sk-... \
        extract-schema --chunks c.jsonl --schema-card card.json --out prop.json

# run against a local ollama without touching the environment
ontorag --base-url http://localhost:11434/v1 --api-key ollama --model qwen2.5:14b \
        extract-instances --chunks c.jsonl --schema-card card.json --out-ttl inst.ttl
Flag Overrides / env Applies to
--model / -m OPENROUTER_MODEL extract-schema, align-schema, extract-instances
--api-key OPENROUTER_API_KEY ”
--base-url OPENROUTER_BASE_URL ”
--app-name OPENROUTER_APP_NAME ”
--site-url OPENROUTER_SITE_URL ”
--concurrency / -j ONTORAG_CONCURRENCY (default 4) extract-schema, extract-instances
--slim-card ONTORAG_SLIM_CARD (default off) extract-schema, extract-instances

Speed: the per-chunk LLM call is latency-bound, so extraction runs chunks concurrently (-j, default 4). Raise it for long documents (-j 8), or set -j 1 for strictly sequential. --slim-card prunes the schema card to chunk-relevant terms — smaller, cheaper prompts, but it can lower instance-extraction recall on large baselines, so it is off by default.

ontorag doctor prints the effective model, base URL, and whether a key is set.


CLI reference

All commands are available via ontorag <command> --help. LLM settings are global flags placed before the subcommand (see Configuration above).

Ontology catalog commands

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 9020

Document processing commands

Ingest a document:

ontorag ingest data/raw/manual.pdf --out data/dto          # builtin engine (default)
ontorag ingest data/raw/handbook.epub --out data/dto

# Pick a different engine explicitly (or set ONTORAG_INGEST_ENGINE):
ontorag ingest data/raw/manual.pdf --engine docling --out data/dto
ontorag ingest data/raw/manual.pdf --engine pageindex --out data/dto   # needs PAGEINDEX_API_KEY

# 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 --force

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

The engine is selected with --engine {builtin|pageindex|llamaindex|docling|unstructured} (default builtin, no keys/deps). Whatever the engine, the output is the same stable DocumentDTO + ChunkDTOs (JSON + JSONL). Run ontorag doctor to see which engines are installed:

ontorag doctor
# → OntoRAG environment
#   LLM: OpenRouter (model=~deepseek/deepseek-v4-flash-latest)
#   ingest engines: builtin ✓  pageindex ✓  llamaindex (pip install 'ontorag[llamaindex]')  ...

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

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

Align the proposal to a baseline (optional but recommended):

ontorag align-schema \
  --proposal data/proposals/doc_x.schema.json \
  --baseline data/schema/schema_card.json \
  --out data/proposals/doc_x.alignment.json

For each induced class/property, the LLM decides whether it should reuse a baseline term, extend one (subclass/subproperty), or stand as new — with a rationale for each decision. This keeps the graph anchored to standard vocabularies instead of reinventing them. Alignment supports partial-save and auto-resume: if interrupted, re-running resumes from the last completed category. The aligned JSON is a drop-in replacement for the raw proposal in the next two steps.

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

Deterministically 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.ttl

Extracts structured instances constrained to the schema card, then converts to RDF with PROV-style provenance (quote, page, section for every fact).

Governed stages over a dataset directory

run-stage runs the pipeline over a dataset directory — a manifest.json with dataset.base_iri (and optionally baselines), plus source files in content/sources/. It is the single definition shared by CI workflows, the Hub and the desktop app.

ontorag run-stage propose ./my-dataset   # ingest new files, propose + align, build the proposed card
# review ontology/schema_card.proposed.json; save the approved card as ontology/schema_card.json
ontorag run-stage extract ./my-dataset   # export schema.ttl, extract instances, complete the dataset

Stages are incremental: only documents not yet processed reach the model, and approving a different schema card triggers a full re-extraction. --full reprocesses everything. extract extracts against the approved card when there is one, else the proposed card, and finishes with build-dataset.

Knowledge graph commands

Upload TTL to Blazegraph:

ontorag load-ttl \
  --file data/schema/staging_schema.ttl \
  --graph urn:staging:schema

Project the graph into Neo4j:

pip install 'ontorag[neo4j]'
ontorag load-neo4j \
  --file data/instances/doc_x.instances.ttl \
  --uri bolt://localhost:7687 --user neo4j --password ******

Neo4j is a labelled property graph, not a triple store: it does not answer SPARQL, and neosemantics imports RDF without changing that. So this is a parallel serving target -- the TTL stays canonical -- and the projection is native rather than RDF-shaped, so the result is pleasant to query in Cypher:

RDF Neo4j
ns:Character/ab12 a ns:Character (:Character:Resource {iri, label})
datatype property node property
object property -[:MEMBER_OF]-> (relationship)
object property to an external IRI node property holding the IRI
orp:hasMention -> orp:Mention -[:DERIVED_FROM]->(:Mention {quote, chunkId, source, page, section})

Re-running is idempotent: instances MERGE on their stable IRI, and mentions (blank nodes in RDF, so identity-less) MERGE on a key minted from instance + chunk + quote. Use --dry-run to see the mapping counts without connecting, and --wipe to clear previously loaded :Resource/:Mention nodes first.

Citations survive the trip:

MATCH (n:Resource)-[:DERIVED_FROM]->(m:Mention)
RETURN n.label, m.quote, m.sourcePath, m.page

Execute a SPARQL UPDATE:

ontorag sparql-update --query-file queries/promote_schema.rq

Start the local SPARQL server:

ontorag sparql-server \
  --onto data/schema/staging_schema.ttl \
  --inst data/instances/doc_x.instances.ttl \
  --port 8890

Endpoints:

  • GET/POST /sparql -- SPARQL queries (SELECT, ASK, CONSTRUCT, DESCRIBE)
  • GET /health -- health check with triple count
  • GET /stats -- SPARQL-based statistics
  • POST /reload -- reload graph from files

Supports content negotiation: JSON, CSV, TSV, XML, Turtle, N-Triples, JSON-LD.

Vector index

A dataset's embeddings are committed as per-document JSONL shards (embeddings/vectors/<doc>.jsonl) — diffable, and incremental in the same way extraction is: a new document is a new file, not a rewritten blob. That is the right storage form and a poor query form. A consumer that wants nearest-neighbour search has to read every shard and hold every vector in memory before it can answer anything: 144 MB of JSONL and ~555 MB of Python objects, for a 17,942-chunk corpus.

ontorag index builds the query form from the shards — one SQLite file, searched with sqlite-vector:

ontorag index --download           # fetch the extension once, then build
ontorag index /path/to/dataset     # rebuild only if the shards changed

Measured on that corpus (17,942 chunks × 768 dims, ollama nomic-embed-text):

open query RSS file
every vector in a dict (what a consumer does today) 5.7 s — 555 MB 144 MB of JSONL
vectors.db, exact scan 26 ms 65 ms 14 MB 106 MB
vectors.db, TurboQuant-4 46 ms 19 ms 27 MB 106 MB
--ship, quantised only 46 ms 19 ms 27 MB 9 MB
--ship with chunk text 46 ms 19 ms 27 MB 33 MB

--quantize trades index size for recall (recall@10 against an exact scan, same corpus): turbo4 94.8%, turbo3 92.0%, turbo2 85.2%, int8 98.8%, 1bit 70.0%, none for exact scans only.

It is derived data. The build writes to <dataset>/.ontorag/vectors.db and drops a .gitignore beside it, because the file is a cache: rebuilt in ~7 s whenever the shards change, and never committed. A single binary in git history would undo exactly the property the shards exist for — every re-index would rewrite the whole file, and git keeps every version.

--ship is the other artefact. It drops the full-precision column and keeps the quantised index, which still answers:

ontorag index . --ship --no-chunks    #  9 MB: ids + distances
ontorag index . --ship                # 33 MB: answers with the passage text

Small enough to attach to a GitHub Release, which is how to publish vectors without putting a binary in a repo's history — and a release asset on a private dataset repo is a way to share a reusable index with people who cannot clone the dataset itself.

The extension is a binary, not a Python package (the sqlite-vector name on PyPI is an unrelated, abandoned project). --download fetches the pinned build for your platform into ~/.cache/ontorag/; ONTORAG_VECTOR_EXTENSION points at your own copy. ontorag doctor reports which. Note that SQLite derives an extension's entry point from its file name, so the file has to stay vector.so / .dylib / .dll.

Publish to the Hub

Complete a dataset directory into the published format. Services such as ontorag-mcp read datasets in the OntoRAG dataset format: a manifest.json, per-source chunk files (content/chunks/<source>.jsonl), an entity index (ontology/entities.jsonl) and source/pack registries. build-dataset derives them from the pipeline's working files and merges the required fields into an existing manifest without dropping any keys. run-stage extract runs it automatically.

ontorag build-dataset ./my-dataset --name "My rules" --license CC-BY-4.0

Push a locally-built dataset to GitHub so ontorag-mcp (and the OntoRAG Hub, in development) can serve it. Unless the directory already has a complete manifest, it is first completed as above; the dataset is then committed in one commit via the GitHub API. Auth is a GitHub token with repo scope, via --token or GITHUB_TOKEN / GH_TOKEN.

# publish the whole dataset (ontology + graph + the original corpus)
ontorag hub push ./my-dataset --repo myorg/my-dataset

# publish only the derived ontology + graph, not the source documents
ontorag hub push ./my-dataset --repo myorg/my-dataset --no-include-sources --public

Key options: --include-sources/--no-include-sources (upload the raw corpus under content/sources/ or only the derived ontology + graph), --private/--public (visibility on creation), --graph (path to the world/instance TTL, default ontology/world.ttl), --base-iri / --name / --license (override the generated manifest), --regenerate-manifest (rewrite an existing manifest). Re-running updates the repo; intermediate DTOs and .env are never uploaded.

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/sparql

# Neo4j backend (after `load-neo4j`)
ontorag mcp-server --neo4j --uri bolt://localhost:7687 --user neo4j --password ******

The navigation tools -- describe, list_by_class, outgoing, incoming, and mentions (the citation primitive: which source passages a fact came from) -- are identical whichever backend is serving, and all return SPARQL Results JSON. The query-language tool follows the store: RDF backends expose sparql_select / sparql_construct, Neo4j exposes cypher_query instead, so a client is never offered a tool that has to fail. Cypher runs in a read transaction, so the server itself rejects writes.

describe against Neo4j rebuilds RDF from the projection, and the provenance walk survives the round trip -- the mention hangs off orp:hasMention and its payload keeps the predicate IRIs it was recorded with, not invented ones. mentions also reads graphs written before orp: (the per-dataset mcp:Mention model).


End-to-end workflow

# 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. Align the proposal to the baseline (reuse / extend / new)
ontorag align-schema \
  --proposal data/proposals/report.schema.json \
  --baseline data/schema/schema_card.json \
  --out data/proposals/report.alignment.json

# 6. Merge the aligned proposal into the schema card
ontorag build-schema-card \
  --previous data/schema/schema_card.json \
  --proposal data/proposals/report.alignment.json \
  --out data/schema/schema_card.json

# 7. Export schema to Turtle
ontorag export-schema-ttl \
  --proposal data/proposals/report.alignment.json \
  --out data/schema/staging_schema.ttl

# 8. 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

# 9. Inspect the graph locally
ontorag sparql-server \
  --onto data/schema/staging_schema.ttl \
  --inst data/instances/report.instances.ttl

# 10. Expose to LLM agents
ontorag mcp-server \
  --onto data/schema/staging_schema.ttl \
  --inst data/instances/report.instances.ttl

# 11. (optional) Publish to the Hub — explore/fork it from the web
ontorag hub push . --repo myorg/report-dataset --no-include-sources

Validated end-to-end run

The full pipeline has been validated against the public rpg-schema baseline ontology (68 classes / 47 datatype / 93 object properties) using ~deepseek/deepseek-v4-flash-latest, on two independent, unrelated RPG rulebooks — proving the process is corpus-agnostic (same commands, same baseline, different documents):

Stage Daggerheart SRD (0.9 MB PDF) D&D 5.2.1 SRD (6 MB PDF)
ingest (builtin / PyMuPDF) 181 chunks 503 chunks
extract-schema (4 chunks) 17 classes, 5 dt, 9 obj 66 classes, 0 dt, 5 obj
align-schema → rpg baseline reuse 0 · extend 11 · new 20 reuse 0 · extend 41 · new 30
export-schema-ttl 70 triples 122 triples
extract-instances (4 chunks) 63 instances, 605 triples validated on Daggerheart (see note)
instance types found Character, GameMaster, DualityDice, DeathMove, RuleSet, CampaignFrame, … 41 induced classes extend rpg-schema classes

Both runs use the identical command sequence and the identical rpg-schema baseline — only the input file changes. The D&D run drove the alignment harder (66 induced classes vs 17): 41 were aligned as extend (domain specializations — subclasses of rpg-schema classes) and 30 as new, each with a recorded rationale.

Note on extract-instances speed. The per-chunk LLM call is latency-bound (a minute or more each on some hosted models), so the extractors process chunks concurrently (-j, default 4) — a long document sees a roughly N× wall-clock speedup. Raise -j for large corpora. Prompt size is a much smaller factor; --slim-card trims it further (cheaper tokens) at some recall cost, so it is off by default.

Reproduce it (any RPG PDF works — swap the file):

pip install 'ontorag[pdf]'
export OPENROUTER_API_KEY=sk-...
export OPENROUTER_MODEL='~deepseek/deepseek-v4-flash-latest'

# rpg-schema as the baseline ontology
curl -sL https://raw.githubusercontent.com/rpg-schema/rpg-schema.github.io/refs/heads/main/src/data/rpg-schema.ttl -o rpg-schema.ttl
ontorag register-ontology rpg ./rpg-schema.ttl --catalog data/ont --label "RPG Schema"
ontorag init-schema-card --baselines rpg --catalog data/ont \
  --namespace http://ontorag.dev/dh/ --out data/schema/card.json

# ingest → induce → align → merge → export → instances
ontorag ingest your-rulebook.pdf --out data/dto
CH=$(ls data/dto/chunks/*.jsonl | head -1)
ontorag extract-schema  --chunks "$CH" --schema-card data/schema/card.json --out data/proposal.json
ontorag align-schema    --proposal data/proposal.json --baseline data/schema/card.json --out data/alignment.json
ontorag build-schema-card --previous data/schema/card.json --proposal data/alignment.json --out data/schema/card2.json
ontorag export-schema-ttl --proposal data/alignment.json --namespace http://ontorag.dev/dh/ --out data/schema.ttl
ontorag extract-instances --chunks "$CH" --schema-card data/schema/card2.json --out-ttl data/instances.ttl

Origin tracking

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


Project structure

ontorag/
  __init__.py
  cli.py                            # Typer CLI (19 commands, incl. doctor, build-dataset and hub push)
  llm_config.py                     # OpenRouter settings resolver (CLI flag > env > default)
  hub_push.py                       # publish a dataset to GitHub (Git Data API)
  dataset_package.py                # complete a dataset directory into the dataset format (build-dataset)
  sources.py                        # source/pack naming shared by the graph writer and the packager
  run_stage.py                      # governed stages over a dataset directory (run-stage propose|extract)
  parallel.py                       # bounded-concurrency chunk processing (--concurrency)
  card_slim.py                      # opt-in per-chunk schema-card pruning (--slim-card)
  dto.py                            # DocumentDTO, ChunkDTO, ProvenanceDTO + content hashing
  extractor_ingest.py               # pluggable ingest engines (builtin default; pageindex/llamaindex/docling/unstructured)
  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
  neo4j_store.py                    # Neo4j sink: RDF -> labelled property graph
  sparql_server.py                  # FastAPI in-memory SPARQL endpoint
  mcp_backend.py                    # GraphBackend ABC + SparqlBackend + Local/Remote
  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

data/
  ontologies/
    catalog.json                    # Ontology catalog manifest
    *.ttl                           # Registered baseline ontologies

What OntoRAG is not

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


Status

This project is:

  • experimental but functional,
  • architecture-first,
  • designed for research, enterprise prototyping, and public-sector semantics.

APIs may evolve, concepts will stabilize.


License

Apache 2.0


Philosophy

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.

Metadata

Release files for ontorag 0.1.13

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for ontorag 0.1.13
File Size Uploaded
ontorag-0.1.13.tar.gz 440.1 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for ontorag 0.1.13
File Interpreter ABI Platform
ontorag-0.1.13-py3-none-any.whl Python 3 none any Details

Total release size: 556.4 kB

Release files / ontorag-0.1.13.tar.gz

Download URL ontorag-0.1.13.tar.gz
Size 440.1 kB
Tags Source
SHA-256 checksum
How to use checksums
c6458b5507758e766c3b64b79bd3462123308588fe7fd7eeea3693556bb42a5f
BLAKE2b-256 checksum
How to use checksums
f932233eb700e856ef574350e85dbdf1b1458a734f95b7ab2dbcadc373082785
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 23, 2026.

Transparency log

Release files / ontorag-0.1.13-py3-none-any.whl

Download URL ontorag-0.1.13-py3-none-any.whl
Size 116.3 kB
Tags Python 3
SHA-256 checksum
How to use checksums
96de7de076ec25dac147fea73cb3c99eb40ece9dbed00956ffd84231c129a595
BLAKE2b-256 checksum
How to use checksums
7fac5e821b01c1429244686f005208e8490973b39104471fc5e76eed7b77ab3e
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 23, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.1.13 This release

2 release files

0.1.12

2 release files

0.1.11

2 release files

0.1.10

2 release files

0.1.9

2 release files

0.1.8

2 release files

0.1.7

2 release files

0.1.6

2 release files

0.1.5

2 release files

0.1.4

2 release files

0.1.3

2 release files

0.1.2

2 release files

0.1.1

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page