Context Graph · Feature Guide

The Ontology

An LLM extracts whatever it sees. An ontology tells it what should be there — the object types, link types, and typed properties your domain actually has — and validates every extraction against that schema, coercing raw text into real values as it goes.

typed object & link types 9 property kinds open- / closed-world coercing validation NL → schema author per-workspace
The purpose

What it does

An ontology is a typed schema for a workspace. You declare the object types (entity kinds like Person, Order), each with typed properties, and the directed link types (relations like approved: Person → Order) that connect them. Then every entity and relation the graph extracts is checked against it.

It is deliberately not RDF or OWL. It's a plain dataclass model, persisted as JSON, one file per workspace. And validation isn't just a yes/no gate — the same pass that checks a record also normalises it, so "$25,000" becomes 25000.0 and "20%" becomes 0.2, using the exact same parsers as the Rules Engine.

◆ Why it exists
Its real job is to give downstream reasoning typed values to work with. The Business Rules Engine becomes dramatically stronger once the ontology hands it a real money or percent instead of a free-text blob to regex.
The shape

The data model

An ontology document has a name, an integer version, a list of object types, and a list of link types:

{
  "name": "sales",
  "version": 1,
  "object_types": [
    { "name": "Person", "description": "a human",
      "properties": [ { "name": "email", "kind": "string" } ] },
    { "name": "Order", "description": "a deal",
      "properties": [
        { "name": "value",  "kind": "money", "required": true },
        { "name": "status", "kind": "enum",  "enum_values": ["open","won","lost"] }
      ] }
  ],
  "link_types": [
    { "name": "approved", "source_types": ["Person"], "target_types": ["Order"],
      "cardinality": "1:N" }
  ]
}

Property kinds

A property's kind is one of nine values. Numeric kinds accept optional minimum/maximum bounds; enum requires enum_values.

KindCoerces toNotes
string · textstrFree text.
integer · floatnumberNumeric; range-checkable.
moneyfloatParsed from text — "$8k"8000.0. Numeric.
percentfloat"20%"0.20. Numeric.
booleanboolFrom common string forms.
dateISO dateYYYY-MM-DD via date.fromisoformat.
enummemberValue must be in enum_values.

Link types

A link type constrains a relation's endpoints and can carry its own typed properties (which map onto the edge's RelationContext). source_types/target_types list the allowed head/tail object types — an empty list means "any". cardinality is one of 1:1, 1:N, N:1, N:M (default N:M), and directed defaults to true.

Descriptive, not enforced
Cardinality is stored and shown, but there is no multiplicity enforcement at validation time — it documents intent rather than rejecting a second edge. Endpoint types, required properties, enums, and ranges are enforced.
Plain English in

The NL author agent

You rarely hand-write JSON. Describe the domain in a sentence and the OntologyAuthor drafts, validates, repairs, and dry-runs a schema for you — using the workspace's own LLM.

POST /ontology/generate
{
  "description": "A school district. Schools employ teachers. Teachers teach courses
                  to students. Students enroll in courses and receive grades.",
  "extend": true,      // merge into the existing ontology, draft wins on conflicts
  "max_repairs": 1,   // 0–3 self-repair attempts on validation failure
  "save": false       // persist only if valid
}

Each attempt runs a strict pipeline, and any failure feeds the error back into the next repair prompt:

StepCheck
1 · DraftThe LLM returns strict JSON: an ontology, sample entities/relations, and a one-sentence explanation. Nouns become object types, verbs become link types, and it picks the most specific property kind.
2 · MergeIf extend, union with the current ontology by type name (draft wins on conflict).
3 · ParseMust deserialize into a real Ontology — malformed shapes are rejected.
4 · LintMust pass self-consistency: no link may reference an undefined object type.
5 · Dry-runThe model's own sample records are validated against the fresh schema; the report is returned so you can see it conform.

The response carries valid, ontology, lint, dry_run, explanation, errors, and attempts. Nothing is saved unless save=true and the result is valid.

The gate

Extraction validation

The ExtractionValidator checks extracted entities and relations against the ontology and returns a per-item report. Two policies control how strict it is:

Open-world default
A type the ontology doesn't define is flagged unknown_type but stays ok — recorded as a warning. New kinds are tolerated.
Closed-world
The same unknown type becomes a violation (ok=false). Only the declared schema is allowed.

What counts as a violation vs a warning

ConditionResult
Missing a required propertyviolation
Enum value not in enum_values, or numeric out of rangeviolation
Relation endpoint is a known-but-disallowed typeviolation
Relation endpoint type is unknown (can't be verified)warning
A property not declared on the typewarning
Unknown object/link type (open-world)warning
Endpoint types: warn, don't block
If a relation's endpoint type simply isn't known — the extraction didn't say what type the head or tail is — the validator emits "domain not verified" as a warning rather than a violation. It only errors when an endpoint is a known type that the link type explicitly disallows. The validator also backfills endpoint types from the other entities in the same batch, so it can check domain/range even when a relation omits them.

The report's summary() gives you total, conforming, violations, by_status, unknown_types, and the active policy. Each item also returns its coerced values — the normalised form the graph should store.

The interface

The /ontology API

All endpoints are workspace-scoped via LIGHTRAG-WORKSPACE and return 503 unless the server runs in Context Graph mode (USE_CONTEXT_GRAPH=true).

Method · PathPurposeReturns
GET /ontologyCurrent schema summaryOntologySummaryResponse
POST /ontologySave a schema (JSON)Summary (400 if malformed)
DELETE /ontologyDelete this workspace's schema{deleted, workspace}
POST /ontology/generateNL → schema (LLM)Generation result + saved (503 if no LLM)
POST /ontology/validateValidate extracted recordsReport (404 if no schema)

The summary describes the schema at a glance — object types with property counts, link types with their source/target types and cardinality, plus any lint messages and the full ontology for round-tripping into an editor:

# Validate a batch of extractions against the workspace schema
curl -X POST http://localhost:9621/ontology/validate \
  -H "LIGHTRAG-WORKSPACE: sales" -H "Content-Type: application/json" \
  -d '{
    "entities": [{"entity_name":"Acme Deal","entity_type":"Order","properties":{"status":"won"}}],
    "relations": [],
    "closed_world": false
  }'

# -> Order:Acme Deal  status=invalid  "missing required property 'value'"
Persistence

Storage & versioning

The default store writes one atomic JSON file per workspace — ontology_<workspace>.json — holding the workspace name, an updated_at timestamp, and the ontology itself. An in-memory store is available for tests and the API cache.

  • Validated on save. A schema whose links reference undefined object types is rejected at author time, never persisted broken.
  • Monotonic version. Each save bumps an integer version (previous + 1). The stored version overrides whatever the incoming document carried.
  • Single current copy. There is one file per workspace — the version counts changes; it is not a per-version history or rollback log.
◆ The arc
Facts → decisions → policy → a typed ontology → governed actions, each stage sharpening the next. Pair this with the Rules Engine for governance and the Web Ingester to fill the graph from real sources.
↑ Top