Skip to content

Reasoning & inference

View Markdown

A query can return the facts you wrote and the facts that follow from your schema. Say the schema states that an active study site is a study site. A query for study sites can then include the active ones.

You choose this per query with the entailment option. Stored data does not change. This page uses the recall graph from Build with JSON records. The RDF words are explained in core concepts.

RDFS and OWL are W3C standards for statements about types and links. rdfs:subClassOf, for example, states that every record of one type also has another type.

The entailment option selects which of these statements a query applies:

entailment The query also returns Statements it reads
none (default) Nothing extra: the facts as written. None.
subclass Records of every subtype. A query for study sites matches active study sites. rdfs:subClassOf
rdfs Everything subclass returns. Values written under a more specific link or field. Types that follow from a link’s declared source or target type. rdfs:subClassOf, rdfs:subPropertyOf, rdfs:domain, rdfs:range
owl Everything rdfs returns. Equal types, links and records, reverse links, symmetric and transitive links, link chains, and class rules. The OWL statements below.

In RDF, a type is a class and a link or field is a property. A “subproperty” is a more specific link or field.

Reasoning follows these rules:

  • It runs when the query runs. No fact is copied, and no index is rebuilt.
  • It reads the schema statements of the commit the query reads. The same query at the same commit returns the same rows.
  • A new schema statement counts from the commit that writes it. A strong read applies it at once. An eventual read applies it once that commit is published.
  • With as_of_commit_seq, a query reasons over the data and the schema of that earlier commit.
  • A super_types link is part of the JSON schema, which changes without a commit. A strong read applies a new link at once. An eventual read applies it after the graph republishes. A read of an earlier commit can apply links added later.
Surface How to pass it
POST /v1/query/sparql-text Body field "entailment": "subclass"
GET or POST /sparql URL parameter entailment=subclass
TypeScript SDK lbb.sparqlText({ query, entailment }) or lbb.sparqlRows({ query, entailment })
Python SDK lbb.sparql(query, entailment="subclass")
MCP lbb_query with mode: "sparql" and entailment
Console Knowledge graph → Ontology → Inferred facts, see In the console

The structured JSON query, POST /v1/query/sparql, has no entailment field. It matches the facts as written. An unknown entailment value returns HTTP 400 invalid_request.

A JSON schema can list super_types for a type. The graph stores each super type as an rdfs:subClassOf statement. A query can then reason over it. The recall graph declares ActiveStudySite with super_types: ["StudySite"], so the graph holds this statement:

<https://littlebigbrain.com/class/activestudysite> <http://www.w3.org/2000/01/rdf-schema#subClassOf> <https://littlebigbrain.com/class/studysite> .

Each type you declare in JSON has a global name, an IRI. It is the type name in lowercase after https://littlebigbrain.com/class/.

To give an existing type a parent, call evolve with add_super_types. Repeating a link does nothing. A strong read applies a new link at once. An eventual read applies it once the graph republishes, usually within seconds.

The code below adds the link and waits for publication. Then it runs one query twice, without and with entailment. Add each snippet on this page to the end of one file, as in the quickstart.

import { LbbClient } from "@littlebigbrain/client";
const lbb = new LbbClient({
baseUrl: process.env.LBB_URL!,
apiKey: process.env.LBB_API_KEY!,
graph: "recall",
});
await lbb.graph("recall").ontology.evolve({
ops: [{ op: "add_super_types", entity_type: "ActiveStudySite", super_types: ["StudySite"] }],
});
const { head_seq } = await lbb.publicationStatus();
await lbb.waitForPublished(head_seq);
const sites = `
PREFIX dclass: <https://littlebigbrain.com/class/>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
SELECT ?site WHERE { ?s a dclass:studysite ; rdfs:label ?site } ORDER BY ?site`;
const asWritten = await lbb.sparqlRows({ query: sites });
const withSubtypes = await lbb.sparqlRows({ query: sites, entailment: "subclass" });
console.log(asWritten.rows, withSubtypes.rows);

The two queries return different rows from the same records:

entailment Rows
none Oslo Cardiology Study Site
subclass Berlin Cardiology Study Site, Oslo Cardiology Study Site

Berlin is an ActiveStudySite. The hierarchy makes it a study site as well. No record changed.

An agent adds the same link with the MCP tool evolve_ontology. See MCP for agents. For a class outside the JSON schema, write the same statement as RDF. Load it with RDF import or SPARQL Update.

OWL statements describe links as well as types. Three statements let one plain pattern answer the recall question. Which released batches are exposed to the recall?

@prefix owl: <http://www.w3.org/2002/07/owl#> .
@prefix dr: <https://littlebigbrain.com/r/> .
@prefix ex: <https://example.org/recall/> .
dr:produced_from a owl:TransitiveProperty .
ex:recalledBy owl:inverseOf dr:recalls .
ex:exposedTo owl:propertyChainAxiom ( dr:produced_from ex:recalledBy ) .
  • owl:TransitiveProperty makes a chain of PRODUCED_FROM links count as one link. A batch is then produced from every lot upstream of it.
  • owl:inverseOf declares ex:recalledBy as the reverse of RECALLS.
  • owl:propertyChainAxiom defines ex:exposedTo as PRODUCED_FROM, then recalledBy. A batch is exposed to each notice that recalls a lot it came from.

The JSON schema already names RECALLED_BY as the inverse of RECALLS. That name works as a query alias, dr:recalled_by. OWL statements do not see aliases, so the document declares the reverse link with owl:inverseOf.

The code below loads the document with the RDF import. The import adds Resource, RdfLiteral and RDF_TRIPLE to the graph’s schema. After that, running the guide’s define call again fails with 400 ontology_identity_breaking_change. Use evolve for later schema changes.

const axioms = `
@prefix owl: <http://www.w3.org/2002/07/owl#> .
@prefix dr: <https://littlebigbrain.com/r/> .
@prefix ex: <https://example.org/recall/> .
dr:produced_from a owl:TransitiveProperty .
ex:recalledBy owl:inverseOf dr:recalls .
ex:exposedTo owl:propertyChainAxiom ( dr:produced_from ex:recalledBy ) .
`;
const loaded = await lbb.graph("recall").facts.importRdf(axioms, {
format: "turtle",
idempotencyKey: "recall-axioms-1",
});
await lbb.waitForPublished(loaded.committed_commit_seq!);
const exposed = await lbb.sparqlRows({
query: `
PREFIX dclass: <https://littlebigbrain.com/class/>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
PREFIX ex: <https://example.org/recall/>
SELECT ?batch WHERE {
?b a dclass:releasedbatch ; ex:exposedTo ?notice ; rdfs:label ?batch .
?notice rdfs:label "FDA valsartan recall" .
}`,
entailment: "owl",
});
console.log(exposed.rows);

The query returns one row, Cardiovex batch CVX-A17. The batch reaches the recalled lot through the granulation lot. With entailment: "none", the same query returns no rows, because no exposedTo fact is stored.

entailment: "owl" applies everything rdfs applies. On top of that, it reads these OWL statements from the schema:

Group Statements What a query returns
Equivalence owl:equivalentClass, owl:equivalentProperty Equal types share members, and equal links share values, in both directions.
Link rules owl:inverseOf A fact answers from either direction. This includes facts on a subproperty of either side: x directlyOwns y answers y ownedBy x when directlyOwns is a subproperty of owns.
owl:SymmetricProperty The link matches in both orders.
owl:TransitiveProperty Chains of the link and its subproperties count as one link.
owl:propertyChainAxiom A chain of links derives the head link. A chain may use another chain’s head, up to 8 levels and 256 expanded path terms. Chains must not be recursive.
Class rules owl:unionOf Each member type is a subtype of the union.
owl:intersectionOf The intersection is a subtype of each part, and a record in every part is a member.
owl:oneOf The listed records are members of the type.
Restrictions owl:hasValue A record with the value is a member, and a member has the value.
owl:someValuesFrom A record linked to a record of the filler type is a member.
owl:allValuesFrom A record reached from a member through the restricted link gets the filler type.
Identity owl:sameAs Records in one sameAs group are one record. A fixed IRI matches through any member, and a variable binds every member.

The restriction rules combine with inverse, symmetric, transitive, subproperty, chain and hasValue facts. A restriction names its link with owl:onProperty.

Some schemas are outside what the regimes can answer exactly. The query then fails with HTTP 400 invalid_request. It never returns a partial answer. The message starts with OWL entailment is unavailable or RDFS entailment is unavailable.

Limit Value Regimes refused
Schema closure (type pairs, link pairs and domain and range pairs) 1,000,000 pairs subclass, rdfs, owl
RDF list cells behind unionOf, intersectionOf, oneOf and chains 100,000 owl
Property chain composition 8 levels, 256 expanded terms, no recursion owl
owl:sameAs statements 100,000 pairs owl
One owl:sameAs group 256 records owl

A query without entailment keeps working on the same graph.

These are not inferred:

  • Existential superclasses and facts derived from cardinality.
  • Documents named by owl:imports. Import each document instead.
  • Axiomatic RDF triples, container membership, and rules about rdfs:Resource or rdfs:Datatype.
  • Schema statements whose subject or object is a blank node. OWL restriction classes are the exception.

Cardinality declarations are checked as validation rules. They appear in the published conformance report.

Entailment does not apply inside a property path, such as ?b ex:exposedTo/rdfs:label ?x or dr:produced_from+. Write each step as its own pattern. A path over the stored statements walks a type hierarchy without entailment: ?s a/rdfs:subClassOf* dclass:studysite.

  • The schema rules are computed once per published commit and schema version. The first query that uses entailment computes them. Later queries reuse them.
  • A query pattern that no schema statement mentions runs the same plan with and without entailment.
  • Transitive links and chains are read as index traversals from the bound end of the pattern. The normal query time limit applies.
  • An aggregate over a large type with subtypes reads every member. The answer is exact, and the cost grows with the member count.

A query cannot run custom inference rules. A request with reason: true returns HTTP 400 invalid_request. The message names the entailment regimes to use instead. reason=True in the Python SDK’s sparql() returns the same error.

Knowledge graph → Ontology always lists a class with its subclasses’ records. The Inferred facts switch adds what the ontology implies, by OWL reasoning, to instance lists and records. Inferred results are marked in amber. Query errors appear inline, so you can retry or turn the switch off.

OWL axioms shows the schema statements as diagrams, grouped by type and link. The legend explains three marks:

  • A solid arrow is a link in your data.
  • An amber dashed arrow is what reasoning adds.
  • A Validation badge marks a rule that is checked and never inferred.

For example, the path memberOf then owns is drawn above a dashed worksOn arrow. Both connect the same two records. A link with a declared source and target type starts with a Connects diagram.

More behavior of the OWL axioms view:

  • Hover or tap the ⓘ next to a rule title to read it as a sentence.
  • Search, or filter by Types and Relationships.
  • owl:sameAs links between individuals appear on the individual’s card in Ontology.
  • The header arrow opens the entity. OWL details shows the full IRI, predicates and expressions such as worksOn some Project.
  • Lettered nodes stand for any record. They are not query results.
  • Diagrams show stored schema statements. They do not change with the reasoning mode.
  • Cards come in pages of 24. The view reads at most 20,000 statements and labels a partial result.
  • Imported ontology metadata stored outside the RDF graph does not appear here.

Knowledge graph → Ontology shows one class or entity at a time. An instance opens as a record: its values, and its relationships beside them. Map shows it among its links. Click a linked item to move to it. The trail above the view leads back.

A class card shows its definition. Browse instances pages through every member, with an exact count and a label filter. With Inferred facts on, members added by reasoning are marked inferred.

  • The same answer each run. Transitive links, type hierarchies and classifications follow from the schema. An LLM that derives them itself can answer differently each call, and it spends tokens.
  • Broader answers without more writes. A query for a general type returns the records of its subtypes. The agent does not list every subtype.
  • One record under many names. With owl:sameAs, an agent writes facts under the IRI it has. It reads the facts written under every equal IRI.