The most common pattern is the Reasoner for IF/THEN forward-chaining:
from semantica.reasoning import Reasoner, Rule, RuleTypereasoner = Reasoner()# Add facts as strings in predicate(args) formreasoner.add_fact("Manager(Alice)")reasoner.add_fact("Employee(Alice)")# Add an IF-THEN rule using the string formreasoner.add_rule("IF Manager(?x) THEN HasAuthority(?x)")# Run forward chaining: returns List[InferenceResult]results = reasoner.forward_chain()for r in results: print(r.conclusion) # "HasAuthority(Alice)" print(r.confidence) # 1.0 if r.rule_used: print(r.rule_used.name) # name of the rule applied
Or build rules programmatically using the Rule dataclass:
Reasoner is the unified entry point for rule-based inference: iterates facts and rules to a fixpoint, then optionally proves a specific goal via backward chaining:
from semantica.reasoning import Reasoner, Rule, RuleType, InferenceResultreasoner = Reasoner()# Facts can be strings, KG entity dicts, or KG relationship dictsreasoner.add_fact("Manager(John)")reasoner.add_fact("Employee(John)")# IF-THEN string formreasoner.add_rule("IF Manager(?x) AND Employee(?x) THEN SeniorStaff(?x)")# Forward chaining: iterates until fixpointresults = reasoner.forward_chain()for r in results: print(r.conclusion) # e.g. "SeniorStaff(John)" print(r.premises) # list of premise strings matched print(r.confidence) # float# Backward chaining: prove a specific goalresult = reasoner.backward_chain("SeniorStaff(John)", max_depth=10)if result: print(f"Proven: {result.conclusion}") print(f"Premises: {result.premises}")# infer_facts() loads facts and rules in one call, returns conclusion stringsconclusions = reasoner.infer_facts( facts=["Manager(Alice)", "Employee(Alice)"], rules=["IF Manager(?x) THEN HasAuthority(?x)"],)# → ["HasAuthority(Alice)"]
High-performance Rete pattern matching for large rule sets:
from semantica.reasoning import ReteEngine, Rule, Fact, RuleTypeengine = ReteEngine()# Build the Rete network from a list of Rule objectsrules = [ Rule( rule_id="r1", name="manager_authority", conditions=["Manager(?x)"], conclusion="HasAuthority(?x)", )]engine.build_network(rules)# Add facts to working memoryengine.add_fact(Fact(fact_id="f1", predicate="Manager", arguments=["Alice"]))# Match patterns and executematches = engine.match_patterns()results = engine.execute_matches(matches)# results is a list of conclusion values from matched rules# Network statisticsstats = engine.get_network_stats()# → {"total_nodes": N, "alpha_nodes": A, "beta_nodes": B, "terminal_nodes": T, "facts": F}engine.reset()
SPARQLReasoner( config=None, # optional config dict triplet_store=None, # optional TripletStore instance for live query execution enable_inference=True,)
execute_query() returns empty bindings when no triplet_store is configured. Pass a TripletStore instance via the triplet_store= kwarg to execute queries against a live backend.
Pure-Python bottom-up semi-naive fixpoint evaluation for recursive Horn clause rules. Termination is guaranteed: the engine detects fixpoint convergence and stops:
from semantica.reasoning import DatalogReasoner, DatalogFactdatalog = DatalogReasoner()# Add base facts: string form is the simplestdatalog.add_fact("parent(alice, bob)")datalog.add_fact("parent(bob, charlie)")# Or use DatalogFact directly (args is a tuple of strings)datalog.add_fact(DatalogFact(predicate="parent", args=("charlie", "dave")))# Add recursive rules using Horn clause syntaxdatalog.add_rule("ancestor(X, Y) :- parent(X, Y).")datalog.add_rule("ancestor(X, Z) :- parent(X, Y), ancestor(Y, Z).")# Evaluate to fixpoint: returns all derived fact stringsall_facts = datalog.derive_all()# e.g. ["parent(alice, bob)", "parent(bob, charlie)", ..., "ancestor(alice, bob)", ...]# Query with variable pattern: variables start with uppercase or ?results = datalog.query("ancestor(alice, ?Z)")# → [{"Z": "bob"}, {"Z": "charlie"}, {"Z": "dave"}]# Clear and start overdatalog.clear()
from semantica.reasoning import DatalogFact, DatalogRule# DatalogFact: ground fact; args must all be constants (lowercase start)fact = DatalogFact(predicate="parent", args=("alice", "bob"))# DatalogRule: parsed from string; head and body are set by the parser# Use add_rule("head(X, Y) :- body(X, Z), body2(Z, Y)."): do not construct directly
Pure-Python Allen interval algebra: all 13 relations, no LLM calls:
from datetime import datetimefrom semantica.reasoning import TemporalReasoningEngine, TemporalInterval, IntervalRelationengine = TemporalReasoningEngine()ceo_tenure = TemporalInterval(start=datetime(1997, 9, 16), end=datetime(2011, 8, 24))board_member = TemporalInterval(start=datetime(2000, 1, 1), end=datetime(2012, 6, 1))# Compute Allen relation: method is relation(), not get_relation()rel = engine.relation(ceo_tenure, board_member)# → IntervalRelation.DURING (ceo_tenure is fully inside board_member)# Other helpersengine.overlaps(ceo_tenure, board_member) # boolengine.contains(board_member, ceo_tenure) # bool# Is a given point in time inside an interval?engine.active_at(ceo_tenure, datetime(2005, 6, 1)) # True
All 13 Allen interval algebra relations:
Relation
Meaning
BEFORE
A ends before B starts
MEETS
A ends exactly when B starts
OVERLAPS
A starts before B, ends inside B
DURING
A is fully inside B
STARTS
A and B start together, A ends first
FINISHES
A and B end together, A starts later
EQUALS
Identical intervals
AFTER
Inverse of BEFORE
MET_BY
Inverse of MEETS
OVERLAPPED_BY
Inverse of OVERLAPS
CONTAINS
Inverse of DURING
STARTED_BY
Inverse of STARTS
FINISHED_BY
Inverse of FINISHES
TemporalInterval.start expects a datetime object, not a string. Import datetime from the standard library and construct intervals with datetime(year, month, day).
Generate structured explanations for any InferenceResult:
from semantica.reasoning import ExplanationGenerator, Reasoner, Rulereasoner = Reasoner()reasoner.add_fact("Manager(John)")reasoner.add_rule("IF Manager(?x) THEN HasAuthority(?x)")results = reasoner.forward_chain()# ExplanationGenerator takes no positional argsgenerator = ExplanationGenerator()# Pass an InferenceResult object: not a dictexplanation = generator.generate_explanation(results[0])print(f"Type: {explanation.explanation_type}") # "inference"print(f"Conclusion: {explanation.conclusion}")print(f"NL: {explanation.natural_language}")if explanation.reasoning_path: for step in explanation.reasoning_path.steps: print(f" Step {step.step_id}: {step.description}") if step.rule_applied: print(f" Rule: {step.rule_applied.name}")# Justify a conclusion with a reasoning pathpath = generator.show_reasoning_path(results[0])justification = generator.justify_conclusion(results[0].conclusion, path)print(justification.explanation_text)
For recursive rules (e.g. ancestor, reachability, transitivity), use DatalogReasoner: it guarantees termination via semi-naive bottom-up fixpoint evaluation. Reasoner.forward_chain() has a max_iterations cap (default 50) and will silently stop early with deep recursion.
GraphReasoner requires a configured LLM provider. If the provider fails to initialize, reason() returns an error string instead of raising. Check reasoner.provider is not None before calling if you need to surface failures explicitly.
Knowledge Graph
The knowledge graph being reasoned over.
Ontology
Ontology axioms and SHACL constraints for logical reasoning.
Triplet Store
RDF backend for SPARQL-based reasoning.
Context
Reasoning integrated into agent decision intelligence.