Reasoning & inference
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.
What entailment does
Section titled “What entailment does”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_typeslink 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.
Where you set it
Section titled “Where you set it”| 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.
Add a type hierarchy to JSON records
Section titled “Add a type hierarchy to JSON records”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);import os
from lbb import LbbClient
lbb = LbbClient(os.environ["LBB_URL"], api_key=os.environ["LBB_API_KEY"], graph="recall")
lbb.ontology.evolve({ "ops": [{"op": "add_super_types", "entity_type": "ActiveStudySite", "super_types": ["StudySite"]}],})lbb.wait_for_published(lbb.publication_status()["head_seq"])
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"""
print(lbb.sparql(sites).rows())print(lbb.sparql(sites, entailment="subclass").rows())curl --fail-with-body -sS -X POST "$LBB_URL/v1/ontology/evolve?graph=recall" \ -H "Authorization: Bearer $LBB_API_KEY" -H "Content-Type: application/json" \ -d '{"ops": [{"op": "add_super_types", "entity_type": "ActiveStudySite", "super_types": ["StudySite"]}]}'
until curl -sS "$LBB_URL/v1/graph/publication-status?graph=recall" \ -H "Authorization: Bearer $LBB_API_KEY" | grep -q '"state": *"current"'; do sleep 1; done
for mode in none subclass; do curl --fail-with-body -sS -X POST "$LBB_URL/v1/query/sparql-text?graph=recall" \ -H "Authorization: Bearer $LBB_API_KEY" -H "Content-Type: application/json" \ -d "{\"entailment\": \"$mode\", \"query\": \"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\"}" \ | jq -r '.results | fromjson | .results.bindings[].site.value'doneThe 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.
Answer a question with OWL
Section titled “Answer a question with OWL”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:TransitivePropertymakes a chain ofPRODUCED_FROMlinks count as one link. A batch is then produced from every lot upstream of it.owl:inverseOfdeclaresex:recalledByas the reverse ofRECALLS.owl:propertyChainAxiomdefinesex:exposedToasPRODUCED_FROM, thenrecalledBy. 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);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 ) ."""loaded = lbb.graph("recall").facts.import_rdf( axioms, format="turtle", idempotency_key="recall-axioms-1")lbb.wait_for_published(loaded["committed_commit_seq"])
exposed = lbb.sparql("""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")print(exposed.rows())Save the three statements as axioms.ttl, then:
curl --fail-with-body -sS -X POST "$LBB_URL/v1/graph/import/rdf?graph=recall&format=turtle" \ -H "Authorization: Bearer $LBB_API_KEY" -H "Content-Type: text/turtle" \ -H "Idempotency-Key: recall-axioms-1" --data-binary @axioms.ttl
until curl -sS "$LBB_URL/v1/graph/publication-status?graph=recall" \ -H "Authorization: Bearer $LBB_API_KEY" | grep -q '"state": *"current"'; do sleep 1; done
curl --fail-with-body -sS -X POST "$LBB_URL/v1/query/sparql-text?graph=recall" \ -H "Authorization: Bearer $LBB_API_KEY" -H "Content-Type: application/json" \ -d '{"entailment": "owl", "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\" . }"}' \ | jq -r '.results | fromjson | .results.bindings[].batch.value'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.
Supported OWL statements
Section titled “Supported OWL statements”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.
Limits
Section titled “Limits”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:Resourceorrdfs: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.
Performance
Section titled “Performance”- The schema rules are computed once per published commit and schema version.
The first query that uses
entailmentcomputes 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.
Rules at query time
Section titled “Rules at query time”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.
In the console
Section titled “In the console”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:sameAslinks 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.
Why reasoning helps an agent
Section titled “Why reasoning helps an agent”- 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.
Related
Section titled “Related”- Build with JSON records: the
recallgraph this page uses. - Load RDF data: import RDF and OWL documents.
- Query and validate: the query surfaces that reasoning applies to.
- Replay an answer: pin a query to an earlier commit.
- Core concepts