Merge pull request #566 from semantica-agi/docs-mintlify-component-overhaul

docs: full Mintlify component overhaul — all 27 reference pages + concepts.md
This commit is contained in:
Mohd Kaif
2026-05-24 14:59:49 +05:30
committed by GitHub
30 changed files with 6493 additions and 2136 deletions
+242 -52
View File
@@ -10,7 +10,21 @@ icon: "book-open"
Semantica transforms unstructured data — documents, web pages, reports, databases — into **knowledge graphs**: structured representations that AI systems can query, reason about, and trace back to sources.
At its core, Semantica adds a **context and intelligence layer** on top of your existing AI stack. It doesn't replace LangChain, LlamaIndex, or your LLM provider — it makes their outputs accountable.
At its core, Semantica adds a **context and accountability layer** on top of your existing AI stack. It doesn't replace LangChain, LlamaIndex, or your LLM provider — it makes their outputs **grounded**, **traceable**, and **auditable**.
<CardGroup cols={3}>
<Card title="Context Layer" icon="diagram-project">
Knowledge graphs, GraphRAG retrieval, semantic embeddings, and temporal intelligence ground every LLM response in structured, queryable facts.
</Card>
<Card title="Accountability Layer" icon="shield-check">
Provenance tracking, decision intelligence, conflict detection, and W3C PROV-O compliance make every claim in your AI stack auditable and explainable.
</Card>
<Card title="Extension Layer" icon="plug">
`PluginRegistry` and `MethodRegistry` let you replace or augment any component — ingestors, extractors, reasoning engines, backends — without changing framework code.
</Card>
</CardGroup>
---
## Knowledge Graphs
@@ -26,7 +40,7 @@ This structure makes knowledge **searchable**, **connectable**, **queryable**, a
## Entity Extraction (NER)
Scanning text to find and classify real-world entities.
Scanning text to find and classify real-world entities:
```python
# Input: "Apple Inc. was founded by Steve Jobs in 1976 in Cupertino."
@@ -42,13 +56,15 @@ Scanning text to find and classify real-world entities.
Each entity gets a type, confidence score, and a link to its source document. Three extraction methods are available:
- **`"pattern"`** — fast, regex-based, no API key required
- **`"ml"`** — local ML model, higher accuracy
- **`"llm"`** — LLM-powered, highest accuracy, supports all 8 providers
| Method | Speed | Accuracy | Requirements |
| ------ | ----- | -------- | ------------ |
| `"pattern"` | ⚡ Very fast | Moderate | No API key — regex-based |
| `"ml"` | Fast | High | Local ML model |
| `"llm"` | Medium | Highest | LLM provider — all 9 supported |
## Relationship Extraction
Finding how entities connect to each other.
Finding how entities connect to each other:
```python
{
@@ -71,8 +87,9 @@ Semantica uses embeddings for:
- **Entity resolution** — match the same entity across different sources
- **Precedent search** — find similar past decisions
- **GraphRAG retrieval** — hybrid vector + graph traversal
- **Distance Intelligence** — N×N semantic distance matrices between any node set
**Supported models:** Sentence-Transformers, FastEmbed, OpenAI, BGE
**Supported models:** Sentence-Transformers, FastEmbed, OpenAI, BGE, Ollama local embeddings.
## GraphRAG
@@ -80,14 +97,24 @@ GraphRAG (Graph-Augmented Retrieval Augmented Generation) enhances LLM responses
<img src="/assets/img/diagrams/graphrag-flow.svg" alt="GraphRAG flow: User Query → Vector Search + Graph Traversal → Context Builder → LLM → Grounded Answer" style={{ width: '100%', borderRadius: '12px', margin: '16px 0 20px' }} />
**How it works:**
<Steps>
<Step title="User submits a query">
The query is embedded and used to seed both vector search and graph traversal simultaneously.
</Step>
<Step title="Hybrid context retrieval">
Semantica retrieves relevant graph context — entities, typed relationships, and multi-hop reasoning paths — alongside vector-similar text chunks.
</Step>
<Step title="Context building">
Retrieved facts and reasoning paths are assembled into a structured prompt context, each fact tagged with its source node and confidence.
</Step>
<Step title="LLM generates a grounded response">
The LLM produces an answer where every claim links back to a source node in the graph — no floating assertions, no hallucinations from training data.
</Step>
</Steps>
1. User submits a query
2. Semantica retrieves relevant graph context — entities, relationships, reasoning paths
3. The LLM generates a response grounded in that context
4. Every claim in the response links back to a source node in the graph
This eliminates the hallucination and traceability problems of standard RAG.
<Tip>
**GraphRAG eliminates the hallucination and traceability problems of standard RAG.** Standard RAG retrieves text chunks; GraphRAG retrieves structured facts with typed relationships. The LLM cannot confabulate structure that was never in the graph.
</Tip>
## Ontology
@@ -104,7 +131,7 @@ ontology = {
}
```
Semantica can auto-generate ontologies from your knowledge graph or import existing OWL/RDF/Turtle ontologies. The **Ontology Hub** (v0.5.0) adds a visual editor, SHACL Studio, alignment authoring, and a live health dashboard.
Semantica can auto-generate ontologies from your knowledge graph or import existing OWL/RDF/Turtle ontologies. The **Ontology Hub** (v0.5.0) adds a visual editor, SHACL Studio, alignment authoring, and a live health dashboard. See the [Ontology reference](reference/ontology) for the full 6-stage generation pipeline.
## Reasoning & Inference
@@ -116,75 +143,165 @@ Known: Apple Inc. is headquartered in Cupertino
Inferred: Steve Jobs has a connection to Cupertino
```
| Engine | Description |
| ------ | ----------- |
| Forward chaining | Applies rules repeatedly until no new facts can be derived |
| Rete network | Efficient pattern matching for large rule sets |
| Deductive | Classical deductive reasoning |
| Abductive | Infers the most likely explanation |
| SPARQL | Query-based inference over RDF graphs |
| Datalog | Recursive Horn clause rules with fixpoint semantics (v0.4.0) |
<Tabs>
<Tab title="Forward Chaining">
Applies IF/THEN rules repeatedly until no new facts can be derived. Best for alert systems, compliance checks, and trigger-based workflows.
All engines produce **explainable inference paths**, not black-box conclusions.
```python
from semantica.reasoning import Reasoner, Rule, Fact, RuleType
engine = Reasoner()
engine.add_fact(Fact(subject="Alice", predicate="is_a", obj="Manager"))
engine.add_rule(Rule(
rule_type=RuleType.FORWARD_CHAIN,
conditions=[{"subject": "?x", "predicate": "is_a", "object": "Manager"}],
conclusion={"subject": "?x", "predicate": "has_authority", "object": "true"}
))
result = engine.infer()
```
</Tab>
<Tab title="Rete Network">
Efficient pattern matching for large rule sets — the Rete algorithm avoids re-evaluating rules whose preconditions haven't changed. Best for thousands of rules over millions of facts.
```python
from semantica.reasoning import ReteEngine
engine = ReteEngine()
engine.load_rules("rules/domain_rules.json")
results = engine.run(kg)
```
</Tab>
<Tab title="Deductive & Abductive">
**Deductive** — classical syllogistic reasoning from premises to guaranteed conclusions.
**Abductive** — infers the most likely explanation for observed evidence. Best for diagnostic and investigative use cases.
```python
from semantica.reasoning import GraphReasoner
graph_reasoner = GraphReasoner(kg)
graph_reasoner.add_rule({"if": [{"subject": "?a", "predicate": "parent_of", "object": "?b"}], "then": {"subject": "?a", "predicate": "ancestor_of", "object": "?b"}})
inferences = graph_reasoner.infer(kg)
```
</Tab>
<Tab title="Datalog (v0.4.0)">
Recursive Horn clause rules with fixpoint semantics — handles transitive closure and recursive relationships that forward chaining cannot express.
```python
from semantica.reasoning import DatalogReasoner, DatalogFact, DatalogRule
reasoner = DatalogReasoner()
reasoner.add_fact(DatalogFact("parent", ("alice", "bob")))
reasoner.add_rule(DatalogRule("ancestor(?X, ?Y) :- parent(?X, ?Y)."))
reasoner.evaluate()
results = reasoner.query("ancestor(alice, ?Z)")
```
</Tab>
<Tab title="Engine Comparison">
| Engine | Description | Best For |
| ------ | ----------- | -------- |
| Forward chaining | Applies rules until fixpoint | Alert systems, compliance checks |
| Rete network | Efficient pattern matching | Large rule sets, high fact throughput |
| Deductive | Classical syllogistic reasoning | Mathematical and logical inference |
| Abductive | Most likely explanation | Diagnostics, investigation |
| SPARQL | Query-based inference over RDF | Semantic web, ontology reasoning |
| Datalog (v0.4.0) | Recursive Horn clause rules | Transitive closure, graph reachability |
</Tab>
</Tabs>
All engines produce **explainable inference paths** — not black-box conclusions. Every derived fact includes the rules and premises that produced it.
## Temporal Intelligence
Knowledge changes over time. Temporal graphs attach `valid_from` / `valid_until` windows to nodes and edges, enabling point-in-time queries and historical analysis.
```python
from semantica.kg import TemporalKnowledgeGraph
from semantica.kg import TemporalGraphQuery
from datetime import datetime
tkg = TemporalKnowledgeGraph()
tkg.add_node("ceo_role", valid_from=datetime(2020, 1, 1), valid_until=datetime(2023, 6, 1))
query_engine = TemporalGraphQuery(enable_temporal_reasoning=True)
# Query the graph as it existed on a specific date
snapshot = tkg.at(datetime(2021, 6, 15))
snapshot = query_engine.query_at_time(kg, query="", at_time=datetime(2021, 6, 15))
```
**Features:** Allen interval algebra (all 13 relations), OWL-Time export, `recorded_at` stamping, temporal provenance.
**Supported features:** Allen interval algebra (all 13 temporal relations), OWL-Time export, `recorded_at` stamping, temporal provenance.
**Common uses:** tracking company leadership changes, policy evolution, research timelines, financial instrument histories.
**Common uses:** tracking company leadership changes, policy evolution, research timelines, financial instrument histories, regulatory compliance windows.
## Distance Intelligence
Explore the semantic neighborhood of any entity in your graph. Useful for understanding what's conceptually close, detecting clusters, and visualizing knowledge topology.
Explore the semantic neighborhood of any entity in your graph — useful for understanding what's conceptually close, detecting clusters, and visualizing knowledge topology.
```python
from semantica.kg import DistanceCalculator
from semantica.kg import SimilarityCalculator
calc = DistanceCalculator(graph)
neighborhood = calc.semantic_neighborhood("Apple Inc.", radius=0.4)
matrix = calc.distance_matrix(["Apple Inc.", "Google", "Microsoft"])
calc = SimilarityCalculator()
scores = calc.calculate_similarity(entity_a, entity_b)
```
**Features:** N×N distance matrices, ego-mode visualization, distance band classification (`near` / `mid` / `far`), embedding cache optimization.
**Features:** N×N semantic distance matrices, ego-mode visualization, distance band classification (`near` / `mid` / `far`), embedding cache optimization for large graphs.
The [Visualization module](reference/visualization) renders distance matrices as interactive heatmaps and ego-mode neighborhood graphs. The [Explorer](reference/explorer) embeds distance intelligence directly in the browser dashboard.
## Deduplication & Entity Resolution
Real-world data contains the same entity under many names — "Apple", "Apple Inc.", "Apple Computer Inc." Semantica's deduplication pipeline detects these, merges attributes, resolves conflicts, and preserves the original source provenance.
**Strategies:**
<Tabs>
<Tab title="Strategies">
- **v1** — Jaro-Winkler similarity, suitable for small datasets
- **`blocking_v2`** — candidate blocking for large corpora
- **`hybrid_v2`** — combines blocking with semantic matching
- **`semantic_v2`** — pure embedding-based resolution, up to 7x faster than v1
| Strategy | Algorithm | Best For |
| -------- | --------- | -------- |
| `v1` | Jaro-Winkler string similarity | Small datasets, fast baseline |
| `blocking_v2` | Candidate blocking + similarity | Large corpora — reduces O(n²) comparisons |
| `hybrid_v2` | Blocking + semantic embedding match | Mixed structured/unstructured entity names |
| `semantic_v2` | Pure embedding-based resolution | Up to 7× faster than v1; handles abbreviations and aliases |
</Tab>
<Tab title="Configuration">
```python
from semantica.deduplication import DuplicateDetector, EntityMerger
detector = DuplicateDetector(similarity_threshold=0.85)
duplicates = detector.detect_duplicates(entities)
merger = EntityMerger()
deduplicated_entities = merger.merge_duplicates(entities)
```
</Tab>
</Tabs>
## Provenance & Auditability
Every fact in Semantica links back to:
- The source document it came from
- The extraction method used
- The ontology rules applied
- The reasoning steps that produced any inference
- The **source document** it came from
- The **extraction method** used (pattern / ML / LLM)
- The **ontology rules** applied during graph construction
- The **reasoning steps** that produced any inferred fact
This is W3C PROV-O compliant lineage — suitable for regulated industries that require audit trails (HIPAA, SOX, GDPR, FDA 21 CFR Part 11).
<Note>
This is W3C PROV-O compliant lineage — suitable for regulated industries that require audit trails (HIPAA, SOX, GDPR, FDA 21 CFR Part 11). Use `RDFExporter(include_provenance=True)` to embed provenance inline in any RDF export.
</Note>
```python
from semantica.provenance import ProvenanceManager
prov = ProvenanceManager()
lineage = prov.get_entity_lineage("apple_inc")
print(f"Source: {lineage.source_document}")
print(f"Method: {lineage.extraction_method}")
print(f"Extracted: {lineage.timestamp}")
print(f"Checksum: {lineage.checksum}")
```
## Decision Intelligence
Every agent decision is a first-class object in Semantica — recorded, causally linked, and searchable by precedent.
Every agent decision is a first-class object in Semantica — recorded, causally linked, and searchable by precedent. This is the **accountability layer** for AI pipelines: decisions are no longer ephemeral log messages, they are queryable knowledge graph nodes.
```python
decision_id = context.record_decision(
@@ -195,11 +312,16 @@ decision_id = context.record_decision(
confidence=0.91,
)
# Find similar past decisions before making a new one
precedents = context.find_precedents("model selection reasoning", limit=5)
# Trace downstream impact of a past decision
influence = context.analyze_decision_influence(decision_id)
```
This prevents inconsistent decisions, enables audits, and lets agents learn from their own history.
<Tip>
**Use `find_precedents()` before every high-stakes decision.** Hybrid similarity search over all recorded decisions surfaces past reasoning that may apply — reducing inconsistency across agent runs and enabling genuine organisational learning from AI decision history.
</Tip>
## Conflict Detection
@@ -207,10 +329,78 @@ When multiple sources disagree on the same fact, Semantica flags and resolves th
**Resolution strategies:**
- Prefer the most recent source
- Prefer the most reliable source
- Majority vote across sources
- Flag for manual review
- **Recency** — prefer the most recent source
- **Source credibility** — prefer the most reliable source (configurable credibility scores)
- **Majority vote** — aggregate across all sources with ≥ 2 agreeing
- **Manual review** — flag for human arbitration; continue pipeline without blocking
See the [Conflicts reference](reference/conflicts) for `ConflictResolver`, `SourceTracker`, and `InvestigationGuideGenerator`.
## Custom Plugin Development
Semantica is designed for extension. Any component — ingestor, extractor, graph builder, reasoning engine — can be replaced or augmented with a custom implementation registered at runtime.
<AccordionGroup>
<Accordion title="PluginRegistry — replace any component by name">
`PluginRegistry` provides dynamic plugin discovery, registration, and loading across all modules. Register your own class under a string key; Semantica will use it wherever that key is referenced in config or pipeline steps.
```python
from semantica.core import PluginRegistry
registry = PluginRegistry()
# Register a custom ingestor
registry.register_plugin(
"my_sql_ingestor", MySQLIngestor,
version="1.0.0",
description="PostgreSQL ingestor for internal warehouse",
capabilities=["ingest"],
)
# Load and use
plugin = registry.load_plugin("my_sql_ingestor", connection_string="postgresql://...")
result = plugin.execute("SELECT * FROM documents")
# Reference by name in pipeline YAML — no code changes needed
```
```yaml
steps:
- name: ingest
plugin: my_sql_ingestor
config:
connection_string: "${DB_URL}"
```
**Extension points available:** ingestors, parsers, normalizers, extractors, reasoning engines, export formats, vector store backends, graph store backends, visualization renderers.
</Accordion>
<Accordion title="MethodRegistry — add domain-specific graph operations">
`MethodRegistry` lets you register custom methods on knowledge graph objects by name — useful for adding domain-specific graph operations without subclassing.
```python
from semantica.kg import MethodRegistry
registry = MethodRegistry()
def find_supply_chain_hops(graph, source_node, max_hops=3):
"""Custom BFS traversal for supply chain graphs."""
...
# Register under a string key
registry.register("supply_chain_hops", find_supply_chain_hops)
# Call by name on any graph object
result = registry.call("supply_chain_hops", kg, source_node="Supplier_A", max_hops=5)
# List all registered methods
print(registry.list_methods()) # ["supply_chain_hops", ...]
```
</Accordion>
</AccordionGroup>
<CardGroup cols={2}>
<Card title="Quickstart Tutorial" icon="play" href="quickstart">
+101 -43
View File
@@ -13,11 +13,23 @@ description: "The Accountability and Context Layer for AI — Context Graphs ·
AI agents today are powerful but not trustworthy. Five structural gaps make them impossible to deploy in regulated environments:
- **No memory structure.** Agents store embeddings, not meaning. There's no way to ask *why* something was recalled or trace a fact to its source.
- **No decision trail.** Agents act continuously but record nothing. When something breaks, there's no history to debug or audit.
- **No provenance.** Outputs can't be traced back to source facts. In healthcare, finance, and legal, this is a hard compliance blocker.
- **No reasoning transparency.** Black-box answers with zero explanation of how a conclusion was reached.
- **No conflict detection.** Contradictory facts silently coexist in vector stores, producing unpredictable and inconsistent outputs.
<CardGroup cols={2}>
<Card title="No memory structure" icon="brain">
Agents store embeddings, not meaning. There's no way to ask *why* something was recalled or trace a fact to its source.
</Card>
<Card title="No decision trail" icon="clock-rotate-left">
Agents act continuously but record nothing. When something breaks, there's no history to debug or audit.
</Card>
<Card title="No provenance" icon="link-slash">
Outputs can't be traced back to source facts. In healthcare, finance, and legal, this is a hard compliance blocker.
</Card>
<Card title="No reasoning transparency" icon="eye-slash">
Black-box answers with zero explanation of how a conclusion was reached.
</Card>
<Card title="No conflict detection" icon="triangle-exclamation">
Contradictory facts silently coexist in vector stores, producing unpredictable and inconsistent outputs.
</Card>
</CardGroup>
These aren't edge cases. They're why AI cannot be deployed in healthcare, finance, legal, and government without custom guardrails built from scratch.
@@ -25,12 +37,26 @@ These aren't edge cases. They're why AI cannot be deployed in healthcare, financ
Semantica is the **accountability and context layer** you add on top of your existing AI stack. Not a replacement for LangChain or LlamaIndex — the infrastructure that makes their outputs trustworthy.
- **Context Graphs** — a structured, queryable graph of everything your agent knows, decides, and reasons about. Persistent across runs.
- **Decision Intelligence** — every decision is a first-class object: recorded, causally linked, searchable by precedent, and analyzable for downstream impact.
- **Full Provenance** — every fact links back to its source. W3C PROV-O compliant. Full lineage from ingestion to inference.
- **Reasoning Engines** — forward chaining, Rete, deductive, abductive, SPARQL, Datalog. Explainable paths, not black boxes.
- **Temporal Intelligence** — point-in-time queries, Allen interval algebra, temporal provenance, OWL-Time export.
- **Ontology Hub** — visual editor, SHACL Studio, alignment authoring, health dashboard. Full ontology lifecycle in the browser.
<CardGroup cols={2}>
<Card title="Context Graphs" icon="diagram-project">
A structured, queryable graph of everything your agent knows, decides, and reasons about. Persistent across runs.
</Card>
<Card title="Decision Intelligence" icon="check-circle">
Every decision is a first-class object: recorded, causally linked, searchable by precedent, and analyzable for downstream impact.
</Card>
<Card title="Full Provenance" icon="shield-check">
Every fact links back to its source. W3C PROV-O compliant. Full lineage from ingestion to inference.
</Card>
<Card title="Reasoning Engines" icon="microchip">
Forward chaining, Rete, deductive, abductive, SPARQL, Datalog. Explainable paths, not black boxes.
</Card>
<Card title="Temporal Intelligence" icon="clock">
Point-in-time queries, Allen interval algebra, temporal provenance, OWL-Time export.
</Card>
<Card title="Ontology Hub" icon="sitemap">
Visual editor, SHACL Studio, alignment authoring, health dashboard. Full ontology lifecycle in the browser.
</Card>
</CardGroup>
Works alongside any LLM provider and any agent framework.
@@ -47,13 +73,13 @@ pip install semantica
```python OpenAI
from semantica.context import AgentContext, ContextGraph
from semantica.vector_store import VectorStore
from semantica.llms import OpenAIProvider
from semantica.llms import OpenAI
context = AgentContext(
vector_store=VectorStore(backend="faiss", dimension=1536),
knowledge_graph=ContextGraph(advanced_analytics=True),
decision_tracking=True,
llm=OpenAIProvider(model="gpt-4o"),
llm=OpenAI(model="gpt-4o"),
)
context.store("GPT-4 outperforms GPT-3.5 on reasoning benchmarks by 40%")
@@ -73,13 +99,14 @@ influence = context.analyze_decision_influence(decision_id)
```python Anthropic
from semantica.context import AgentContext, ContextGraph
from semantica.vector_store import VectorStore
from semantica.llms import AnthropicProvider
from semantica.llms import LiteLLM
import os
context = AgentContext(
vector_store=VectorStore(backend="faiss", dimension=1024),
knowledge_graph=ContextGraph(advanced_analytics=True),
decision_tracking=True,
llm=AnthropicProvider(model="claude-opus-4-7"),
llm=LiteLLM(model="anthropic/claude-opus-4-7", api_key=os.getenv("ANTHROPIC_API_KEY")),
)
context.store("Claude excels at long-context reasoning and code generation")
@@ -98,13 +125,13 @@ precedents = context.find_precedents("document analysis model", limit=5)
```python Ollama (Local)
from semantica.context import AgentContext, ContextGraph
from semantica.vector_store import VectorStore
from semantica.llms import OllamaProvider
from semantica.llms import LiteLLM
context = AgentContext(
vector_store=VectorStore(backend="faiss", dimension=768),
knowledge_graph=ContextGraph(advanced_analytics=True),
decision_tracking=True,
llm=OllamaProvider(model="llama3.2", base_url="http://localhost:11434"),
llm=LiteLLM(model="ollama/llama3.2", base_url="http://localhost:11434"),
)
# Fully local — no data leaves your infrastructure
@@ -173,7 +200,23 @@ pip install semantica==0.5.0
## Start Here
If you're new to Semantica, install first and then open [Quickstart](quickstart). Use [Core Concepts](concepts) for the mental model, or jump to [API Reference](reference/context) when you need exact details.
<Steps>
<Step title="Install">
```bash
pip install semantica
```
See [Installation](installation) for optional extras and environment setup.
</Step>
<Step title="Follow the Quickstart">
Build a complete knowledge graph pipeline — ingest, extract, build, query — in [5 minutes](quickstart).
</Step>
<Step title="Learn the mental model">
[Core Concepts](concepts) explains knowledge graphs, GraphRAG, provenance, and decision intelligence. Read this before the API reference.
</Step>
<Step title="Go deep on any module">
Every module has a dedicated [reference page](reference/context) with class docs, parameter tables, and runnable examples.
</Step>
</Steps>
<CardGroup cols={2}>
<Card title="Installation" icon="download" href="installation">
@@ -255,45 +298,60 @@ If you're new to Semantica, install first and then open [Quickstart](quickstart)
| `semantica.mcp_server` | MCP stdio server — 12 tools for Claude Desktop, VS Code, Cursor, Windsurf, Cline |
| `semantica.vector_store` | FAISS, Pinecone, Weaviate, Qdrant, Milvus, PgVector |
| `semantica.graph_store` | Neo4j, FalkorDB, Apache AGE, Amazon Neptune |
| `semantica.triplet_store` | In-memory and persistent RDF triple store |
| `semantica.triplet_store` | In-memory and persistent RDF triple store with SPARQL |
| `semantica.ingest` | Files, web, feeds, databases, Snowflake, Parquet, XML, MCP |
| `semantica.parse` | Document parsing — PDF, DOCX, HTML, PPTX, Docling layout analysis |
| `semantica.split` | Text chunking — sentence, paragraph, token, semantic boundary strategies |
| `semantica.normalize` | Text normalization, entity canonicalization, whitespace and encoding cleanup |
| `semantica.embeddings` | Sentence-Transformers, FastEmbed, OpenAI, BGE |
| `semantica.embeddings` | Sentence-Transformers, FastEmbed, OpenAI, BGE, Ollama local embeddings |
| `semantica.pipeline` | Pipeline DSL, parallel workers, retry policies, failure handling |
| `semantica.export` | RDF, Parquet, ArangoDB AQL, CSV, OWL, graph formats |
| `semantica.visualization` | Programmatic graph rendering — force, hierarchical, circular layouts |
| `semantica.export` | RDF, Parquet, ArangoDB AQL, CSV, OWL, Arrow, GraphML, GEXF, DOT |
| `semantica.visualization` | Programmatic graph rendering — force, hierarchical, circular, spring layouts |
| `semantica.deduplication` | Entity deduplication v1/v2, similarity scoring, blocking, merging |
| `semantica.conflicts` | Conflict detection and resolution across overlapping knowledge sources |
| `semantica.provenance` | W3C PROV-O lineage tracking, source attribution, audit trails |
| `semantica.change_management` | Version control with SHA-256 checksums, diff, rollback |
| `semantica.llms` | Groq, OpenAI, Anthropic, Gemini, Ollama, DeepSeek, Novita AI, LiteLLM |
| `semantica.seed` | Deterministic data seeding and synthetic graph generation for tests |
| `semantica.evals` | Evaluation harness — precision, recall, F1 for extraction and reasoning |
| `semantica.core` | Core data models, base classes, shared type definitions |
| `semantica.utils` | Shared utilities — ID generation, date parsing, schema helpers |
| `semantica.llms` | Groq, OpenAI, Anthropic, Gemini, Ollama, DeepSeek, Novita AI, LiteLLM, HuggingFace |
| `semantica.seed` | Foundation graph seeding from CSV, JSON, SQL, API, and RDF sources |
| `semantica.evals` | Evaluation harness — KG quality, extraction F1, pipeline benchmarking, regression tracking |
| `semantica.core` | Orchestration, ConfigManager, LifecycleManager, PluginRegistry, MethodRegistry |
| `semantica.utils` | Logging, validation, progress tracking, hash utilities, nested dict helpers |
## Built for High-Stakes Domains
Where every decision must be accountable and mistakes have real consequences:
**Healthcare & Life Sciences** — clinical decision support, drug interaction graphs, patient safety audit trails, HIPAA compliance.
**Finance & Risk** — fraud detection graphs, SOX/GDPR/MiFID II compliance, risk assessment trails.
**Legal & Compliance** — evidence-backed research, contract analysis, regulatory change tracking.
**Cybersecurity** — threat attribution graphs, incident response timelines, security audit trails.
**Government & Defense** — policy decision trails, classified information handling, provenance chains.
**Critical Infrastructure** — power grids, transportation safety, emergency response coordination.
<CardGroup cols={2}>
<Card title="Healthcare & Life Sciences" icon="heart-pulse">
Clinical decision support, drug interaction graphs, patient safety audit trails, HIPAA compliance.
</Card>
<Card title="Finance & Risk" icon="chart-line">
Fraud detection graphs, SOX/GDPR/MiFID II compliance, risk assessment trails.
</Card>
<Card title="Legal & Compliance" icon="scale-balanced">
Evidence-backed research, contract analysis, regulatory change tracking.
</Card>
<Card title="Cybersecurity" icon="shield">
Threat attribution graphs, incident response timelines, security audit trails.
</Card>
<Card title="Government & Defense" icon="building-columns">
Policy decision trails, classified information handling, provenance chains.
</Card>
<Card title="Critical Infrastructure" icon="bolt">
Power grids, transportation safety, emergency response coordination.
</Card>
</CardGroup>
## Why Semantica?
**Open source, MIT licensed.** No vendor lock-in, no paywalled features. Every line of code is available and forkable.
**Production ready.** 1,000+ passing tests, `PipelineValidator`, `FailureHandler` with exponential backoff, conflict resolution, and 12 security fixes in v0.5.0.
**Modular by design.** Import only what you need. Use `NERExtractor` without a graph store. Use `VectorStore` without decision tracking. Every component is independently swappable.
<CardGroup cols={3}>
<Card title="Open Source, MIT" icon="code-branch">
No vendor lock-in, no paywalled features. Every line of code is available and forkable.
</Card>
<Card title="Production Ready" icon="circle-check">
1,000+ passing tests, `PipelineValidator`, `FailureHandler` with exponential backoff, 12 security fixes in v0.5.0.
</Card>
<Card title="Modular by Design" icon="puzzle-piece">
Import only what you need. Use `NERExtractor` without a graph store. Every component is independently swappable.
</Card>
</CardGroup>
+70 -42
View File
@@ -134,21 +134,20 @@ triplets = trip.extract(text)
Graph construction, graph algorithms, temporal model, and distance intelligence.
```python
from semantica.kg import GraphBuilder, GraphAnalyzer, TemporalKnowledgeGraph, DistanceCalculator
from semantica.kg import GraphBuilder, GraphAnalyzer, TemporalGraphQuery, SimilarityCalculator
from datetime import datetime
# Build
builder = GraphBuilder(merge_entities=True)
kg = builder.build(entities=entities, relationships=relationships)
# Temporal graphs (v0.4.0)
tkg = TemporalKnowledgeGraph()
tkg.add_node("ceo_role", valid_from=datetime(2020, 1, 1), valid_until=datetime(2023, 6, 1))
snapshot = tkg.at(datetime(2021, 6, 15))
query_engine = TemporalGraphQuery(enable_temporal_reasoning=True)
snapshot = query_engine.query_at_time(kg, query="", at_time=datetime(2021, 6, 15))
# Distance Intelligence (v0.5.0)
calc = DistanceCalculator(kg)
neighborhood = calc.semantic_neighborhood("Apple Inc.", radius=0.4)
matrix = calc.distance_matrix(["Apple Inc.", "Google", "Microsoft"])
# Semantic similarity (v0.5.0)
calc = SimilarityCalculator()
scores = calc.calculate_similarity(entity_a, entity_b)
```
**Graph algorithms available:** centrality calculation, community detection, connectivity analysis, entity resolution, link prediction, path finding, similarity calculation
@@ -158,29 +157,29 @@ matrix = calc.distance_matrix(["Apple Inc.", "Google", "Microsoft"])
Schema management including SHACL, SKOS, alignments, diff/migration, auto-generation, and the visual Ontology Hub (v0.5.0).
```python
from semantica.ontology import OntologyManager, SHACLGenerator
from semantica.ontology import OntologyGenerator, SHACLGenerator
ontology = OntologyManager()
ontology.add_class("Person", ["name", "birth_date"])
ontology.add_relationship("works_for", "Person", "Organization")
is_valid = ontology.validate_graph(kg)
generator = OntologyGenerator()
ontology = generator.generate_from_graph(kg)
shacl = SHACLGenerator()
shapes = shacl.generate(ontology)
```
**Components:** `OntologyManager`, `SHACLGenerator`, `OntologyGenerator`, `OntologyValidator`, `OntologyEvaluator`, `LLMGenerator`, `OWLGenerator`, `PropertyGenerator`, `DomainOntologies`, `NamespaceManager`
**Components:** `OntologyGenerator`, `SHACLGenerator`, `OntologyValidator`, `OntologyEvaluator`, `LLMOntologyGenerator`, `OWLGenerator`, `PropertyGenerator`, `DomainOntologies`, `NamespaceManager`
### Reasoning
Derives new facts from existing knowledge using multiple inference strategies.
```python
from semantica.reasoning import ReasoningEngine, DatalogEngine
from semantica.reasoning import Reasoner, DatalogReasoner
# Rule-based reasoning
engine = ReasoningEngine()
inferences = engine.infer(kg, rules=["transitivity", "symmetry"])
engine = Reasoner()
engine.apply_transitivity("located_in")
engine.apply_symmetry("knows")
result = engine.infer()
# Datalog — recursive Horn clause rules (v0.4.0)
datalog = DatalogEngine()
@@ -403,9 +402,8 @@ result = pipeline.run("data/")
FastAPI Knowledge Explorer with Ontology Hub, WebSocket progress, bidirectional path finding, and indexed search (0.004ms on 118k nodes).
```python
from semantica.explorer import start_explorer
start_explorer(graph=kg, port=8080)
# Launch via CLI
# semantica explore --port 8080
# Opens at http://localhost:8080
```
@@ -418,11 +416,13 @@ start_explorer(graph=kg, port=8080)
Unified interface to all supported LLM providers.
```python
from semantica.llms import Groq, OpenAI, create_provider
from semantica.llms import Groq, OpenAI, LiteLLM
import os
llm = Groq(model="llama-3.3-70b-versatile")
llm = OpenAI(model="gpt-4o")
llm = create_provider("anthropic", model="claude-opus-4-7")
llm = Groq(model="llama-3.3-70b-versatile", api_key=os.getenv("GROQ_API_KEY"))
llm = OpenAI(model="gpt-4o", api_key=os.getenv("OPENAI_API_KEY"))
# Anthropic, Gemini, Ollama, DeepSeek via LiteLLM:
llm = LiteLLM(model="anthropic/claude-opus-4-7", api_key=os.getenv("ANTHROPIC_API_KEY"))
```
**Supported providers:** OpenAI, Anthropic, Google Gemini, Groq, Ollama, DeepSeek, Novita AI, LiteLLM (20+ models via one interface)
@@ -439,41 +439,69 @@ python -m semantica.mcp_server
### Seed
Deterministic data seeding for testing and development.
Bootstrap knowledge graphs from verified structured sources — fixed-point reference data, controlled vocabularies, and domain anchors.
```python
from semantica.seed import SeedManager
seed = SeedManager()
seed.populate(kg, dataset="companies", count=100)
# Load domain seeds from file or built-in datasets
seed.load_from_file("seed_data/industries.json")
seed.inject(kg) # merges seed nodes without duplicating existing entities
```
**Use cases:** anchoring extraction with known entities, pre-populating ontology classes, deterministic test graph generation.
### Evals
Evaluation harness for extraction and reasoning quality.
Evaluation framework for measuring KG quality, extraction accuracy, and pipeline performance.
```python
from semantica.evals import Evaluator
from semantica.evals import KGEvaluator, ExtractionEvaluator, PipelineEvaluator, RegressionTracker
evaluator = Evaluator()
scores = evaluator.evaluate(predicted_entities, ground_truth)
# Returns: {"precision": 0.91, "recall": 0.87, "f1": 0.89}
# KG quality
report = KGEvaluator().evaluate(kg, ontology=ontology)
print(f"Completeness: {report.completeness:.2%} Consistency: {report.consistency:.2%}")
# Extraction accuracy
report = ExtractionEvaluator().evaluate_ner(predictions=extracted, gold_standard=annotated)
print(f"Precision: {report.precision:.3f} Recall: {report.recall:.3f} F1: {report.f1:.3f}")
# Pipeline throughput and latency
metrics = PipelineEvaluator().benchmark(pipeline, data="data/", bench_runs=5)
print(f"Throughput: {metrics.docs_per_second:.1f} docs/sec")
# Regression tracking across runs
tracker = RegressionTracker(db_path="eval_history.db")
run_id = tracker.record_run(pipeline_version="v1.2.0", metrics=metrics)
diff = tracker.compare(run_id, baseline_run_id="run_abc123")
```
**Metrics:** precision, recall, F1 for NER, relation extraction, and reasoning
**Components:** `KGEvaluator`, `ExtractionEvaluator`, `PipelineEvaluator`, `RegressionTracker`
### Core
Base classes, shared data models, and the plugin registry used across all modules.
```python
from semantica.core import Orchestrator, PluginRegistry
from semantica.core import Semantica, PluginRegistry, ConfigManager
# Top-level orchestrator
sem = Semantica(config_path="config.yaml")
sem.initialize()
# Plugin registry — register custom components
registry = PluginRegistry()
registry.register("my_ingestor", MyCustomIngestor)
# Config management
config = ConfigManager(config_path="config.yaml")
batch = config.get("processing.batch_size", default=32)
```
**Components:** `Orchestrator`, `PluginRegistry`, `ConfigManager`, `Lifecycle`
**Components:** `Semantica`, `PluginRegistry`, `ConfigManager`, `LifecycleManager`, `HealthMonitor`, `Config`
### Utils
@@ -504,16 +532,16 @@ from semantica.utils import helpers, validators, logging
| [ingest](reference/ingest) | Data ingestion | `FileIngestor`, `WebIngestor`, `ParquetIngestor`, `XMLIngestor` |
| [parse](reference/parse) | Document parsing | `DocumentParser`, `DoclingParser` |
| [split](reference/split) | Text chunking | `TextSplitter` |
| [normalize](reference/normalize) | Data cleaning | `DataNormalizer` |
| [semantic_extract](reference/semantic_extract) | NER & relation extraction | `NERExtractor`, `RelationExtractor`, `TripletExtractor` |
| [kg](reference/kg) | Graph construction | `GraphBuilder`, `TemporalKnowledgeGraph`, `DistanceCalculator` |
| [ontology](reference/ontology) | Schema management | `OntologyManager`, `SHACLGenerator` |
| [reasoning](reference/reasoning) | Logical inference | `ReasoningEngine`, `DatalogEngine` |
| [normalize](reference/normalize) | Data cleaning | `TextNormalizer`, `EntityNormalizer`, `LanguageDetector` |
| [semantic_extract](reference/semantic_extract) | NER & relation extraction | `NERExtractor`, `RelationExtractor`, `TripletExtractor`, `SemanticAnalyzer`, `SemanticNetworkExtractor`, `ExtractionValidator` |
| [kg](reference/kg) | Graph construction | `GraphBuilder`, `TemporalGraphQuery`, `SimilarityCalculator` |
| [ontology](reference/ontology) | Schema management | `OntologyGenerator`, `SHACLGenerator` |
| [reasoning](reference/reasoning) | Logical inference | `Reasoner`, `DatalogReasoner` |
| [embeddings](reference/embeddings) | Vector embeddings | `EmbeddingGenerator` |
| [vector_store](reference/vector_store) | Vector database | `VectorStore` |
| [graph_store](reference/graph_store) | Graph database | `GraphStore` |
| [triplet_store](reference/triplet_store) | RDF triple store | `TripletStore` |
| [deduplication](reference/deduplication) | Entity resolution | `EntityResolver`, `DuplicateDetector` |
| [deduplication](reference/deduplication) | Entity resolution | `EntityResolver`, `DuplicateDetector`, `ClusterBuilder`, `MergeStrategyManager` |
| [conflicts](reference/conflicts) | Conflict resolution | `ConflictDetector` |
| [context](reference/context) | Agent context & decisions | `AgentContext`, `ContextGraph` |
| [provenance](reference/provenance) | W3C PROV-O lineage | `ProvenanceManager` |
@@ -524,9 +552,9 @@ from semantica.utils import helpers, validators, logging
| [explorer](reference/explorer) | Knowledge Explorer UI | `start_explorer` |
| [llms](reference/llms) | LLM providers | `Groq`, `OpenAI`, `create_provider` |
| [mcp_server](reference/mcp_server) | MCP stdio server | `python -m semantica.mcp_server` |
| [seed](reference/seed) | Test data seeding | `SeedManager` |
| [evals](reference/evals) | Quality evaluation | `Evaluator` |
| [core](reference/core) | Base classes & registry | `Orchestrator`, `PluginRegistry` |
| [seed](reference/seed) | KG bootstrapping from structured sources | `SeedManager` |
| [evals](reference/evals) | Quality evaluation | `KGEvaluator`, `ExtractionEvaluator`, `PipelineEvaluator`, `RegressionTracker` |
| [core](reference/core) | Base classes & registry | `Semantica`, `ConfigManager`, `PluginRegistry`, `LifecycleManager` |
| [utils](reference/utils) | Shared utilities | `helpers`, `validators` |
<CardGroup cols={2}>
+248 -67
View File
@@ -4,38 +4,116 @@ description: "Version control, SHA-256 checksums, diff analysis, rollback, and a
icon: "clock-rotate-left"
---
`semantica.change_management` provides enterprise-grade versioning and audit trails for knowledge graphs and ontologies SHA-256 checksums, snapshot history, diff analysis, rollback protection, and compliance-ready audit export (HIPAA, SOX, FDA 21 CFR Part 11).
`semantica.change_management` provides enterprise-grade versioning and audit trails for knowledge graphs and ontologies. Every snapshot carries a SHA-256 checksum, every modification is logged, and every state can be diffed or rolled back — giving you a complete, tamper-evident record suitable for regulated industries.
<Note>
Compliance frameworks supported out of the box: **HIPAA**, **SOX**, **GDPR**, and **FDA 21 CFR Part 11**.
</Note>
## Exported Classes
```python
from semantica.change_management import (
# Change metadata
ChangeLogEntry, # snapshot record: version, author, message, checksum, changes
# Storage backends
VersionStorage, # abstract storage interface
InMemoryVersionStorage, # fast in-memory backend (dev/test only)
SQLiteVersionStorage, # persistent SQLite backend (production)
# Integrity utilities
compute_checksum, # SHA-256 checksum of a graph state
verify_checksum, # verify graph against a stored checksum
# Version managers
TemporalVersionManager, # KG version management: snapshot, diff, rollback
OntologyVersionManager, # ontology version management
BaseVersionManager, # base class for custom version managers
# Ontology versioning (moved from ontology module)
VersionManager, # OWL ontology version control
OntologyVersion, # ontology version metadata dataclass
)
```
## What You Get
- **`TemporalVersionManager`** — snapshot, diff, rollback, and audit trail for knowledge graphs
- **`OntologyVersionManager`** — version control for OWL ontologies with diff and migration support
- **`VersionStorage`** — pluggable storage: `InMemoryVersionStorage` for tests, `SQLiteVersionStorage` for production
- **`compute_checksum` / `verify_checksum`** — SHA-256 integrity verification
- **`ChangeLogEntry`** — structured record of every change in a snapshot
<CardGroup cols={2}>
<Card title="TemporalVersionManager" icon="code-branch">
Snapshot, diff, rollback, and per-entity audit trail for knowledge graphs.
</Card>
<Card title="OntologyVersionManager" icon="sitemap">
Version control for OWL ontologies with diff and schema migration support.
</Card>
<Card title="VersionStorage" icon="database">
Pluggable backends — `InMemoryVersionStorage` for tests, `SQLiteVersionStorage` for production.
</Card>
<Card title="Integrity Verification" icon="shield-check">
SHA-256 / SHA-512 checksums to detect any unauthorised graph modification.
</Card>
<Card title="ChangeLogEntry" icon="list-check">
Structured record of every change: author, timestamp, checksum, and change list.
</Card>
<Card title="Version History" icon="file-shield">
Full tamper-evident version history via `list_versions()` and `diff()` for regulatory review.
</Card>
</CardGroup>
## Typical Workflow
<Steps>
<Step title="Initialise the version manager">
```python
from semantica.change_management import TemporalVersionManager
manager = TemporalVersionManager(storage_path="versions.db")
```
</Step>
<Step title="Snapshot before every destructive operation">
```python
snapshot_id = manager.create_snapshot(
graph=kg,
version="v1.0",
author="user@example.com",
message="Before deduplication run"
)
print(f"Snapshot: {snapshot_id}")
print(f"Checksum: {manager.get_checksum(snapshot_id)}")
```
</Step>
<Step title="Make your changes">
Run deduplication, conflict resolution, merges, or any graph modification. The version manager tracks nothing automatically — you control when snapshots are taken.
</Step>
<Step title="Snapshot the result">
```python
snapshot_v2 = manager.create_snapshot(
graph=kg,
version="v2.0",
author="user@example.com",
message="After deduplication — 1 342 duplicates merged"
)
```
</Step>
<Step title="Diff to review what changed">
```python
diff = manager.diff("v1.0", "v2.0")
print(diff.summary)
for change in diff.changes:
print(f" [{change.type}] {change.element}: {change.description}")
```
</Step>
</Steps>
## TemporalVersionManager
Version control for knowledge graphs — snapshot, diff, and rollback:
Version control for knowledge graphs — snapshot, diff, and rollback.
```python
from semantica.change_management import TemporalVersionManager
### Constructor Parameters
manager = TemporalVersionManager(storage_path="versions.db")
| Parameter | Type | Default | Description |
| --------- | ---- | ------- | ----------- |
| `storage_path` | `str` | `None` | Path to SQLite database; uses in-memory if omitted |
| `storage` | `VersionStorage` | `None` | Explicit storage backend instance — overrides `storage_path` |
# Create a snapshot
snapshot_id = manager.create_snapshot(
graph=kg,
version="v1.0",
author="user@example.com",
message="Initial knowledge graph"
)
print(f"Snapshot: {snapshot_id}")
print(f"Checksum: {manager.get_checksum(snapshot_id)}")
```
### List, Retrieve, and Rollback
### List and Retrieve
```python
# List all versions
@@ -45,33 +123,45 @@ for v in versions:
# Retrieve a specific version
kg_v1 = manager.get_version("v1.0")
# Rollback to a previous version (safe by default — fails if data would be lost)
manager.rollback(target_version="v1.0", allow_data_loss=False)
```
### Constructor Parameters
| Parameter | Type | Default | Description |
| --------- | ---- | ------- | ----------- |
| `storage_path` | `str` | `None` | Path to SQLite database; uses in-memory if omitted |
| `storage` | `VersionStorage` | `None` | Explicit storage backend instance |
## Diff Analysis
Compare any two snapshots to see exactly what changed:
Compare any two snapshots to see exactly what changed — useful for code review, incident investigation, and regulatory audit:
```python
diff = manager.diff("v1.0", "v2.0")
print(f"Added nodes: {len(diff.added_nodes)}")
print(f"Removed nodes: {len(diff.removed_nodes)}")
print(f"Modified nodes: {len(diff.modified_nodes)}")
print(f"Added edges: {len(diff.added_edges)}")
print(f"Removed edges: {len(diff.removed_edges)}")
print(f"Modified edges: {len(diff.modified_edges)}")
for change in diff.changes:
print(f" [{change.type}] {change.element}: {change.description}")
```
<Accordion title="DiffResult schema">
```python
@dataclass
class DiffResult:
from_version: str # source snapshot ID
to_version: str # target snapshot ID
added_nodes: List[str] # IDs of newly added entities
removed_nodes: List[str] # IDs of deleted entities
modified_nodes: List[str] # IDs of entities with changed properties
added_edges: List[str] # IDs of newly added relationships
removed_edges: List[str] # IDs of deleted relationships
modified_edges: List[str] # IDs of relationships with changed properties
changes: List[ChangeRecord] # ordered list of all individual changes
summary: str # human-readable summary line
```
</Accordion>
## OntologyVersionManager
Version control for OWL ontologies — save, diff, and track schema migrations:
@@ -97,21 +187,38 @@ for change in diff.changes:
## VersionStorage Backends
```python
from semantica.change_management import (
InMemoryVersionStorage,
SQLiteVersionStorage,
)
<Tabs>
<Tab title="SQLite (production)">
```python
from semantica.change_management import SQLiteVersionStorage, TemporalVersionManager
# In-memory — for tests and development (data not persisted)
storage = InMemoryVersionStorage()
storage = SQLiteVersionStorage(db_path="versions.db")
manager = TemporalVersionManager(storage=storage)
```
# SQLite — for production (persistent across restarts)
storage = SQLiteVersionStorage(db_path="versions.db")
Persists all version history to disk. Survives process restarts. Recommended for any environment where you need to retain the audit trail.
# Pass to a version manager
manager = TemporalVersionManager(storage=storage)
```
You can also pass the path directly to `TemporalVersionManager`:
```python
manager = TemporalVersionManager(storage_path="versions.db")
```
</Tab>
<Tab title="In-Memory (tests)">
```python
from semantica.change_management import InMemoryVersionStorage, TemporalVersionManager
storage = InMemoryVersionStorage()
manager = TemporalVersionManager(storage=storage)
```
Fast and zero-setup. Data is **not persisted** — all version history is lost when the process exits. Use this for unit tests and development only.
</Tab>
</Tabs>
<Warning>
The default `TemporalVersionManager()` with no arguments uses in-memory storage. Always pass `storage_path="versions.db"` or an explicit `SQLiteVersionStorage` in production — otherwise your entire version history disappears on restart.
</Warning>
## Integrity Verification
@@ -130,38 +237,112 @@ if not is_valid:
raise RuntimeError("Graph has been modified since the checksum was recorded")
```
## Audit Trail
Full per-entity audit trail with CSV and JSON export for compliance reporting:
```python
# Get all changes for a specific entity
trail = manager.get_audit_trail(entity_id="apple_inc")
for entry in trail:
print(f"{entry.timestamp}{entry.author}: {entry.action}{entry.description}")
# Export audit trail for compliance
manager.export_audit_trail("audit.csv", format="csv")
manager.export_audit_trail("audit.json", format="json")
```
<Tip>
`verify_checksum` is deterministic — the same graph always produces the same digest. Use it as a pre-flight check before any compliance export to confirm the graph hasn't been tampered with since the last snapshot.
</Tip>
## ChangeLogEntry
Every version snapshot includes a structured `ChangeLogEntry`:
Every version snapshot includes a structured `ChangeLogEntry` that records the full context of a change:
```python
from semantica.change_management import ChangeLogEntry
entry: ChangeLogEntry = manager.get_log_entry(snapshot_id)
# Retrieve a version entry
entry = manager.get_version("v1.0")
print(entry.version) # "v1.0"
print(entry.author) # "user@example.com"
print(entry.message) # "Initial knowledge graph"
print(entry.checksum) # SHA-256 hex digest
print(entry.created_at) # datetime
print(entry.changes) # list of individual change records
print(entry.checksum) # SHA-256 hex digest of the full graph state
print(entry.created_at) # datetime of snapshot creation
print(entry.node_count) # total nodes at this snapshot
print(entry.edge_count) # total edges at this snapshot
print(entry.changes) # list[ChangeRecord] — individual property-level changes
```
<Accordion title="ChangeLogEntry schema">
```python
@dataclass
class ChangeLogEntry:
snapshot_id: str # unique snapshot identifier
version: str # human-assigned version tag, e.g. "v1.0"
author: str # identity of the user or process that created it
message: str # commit-style description of what changed
checksum: str # SHA-256 hex digest — changes if graph is tampered
created_at: datetime # UTC timestamp of snapshot creation
node_count: int # total entity count at this point in time
edge_count: int # total relationship count at this point in time
changes: List[ChangeRecord] # granular per-property change records
metadata: Dict # arbitrary key-value pairs for custom tagging
```
</Accordion>
## Compliance and Version History
All version snapshots form a tamper-evident audit trail. Use `list_versions()` and `diff()` to reconstruct and review changes for regulatory purposes:
```python
from semantica.change_management import TemporalVersionManager
manager = TemporalVersionManager(storage_path="versions.db")
# Enumerate the full version history
for entry in manager.list_versions():
print(f"{entry.created_at.isoformat()} | {entry.author} | {entry.version} | {entry.message}")
# Diff any two snapshots for a change report
diff = manager.diff("v1.0", "v2.0")
print(f"Added: {len(diff.added_nodes)} | Removed: {len(diff.removed_nodes)} | Modified: {len(diff.modified_nodes)}")
for change in diff.changes:
print(f" [{change.type}] {change.element}: {change.description}")
```
Use `verify_checksum()` before any compliance export to confirm graph integrity:
```python
from semantica.change_management import verify_checksum
is_valid = verify_checksum(kg, expected_checksum=entry.checksum)
if not is_valid:
raise RuntimeError("Graph has been modified since the snapshot was taken")
```
### Compliance Coverage
<AccordionGroup>
<Accordion title="HIPAA — subject-access requests">
Use `get_audit_trail(entity_id="patient_001")` to retrieve every change ever made to a patient entity, then export to JSON for the access request response. The SHA-256 checksum on each entry proves the record has not been altered.
</Accordion>
<Accordion title="SOX — quarterly reviews">
Use `get_audit_trail(from_date=..., to_date=...)` to scope the export to the relevant quarter. Export to CSV for upload to your audit management system. The immutable snapshot chain provides the chain of custody required by SOX Section 404.
</Accordion>
<Accordion title="GDPR — right to erasure verification">
After deleting a data subject's entities, snapshot the graph and diff against the pre-deletion snapshot. `diff.removed_nodes` provides a machine-readable record of exactly what was deleted and when, satisfying Article 17 documentation requirements.
</Accordion>
<Accordion title="FDA 21 CFR Part 11 — electronic records">
Every `ChangeLogEntry` includes `author`, `timestamp`, and `checksum` — the three fields required for a compliant electronic record. `verify_checksum()` provides the tamper-evidence required by 21 CFR § 11.10(e).
</Accordion>
</AccordionGroup>
## Tips and Common Pitfalls
<Tip>
**Use `SQLiteVersionStorage` in production.** The default in-memory storage loses all version history when the process exits. Pass `storage_path="versions.db"` to `TemporalVersionManager` or create `SQLiteVersionStorage(db_path="versions.db")` explicitly.
</Tip>
<Warning>
**Snapshot before every destructive operation.** Call `manager.create_snapshot()` before running deduplication, conflict resolution, or merge operations. `rollback()` is only possible if a snapshot exists before the change.
</Warning>
<Tip>
**Use `diff()` for code review and incident investigation.** `manager.diff("v1.0", "v2.0")` produces a human-readable change summary in seconds — faster than comparing raw graph exports. Use it to review what changed before approving a version for production.
</Tip>
<Tip>
**Use `list_versions()` and `diff()` for compliance reviews.** `manager.list_versions()` enumerates the full version history and `manager.diff(v1, v2)` produces a machine-readable change report. Run `verify_checksum()` first to confirm the graph hasn't been modified since the snapshot was taken.
</Tip>
<CardGroup cols={2}>
<Card title="Provenance" icon="link" href="provenance">
W3C PROV-O lineage tracking.
+334 -83
View File
@@ -6,74 +6,258 @@ icon: "triangle-exclamation"
`semantica.conflicts` detects and resolves contradictions when multiple sources disagree on the same fact. It surfaces five conflict types, seven resolution strategies, and generates investigation guides for manual review — so conflicts never silently corrupt your knowledge graph.
## Why Detect Conflicts?
When you ingest data from multiple sources, contradictions are inevitable. One annual report says Apple's revenue was $391B; a financial newswire says $383B. Without conflict detection, both values land in your graph and queries silently return inconsistent answers.
Semantica's conflict detection makes disagreements explicit and actionable:
- **Value conflicts** — SEC says revenue is $391B; Reuters says $383B
- **Type conflicts** — "Python" is a `ProgrammingLanguage` in one source, a `Snake` species in another
- **Temporal conflicts** — a CEO had two different employers during overlapping date ranges
- **Logical conflicts** — an entity simultaneously holds two mutually exclusive properties
- **Relationship conflicts** — the same relationship has inconsistent cardinality or properties across sources
## Exported Classes
```python
from semantica.conflicts import (
# Detection
ConflictDetector, # detect value, type, temporal, logical, relationship conflicts
Conflict, # {id, entity_id, attribute, values, sources, conflict_type, severity}
ConflictType, # enum: VALUE_CONFLICT, TYPE_CONFLICT, TEMPORAL_CONFLICT, ...
# Resolution
ConflictResolver, # resolve conflicts with configurable strategy
ResolutionStrategy, # enum: VOTING, CREDIBILITY_WEIGHTED, MOST_RECENT, FIRST_SEEN, ...
ResolutionResult, # outcome of a resolve_conflicts() call
# Convenience strategy aliases
voting, credibility_weighted, most_recent, first_seen, highest_confidence,
manual_review, expert_review,
# Source tracking
SourceTracker, # track which source contributed each property value
SourceReference, # {source_id, credibility, timestamp}
PropertySource, # per-property source attribution record
# Analysis
ConflictAnalyzer, # analyze patterns, severity distribution, source stats
ConflictPattern, # recurring conflict pattern detected across entities
# Investigation
InvestigationGuideGenerator, # generate step-by-step checklists for manual review
InvestigationGuide, # {title, context, steps}
InvestigationStep, # {order, description, check, priority}
# Convenience functions
detect_conflicts, # quick: detect_conflicts(entities, attribute="name")
resolve_conflicts, # quick: resolve_conflicts(conflicts, strategy=voting)
analyze_conflicts, # quick: analyze_conflicts(conflicts)
track_sources, # quick: track_sources(entities)
generate_investigation_guide,# quick: generate_investigation_guide(conflict)
)
```
## What You Get
- **`ConflictDetector`** — value, type, temporal, logical, and relationship conflict detection
- **`ConflictResolver`** — 7 resolution strategies including voting, credibility-weighted, and temporal
- **`SourceTracker`** — track which source each conflicting fact came from, with credibility scores
- **`ConflictAnalyzer`** — pattern analysis, severity grouping, and trend identification
- **`InvestigationGuideGenerator`** — auto-generate step-by-step investigation checklists for human review
<CardGroup cols={2}>
<Card title="ConflictDetector" icon="magnifying-glass">
Value, type, temporal, logical, and relationship conflict detection across all entity pairs.
</Card>
<Card title="ConflictResolver" icon="check">
7 resolution strategies including voting, credibility-weighted, and temporal preference.
</Card>
<Card title="SourceTracker" icon="link">
Track which source each conflicting fact came from, with per-source credibility scores.
</Card>
<Card title="ConflictAnalyzer" icon="chart-line">
Pattern analysis, severity grouping, source-level statistics, and trend identification.
</Card>
<Card title="InvestigationGuideGenerator" icon="list-check">
Auto-generate step-by-step investigation checklists for human and expert review.
</Card>
<Card title="Convenience Functions" icon="bolt">
`detect_conflicts()` and `resolve_conflicts()` for one-call workflows.
</Card>
</CardGroup>
## Quick Start
<Steps>
<Step title="Set credibility scores before ingestion">
```python
from semantica.conflicts import SourceTracker
tracker = SourceTracker()
tracker.set_source_credibility("sec_filings", 0.95)
tracker.set_source_credibility("pubmed", 0.92)
tracker.set_source_credibility("wikipedia", 0.80)
tracker.set_source_credibility("news_articles", 0.65)
```
</Step>
<Step title="Detect conflicts after building the graph">
```python
from semantica.conflicts import ConflictDetector
detector = ConflictDetector()
conflicts = detector.detect_conflicts(kg)
print(f"Found {len(conflicts)} conflicts")
for conflict in conflicts:
print(f"[{conflict.conflict_type}] entity='{conflict.entity_id}' attr='{conflict.attribute}'")
print(f" Values: {conflict.values} Severity: {conflict.severity:.2f}")
```
</Step>
<Step title="Triage by severity">
```python
from semantica.conflicts import ConflictAnalyzer
analyzer = ConflictAnalyzer()
analysis = analyzer.analyze_conflicts(conflicts)
by_severity = analysis["by_severity"]
print(f"Critical: {len(by_severity.get('critical', []))}")
print(f"High: {len(by_severity.get('high', []))}")
print(f"Low: {len(by_severity.get('low', []))}")
```
</Step>
<Step title="Auto-resolve low-severity, escalate critical">
```python
from semantica.conflicts import ConflictResolver, InvestigationGuideGenerator, ResolutionStrategy
resolver = ConflictResolver(source_tracker=tracker)
# Auto-resolve low-severity
auto_resolved = resolver.resolve_conflicts(
by_severity["low"],
strategy=ResolutionStrategy.CREDIBILITY_WEIGHTED,
)
# Generate investigation guides for critical conflicts
generator = InvestigationGuideGenerator()
for conflict in by_severity["critical"]:
guide = generator.generate_guide(conflict)
print(f"\n{guide.title}")
for step in guide.steps:
print(f" [{step.order}] ({step.priority.upper()}) {step.description}")
```
</Step>
</Steps>
## ConflictDetector
```python
from semantica.conflicts import ConflictDetector
detector = ConflictDetector()
detector = ConflictDetector()
conflicts = detector.detect_conflicts(kg)
for conflict in conflicts:
print(f"[{conflict.conflict_type}] '{conflict.entity}'{conflict.attribute}")
print(f" Sources: {conflict.sources}")
print(f" Severity: {conflict.severity:.2f}")
```
### Detection Types
| Type | What It Detects |
| ---- | --------------- |
| `VALUE` | Same entity, same attribute, different values across sources |
| `TYPE` | Same entity classified as different types in different sources |
| `TEMPORAL` | Overlapping validity windows with contradictory facts |
| `LOGICAL` | Facts that violate ontology axioms or SHACL constraints |
| `RELATIONSHIP` | Inconsistent relationship properties across sources |
| Type | What It Detects | Example |
| ---- | --------------- | ------- |
| `VALUE` | Same entity, same attribute, different values across sources | Revenue $391B vs $383B |
| `TYPE` | Same entity classified as different types | "Python" as Language vs Snake |
| `TEMPORAL` | Overlapping validity windows with contradictory facts | CEO at two companies simultaneously |
| `LOGICAL` | Facts that violate ontology axioms or SHACL constraints | `is_alive=True` but `death_date` set |
| `RELATIONSHIP` | Inconsistent relationship properties across sources | Edge weight 0.9 vs 0.3 from two sources |
Run targeted detection by type:
```python
# Detect all types (default)
# Detect all types at once (default)
conflicts = detector.detect_conflicts(kg)
# Detect specific types only
value_conflicts = detector.detect_value_conflicts(entities, "name")
# Detect specific types only — faster for targeted checks
value_conflicts = detector.detect_value_conflicts(entities, "revenue")
type_conflicts = detector.detect_type_conflicts(entities)
relation_conflicts = detector.detect_relationship_conflicts(kg)
```
**Key behaviours:**
- Severity scores are computed from the magnitude of disagreement — a $8B revenue discrepancy scores higher than a $1M discrepancy
- `LOGICAL` conflicts require an ontology or SHACL schema to be loaded; without one, they are not detected
- Detection runs in O(n·sources) time — it groups by entity+attribute and checks disagreement within each group
## ConflictResolver
```python
from semantica.conflicts import ConflictResolver, ResolutionStrategy
resolver = ConflictResolver()
results = resolver.resolve_conflicts(conflicts, strategy=ResolutionStrategy.VOTING)
results = resolver.resolve_conflicts(conflicts, strategy=ResolutionStrategy.VOTING)
for result in results:
print(f"Resolved '{result.attribute}' → {result.resolved_value}")
print(f" Strategy: {result.strategy}")
print(f" Strategy: {result.strategy} Confidence: {result.confidence:.2f}")
```
### Resolution Strategies
### Choosing a Resolution Strategy
| Strategy | Enum | Description |
|----------|------|-------------|
| Majority vote | `ResolutionStrategy.VOTING` | Most common value wins |
| Credibility-weighted | `ResolutionStrategy.CREDIBILITY_WEIGHTED` | Weighted by source credibility score |
| Most recent | `ResolutionStrategy.MOST_RECENT` | Prefer the most recently updated fact |
| First seen | `ResolutionStrategy.FIRST_SEEN` | Prefer the first observed value |
| Highest confidence | `ResolutionStrategy.HIGHEST_CONFIDENCE` | Prefer the fact with the highest confidence score |
| Manual review | `ResolutionStrategy.MANUAL_REVIEW` | Flag for human review |
| Expert review | `ResolutionStrategy.EXPERT_REVIEW` | Escalate to a domain expert |
<Tabs>
<Tab title="CREDIBILITY_WEIGHTED (recommended)">
Weights each source's value by its assigned credibility score — favors authoritative sources automatically:
```python
from semantica.conflicts import ConflictResolver, SourceTracker, ResolutionStrategy
tracker = SourceTracker()
tracker.set_source_credibility("sec_filings", 0.92)
tracker.set_source_credibility("wikipedia", 0.80)
tracker.set_source_credibility("news_articles", 0.65)
resolver = ConflictResolver(source_tracker=tracker)
results = resolver.resolve_conflicts(
conflicts,
strategy=ResolutionStrategy.CREDIBILITY_WEIGHTED,
)
```
Best for: sources with known reliability rankings (SEC > blog).
</Tab>
<Tab title="VOTING">
Majority vote — most common value across sources wins:
```python
results = resolver.resolve_conflicts(conflicts, strategy=ResolutionStrategy.VOTING)
```
Best for: 3+ sources with roughly equal credibility. When all sources have identical credibility scores, `CREDIBILITY_WEIGHTED` behaves identically to `VOTING`.
</Tab>
<Tab title="MOST_RECENT / FIRST_SEEN">
```python
# Most recent source wins — for fast-changing facts
results = resolver.resolve_conflicts(conflicts, strategy=ResolutionStrategy.MOST_RECENT)
# First seen wins — for stable facts (founding date, original name)
results = resolver.resolve_conflicts(conflicts, strategy=ResolutionStrategy.FIRST_SEEN)
```
</Tab>
<Tab title="MANUAL_REVIEW / EXPERT_REVIEW">
```python
from semantica.conflicts import InvestigationGuideGenerator
# Flag for human review — use with InvestigationGuideGenerator
results = resolver.resolve_conflicts(conflicts, strategy=ResolutionStrategy.MANUAL_REVIEW)
generator = InvestigationGuideGenerator()
for conflict in conflicts:
guide = generator.generate_guide(conflict)
print(f"{guide.title}")
for step in guide.steps:
print(f" [{step.order}] {step.description}")
```
Best for: high-stakes decisions (severity > 0.8), regulated data (HIPAA/SOX), and domain-specific ambiguity.
</Tab>
<Tab title="Strategy Comparison">
| Strategy | Enum | When to Use |
| -------- | ---- | ----------- |
| Majority vote | `VOTING` | 3+ sources with roughly equal credibility |
| Credibility-weighted | `CREDIBILITY_WEIGHTED` | Sources have different authority levels |
| Most recent | `MOST_RECENT` | Fast-changing facts: stock price, headcount, status |
| First seen | `FIRST_SEEN` | Stable facts: founding date, original name |
| Highest confidence | `HIGHEST_CONFIDENCE` | Extraction pipeline outputs confidence scores |
| Manual review | `MANUAL_REVIEW` | High-stakes decisions, regulated data |
| Expert review | `EXPERT_REVIEW` | Domain-specific ambiguity — escalate to a specialist |
</Tab>
</Tabs>
Use the convenience aliases for shorter code:
@@ -83,92 +267,159 @@ from semantica.conflicts import voting, credibility_weighted, most_recent, highe
results = resolver.resolve_conflicts(conflicts, strategy=voting)
```
## Source Credibility Scoring
Assign credibility weights per source so `CREDIBILITY_WEIGHTED` resolution favors authoritative sources:
## SourceTracker
```python
from semantica.conflicts import SourceTracker
from datetime import datetime
tracker = SourceTracker()
tracker.set_credibility("pubmed", 0.95)
tracker.set_credibility("wikipedia", 0.80)
tracker.set_credibility("user_input", 0.60)
tracker.set_source_credibility("sec_10k", 0.92)
tracker.set_source_credibility("wikipedia", 0.80)
resolver = ConflictResolver(source_tracker=tracker)
results = resolver.resolve_conflicts(
conflicts,
strategy=ResolutionStrategy.CREDIBILITY_WEIGHTED
tracker.track_property_source(
entity_id="apple_inc",
property_name="revenue",
value="$391B",
source="sec_10k_2023",
timestamp=datetime(2024, 1, 26),
)
```
`SourceTracker` also builds full traceability chains:
```python
from semantica.conflicts import SourceTracker
tracker = SourceTracker()
tracker.track_entity_source("apple_inc", "crunchbase")
tracker.track_property_source("apple_inc", "revenue", "annual_report_2023")
sources = tracker.get_property_sources("apple_inc", "revenue")
for s in sources:
print(f"{s.source}: {s.value} (credibility: {s.credibility:.2f})")
chain = tracker.get_traceability_chain("apple_inc")
```
## ConflictAnalyzer
**Key behaviours:**
- Credibility scores default to 0.50 for any source not explicitly set
- `SourceTracker` stores property-level provenance — so you can trace exactly which source contributed each value
Identify patterns and trends across large conflict sets:
## ConflictAnalyzer
```python
from semantica.conflicts import ConflictAnalyzer
analyzer = ConflictAnalyzer()
# Detect recurring patterns
patterns = analyzer.identify_patterns(conflicts)
for pattern in patterns:
print(f"Pattern: {pattern.type}{pattern.frequency} occurrences")
analysis = analyzer.analyze_conflicts(conflicts)
patterns = analysis["patterns"]
by_severity = analysis["by_severity"]
source_stats = analysis["by_source"]
trends = analyzer.analyze_trends(conflicts)
# Group by severity
by_severity = analyzer.group_by_severity(conflicts)
print(f"Critical: {len(by_severity['critical'])}")
print(f"High: {len(by_severity['high'])}")
print(f"Low: {len(by_severity['low'])}")
# Trend analysis over time
trends = analyzer.analyze_trends(conflicts, time_window="30d")
print(f"Trend direction: {trends['direction']}") # "increasing" | "stable" | "decreasing"
print(f"Change: {trends['change_pct']:.1f}%")
```
**Key behaviours:**
- `analyze_conflicts()["patterns"]` groups conflicts by attribute name and type — use it to find systemic data quality issues
- `analyze_conflicts()["by_source"]` flags sources with disproportionate conflict rates — a signal that a source's pipeline needs review
- `analyze_trends()` compares conflict counts over time — a rising trend means a data source is degrading
## InvestigationGuideGenerator
Auto-generate human-readable investigation guides for conflicts that can't be automatically resolved:
Auto-generate human-readable investigation checklists for conflicts requiring manual or expert review:
```python
from semantica.conflicts import InvestigationGuideGenerator, InvestigationGuide
from semantica.conflicts import InvestigationGuideGenerator
generator = InvestigationGuideGenerator()
guide: InvestigationGuide = generator.generate(conflict)
guide = generator.generate_guide(conflict)
print(f"Title: {guide.title}")
print(f"Context: {guide.context}")
print(guide.title)
print(guide.context)
for step in guide.steps:
print(f" [{step.order}] {step.description}")
print(f" Check: {step.check}")
print(f" [{step.order}] ({step.priority.upper()}) {step.description}")
print(f" → Verify: {step.check}")
```
## Convenience Functions
## Schemas
<AccordionGroup>
<Accordion title="Conflict schema">
```python
from semantica.conflicts import (
detect_conflicts, resolve_conflicts, analyze_conflicts,
track_sources, generate_investigation_guide
)
conflicts = detect_conflicts(entities, method="value")
resolved = resolve_conflicts(conflicts, strategy="voting")
analysis = analyze_conflicts(conflicts, method="pattern")
guide = generate_investigation_guide(conflicts[0])
@dataclass
class Conflict:
id: str
entity_id: str # the entity involved
attribute: str # the conflicting property name
values: List[str] # conflicting values (one per source)
sources: List[str] # source IDs for each value
conflict_type: ConflictType # VALUE | TYPE | TEMPORAL | LOGICAL | RELATIONSHIP
severity: float # 0.0 (minor) to 1.0 (critical)
confidence: float # detection confidence 01
detected_at: datetime
metadata: Dict[str, Any]
```
</Accordion>
<Accordion title="ConflictType enum">
```python
from semantica.conflicts import ConflictType
ConflictType.VALUE_CONFLICT # revenue is $391B in source A, $383B in source B
ConflictType.TYPE_CONFLICT # "Apple" is ORGANIZATION in one source, PRODUCT in another
ConflictType.TEMPORAL_CONFLICT # overlapping validity windows with contradictory states
ConflictType.LOGICAL_CONFLICT # fact violates an ontology axiom or SHACL constraint
ConflictType.RELATIONSHIP_CONFLICT # inconsistent relationship properties across sources
```
</Accordion>
<Accordion title="InvestigationGuide and InvestigationStep schemas">
```python
@dataclass
class InvestigationGuide:
title: str # human-readable title for the conflict
context: str # summary of the disagreement
steps: List[InvestigationStep] # ordered checklist for the reviewer
@dataclass
class InvestigationStep:
order: int
description: str # what to do
check: str # specific fact or document to verify
priority: str # "high" | "medium" | "low"
```
</Accordion>
</AccordionGroup>
## Tips and Common Pitfalls
<Warning>
**Detect before you merge, not after.** Run conflict detection on raw entity data before deduplication and graph construction. Detecting conflicts in a live graph that already contains merged entities is harder — you lose the original source attribution.
</Warning>
<Warning>
**Always set credibility scores.** The default credibility is 0.50 for all sources. Without explicit scores, `CREDIBILITY_WEIGHTED` behaves identically to `VOTING`. The power of this strategy is in the differentiation.
</Warning>
<Tip>
**Don't auto-resolve everything.** Use `MANUAL_REVIEW` for conflicts with severity > 0.8 — high severity means the disagreement is large and the stakes of getting it wrong are high.
</Tip>
<Warning>
**LOGICAL conflicts need a schema.** `detect_type_conflicts()` and `LOGICAL` detection only work if an OWL ontology or SHACL schema is loaded. Without one, `detect_conflicts()` will skip those types silently.
</Warning>
<Tip>
**Use `analyze_sources()` to identify bad data feeds.** A single source causing 80% of your conflicts is a data quality problem upstream, not a conflict to resolve record by record. Flag it and investigate the source pipeline.
</Tip>
<Tip>
**Severity is relative, not absolute.** A 0.5 severity score on a $1B revenue discrepancy and on a minor label difference both score 0.5 — the number reflects the disagreement structure, not the business impact. Domain context determines what to prioritize.
</Tip>
<Tip>
**Combine with provenance.** The `SourceTracker` feeds directly into the [Provenance](provenance) module's audit trail. If you need to explain how a resolved value was chosen, provenance records give you the full chain.
</Tip>
<CardGroup cols={2}>
<Card title="Deduplication" icon="copy" href="deduplication">
Resolve duplicate entities before conflict detection.
+580 -139
View File
@@ -1,127 +1,264 @@
---
title: "Context Module"
description: "Agent context graphs, decision tracking, causal chains, precedent search, and policy enforcement."
description: "Agent context graphs, decision tracking, causal chains, precedent search, policy enforcement, and multi-hop GraphRAG."
icon: "brain"
---
`semantica.context` is the memory and decision layer for AI agents. It stores facts with provenance, records decisions as first-class objects with causal chains, and lets agents search their own history to stay consistent across runs.
`semantica.context` is the memory and decision layer for AI agents. It stores facts with provenance, records decisions as first-class objects with full causal chains, lets agents search their own history to stay consistent across runs, and answers complex queries by traversing the knowledge graph.
## Exported Classes
```python
from semantica.context import (
# High-level interfaces
AgentContext, # primary entry point: store, retrieve, record_decision, find_precedents
DecisionContext, # decision-focused facade (wraps AgentContext + DecisionRecorder)
# Graph primitives
ContextGraph, # in-memory graph: add/get entities, record decisions, find precedents
ContextNode, # {id, label, node_type, properties, embedding, confidence}
ContextEdge, # {source, target, edge_type, weight, properties}
# Memory
AgentMemory, # RAG memory: store(text), retrieve(query, max_results)
MemoryItem, # {id, content, timestamp, conversation_id, embedding, metadata}
# Retrieval
ContextRetriever, # retrieve(query, max_results, use_graph, min_score)
RetrievedContext, # {content, score, source, metadata}
TemporalGraphRetriever, # retrieval with temporal decay weighting
# Entity linking
EntityLinker, # link_entity(text, entity_type) -> LinkedEntity with URI
EntityLink, # {entity_id, uri, source_text, confidence}
LinkedEntity, # {canonical_id, uri, aliases, type, properties}
# Decision tracking models
Decision, # {id, category, scenario, reasoning, outcome, confidence, timestamp}
Policy, # {id, name, conditions, action, priority}
PolicyException, # {policy_id, decision_id, reason, override_authority}
Precedent, # {decision_id, scenario, outcome, similarity, timestamp}
ApprovalChain, # ordered list of approvers for escalation
# Decision tracking classes
DecisionRecorder, # record and persist decisions with embeddings
DecisionQuery, # query decisions: by_category, by_outcome, by_date_range
CausalChainAnalyzer, # trace causality: get_causal_chain, analyze_impact
PolicyEngine, # check_compliance, get_applicable_policies, enforce_policy
# Convenience functions
record_decision, # record_decision(category, scenario, reasoning, outcome, confidence)
find_precedents, # find_precedents(scenario, category, limit)
analyze_decision_impact, # analyze_decision_impact(decision_id)
check_decision_compliance, # check_decision_compliance(decision, policies)
)
```
## What You Get
- **`AgentContext`** — unified interface for memory, decision tracking, and graph-backed retrieval
- **`ContextGraph`** — persistent knowledge graph with centrality analysis, community detection, and decision management
- **`AgentMemory`** — low-level embedding-backed memory with TTL, tagging, and importance scoring
- **`DecisionRecorder`** — records decisions with causal chains, confidence scores, and outcome tracking
- **`CausalAnalyzer`** — traces downstream impact of any decision
- **`PolicyEngine`** — validates decisions against configurable rules before they're recorded
<CardGroup cols={2}>
<Card title="AgentContext" icon="brain">
Unified interface for memory, decision tracking, graph-backed retrieval, conversation history, checkpoints, and persistence.
</Card>
<Card title="ContextGraph" icon="diagram-project">
Thread-safe in-memory knowledge graph with centrality analysis, community detection, temporal validity, cross-graph links, and decision management.
</Card>
<Card title="AgentMemory" icon="database">
Embedding-backed memory with TTL, tagging, importance scoring, and LRU eviction.
</Card>
<Card title="DecisionRecorder" icon="list-check">
Records decisions with causal chains, confidence scores, temporal validity windows, and cross-system context capture.
</Card>
<Card title="PolicyEngine" icon="shield-check">
Validates decisions against configurable lambda rules before they're recorded; creates approval chains for human-in-the-loop gating.
</Card>
<Card title="EntityLinker" icon="link">
Maps entity mentions to canonical URIs — prevents "Apple", "Apple Inc.", and "AAPL" from becoming three separate nodes.
</Card>
<Card title="ContextRetriever" icon="magnifying-glass">
Hybrid retrieval fusing vector similarity, graph traversal, and agent memory for richer context than pure vector search.
</Card>
<Card title="CausalChainAnalyzer" icon="arrow-trend-up">
Traces upstream causes and downstream effects of any decision through the knowledge graph.
</Card>
</CardGroup>
<img src="/assets/img/diagrams/agent-context-flow.svg" alt="AgentContext hub: AI Agent calls store/retrieve against VectorStore and record_decision against ContextGraph" style={{ width: '100%', borderRadius: '12px', margin: '0 0 24px' }} />
## Quick Start
<Steps>
<Step title="Initialize the agent context">
```python
from semantica.context import AgentContext, ContextGraph
from semantica.vector_store import VectorStore
context = AgentContext(
vector_store=VectorStore(backend="faiss", dimension=768, index_path="context.faiss"),
knowledge_graph=ContextGraph(advanced_analytics=True),
decision_tracking=True,
retention_days=90, # auto-expire memories older than 90 days
max_memories=50_000,
)
```
</Step>
<Step title="Store facts and retrieve by semantic similarity">
```python
memory_id = context.store(
"GPT-4 outperforms GPT-3.5 on reasoning benchmarks by 40%",
metadata={"source": "openai_blog", "date": "2024-01"}
)
results = context.retrieve("LLM benchmark comparisons", max_results=5)
for r in results:
print(f"{r['content']} (score: {r['score']:.3f})")
```
</Step>
<Step title="Record decisions with full provenance">
```python
decision_id = context.record_decision(
category="model_selection",
scenario="Choose LLM for production reasoning pipeline",
reasoning="GPT-4 benchmark advantage justifies 3x cost increase",
outcome="selected_gpt4",
confidence=0.91,
entities=["gpt-4", "gpt-3.5"],
decision_maker="pipeline_agent",
)
```
</Step>
<Step title="Find precedents and trace causal chains">
```python
# Search past decisions — prevents contradictory choices across runs
precedents = context.find_precedents("model selection reasoning", limit=5)
for p in precedents:
print(f"[{p.category}] {p.outcome} (confidence: {p.confidence:.2f})")
print(f" Reasoning: {p.reasoning}")
# Trace what downstream decisions were influenced by this one
chain = context.get_causal_chain(decision_id, direction="downstream", max_depth=5)
print(f"Downstream decisions: {len(chain)}")
# Full explainability — upstream causes + downstream effects + relationship paths
explanation = context.trace_decision_explainability(decision_id)
print(f"Total connections: {explanation['total_connections']}")
```
</Step>
</Steps>
## AgentContext
The main entry point. Wraps memory, graph, and decision tracking behind a single API.
```python
from semantica.context import AgentContext, ContextGraph
from semantica.vector_store import VectorStore
context = AgentContext(
vector_store=VectorStore(backend="faiss", dimension=768),
knowledge_graph=ContextGraph(advanced_analytics=True),
decision_tracking=True,
)
```
### Store and Retrieve Memories
```python
# Store a fact — embedded and indexed automatically
memory_id = context.store(
"GPT-4 outperforms GPT-3.5 on reasoning benchmarks by 40%",
metadata={"source": "openai_blog", "date": "2024-01"}
)
# Retrieve by semantic similarity
results = context.retrieve("LLM benchmark comparisons", top_k=5)
for r in results:
print(f"{r['content']} (score: {r['score']:.3f})")
```
### Record and Search Decisions
```python
decision_id = context.record_decision(
category="model_selection",
scenario="Choose LLM for production reasoning pipeline",
reasoning="GPT-4 benchmark advantage justifies 3x cost increase",
outcome="selected_gpt4",
confidence=0.91,
)
# Find similar past decisions — prevents inconsistent choices
precedents = context.find_precedents("model selection reasoning", limit=5)
# Analyze downstream impact
influence = context.analyze_decision_influence(decision_id)
print(f"Decisions influenced: {len(influence.downstream_decisions)}")
```
### Multi-Hop GraphRAG
```python
from semantica.llms import Groq
llm = Groq(model="llama-3.3-70b-versatile")
result = context.query_with_reasoning(
query="What technologies have we chosen and why?",
llm_provider=llm,
max_hops=2,
)
print(result["response"])
for step in result["reasoning_path"]:
print(f" {step}")
```
### Constructor Parameters
| Parameter | Type | Default | Description |
| --------- | ---- | ------- | ----------- |
| `vector_store` | `VectorStore` | required | Backend for embedding-based memory retrieval |
| `knowledge_graph` | `ContextGraph` | `None` | Enables graph-backed relationships and analytics |
| `vector_store` | `VectorStore` | **required** | Backend for embedding-based memory retrieval |
| `knowledge_graph` | `ContextGraph` | `None` | Enables graph-backed relationships and GraphRAG |
| `decision_tracking` | `bool` | `False` | Activates `DecisionRecorder` for every decision |
| `retention_days` | `Optional[int]` | `30` | Auto-expire memories older than N days; `None` = keep forever |
| `max_memories` | `int` | `10000` | Hard cap before LRU eviction |
| `graph_expansion` | `bool` | `True` | Auto-expands graph from stored memories |
| `advanced_analytics` | `bool` | `True` | Enables centrality and community analysis |
| `max_expansion_hops` | `int` | `2` | Max hops for graph expansion during retrieval |
| `hybrid_alpha` | `float` | `0.5` | Balance between vector (`0.0`) and graph (`1.0`) retrieval |
| `advanced_analytics` | `bool` | `True` | Enables PageRank, centrality, and community analysis |
| `kg_algorithms` | `bool` | `True` | Adds path-finding and link prediction |
### Core Methods
### Memory Methods
| Method | Returns | Description |
| ------ | ------- | ----------- |
| `store(content, metadata)` | `str` (memory_id) | Embed and store a fact |
| `retrieve(query, top_k)` | `List[Dict]` | Semantic similarity search |
| `record_decision(category, scenario, reasoning, outcome, confidence)` | `str` (decision_id) | Record a decision with full provenance |
| `find_precedents(scenario, category, limit)` | `List[Decision]` | Find similar past decisions |
| `analyze_decision_influence(decision_id)` | `InfluenceResult` | Trace downstream impact |
| `query_with_reasoning(query, llm_provider, max_hops)` | `Dict` | GraphRAG with multi-hop traversal |
| `get_context_insights()` | `Dict` | Analytics summary |
| `store(content, metadata, conversation_id, user_id)` | `str` | Embed and store a fact or list of facts |
| `batch_store(items)` | `List[str]` | Store multiple items at once — returns list of memory IDs |
| `retrieve(query, max_results, min_score, use_graph, conversation_id)` | `List[Dict]` | Semantic retrieval; auto-selects GraphRAG if `knowledge_graph` is set |
| `forget(memory_id, conversation_id, days_old)` | `int` | Delete memories by ID, conversation, or age |
| `update(memory_id, content, metadata)` | `bool` | Update content or metadata of a stored memory |
| `get_memory(memory_id)` | `Optional[Dict]` | Fetch a specific memory by ID |
| `stats()` | `Dict` | Memory counts, vector store status, graph stats |
| `health()` | `Dict` | System health — all backends, status flags |
| `save(path)` | `None` | Persist full context state (memory + graph) to disk |
| `load(path)` | `None` | Restore context state from disk |
| `export(conversation_id, format)` | `str \| Dict` | Export memories as JSON or dict |
| `import_data(data, format)` | `int` | Import memories from JSON or dict |
### Conversation Methods
```python
# Store turns in a conversation thread
context.store("User asked about deployment options", conversation_id="conv_001")
context.store("Agent recommended Docker + Kubernetes", conversation_id="conv_001")
# Retrieve full conversation history
history = context.conversation("conv_001", max_items=50)
for turn in history:
print(f"[{turn['timestamp']}] {turn['content']}")
# Retrieve across all conversations with a query
results = context.retrieve("deployment recommendations", conversation_id="conv_001", max_results=10)
```
### Multi-Hop GraphRAG
Requires `knowledge_graph` to be set at construction:
```python
from semantica.llms import Groq
llm = Groq(model="llama-3.3-70b-versatile", api_key=os.getenv("GROQ_API_KEY"))
result = context.query_with_reasoning(
query="What technologies have we chosen and why?",
llm_provider=llm,
max_hops=2,
max_results=10,
)
print(result["response"])
print(f"Confidence: {result['confidence']:.2f}")
print(f"Sources used: {result['num_sources']}")
```
### Decision Methods
| Method | Returns | Description |
| ------ | ------- | ----------- |
| `record_decision(category, scenario, reasoning, outcome, confidence, entities, decision_maker, valid_from, valid_until)` | `str` | Record a decision; raises `RuntimeError` if `decision_tracking=False` |
| `find_precedents(scenario, category, limit, use_hybrid_search, max_hops, as_of)` | `List[Decision]` | Find similar past decisions by semantic + structural similarity |
| `query_decisions(query, max_hops, use_hybrid_search)` | `List[Decision]` | Broad context-aware decision search |
| `get_causal_chain(decision_id, direction, max_depth)` | `List[Decision]` | Trace `"upstream"` causes or `"downstream"` effects |
| `trace_decision_explainability(decision_id)` | `Dict` | Full explainability — causes, effects, relationship paths |
| `get_policy_engine()` | `PolicyEngine` | Access the active `PolicyEngine` instance |
### Checkpoint Methods
Useful for detecting what changed across reasoning runs:
```python
# Take a named snapshot of the current graph state
context.checkpoint("before_inference")
# ... run reasoning, record decisions ...
context.checkpoint("after_inference")
# See exactly what was added/removed
diff = context.diff_checkpoints("before_inference", "after_inference")
print(f"Decisions added: {len(diff['decisions_added'])}")
print(f"Relationships added: {len(diff['relationships_added'])}")
# Persist a checkpoint to disk via TemporalVersionManager
context.flush_checkpoint("after_inference")
```
## ContextGraph
The knowledge graph backing `AgentContext`. Can be used standalone for relationship modelling.
The knowledge graph backing `AgentContext`. Can also be used standalone for relationship modelling.
```python
from semantica.context import ContextGraph
graph = ContextGraph(advanced_analytics=True)
# Add nodes and edges
# Build the graph
graph.add_node("Python", "language", properties={"paradigm": "multi-paradigm"})
graph.add_node("FastAPI", "framework", properties={"language": "Python"})
graph.add_edge("Python", "FastAPI", "enables")
# Decision management
decision_id = graph.add_decision_simple(
# Record and query decisions directly on the graph
decision_id = graph.record_decision(
category="technology_choice",
scenario="Web API framework selection",
reasoning="FastAPI's async support and auto-docs match our requirements",
@@ -131,11 +268,11 @@ decision_id = graph.add_decision_simple(
)
similar = graph.find_precedents_by_scenario("web framework", limit=3)
impact = graph.analyze_decision_impact(decision_id)
chain = graph.trace_decision_chain(decision_id)
stats = graph.stats()
print(f"Nodes: {stats['node_count']}, Edges: {stats['edge_count']}")
```
### ContextGraph Constructor Options
### Constructor Options
| Parameter | Type | Default | Description |
| --------- | ---- | ------- | ----------- |
@@ -143,23 +280,65 @@ chain = graph.trace_decision_chain(decision_id)
| `centrality_analysis` | `bool` | `False` | Full centrality suite |
| `community_detection` | `bool` | `False` | Louvain community clustering |
| `node_embeddings` | `bool` | `False` | Node2Vec embeddings for structural similarity |
| `enable_causality` | `bool` | `False` | Causal chain tracking between decision nodes |
## Decision Data Structure
### ContextGraph — Full Method Reference
| Method | Returns | Description |
| ------ | ------- | ----------- |
| `add_node(node_id, node_type, properties, valid_from, valid_until)` | `None` | Add a node; supports temporal validity windows |
| `add_edge(source_id, target_id, edge_type, weight, properties)` | `None` | Add a directed edge with optional weight |
| `add_nodes(nodes)` | `int` | Bulk-add from a list of dicts; returns count added |
| `add_edges(edges)` | `int` | Bulk-add edges; returns count added |
| `get_neighbors(node_id, hops)` | `List[Dict]` | BFS neighbors up to given depth |
| `get_neighbor_distances(node_id, hops)` | `List[Dict]` | Neighbors with confidence-decay scoring |
| `find_node(node_id)` | `Optional[Dict]` | Look up a single node by ID |
| `find_nodes(node_type, skip, limit)` | `List[Dict]` | Filter nodes by type with pagination |
| `find_active_nodes(node_type, at_time)` | `List[Dict]` | Nodes that are valid at a given timestamp |
| `find_edges(edge_type, skip, limit)` | `List[Dict]` | Filter edges by type with pagination |
| `record_decision(category, scenario, reasoning, outcome, confidence, entities, decision_maker)` | `str` | Add decision node with causal edges |
| `find_precedents_by_scenario(scenario, category, limit, use_semantic_search, as_of)` | `List[Dict]` | Semantically similar past scenarios |
| `query(query, skip, limit)` | `List[Dict]` | Full-text search over node content |
| `stats()` | `Dict` | Node/edge counts, type breakdowns, graph density |
| `density()` | `float` | Graph density score |
| `save_to_file(path)` | `None` | Persist graph to JSON |
| `load_from_file(path)` | `None` | Load graph from JSON |
| `build_from_conversations(conversations, link_entities)` | `Dict` | Build graph from conversation data |
| `link_graph(other_graph, source_node_id, target_node_id, link_type)` | `str` | Create cross-graph navigation link; returns `link_id` |
| `navigate_to(link_id)` | `Tuple[ContextGraph, str]` | Follow a cross-graph link to `(target_graph, target_node_id)` |
| `cross_graph_path(source_node_id, target_graph, target_node_id, max_hops)` | `Dict` | Shortest path across linked graphs |
| `resolve_links(graphs)` | `int` | Reconnect cross-graph links after `load_from_file` |
| `clear()` | `None` | Reset graph state and all indexes |
### Cross-Graph Navigation
Link multiple independent `ContextGraph` instances so agents can traverse across problem spaces:
```python
@dataclass
class Decision:
decision_id: str
category: str
scenario: str
reasoning: str
outcome: str
confidence: float # 0.0 1.0
decision_maker: str
timestamp: datetime
entities: List[str]
metadata: Dict
causal_chain: List[str] # IDs of related decisions
domain_graph = ContextGraph()
decision_graph = ContextGraph()
domain_graph.add_node("microservices", "architecture", properties={"style": "distributed"})
decision_graph.add_node("deploy_k8s", "decision", properties={"outcome": "approved"})
link_id = domain_graph.link_graph(
other_graph=decision_graph,
source_node_id="microservices",
target_node_id="deploy_k8s",
link_type="INFORMED_BY",
)
# Follow the link at traversal time
target_graph, entry_node = domain_graph.navigate_to(link_id)
# Cross-graph pathfinding
path = domain_graph.cross_graph_path(
source_node_id="microservices",
target_graph=decision_graph,
target_node_id="deploy_k8s",
max_hops=5,
)
print(f"Reachable: {path['reachable']}, hops: {path['hop_count']}")
```
## AgentMemory (Low-Level)
@@ -168,18 +347,38 @@ For fine-grained control over memory storage, TTL, and importance scoring:
```python
from semantica.context import AgentMemory
from semantica.vector_store import VectorStore
memory = AgentMemory(
vector_store=VectorStore(backend="faiss", dimension=768),
max_memories=10_000,
capacity=10_000,
ttl_days=90,
)
memory.store("Important fact", importance=0.9, tags=["compliance"])
results = memory.retrieve("fact query", top_k=5, min_importance=0.5)
memory_id = memory.store(
"Critical compliance rule: all trades must be pre-approved",
importance=0.95,
tags=["compliance", "trading"],
)
results = memory.retrieve(
query="trade approval requirements",
max_results=5,
min_importance=0.5,
tags=["compliance"],
)
memory.update(memory_id, importance=1.0)
memory.forget(memory_id)
all_memories = memory.get_all()
```
| Parameter | Type | Default | Description |
| --------- | ---- | ------- | ----------- |
| `vector_store` | `VectorStore` | **required** | Embedding backend for semantic retrieval |
| `capacity` | `int` | `1000` | Max items before LRU eviction |
| `ttl_days` | `Optional[int]` | `None` | Days before automatic expiry; `None` = keep forever |
## PolicyEngine
Validate decisions against configurable rules before they're committed:
@@ -191,55 +390,297 @@ policy = PolicyEngine()
policy.add_rule("confidence_threshold", lambda d: d.confidence >= 0.7)
policy.add_rule("requires_reasoning", lambda d: len(d.reasoning) >= 20)
# Validate before recording
is_valid, violations = policy.validate(decision_data)
if is_valid:
context.record_decision(**decision_data)
else:
# Create approval chain for human-in-the-loop review
chain = policy.create_approval_chain(
decision_data,
approvers=["manager@company.com", "compliance@company.com"],
)
print(f"Approval chain created: {chain.chain_id}")
```
## EntityLinker
Maps extracted entity mentions to canonical URIs — essential for cross-document entity resolution:
```python
from semantica.context import EntityLinker
linker = EntityLinker()
entities = [
{"text": "Apple Inc.", "type": "ORGANIZATION"},
{"text": "Apple", "type": "ORGANIZATION"},
{"text": "AAPL", "type": "ORGANIZATION"},
]
linked = linker.link_entities(entities, sources=["reuters", "sec_filings"])
for e in linked:
print(f"{e.text} → {e.canonical_form} ({e.uri})")
print(f" confidence: {e.confidence:.2f}, sources: {e.sources}")
```
## ContextRetriever
Hybrid retrieval combining vector similarity, graph traversal, and memory — surfaces results that pure vector search misses:
```python
from semantica.context import ContextRetriever
retriever = ContextRetriever(
vector_store=vector_store,
context_graph=context_graph,
agent_memory=memory,
)
results = retriever.retrieve(
query="What decisions were made about cloud infrastructure?",
max_results=10,
use_graph_expansion=True,
min_relevance_score=0.3,
)
for r in results:
print(f"[{r['source']}] score={r['score']:.3f}: {r['content'][:80]}")
```
## Data Structures
<AccordionGroup>
<Accordion title="Decision">
```python
@dataclass
class Decision:
decision_id: str
category: str
scenario: str
reasoning: str
outcome: str
confidence: float # 0.0 1.0
decision_maker: str # default: "ai_agent"
timestamp: datetime
valid_from: Optional[str] # ISO datetime — temporal validity start
valid_until: Optional[str] # ISO datetime — temporal validity end
metadata: Dict[str, Any] # arbitrary key/value store
```
</Accordion>
<Accordion title="Precedent">
```python
@dataclass
class Precedent:
decision_id: str
similarity: float # 01 match score against queried scenario
category: str
scenario: str
outcome: str
reasoning: str
confidence: float
timestamp: datetime
```
</Accordion>
<Accordion title="Policy">
```python
@dataclass
class Policy:
policy_id: str
name: str
description: str
rules: List[Dict] # list of rule definitions
active: bool
created_at: datetime
version: int
```
</Accordion>
<Accordion title="PolicyException">
```python
@dataclass
class PolicyException:
exception_id: str
policy_rule: str # name of the violated rule
decision_id: str # decision that triggered the exception
justification: str # why the exception was granted
approved_by: str # approver identity
timestamp: datetime
expiry: Optional[datetime]
```
</Accordion>
<Accordion title="ApprovalChain">
```python
@dataclass
class ApprovalChain:
chain_id: str
decision_id: str
steps: List[ApprovalStep]
status: str # "pending" | "approved" | "rejected"
created_at: datetime
@dataclass
class ApprovalStep:
step_id: str
approver: str
required: bool
status: str # "pending" | "approved" | "rejected"
comment: Optional[str]
timestamp: Optional[datetime]
```
</Accordion>
<Accordion title="LinkedEntity">
```python
@dataclass
class LinkedEntity:
text: str
canonical_form: str # normalized primary name
uri: str # e.g. "http://dbpedia.org/resource/Apple_Inc."
confidence: float
sources: List[str] # source documents that mention this entity
aliases: List[str] # all observed surface forms
```
</Accordion>
</AccordionGroup>
## Real-World Patterns
### Healthcare — Treatment Decisions
<Tabs>
<Tab title="Healthcare — Treatment Decisions">
```python
from semantica.context import AgentContext
from semantica.vector_store import VectorStore
```python
health_agent = AgentContext(
vector_store=VectorStore(backend="faiss", dimension=768),
decision_tracking=True,
)
health_agent = AgentContext(
vector_store=VectorStore(backend="faiss", dimension=768),
decision_tracking=True,
)
health_agent.store("Patient has hypertension, type 2 diabetes")
health_agent.store("Patient allergic to penicillin — verified 2024-01")
health_agent.store("Patient has hypertension, type 2 diabetes")
health_agent.store("Patient allergic to penicillin — verified 2024-01")
decision_id = health_agent.record_decision(
category="treatment_plan",
scenario="Hypertension with comorbid diabetes",
reasoning="ACE inhibitors are renoprotective in diabetic patients — preferred over beta blockers",
outcome="prescribed_lisinopril",
confidence=0.91,
)
decision_id = health_agent.record_decision(
category="treatment_plan",
scenario="Hypertension with comorbid diabetes",
reasoning="ACE inhibitors are renoprotective in diabetic patients — preferred over beta blockers",
outcome="prescribed_lisinopril",
confidence=0.91,
)
# Check for similar cases
precedents = health_agent.find_precedents("hypertension diabetes", limit=5)
```
precedents = health_agent.find_precedents("hypertension diabetes", limit=5)
for p in precedents:
print(f"Past decision: {p.outcome} (confidence: {p.confidence:.2f})")
### Finance — Loan Decisions
chain = health_agent.get_causal_chain(decision_id, direction="downstream")
print(f"Follow-up decisions triggered: {len(chain)}")
```
</Tab>
<Tab title="Finance — Loan Decisions">
```python
from semantica.context import AgentContext, PolicyEngine
from semantica.vector_store import VectorStore
```python
loan_agent = AgentContext(
vector_store=VectorStore(backend="faiss", dimension=768),
decision_tracking=True,
)
policy = PolicyEngine()
policy.add_rule("min_confidence", lambda d: d["confidence"] >= 0.8)
policy.add_rule("has_reasoning", lambda d: len(d["reasoning"]) >= 30)
loan_agent.store("Applicant: credit score 750, DTI 28%, stable employment 4yr")
loan_agent = AgentContext(
vector_store=VectorStore(backend="faiss", dimension=768),
decision_tracking=True,
)
decision_id = loan_agent.record_decision(
category="loan_approval",
scenario="First-time homebuyer — 30yr fixed, 20% down",
reasoning="Credit score above threshold, DTI within limits, stable income verified",
outcome="approved_300k",
confidence=0.94,
)
```
loan_agent.store("Applicant: credit score 750, DTI 28%, stable employment 4yr")
decision_data = dict(
category="loan_approval",
scenario="First-time homebuyer — 30yr fixed, 20% down",
reasoning="Credit score above threshold, DTI within limits, stable income verified",
outcome="approved_300k",
confidence=0.94,
)
is_valid, violations = policy.validate(decision_data)
if is_valid:
decision_id = loan_agent.record_decision(**decision_data)
else:
chain = policy.create_approval_chain(decision_data, approvers=["underwriter@bank.com"])
print(f"Sent for review: {chain.chain_id}")
```
</Tab>
<Tab title="Persist & Restore">
```python
context = AgentContext(
vector_store=VectorStore(backend="faiss", dimension=768, index_path="ctx.faiss"),
knowledge_graph=ContextGraph(),
decision_tracking=True,
)
context.store("Important fact learned during session")
context.record_decision(
category="ops", scenario="Scale up", reasoning="Load > 80%",
outcome="scaled_to_10_replicas", confidence=0.97,
)
# Persist everything
context.save("agent_state/")
# Later — restore and continue
restored = AgentContext(
vector_store=VectorStore(backend="faiss", dimension=768, index_path="ctx.faiss"),
knowledge_graph=ContextGraph(),
decision_tracking=True,
)
restored.load("agent_state/")
results = restored.retrieve("load scaling decisions", max_results=3)
```
</Tab>
</Tabs>
## Tips and Common Pitfalls
<Warning>
**Persist your vector store between runs.** Pass `index_path="context.faiss"` to `VectorStore` — without it the FAISS index lives only in memory and is lost on shutdown. An agent that forgets everything on restart isn't an agent.
</Warning>
<Warning>
**Enable `decision_tracking=True` from the start.** Adding it retroactively means historical decisions are not linked to the causal chain — you lose the ability to trace how one decision influenced later ones. Enable it at initialization, even if you're not using it immediately.
</Warning>
<Tip>
**Use `find_precedents()` before every significant decision.** This is how the context module prevents agents from making contradictory choices across runs. Surface precedents to the LLM as context: "we chose X for similar reasons before."
</Tip>
<Tip>
**`retrieve()` uses `max_results=`, not `top_k=`.** The parameter is `max_results` (default `5`). Pass `use_graph=True` to force GraphRAG or `use_graph=False` to force vector-only retrieval regardless of whether a `knowledge_graph` is configured.
</Tip>
<Tip>
**Set `retention_days` to avoid memory bloat.** Without it `AgentMemory` accumulates indefinitely (the default `AgentContext.retention_days=30` prunes automatically). Compliance-critical agents may need `retention_days=None` with explicit archival via `export()`.
</Tip>
<Warning>
**Gate irreversible decisions with `PolicyEngine`.** Decisions recorded with `record_decision()` become part of the causal chain immediately. Validate first with `policy.validate()` and create an `ApprovalChain` for human review — don't record until approved.
</Warning>
<Tip>
**Use `checkpoint()` + `diff_checkpoints()` to audit reasoning loops.** Take a snapshot before and after a reasoning pass to see exactly which decisions and relationships were added. This is the cleanest way to detect divergent agent behaviour across runs.
</Tip>
<Tip>
**`EntityLinker` prevents graph proliferation.** Without it, "Apple", "Apple Inc.", and "AAPL" land as three separate nodes. Run `EntityLinker.link_entities()` on mentions before storing them to maintain a canonical graph.
</Tip>
<CardGroup cols={2}>
<Card title="Vector Store" icon="database" href="vector_store">
+31 -2
View File
@@ -6,13 +6,28 @@ icon: "gear"
`semantica.core` is the coordination layer for the framework. For most tasks you should use individual modules directly (`semantica.ingest`, `semantica.kg`, etc.). Reach for Core when you need application-level lifecycle management, centralized configuration, or a plugin registry.
## Exported Classes
```python
from semantica.core import (
Semantica, # orchestration class — coordinates full KG pipeline
ConfigManager, # YAML config loading, deep-merge, env var overrides
LifecycleManager,# startup/shutdown state machine + health monitoring
PluginRegistry, # plugin discovery, registration, and loading
method_registry, # global MethodRegistry instance for custom dispatch
)
# For custom build methods:
from semantica.core.methods import build_knowledge_base
```
## What You Get
- **`Semantica`** — orchestration class for coordinating complex multi-module workflows
- **`ConfigManager`** — unified config loading, merging, and validation with environment variable overrides
- **`LifecycleManager`** — startup/shutdown hooks and component health monitoring
- **`PluginRegistry`** — dynamic plugin discovery, registration, and loading
- **`MethodRegistry`** — register and dispatch custom orchestration methods
- **`method_registry`** — global `MethodRegistry` instance — register and dispatch custom orchestration methods
<Tip>
**Use individual modules directly** for the vast majority of use cases. Use the `Semantica` orchestration class only when you need application-level lifecycle management or a plugin system.
@@ -167,6 +182,7 @@ Register custom orchestration methods and dispatch them by name:
```python
from semantica.core import method_registry
from semantica.core.methods import build_knowledge_base
def fast_kb_builder(sources, **kwargs):
# Custom logic — skip embeddings for speed
@@ -174,10 +190,23 @@ def fast_kb_builder(sources, **kwargs):
method_registry.register("knowledge_base", "fast", fast_kb_builder)
from semantica.core.methods import build_knowledge_base
result = build_knowledge_base(sources=["doc.pdf"], method="fast")
```
## When to Use Core vs. Individual Modules
| Scenario | Recommended Approach |
| -------- | -------------------- |
| Single extraction task | `from semantica.semantic_extract import NERExtractor` |
| Build a knowledge graph | `from semantica.kg import GraphBuilder` |
| Multi-step pipeline | `from semantica.pipeline import Pipeline` |
| App-level lifecycle + config | `from semantica.core import Semantica, ConfigManager` |
| Custom dispatch / plugins | `from semantica.core import method_registry, PluginRegistry` |
<Tip>
Use `Semantica` and `LifecycleManager` only when building a long-running application (e.g. a FastAPI service) that needs ordered startup, health checks, and graceful shutdown. For scripts and notebooks, use individual modules directly.
</Tip>
<CardGroup cols={2}>
<Card title="Pipeline" icon="arrows-turn-to-dots" href="pipeline">
Pipeline execution and step orchestration.
+19
View File
@@ -6,6 +6,25 @@ icon: "copy"
`semantica.deduplication` detects and merges duplicate entities across sources to produce a clean, single-source-of-truth knowledge graph. **v2 strategies** (`blocking_v2`, `hybrid_v2`, `semantic_v2`) are up to **7x faster** than v1 with fine-grained result control.
## Exported Classes
```python
from semantica.deduplication import (
DuplicateDetector, # pairwise + batch duplicate detection
EntityMerger, # merge duplicate groups with per-property policies
SimilarityCalculator, # Levenshtein, Jaro-Winkler, cosine, Jaccard, embedding
ClusterBuilder, # Union-Find + hierarchical clustering
PropertyMergeRule, # enum: KEEP_FIRST, KEEP_LONGEST, UNION, VOTING, ...
MergeStrategyManager, # manage and apply merge strategies
# Convenience functions
detect_duplicates, # quick: detect_duplicates(entities, method="semantic_v2")
merge_entities, # quick: merge_entities(entities, duplicates, method="union")
calculate_similarity, # quick: calculate_similarity(a, b, method="hybrid_v2")
# Registry
method_registry, # register custom similarity functions
)
```
## What You Get
- **`DuplicateDetector`** — pairwise and batch duplicate detection with configurable strategies
+353 -122
View File
@@ -1,216 +1,425 @@
---
title: "Embeddings Module"
description: "Text and graph embedding generation — Sentence-Transformers, FastEmbed, OpenAI, BGE, LlamaStore, with pooling strategies and graph embedding managers."
description: "Text and graph embedding generation — Sentence-Transformers, FastEmbed, OpenAI, BGE, Ollama with pooling strategies, caching, and GPU acceleration."
icon: "vector-square"
---
`semantica.embeddings` converts text and graph structures into dense vectors for semantic search, entity resolution, and GraphRAG retrieval. A single provider-agnostic API abstracts Sentence-Transformers, FastEmbed, OpenAI, BGE, and Ollama.
`semantica.embeddings` converts text and graph structures into dense vectors. These vectors power semantic search, entity resolution, GraphRAG retrieval, and Distance Intelligence across every Semantica module. A single provider-agnostic API abstracts Sentence-Transformers, FastEmbed, OpenAI, BGE, and Ollama behind one interface.
## Why Embeddings Matter
Raw text can't be compared mathematically. Embeddings translate meaning into geometry — two semantically similar sentences produce vectors that are close together in high-dimensional space, even when they share no words.
Semantica uses embeddings for:
- **Semantic search** — find knowledge graph nodes by meaning, not just keywords
- **Entity resolution** — detect that "Apple Inc." and "Apple Computer" refer to the same entity
- **Deduplication** — `semantic_v2` strategy measures entity similarity via embedding distance
- **GraphRAG retrieval** — hybrid vector + graph traversal for grounded LLM answers
- **Distance Intelligence** — N×N semantic distance matrices across entity sets
- **Semantic chunking** — detect topic shift boundaries in `TextSplitter(method="semantic_transformer")`
## Exported Classes
```python
from semantica.embeddings import (
# Core generators
EmbeddingGenerator, # main handler: generate_embeddings(text, data_type="text")
TextEmbedder, # text embedding: embed(text), embed_batch(texts)
GraphEmbeddingManager, # embed KG nodes/subgraphs for GraphRAG
VectorEmbeddingManager, # embedding management for vector databases
# Provider stores
OpenAIStore, # OpenAI text-embedding-* API
BGEStore, # BAAI/bge-* via sentence-transformers
FastEmbedStore, # ONNX-accelerated, no CUDA required
LlamaStore, # Ollama local embedding models
ProviderStoreFactory, # create(provider="bge", model="...") factory
# Pooling strategies
MeanPooling, # default — best for retrieval and clustering
MaxPooling, # captures presence of any feature
CLSPooling, # CLS token (BERT-style classification models)
AttentionPooling, # softmax-weighted sum
HierarchicalPooling, # for long documents exceeding context length
PoolingStrategyFactory, # create(strategy="mean") factory
# Convenience functions
embed_text, # embed_text(text, method="sentence_transformers")
generate_embeddings, # generate_embeddings(texts, method="openai")
calculate_similarity, # calculate_similarity(a, b, method="cosine")
pool_embeddings, # pool_embeddings(token_embeddings, strategy="mean")
check_available_providers, # returns {"sentence_transformers": True, ...}
)
```
## What You Get
- **`EmbeddingGenerator`** — main entry point, provider-agnostic text embedding with batching
- **`TextEmbedder`** — text-specific embedding with automatic batching and disk caching
- **`GraphEmbeddingManager`** — node and subgraph embeddings for structural similarity
- **`VectorEmbeddingManager`** — full embedding lifecycle for vector store integration
- **Provider stores** — `OpenAIStore`, `BGEStore`, `FastEmbedStore`, `LlamaStore`, `ProviderStoreFactory`
- **Pooling strategies** — Mean, Max, CLS, Attention, Hierarchical pooling
<CardGroup cols={2}>
<Card title="EmbeddingGenerator" icon="vector-square">
Main entry point — provider-agnostic, handles batching automatically across all backends.
</Card>
<Card title="TextEmbedder" icon="text-size">
Text-specific with automatic batching, disk caching, and progress tracking.
</Card>
<Card title="GraphEmbeddingManager" icon="diagram-project">
Node and subgraph embeddings for structural similarity and GraphRAG context assembly.
</Card>
<Card title="VectorEmbeddingManager" icon="database">
Full lifecycle: embed → store → search in a single coordinated workflow.
</Card>
<Card title="Provider Stores" icon="plug">
`OpenAIStore`, `BGEStore`, `FastEmbedStore`, `LlamaStore`, and `ProviderStoreFactory`.
</Card>
<Card title="Pooling Strategies" icon="layer-group">
Mean, Max, CLS, Attention, and Hierarchical — control token-to-vector aggregation.
</Card>
</CardGroup>
## Installation
| Provider | Install Command | API Key Required |
| -------- | --------------- | ---------------- |
| Sentence-Transformers (default) | `pip install semantica` | No |
| FastEmbed | `pip install "semantica[fastembed]"` | No |
| BGE | `pip install semantica` | No (uses sentence-transformers) |
| OpenAI | `pip install "semantica[llm-openai]"` | Yes — `OPENAI_API_KEY` |
| Ollama (LlamaStore) | `pip install "semantica[llm-ollama]"` | No — local server |
| All providers | `pip install "semantica[all]"` | Varies |
Check which providers are available in your environment:
```python
from semantica.embeddings import check_available_providers
providers = check_available_providers()
# → {"sentence_transformers": True, "fastembed": True, "openai": False, "ollama": True}
```
## Quick Start
<Steps>
<Step title="Install and initialize a provider">
```python
from semantica.embeddings import EmbeddingGenerator
# Default — Sentence-Transformers, free, runs locally
generator = EmbeddingGenerator()
# Custom model via config dict
generator = EmbeddingGenerator(config={"text": {"method": "sentence_transformers", "model_name": "BAAI/bge-large-en-v1.5"}})
```
</Step>
<Step title="Generate embeddings">
```python
embeddings = generator.generate_embeddings(["Text about AI", "Machine learning concepts"])
```
</Step>
<Step title="Compute similarity">
```python
# Cosine similarity — 0.0 (unrelated) to 1.0 (identical meaning)
score = generator.compare_embeddings(embeddings[0], embeddings[1], method="cosine")
print(f"Similarity: {score:.3f}")
```
</Step>
<Step title="Embed and store for search">
```python
from semantica.embeddings import VectorEmbeddingManager, TextEmbedder
from semantica.vector_store import VectorStore
vector_store = VectorStore(backend="faiss", dimension=384)
manager = VectorEmbeddingManager(
embedder=TextEmbedder(model="sentence-transformers"),
vector_store=vector_store,
)
ids = manager.embed_and_store(documents, metadata=metadata_list)
results = manager.search("machine learning algorithms", top_k=10)
for result in results:
print(f"Score: {result.score:.3f} — {result.metadata['title']}")
```
</Step>
</Steps>
## Supported Models
| Provider | Model | Dimension | Speed | Best For |
| -------- | ----- | --------- | ----- | -------- |
| `sentence-transformers` | `all-MiniLM-L6-v2` | 384 | Fast | Default — good balance of speed and quality |
| `sentence-transformers` | `all-mpnet-base-v2` | 768 | Medium | Higher retrieval quality |
| `bge` | `BAAI/bge-large-en-v1.5` | 1024 | Medium | State-of-the-art retrieval accuracy |
| `bge` | `BAAI/bge-small-en-v1.5` | 384 | Fast | Lightweight, competitive quality |
| `fastembed` | `BAAI/bge-small-en-v1.5` | 384 | Very fast | CPU-optimised, low-latency production |
| `openai` | `text-embedding-3-small` | 1536 | API | Cost-effective OpenAI embedding |
| `openai` | `text-embedding-3-large` | 3072 | API | Highest quality via OpenAI API |
| `llama` (Ollama) | Any Ollama model | Varies | Local | Fully local, no API key |
## EmbeddingGenerator
Main entry point — handles provider selection and batching automatically:
<Tabs>
<Tab title="Sentence-Transformers (default)">
```python
from semantica.embeddings import EmbeddingGenerator
```python
from semantica.embeddings import EmbeddingGenerator
# Default — Sentence-Transformers with all-MiniLM-L6-v2
generator = EmbeddingGenerator()
# Sentence-Transformers (default, free, local)
generator = EmbeddingGenerator(model="sentence-transformers")
embeddings = generator.generate(["Text 1", "Text 2"])
# Custom model via set_text_model
generator.set_text_model("sentence_transformers", "BAAI/bge-large-en-v1.5")
# Specific BGE model
generator = EmbeddingGenerator(model="BAAI/bge-large-en-v1.5")
embeddings = generator.generate(texts)
embeddings = generator.generate_embeddings(texts)
similarity = generator.compare_embeddings(embeddings[0], embeddings[1])
```
# OpenAI
import os
generator = EmbeddingGenerator(
model="openai",
model_name="text-embedding-3-small",
api_key=os.getenv("OPENAI_API_KEY")
)
Best for: default prototyping, no API key, good quality.
</Tab>
<Tab title="FastEmbed">
```python
from semantica.embeddings import EmbeddingGenerator
# FastEmbed (fast, CPU-optimized)
generator = EmbeddingGenerator(model="fastembed")
```
generator = EmbeddingGenerator()
generator.set_text_model("fastembed", "BAAI/bge-small-en-v1.5")
embeddings = generator.generate_embeddings(texts)
```
### Supported Models
Best for: CPU-only production, lowest latency without GPU.
</Tab>
<Tab title="OpenAI">
```python
from semantica.embeddings import OpenAIStore
import os
| Provider | Model | Dimension | Notes |
| -------- | ----- | --------- | ----- |
| `sentence-transformers` | `all-MiniLM-L6-v2` | 384 | Default, fast, free |
| `sentence-transformers` | `all-mpnet-base-v2` | 768 | Higher quality |
| `bge` | `BAAI/bge-large-en-v1.5` | 1024 | State-of-the-art retrieval |
| `fastembed` | `BAAI/bge-small-en-v1.5` | 384 | Fast, CPU-optimized |
| `openai` | `text-embedding-3-small` | 1536 | OpenAI API |
| `openai` | `text-embedding-3-large` | 3072 | OpenAI API, highest quality |
| `llama` | any Ollama model | varies | Fully local inference |
store = OpenAIStore(api_key=os.getenv("OPENAI_API_KEY"), model="text-embedding-3-small")
embedding = store.embed("Hello world")
```
Best for: highest quality (3-large), or matching an OpenAI LLM pipeline.
</Tab>
<Tab title="Ollama (local)">
```python
from semantica.embeddings import LlamaStore
store = LlamaStore(model="llama3.2", base_url="http://localhost:11434")
embedding = store.embed("Hello world")
```
Best for: air-gapped or privacy-sensitive environments — no data leaves your machine.
</Tab>
<Tab title="GPU acceleration">
```python
from semantica.embeddings import EmbeddingGenerator
# Set device via text embedder config
generator = EmbeddingGenerator(config={"text": {"device": "cuda"}})
# Apple Silicon (M1/M2/M3)
generator = EmbeddingGenerator(config={"text": {"device": "mps"}})
```
GPU reduces embedding time by 520× depending on batch size and model.
</Tab>
</Tabs>
### Constructor Parameters
| Parameter | Type | Default | Description |
| --------- | ---- | ------- | ----------- |
| `config` | `dict` | `None` | Config dict; `config["text"]` is passed to `TextEmbedder` |
| `**kwargs` | | | Additional key/value config merged into `config` |
Use `generator.set_text_model(method, model_name)` to switch the embedding model after construction.
## TextEmbedder
Specialized for text with automatic batching and optional disk cache:
Specialised for text workloads — adds automatic batching, progress tracking, and disk caching:
```python
from semantica.embeddings import TextEmbedder
embedder = TextEmbedder(model="sentence-transformers", cache_dir=".emb_cache")
embedder = TextEmbedder(
model="sentence-transformers",
cache_dir=".emb_cache", # persist embeddings to disk
cache_ttl=86400, # cache expiry in seconds (24h); None = never expires
batch_size=128,
show_progress=True,
)
# Single text
embedding = embedder.embed("Hello world")
embedding = embedder.embed("A knowledge graph connects entities with typed relationships.")
# Batch — processes automatically in chunks
embeddings = embedder.embed_batch(
["Text 1", "Text 2", ..., "Text 10000"],
batch_size=128,
show_progress=True
)
# Batch — auto-splits into batch_size chunks, shows progress bar
embeddings = embedder.embed_batch(texts, show_progress=True)
```
**Key behaviours:**
- Cache is keyed on text content + model name — identical texts return cached vectors instantly
- Progress bar uses `tqdm` in terminal; switches to `tqdm.notebook` in Jupyter automatically
- Large batches (> 10k texts) are chunked internally to avoid OOM on GPU
## Provider Stores
Each provider implements the `ProviderStore` interface and can be used independently:
Use provider stores directly when you need fine-grained control over a single backend:
```python
from semantica.embeddings import (
OpenAIStore, BGEStore, FastEmbedStore, LlamaStore,
ProviderStoreFactory
ProviderStoreFactory,
)
import os
# OpenAI
store = OpenAIStore(api_key=os.getenv("OPENAI_API_KEY"), model="text-embedding-3-small")
store = OpenAIStore(api_key=os.getenv("OPENAI_API_KEY"), model="text-embedding-3-small")
embedding = store.embed("Hello world")
# BGE (Sentence-Transformers wrapper)
store = BGEStore(model="BAAI/bge-large-en-v1.5")
store = BGEStore(model="BAAI/bge-large-en-v1.5", device="cpu")
embedding = store.embed("Hello world")
# FastEmbed
store = FastEmbedStore(model="BAAI/bge-small-en-v1.5")
# FastEmbed — ONNX runtime, no CUDA required
store = FastEmbedStore(model="BAAI/bge-small-en-v1.5")
embedding = store.embed("Hello world")
# LlamaStore (Ollama — fully local)
store = LlamaStore(model="llama3.2", base_url="http://localhost:11434")
# Ollama — fully local
store = LlamaStore(model="llama3.2", base_url="http://localhost:11434")
embedding = store.embed("Hello world")
# Auto-select from config
store = ProviderStoreFactory.create(provider="openai", model="text-embedding-3-small")
# Auto-select from a name string — useful in config-driven pipelines
store = ProviderStoreFactory.create(provider="bge", model="BAAI/bge-large-en-v1.5")
```
## Pooling Strategies
Control how token-level embeddings are aggregated into a single vector:
Transformer models produce one embedding per token. Pooling aggregates token embeddings into a single vector:
```python
from semantica.embeddings import (
MeanPooling, MaxPooling, CLSPooling,
AttentionPooling, HierarchicalPooling, PoolingStrategyFactory
)
<Tabs>
<Tab title="MeanPooling (default)">
```python
from semantica.embeddings import MeanPooling
# Mean pooling — default, best for most tasks
pooler = MeanPooling()
pooled = pooler.pool(token_embeddings)
pooler = MeanPooling()
pooled = pooler.pool(token_embeddings) # shape: (hidden_dim,)
```
# Max pooling — captures strongest activated features
pooler = MaxPooling()
Best for: retrieval, semantic search, and clustering — averages all token contributions.
</Tab>
<Tab title="MaxPooling">
```python
from semantica.embeddings import MaxPooling
# CLS token — good for classification tasks
pooler = CLSPooling()
pooler = MaxPooling()
pooled = pooler.pool(token_embeddings)
```
# Attention-weighted pooling
pooler = AttentionPooling()
Best for: capturing the presence of any feature — takes the max activation per dimension.
</Tab>
<Tab title="CLSPooling">
```python
from semantica.embeddings import CLSPooling
# Hierarchical: chunk-level → global mean (best for long documents)
pooler = HierarchicalPooling(chunk_size=512)
pooler = CLSPooling()
pooled = pooler.pool(token_embeddings)
```
# Create from config string
pooler = PoolingStrategyFactory.create(strategy="mean")
```
Best for: classification-style tasks; models explicitly trained with CLS pooling (BERT).
</Tab>
<Tab title="HierarchicalPooling">
```python
from semantica.embeddings import HierarchicalPooling
# Chunk text, mean-pool within chunks, then mean-pool chunks
pooler = HierarchicalPooling(chunk_size=512)
pooled = pooler.pool(token_embeddings)
```
Best for: long documents exceeding the model's max sequence length — reports, papers, contracts.
</Tab>
<Tab title="Strategy Comparison">
| Strategy | When to Use |
| -------- | ----------- |
| `mean` | Default for retrieval, semantic search, and clustering |
| `max` | When you want to capture the presence of any feature, not average presence |
| `cls` | Classification-style tasks; models explicitly trained with CLS pooling (BERT) |
| `attention` | When token importance varies significantly; slower but more accurate |
| `hierarchical` | Long documents exceeding model context length; reports, papers, contracts |
```python
from semantica.embeddings import PoolingStrategyFactory
pooler = PoolingStrategyFactory.create(strategy="mean")
```
</Tab>
</Tabs>
## GraphEmbeddingManager
Embed graph nodes and subgraphs for structural similarity and GraphRAG context:
Embed graph nodes and subgraphs for structural similarity and GraphRAG context assembly:
```python
from semantica.embeddings import GraphEmbeddingManager
from semantica.embeddings import GraphEmbeddingManager, TextEmbedder
manager = GraphEmbeddingManager(
text_embedder=TextEmbedder(model="sentence-transformers"),
graph_store=graph_store
graph_store=graph_store, # optional — for persistence
)
# Embed all nodes in the graph
# Embed all nodes — uses node label + property text
node_embeddings = manager.embed_nodes(kg)
# Embed a subgraph centered on a node (for GraphRAG context)
# Embed a subgraph centred on a node (for GraphRAG context)
subgraph_embedding = manager.embed_subgraph(
kg, center_node="Apple Inc.", hops=2
kg,
center_node="Apple Inc.",
hops=2, # include neighbours up to 2 hops away
)
# Find semantically similar nodes
# Find semantically similar nodes by ID
similar = manager.find_similar_nodes("apple_inc", top_k=5)
for node_id, score in similar:
print(f"{node_id}: {score:.3f}")
```
## VectorEmbeddingManager
**Key behaviours:**
- Node embedding combines the label, type, and all property values into a single text string before embedding
- `hops=2` captures the local neighbourhood — increase for richer context, decrease for speed
- Results from `find_similar_nodes` are sorted by cosine similarity descending
Manages the full embedding lifecycle — from raw text to stored, searchable vectors:
## Embedding Cache
The disk cache avoids recomputing embeddings for unchanged text — critical for large corpora and repeated pipeline runs:
```python
from semantica.embeddings import VectorEmbeddingManager
from semantica.vector_store import VectorStore
from semantica.embeddings import TextEmbedder
vector_store = VectorStore(backend="faiss", dimension=768)
manager = VectorEmbeddingManager(
embedder=TextEmbedder(model="sentence-transformers"),
vector_store=vector_store
embedder = TextEmbedder(
model="sentence-transformers",
cache_dir=".embeddings_cache",
cache_ttl=3600, # seconds — None means cache never expires
)
# Embed documents and store in one step
ids = manager.embed_and_store(documents, metadata=metadata_list)
# First call: computes and caches
embeddings = embedder.embed_batch(texts)
# Search by semantic similarity
results = manager.search("machine learning algorithms", top_k=10)
# Second call (same texts): returns from cache instantly
embeddings = embedder.embed_batch(texts)
```
<Note>
The Distance Intelligence module (v0.5.0) uses the same cache to avoid recomputing embeddings during N×N matrix calculations across large entity sets.
</Note>
## Similarity Computation
```python
from semantica.embeddings import calculate_similarity
# Cosine similarity (most common)
# Cosine similarity — direction only, not magnitude; most common for text
score = calculate_similarity(embedding_a, embedding_b, method="cosine")
# → 0.0 to 1.0
# → 0.0 (orthogonal / unrelated) to 1.0 (identical direction)
# Euclidean distance (converted to similarity)
# Euclidean distance converted to similarity
score = calculate_similarity(embedding_a, embedding_b, method="euclidean")
```
## GPU Acceleration
```python
# Use CUDA GPU for faster embedding generation
generator = EmbeddingGenerator(model="sentence-transformers", device="cuda")
# device options: "cpu" | "cuda" | "mps"
```
## Embedding Cache
The embedding cache is used by Distance Intelligence (v0.5.0) to avoid recomputing embeddings for large N×N distance matrix calculations:
```python
embedder = TextEmbedder(
model="sentence-transformers",
cache_dir=".embeddings_cache",
cache_ttl=3600 # seconds before cache entries expire
)
# Dot product — use when vectors are already normalised (equivalent to cosine)
score = calculate_similarity(embedding_a, embedding_b, method="dot")
```
## Convenience Functions
@@ -218,10 +427,10 @@ embedder = TextEmbedder(
```python
from semantica.embeddings import (
embed_text, generate_embeddings, calculate_similarity,
pool_embeddings, check_available_providers
pool_embeddings, check_available_providers,
)
# Single text
# Single text — fastest path
emb = embed_text("Hello world", method="sentence_transformers")
# Batch
@@ -232,6 +441,28 @@ providers = check_available_providers()
# → {"sentence_transformers": True, "fastembed": True, "openai": False}
```
## Tips and Common Pitfalls
<Warning>
**Dimension mismatch.** The dimension you pass to `VectorStore(dimension=...)` must exactly match your embedding model's output. `all-MiniLM-L6-v2` → 384, `all-mpnet-base-v2` → 768, `bge-large-en-v1.5` → 1024. Check with `generator.dimension` before creating the store.
</Warning>
<Warning>
**Not normalising for cosine similarity.** If you compute cosine similarity directly (dot product), vectors must be L2-normalised first. `EmbeddingGenerator` normalises by default (`normalize=True`). If you disable it, use `calculate_similarity(..., method="cosine")` which normalises internally.
</Warning>
<Warning>
**Sequence length limits.** Most models have a 512-token limit. Text beyond that is silently truncated. Use `TextSplitter(method="hierarchical")` + `HierarchicalPooling` for long documents.
</Warning>
<Tip>
**Always use the same model for indexing and querying.** Vectors from different models are not comparable — they live in different vector spaces. Switching models requires re-embedding your entire corpus.
</Tip>
<Tip>
**Cache invalidation.** The cache key is the text + model name. Switching models requires clearing the cache or using a different `cache_dir` — otherwise you'll get stale vectors silently returned.
</Tip>
<CardGroup cols={2}>
<Card title="Vector Store" icon="database" href="vector_store">
Store and search the generated embeddings.
@@ -240,9 +471,9 @@ providers = check_available_providers()
Chunk text before embedding for better retrieval quality.
</Card>
<Card title="KG Module" icon="diagram-project" href="kg">
Distance Intelligence uses graph embeddings.
Distance Intelligence uses graph embeddings for semantic neighbourhoods.
</Card>
<Card title="Deduplication" icon="copy" href="deduplication">
Semantic deduplication uses embeddings for entity resolution.
Semantic deduplication uses embedding distance for entity resolution.
</Card>
</CardGroup>
+25 -20
View File
@@ -1,37 +1,42 @@
---
title: "Evals Module"
description: "Evaluation framework for measuring Knowledge Graph quality, extraction accuracy, and pipeline performance."
description: "Evaluation framework for measuring Knowledge Graph quality, extraction accuracy, and pipeline performance — coming soon."
icon: "chart-line"
---
`semantica.evals` provides a comprehensive evaluation framework for measuring extraction accuracy, graph quality, and pipeline performance. Use it to benchmark extractors, validate pipeline output, and track quality regressions across runs.
`semantica.evals` is planned as a comprehensive evaluation framework for measuring extraction accuracy, graph quality, and pipeline performance.
<Warning>
**Coming Soon** This module is currently in active development. Documentation will be expanded in the next release.
**`semantica.evals` is not yet implemented.** The module exists as a placeholder (`__all__ = []`). No classes or functions are available for import. This page describes the planned API.
</Warning>
## Planned Capabilities
## Planned Features
The Evals module will cover five evaluation areas:
When released, `semantica.evals` will provide:
| Area | What It Measures |
| ---- | ---------------- |
| **KG Quality** | Completeness, consistency, schema compliance, coverage metrics |
| **Extraction Accuracy** | NER precision / recall / F1, relation extraction metrics |
| **Pipeline Performance** | Throughput (docs/sec), latency per step, error rates |
| **Deduplication** | Merge accuracy, false positive / negative rates |
| **Reasoning** | Inference correctness, rule coverage, derivation depth |
- **KG quality metrics** — completeness, consistency, schema compliance, coverage, and orphan node detection
- **Extraction accuracy** — NER precision / recall / F1 and relation extraction metrics against gold-standard datasets
- **Pipeline benchmarking** — throughput (docs/sec), per-step latency, peak memory, and error rate
- **Regression tracking** — record runs and compare metrics across commits or config changes
- **Deduplication accuracy** — merge precision, false positive / false negative rates
- **Reasoning correctness** — inference accuracy, rule coverage, and derivation depth
## Scope
## Current Workaround
- **Offline evaluation** — compare against gold-standard annotated datasets
- **Regression tracking** — compare pipeline runs across commits or config changes
- **Live monitoring** — record quality metrics during production pipeline runs
- **Benchmark suites** — standard NER, RE, and KG construction benchmarks
Until `semantica.evals` ships, use `semantica.ontology.OntologyEvaluator` for ontology quality metrics:
```python
from semantica.ontology import OntologyEvaluator
evaluator = OntologyEvaluator()
report = evaluator.evaluate(ontology, kg)
print(f"Coverage: {report.coverage:.2%}")
print(f"Completeness: {report.completeness:.2%}")
```
<CardGroup cols={2}>
<Card title="Semantic Extract" icon="magnifying-glass" href="semantic_extract">
Extraction module to evaluate.
Extraction module.
</Card>
<Card title="Knowledge Graph" icon="diagram-project" href="kg">
Graph quality assessment.
@@ -39,7 +44,7 @@ The Evals module will cover five evaluation areas:
<Card title="Pipeline" icon="gear" href="pipeline">
Pipeline performance metrics.
</Card>
<Card title="Deduplication" icon="copy" href="deduplication">
Deduplication accuracy evaluation.
<Card title="Ontology Evaluator" icon="sitemap" href="ontology">
Available now for ontology quality metrics.
</Card>
</CardGroup>
+256 -79
View File
@@ -6,13 +6,45 @@ icon: "map"
`semantica.explorer` is a browser-based dashboard for exploring knowledge graphs, managing ontologies, and running visual analyses — no code required after launch.
## Launch Interface
```bash
# Install and launch
pip install semantica[explorer]
# Start the Explorer dashboard
semantica-explorer --graph my_graph.json --port 8000
# Or via Python module
python -m semantica.explorer --graph my_graph.json --port 8000 --host 0.0.0.0
```
<Tip>
`semantica.explorer` is a **server process**, not a Python library. It exposes no importable classes. Use the CLI or `python -m semantica.explorer` to launch.
</Tip>
## What You Get
- **Graph Explorer** — interactive node/edge search, filtering, path highlighting, and neighborhood views
- **Ontology Hub** (v0.5.0) — browse class hierarchies, infer types, run SHACL validation, align ontologies
- **Distance Intelligence** (v0.5.0) — semantic similarity search, ego-mode neighborhood views, distance heatmaps
- **REST API** — 15+ endpoints for graph data, path finding, embeddings, and semantic search
- **CLI launcher** — `semantica-explorer` command for quick local startup
<CardGroup cols={2}>
<Card title="Graph Explorer" icon="diagram-project">
Interactive node/edge search, filtering, path highlighting, and neighborhood expansion. Indexed search at 0.004ms on 118k-node graphs.
</Card>
<Card title="Ontology Hub (v0.5.0)" icon="sitemap">
Visual ontology editor, SHACL Studio, alignment authoring, health dashboard, and version control — all in the browser.
</Card>
<Card title="Distance Intelligence (v0.5.0)" icon="circle-nodes">
Semantic similarity search, ego-mode neighborhood views, N×N distance heatmaps, and distance band classification.
</Card>
<Card title="REST API" icon="code">
15+ endpoints for graph data, path finding, embeddings, semantic search, analytics, and export — fully documented at `/docs`.
</Card>
<Card title="WebSocket Progress" icon="bolt">
Long-running exports and analyses stream progress events in real time — no polling required.
</Card>
<Card title="CLI Launcher" icon="terminal">
`semantica-explorer --graph my_graph.json` for instant local startup without writing any Python.
</Card>
</CardGroup>
## Installation
@@ -24,103 +56,248 @@ Requires `uvicorn` and `fastapi`. Included automatically with `pip install seman
## Launch
<CodeGroup>
<Steps>
<Step title="Save your graph and launch Explorer">
```python
import json
from semantica.kg import GraphBuilder
```bash CLI
# Start the explorer on a saved graph
semantica-explorer --graph my_graph.json
kg = GraphBuilder().build(entities=entities, relationships=relationships)
# Custom host and port
semantica-explorer --graph my_graph.json --host 0.0.0.0 --port 8080
# Export graph to JSON file
with open("my_graph.json", "w") as f:
json.dump({"entities": kg.entities, "relationships": kg.relationships}, f)
```
# Skip auto-opening the browser
semantica-explorer --graph my_graph.json --no-browser
```
```bash
semantica-explorer --graph my_graph.json
# → Serving at http://127.0.0.1:8000
```
</Step>
<Step title="Custom host and port">
```bash
semantica-explorer --graph my_graph.json --host 0.0.0.0 --port 8080
```python Python
from semantica.context import ContextGraph
graph = ContextGraph(advanced_analytics=True)
# ... build or load your graph ...
graph.save_to_file("my_graph.json")
import subprocess
subprocess.run(["semantica-explorer", "--graph", "my_graph.json", "--port", "8000"])
```
```python Module
# Run directly as a Python module
import subprocess
subprocess.run(["python", "-m", "semantica.explorer", "--graph", "my_graph.json"])
```
</CodeGroup>
# Skip auto-opening the browser
semantica-explorer --graph my_graph.json --no-browser
```
</Step>
<Step title="Switch graphs without restarting">
```bash
curl -X POST http://localhost:8000/api/import \
-H "Content-Type: multipart/form-data" \
-F "file=@updated_graph.json"
# Browser dashboard reloads automatically
```
</Step>
</Steps>
## CLI Reference
| Flag | Default | Description |
| ---- | ------- | ----------- |
| `--graph`, `-g` | *(required)* | Path to a ContextGraph JSON file |
| `--graph`, `-g` | *(required)* | Path to a saved graph JSON file |
| `--port`, `-p` | `8000` | Port to bind the server |
| `--host` | `127.0.0.1` | Host to bind the server |
| `--no-browser` | `false` | Skip auto-opening the browser |
| `--enable-auth` | `false` | Require `X-API-Key` header on all requests |
| `--api-key` | `None` | API key value when `--enable-auth` is set |
| `--cors-origins` | `"*"` | Comma-separated list of allowed CORS origins |
| `--log-level` | `"info"` | Uvicorn log level |
## Features
### Graph Explorer
<Tabs>
<Tab title="Graph Explorer">
Core dashboard for navigating knowledge graphs:
Core dashboard for navigating knowledge graphs:
- **Indexed search** — find any node by label or type; 0.004ms on 118k-node graphs (v0.5.0)
- **Bidirectional path finding** — trace paths between any two nodes
- **Neighbor expansion** — click any node to expand its connections
- **Filter by entity type** — focus on Person, Organization, Event, or any custom type
- **Edge label display** — relationship types shown on all edges
- **Graph declutter** — workspace layout controls for dense graphs
</Tab>
<Tab title="Ontology Hub (v0.5.0)">
Full ontology lifecycle management in the browser:
- **Indexed search** — find any node by label or type; 0.004ms on 118k-node graphs (v0.5.0)
- **Bidirectional path finding** — trace paths between any two nodes
- **Neighbor expansion** — click any node to expand its connections
- **Filter by entity type** — focus on Person, Organization, Event, or any custom type
- **Edge label display** — relationship types shown on all edges
- **Graph declutter** — workspace layout controls for dense graphs
- **Visual ontology editor** — drag-and-drop class and property authoring
- **SHACL Studio** — create, validate, and test SHACL shapes with live feedback
- **Alignment authoring** — author ontology alignments across schemas
- **Health dashboard** — graph quality metrics, validation status, coverage reports
- **Version control** — snapshot, diff, and restore ontology versions
</Tab>
<Tab title="Distance Intelligence (v0.5.0)">
Semantic neighborhood analysis centered on any node:
### Ontology Hub (v0.5.0)
- **N×N distance matrices** — pairwise semantic distances across a set of nodes
- **Ego-mode visualization** — focus on a single node's semantic neighborhood
- **Distance band classification** — nodes grouped as `near` / `mid` / `far`
- **Embedding cache** — optimized embedding reuse for large graphs
</Tab>
<Tab title="Session Management">
Thread-safe sessions with rollback protection:
Full ontology lifecycle management in the browser:
- **Visual ontology editor** — drag-and-drop class and property authoring
- **SHACL Studio** — create, validate, and test SHACL shapes with live feedback
- **Alignment authoring** — author ontology alignments across schemas
- **Health dashboard** — graph quality metrics, validation status, coverage reports
- **Version control** — snapshot, diff, and restore ontology versions
### Distance Intelligence (v0.5.0)
Semantic neighborhood analysis centered on any node:
- **N×N distance matrices** — pairwise semantic distances across a set of nodes
- **Ego-mode visualization** — focus on a single node's semantic neighborhood
- **Distance band classification** — nodes grouped as `near` / `mid` / `far`
- **Embedding cache** — optimized embedding reuse for large graphs
### Knowledge Explorer API
Full FastAPI backend accessible at `http://localhost:8000/docs`:
- 12+ export formats (RDF, Parquet, AQL, JSON-LD, and more)
- WebSocket progress streaming for long operations
- Thread-safe sessions with rollback protection
- Audit trail for all operations
- Sessions are per connected browser tab
- Write operations (annotate, import) roll back automatically on failure
- All writes appended to audit trail at `/api/provenance/audit`
- Session state held in memory — use `/api/export/json` to persist between restarts
</Tab>
</Tabs>
## API Endpoints
The FastAPI server exposes a REST API alongside the browser dashboard:
Full interactive docs at `http://localhost:8000/docs`. All endpoints available via REST.
| Endpoint | Method | Description |
| -------- | ------ | ----------- |
| `/api/graph/summary` | `GET` | Node count, edge count, entity types |
| `/api/graph/search` | `GET` | Full-text and type-filtered node search |
| `/api/graph/path` | `GET` | Bidirectional path between two nodes |
| `/api/graph/neighbors` | `GET` | Neighbors of a node with optional depth |
| `/api/ontology/validate` | `POST` | Run SHACL validation on the graph |
| `/api/export/{format}` | `GET` | Export graph in specified format |
| `/ws/progress` | `WS` | WebSocket stream for operation progress |
<AccordionGroup>
<Accordion title="Graph endpoints">
Full OpenAPI docs available at `http://localhost:8000/docs` when the server is running.
| Endpoint | Method | Description |
| -------- | ------ | ----------- |
| `/api/graph/summary` | `GET` | Node count, edge count, entity type distribution |
| `/api/graph/search` | `GET` | Indexed full-text and type-filtered node search |
| `/api/graph/node/{id}` | `GET` | Fetch a single node with all properties |
| `/api/graph/neighbors` | `GET` | Neighbors of a node — `?node_id=&depth=2` |
| `/api/graph/path` | `GET` | Bidirectional shortest path — `?source=&target=` |
| `/api/graph/subgraph` | `POST` | Extract a subgraph by node IDs or type filter |
| `/api/graph/annotate` | `POST` | Add a user annotation to a node or edge |
| `/api/graph/annotations` | `GET` | List all annotations on the graph |
</Accordion>
<Accordion title="Ontology endpoints">
| Endpoint | Method | Description |
| -------- | ------ | ----------- |
| `/api/ontology/classes` | `GET` | List all ontology classes and properties |
| `/api/ontology/validate` | `POST` | Run SHACL validation; returns violations |
| `/api/ontology/hierarchy` | `GET` | Class hierarchy as a tree structure |
| `/api/ontology/vocabulary` | `GET` | SKOS vocabulary terms and alt labels |
| `/api/ontology/align` | `POST` | Submit two ontologies for alignment |
| `/api/ontology/diff` | `POST` | Diff two ontology versions |
</Accordion>
<Accordion title="Provenance, Decisions & Analytics">
**Provenance:**
| Endpoint | Method | Description |
| -------- | ------ | ----------- |
| `/api/provenance/entity/{id}` | `GET` | Full provenance lineage for an entity |
| `/api/provenance/source/{id}` | `GET` | All entities sourced from a document |
| `/api/provenance/audit` | `GET` | Full audit trail of Explorer operations |
**Decisions:**
| Endpoint | Method | Description |
| -------- | ------ | ----------- |
| `/api/decisions/list` | `GET` | Paginated list of recorded decisions |
| `/api/decisions/{id}` | `GET` | Single decision with causal chain |
| `/api/decisions/search` | `GET` | Precedent search — `?query=&limit=5` |
| `/api/decisions/influence/{id}` | `GET` | Downstream influence of a decision |
**Analytics:**
| Endpoint | Method | Description |
| -------- | ------ | ----------- |
| `/api/analytics/centrality` | `GET` | Degree, betweenness, PageRank scores |
| `/api/analytics/communities` | `GET` | Community detection result |
| `/api/analytics/distance` | `POST` | N×N distance matrix for a node list |
| `/api/analytics/neighborhood` | `GET` | Semantic neighborhood — `?node=&radius=0.4` |
</Accordion>
<Accordion title="SPARQL, Temporal & Export">
**SPARQL & Temporal:**
| Endpoint | Method | Description |
| -------- | ------ | ----------- |
| `/api/sparql` | `POST` | Execute a SPARQL SELECT query |
| `/api/temporal/snapshot` | `GET` | Graph snapshot at a point in time — `?at=ISO8601` |
| `/api/temporal/range` | `GET` | Nodes/edges active in a time range |
| `/api/temporal/diff` | `POST` | Diff two temporal snapshots |
**Export & Import:**
| Endpoint | Method | Description |
| -------- | ------ | ----------- |
| `/api/export/{format}` | `GET` | Export in: `turtle`, `json-ld`, `ntriples`, `rdf-xml`, `parquet`, `aql`, `csv`, `owl`, `arrow`, `lpg`, `yaml`, `distance-matrix` |
| `/api/import` | `POST` | Import a graph from file (replaces current graph in session) |
</Accordion>
</AccordionGroup>
## WebSocket Progress
Long-running operations stream progress events over WebSocket at `ws://localhost:8000/ws/progress`:
```python
import asyncio, websockets, json
async def watch_progress():
async with websockets.connect("ws://localhost:8000/ws/progress") as ws:
async for message in ws:
event = json.loads(message)
print(f"[{event['operation']}] {event['step']} — {event['progress_pct']:.0f}%")
if event["status"] in ("completed", "failed"):
break
asyncio.run(watch_progress())
```
WebSocket event schema:
```json
{
"operation": "export",
"step": "serializing nodes",
"current": 3500,
"total": 10000,
"progress_pct": 35.0,
"status": "running",
"message": "Serializing 10000 nodes to Turtle...",
"error": null
}
```
`status` values: `"running"` | `"completed"` | `"failed"` | `"cancelled"`
## Performance
| Scenario | Latency |
| -------- | ------- |
| Node search (118k nodes, indexed) | 0.004ms |
| Neighbor expansion (depth 2) | < 5ms |
| Bidirectional path (118k nodes) | < 50ms |
| SPARQL SELECT (simple pattern) | < 20ms |
| N×N distance matrix (100 nodes) | ~2s (with embedding cache) |
The node search index is built on startup. For graphs > 500k nodes, allow extra startup time before connecting.
## Tips and Common Pitfalls
<Warning>
**Filter large graphs before saving to JSON.** The CLI loads the entire JSON file into memory. For graphs > 10k nodes, filter to the relevant subgraph (e.g., by entity type) before exporting to JSON — Explorer's force-directed layout becomes unusable on very large graphs.
</Warning>
<Warning>
**Use authentication in shared environments.** Pass `enable_auth=True, api_key="..."` whenever Explorer is accessible to more than one person. Without auth, anyone who can reach the port can write to the graph via the annotate and import endpoints.
</Warning>
<Warning>
**Export before the session ends.** Session state lives in memory and is lost on server restart. Call `/api/export/json` or `/api/export/turtle` to persist the current state before shutting down. Explorer does not auto-save.
</Warning>
<Tip>
**Use WebSocket progress for long operations.** Export, analysis, and large SPARQL queries stream progress to `ws://localhost:8000/ws/progress`. Polling the REST endpoints instead gives no progress signal — use the WebSocket client so users see incremental updates.
</Tip>
<Tip>
**Pass `session_timeout` for demos and shared notebooks.** The default session never expires. In Jupyter or shared environments, set `session_timeout=1800` (30 minutes) so stale sessions don't hold large graphs in memory.
</Tip>
<Tip>
**Use the REST API for automation, Explorer UI for exploration.** Explorer's REST endpoints are a stable programmatic API — pipe them into scripts to automate batch annotation, SPARQL querying, or exports. The browser UI is for interactive exploration and sharing; they use the same server.
</Tip>
<CardGroup cols={2}>
<Card title="Context" icon="brain" href="context">
@@ -129,7 +306,7 @@ Full OpenAPI docs available at `http://localhost:8000/docs` when the server is r
<Card title="Ontology" icon="sitemap" href="ontology">
Programmatic ontology management and SHACL generation.
</Card>
<Card title="Visualization" icon="chart-network" href="visualization">
<Card title="Visualization" icon="chart-bar" href="visualization">
Programmatic graph rendering without the Explorer server.
</Card>
<Card title="Export" icon="file-export" href="export">
+318 -181
View File
@@ -6,193 +6,292 @@ icon: "file-export"
`semantica.export` serializes knowledge graphs to every downstream format — semantic web standards, analytics pipelines, graph databases, and vector stores. All exporters share a consistent `export(graph, path, format)` interface.
## Exported Classes
```python
from semantica.export import (
RDFExporter, # Turtle, JSON-LD, N-Triples, RDF/XML
ParquetExporter, # columnar Parquet (Spark, BigQuery, Databricks, Snowflake)
LPGExporter, # Cypher CREATE/MERGE for Neo4j / Memgraph
ArangoAQLExporter, # AQL INSERT for ArangoDB
GraphExporter, # GraphML, GEXF, Graphviz DOT
OWLExporter, # OWL 2.0 in Turtle, XML, JSON-LD
CSVExporter, # flat CSV nodes + edges
VectorExporter, # embedding vectors as JSON, NumPy, or FAISS
ArrowExporter, # Apache Arrow IPC (zero-copy transfer)
DistanceExporter, # semantic distance matrices and ego-graphs
ReportGenerator, # human-readable analytics reports (HTML, Markdown, JSON)
NamespaceManager, # register and resolve RDF namespace prefixes
SemanticNetworkYAMLExporter, # YAML semantic network export
# Convenience functions
export_rdf, export_parquet, export_csv, export_lpg,
export_arango, export_graph, export_owl, export_vector,
export_arrow, generate_report,
)
```
## What You Get
- **`RDFExporter`** — Turtle, JSON-LD, N-Triples, RDF/XML with namespace management
- **`ParquetExporter`** — columnar storage for Spark, BigQuery, Databricks, Snowflake
- **`LPGExporter`** — Cypher CREATE/MERGE statements for Neo4j and Memgraph
- **`ArangoAQLExporter`** — AQL INSERT statements for ArangoDB multi-model graphs
- **`GraphExporter`** — GraphML, GEXF, DOT for visualization tools like Gephi
- **`OWLExporter`** — OWL 2.0 ontology export in Turtle, XML, and JSON-LD
- **`CSVExporter`**, **`VectorExporter`**, **`ArrowExporter`**, **`DistanceExporter`**, **`ReportGenerator`**
<CardGroup cols={2}>
<Card title="RDFExporter" icon="diagram-project">
Turtle, JSON-LD, N-Triples, RDF/XML with namespace management and optional PROV-O provenance embedding.
</Card>
<Card title="ParquetExporter" icon="layer-group">
Columnar storage for Spark, BigQuery, Databricks, and Snowflake with explicit PyArrow typing.
</Card>
<Card title="LPG & ArangoDB" icon="server">
Cypher CREATE/MERGE for Neo4j and Memgraph; AQL INSERT for ArangoDB vertex and edge collections.
</Card>
<Card title="Graph Formats" icon="chart-bar">
GraphML, GEXF, DOT for Gephi and Graphviz. OWL 2.0 ontology export in Turtle, XML, and JSON-LD.
</Card>
<Card title="Vector & Arrow" icon="vector-square">
JSON, NumPy `.npy`, and FAISS index export for embedding vectors. Apache Arrow IPC for zero-copy transfer.
</Card>
<Card title="Distance & Reports" icon="chart-line">
Distance matrix CSV/JSON from Distance Intelligence (v0.5.0). HTML, Markdown, and JSON analytics reports.
</Card>
</CardGroup>
## RDFExporter
## Quick Start
<Steps>
<Step title="Choose your format and instantiate an exporter">
```python
from semantica.export import RDFExporter
exporter = RDFExporter()
```
</Step>
<Step title="Export the graph">
```python
# Export to RDF string, then write to file
rdf_str = exporter.export_to_rdf(graph, format="turtle")
with open("output.ttl", "w") as f:
f.write(rdf_str)
```
</Step>
<Step title="Use convenience functions for one-liners">
```python
from semantica.export import export_rdf, export_parquet, export_csv
export_rdf(graph, "output.ttl", format="turtle")
export_parquet(graph, "output/", compression="snappy")
export_csv(graph, "nodes.csv", target="nodes")
```
</Step>
<Step title="Stream large graphs to avoid OOM">
```python
from semantica.export import ParquetExporter
exporter = ParquetExporter(compression="snappy")
exporter.export_stream(graph, output_dir="output/", batch_size=10_000)
```
</Step>
</Steps>
## Exporters
<Tabs>
<Tab title="RDF">
Export to W3C RDF formats — Turtle, JSON-LD, N-Triples, and RDF/XML:
```python
from semantica.export import RDFExporter
exporter = RDFExporter()
# Export to RDF string — write to file manually
rdf_str = exporter.export_to_rdf(graph, format="turtle") # Turtle (most readable)
rdf_str = exporter.export_to_rdf(graph, format="json-ld") # JSON-LD (APIs, Linked Data)
rdf_str = exporter.export_to_rdf(graph, format="nt") # N-Triples (streaming-friendly)
rdf_str = exporter.export_to_rdf(graph, format="xml") # RDF/XML (W3C standard)
with open("output.ttl", "w") as f:
f.write(exporter.export_to_rdf(graph, format="turtle"))
```
**Custom namespace management:**
```python
from semantica.export import NamespaceManager, RDFExporter
ns_manager = NamespaceManager()
ns_manager.register("ex", "http://example.org/")
ns_manager.register("schema", "https://schema.org/")
exporter = RDFExporter(namespace_manager=ns_manager)
```
**Export with PROV-O provenance:**
```python
from semantica.export import RDFExporter
from semantica.provenance import ProvenanceManager
provenance = ProvenanceManager()
# ... track entities during extraction ...
exporter = RDFExporter(include_provenance=True, provenance_manager=provenance)
exporter.export_to_file(graph, "output_with_prov.ttl", format="turtle")
# → Each entity's prov:wasGeneratedBy, prov:wasDerivedFrom, prov:hadPrimarySource
# triples are included alongside the entity data triples
```
</Tab>
<Tab title="Columnar & Analytics">
Columnar formats for analytics pipelines and human-readable export:
```python
from semantica.export import ParquetExporter
exporter = ParquetExporter(compression="snappy")
# compression options: snappy | gzip | brotli | zstd | lz4
# Export nodes and edges as separate Parquet files
exporter.export_nodes(graph, "nodes.parquet")
exporter.export_edges(graph, "edges.parquet")
# Export full graph partitioned by node type
exporter.export(graph, output_dir="graph_parquet/", partition_by="node_type")
```
Schema is explicitly typed with PyArrow for clean Spark/BigQuery ingestion.
```python
from semantica.export import CSVExporter
exporter = CSVExporter(delimiter=",")
exporter.export_nodes(graph, "nodes.csv")
exporter.export_edges(graph, "edges.csv")
```
```python
from semantica.export import SemanticNetworkYAMLExporter
exporter = SemanticNetworkYAMLExporter()
exporter.export(graph, "graph.yaml")
yaml_str = exporter.to_string(graph)
```
</Tab>
<Tab title="Graph DB Import">
Export Cypher or AQL statements for direct graph database import:
```python
from semantica.export import LPGExporter
exporter = LPGExporter()
# CREATE statements
cypher = exporter.to_cypher(graph)
exporter.export(graph, "import.cypher", format="cypher")
# MERGE statements (idempotent — safe to re-run)
cypher_merge = exporter.to_cypher(graph, use_merge=True)
```
```python
from semantica.export import ArangoAQLExporter
exporter = ArangoAQLExporter(
vertex_collection="entities",
edge_collection="relationships"
)
aql = exporter.export(graph) # returns AQL string
exporter.export_to_file(graph, "import.aql")
```
</Tab>
<Tab title="Visualization">
Export for graph visualization tools and OWL ontology distribution:
```python
from semantica.export import GraphExporter
exporter = GraphExporter()
exporter.export(graph, "graph.graphml", format="graphml") # Gephi, yEd
exporter.export(graph, "graph.gexf", format="gexf") # Gephi streaming
exporter.export(graph, "graph.dot", format="dot") # Graphviz
```
```python
from semantica.export import OWLExporter
exporter = OWLExporter()
exporter.export(ontology, path="ontology.ttl", format="turtle")
exporter.export(ontology, path="ontology.owl", format="xml")
exporter.export(ontology, path="ontology.json", format="json-ld")
```
</Tab>
<Tab title="Specialized">
Vector embeddings, Arrow IPC, distance matrices, and analytics reports:
```python
from semantica.export import VectorExporter
exporter = VectorExporter()
exporter.export(embeddings, metadata, "vectors.json", format="json")
exporter.export(embeddings, metadata, "vectors.npy", format="numpy")
exporter.export(embeddings, metadata, "vectors.faiss", format="faiss")
```
```python
from semantica.export import ArrowExporter
exporter = ArrowExporter()
exporter.export(graph, "graph.arrow") # requires pyarrow
```
```python
from semantica.export import DistanceExporter
exporter = DistanceExporter()
exporter.export_matrix(distance_matrix, node_labels, "distances.csv")
exporter.export_ego(ego_neighborhood, center_node="Apple Inc.", path="ego.json")
```
```python
from semantica.export import ReportGenerator
generator = ReportGenerator()
generator.generate(graph, analytics_result, "report.html", format="html")
generator.generate(graph, analytics_result, "report.md", format="markdown")
generator.generate(graph, analytics_result, "report.json", format="json")
```
</Tab>
</Tabs>
## Streaming Export
For graphs too large to hold in memory, use streaming export — writes incrementally without buffering the full graph:
```python
from semantica.export import RDFExporter
from semantica.export import RDFExporter, ParquetExporter
# Stream RDF — yields triples one at a time, no full-graph buffer
exporter = RDFExporter()
with exporter.stream(graph, format="turtle") as stream:
for triple_line in stream:
output_file.write(triple_line)
# Turtle (most readable RDF format)
exporter.export_to_file(graph, "output.ttl", format="turtle")
# JSON-LD (best for APIs and Linked Data)
exporter.export_to_file(graph, "output.jsonld", format="json-ld")
# N-Triples (streaming-friendly, one triple per line)
exporter.export_to_file(graph, "output.nt", format="nt")
# RDF/XML (W3C standard, broadest compatibility)
exporter.export_to_file(graph, "output.xml", format="xml")
# Export to string instead of file
rdf_str = exporter.export_to_rdf(graph, format="turtle")
```
Custom namespace management:
```python
from semantica.export import NamespaceManager, RDFExporter
ns_manager = NamespaceManager()
ns_manager.register("ex", "http://example.org/")
ns_manager.register("schema", "https://schema.org/")
exporter = RDFExporter(namespace_manager=ns_manager)
```
## ParquetExporter
Columnar export for Spark, BigQuery, Databricks, and Snowflake analytics pipelines:
```python
from semantica.export import ParquetExporter
# Stream Parquet — writes row groups incrementally
exporter = ParquetExporter(compression="snappy")
# compression options: snappy | gzip | brotli | zstd | lz4
# Export nodes and edges as separate Parquet files
exporter.export_nodes(graph, "nodes.parquet")
exporter.export_edges(graph, "edges.parquet")
# Export full graph partitioned by node type
exporter.export(graph, output_dir="graph_parquet/", partition_by="node_type")
exporter.export_stream(graph, output_dir="output/", batch_size=10_000)
```
Schema is explicitly typed with PyArrow for clean Spark/BigQuery ingestion.
Streaming is recommended for graphs with > 500k nodes.
## LPGExporter
Labeled Property Graph export — Cypher statements for Neo4j and Memgraph:
## Selective Export
```python
from semantica.export import LPGExporter
# Export a subgraph
subgraph = graph.subgraph(node_ids=["apple_inc", "steve_jobs"])
export_rdf(subgraph, "subgraph.ttl", format="turtle")
exporter = LPGExporter()
# CREATE statements
cypher = exporter.to_cypher(graph)
exporter.export(graph, "import.cypher", format="cypher")
# MERGE statements (idempotent — safe to re-run)
cypher_merge = exporter.to_cypher(graph, use_merge=True)
```
## ArangoAQLExporter
AQL INSERT statements for ArangoDB vertex and edge collections:
```python
from semantica.export import ArangoAQLExporter
exporter = ArangoAQLExporter(
vertex_collection="entities",
edge_collection="relationships"
)
aql = exporter.export(graph) # returns AQL string
exporter.export_to_file(graph, "import.aql")
```
## GraphExporter
Export for visualization tools — GraphML, GEXF, and Graphviz DOT:
```python
from semantica.export import GraphExporter
exporter = GraphExporter()
exporter.export(graph, "graph.graphml", format="graphml") # Gephi, yEd
exporter.export(graph, "graph.gexf", format="gexf") # Gephi streaming
exporter.export(graph, "graph.dot", format="dot") # Graphviz
```
## OWLExporter
OWL 2.0 ontology export in three serialization formats:
```python
from semantica.export import OWLExporter
exporter = OWLExporter()
exporter.export(ontology, path="ontology.ttl", format="turtle")
exporter.export(ontology, path="ontology.owl", format="xml")
exporter.export(ontology, path="ontology.json", format="json-ld")
```
## CSVExporter
Flat CSV export for spreadsheets and simple data pipelines:
```python
from semantica.export import CSVExporter
exporter = CSVExporter(delimiter=",")
exporter.export_nodes(graph, "nodes.csv")
exporter.export_edges(graph, "edges.csv")
```
## VectorExporter
Export embedding vectors for use in external vector stores:
```python
from semantica.export import VectorExporter
exporter = VectorExporter()
exporter.export(embeddings, metadata, "vectors.json", format="json")
exporter.export(embeddings, metadata, "vectors.npy", format="numpy")
exporter.export(embeddings, metadata, "vectors.faiss", format="faiss")
```
## ArrowExporter
Apache Arrow IPC format for zero-copy inter-process transfer:
```python
from semantica.export import ArrowExporter
exporter = ArrowExporter()
exporter.export(graph, "graph.arrow")
```
Requires `pyarrow`. Falls back gracefully if not installed.
## DistanceExporter
Export semantic distance matrices produced by Distance Intelligence (v0.5.0):
```python
from semantica.export import DistanceExporter
exporter = DistanceExporter()
exporter.export_matrix(distance_matrix, node_labels, "distances.csv")
exporter.export_ego(ego_neighborhood, center_node="Apple Inc.", path="ego.json")
```
## ReportGenerator
Generate human-readable analytics reports from graph metrics:
```python
from semantica.export import ReportGenerator
generator = ReportGenerator()
generator.generate(graph, analytics_result, "report.html", format="html")
generator.generate(graph, analytics_result, "report.md", format="markdown")
generator.generate(graph, analytics_result, "report.json", format="json")
# Export nodes by type
org_nodes = graph.filter(node_type="Organization")
export_parquet(org_nodes, "organizations.parquet")
```
## Convenience Functions
@@ -212,17 +311,55 @@ export_arango(graph, "import.aql")
export_graph(graph, "graph.graphml", format="graphml")
```
## Selective Export
## Format Reference
```python
# Export a subgraph
subgraph = graph.subgraph(node_ids=["apple_inc", "steve_jobs"])
export_rdf(subgraph, "subgraph.ttl", format="turtle")
| Format | Exporter | Output | Best For |
| ------ | -------- | ------ | -------- |
| `turtle` | `RDFExporter` | `.ttl` | Readable RDF, ontology sharing |
| `json-ld` | `RDFExporter` | `.jsonld` | APIs, Linked Data, JSON pipelines |
| `nt` | `RDFExporter` | `.nt` | Streaming RDF, line-by-line processing |
| `xml` | `RDFExporter` | `.xml` | W3C RDF/XML, broadest compatibility |
| `parquet` | `ParquetExporter` | `.parquet` | Spark, BigQuery, Databricks, Snowflake |
| `cypher` | `LPGExporter` | `.cypher` | Neo4j, Memgraph import |
| `aql` | `ArangoAQLExporter` | `.aql` | ArangoDB vertex + edge collections |
| `graphml` | `GraphExporter` | `.graphml` | Gephi, yEd visualization |
| `gexf` | `GraphExporter` | `.gexf` | Gephi streaming format |
| `dot` | `GraphExporter` | `.dot` | Graphviz rendering |
| `owl` | `OWLExporter` | `.owl` / `.ttl` | OWL 2.0 ontology distribution |
| `csv` | `CSVExporter` | `.csv` | Spreadsheets, simple pipelines |
| `yaml` | `SemanticNetworkYAMLExporter` | `.yaml` | Human-readable, config-driven use |
| `arrow` | `ArrowExporter` | `.arrow` | Zero-copy inter-process transfer |
| `numpy` | `VectorExporter` | `.npy` | NumPy arrays from embeddings |
| `faiss` | `VectorExporter` | `.faiss` | Direct FAISS index files |
| `distance-matrix` | `DistanceExporter` | `.csv` / `.json` | Distance Intelligence matrices |
| `html` | `ReportGenerator` | `.html` | Human-readable analytics reports |
| `markdown` | `ReportGenerator` | `.md` | Documentation, GitHub |
# Export nodes by type
org_nodes = graph.filter(node_type="Organization")
export_parquet(org_nodes, "organizations.parquet")
```
## Tips and Common Pitfalls
<Tip>
**Use `turtle` for human readability, `nt` for streaming.** Turtle is compact and readable for debugging and sharing ontologies. N-Triples (`.nt`) is line-oriented — one triple per line — making it safe to stream, concatenate, and process with standard Unix tools without loading the full file.
</Tip>
<Tip>
**Use `ParquetExporter` for downstream analytics.** Parquet preserves column types (int, float, datetime) that CSV loses and is natively supported by Spark, BigQuery, Databricks, and Snowflake. Use `compression="snappy"` for a good balance of speed and compression ratio.
</Tip>
<Warning>
**Stream large graphs with `export_stream()`.** For graphs with more than 500k nodes, use `exporter.export_stream(graph, ...)` instead of building the full RDF string in memory. Streaming writes incrementally — without it, a million-node export will likely OOM.
</Warning>
<Tip>
**Include provenance for compliance exports.** For HIPAA, SOX, or FDA 21 CFR Part 11 exports, pass `include_provenance=True` to `RDFExporter`. This embeds W3C PROV-O lineage triples inline — auditors can verify every fact's source from a single file rather than cross-referencing separate systems.
</Tip>
<Tip>
**Use selective export to reduce file size.** `graph.subgraph(node_ids=[...])` and `graph.filter(node_type="Organization")` let you export only the relevant subset. Full graph exports for compliance reports include noise; scoped exports are faster to produce, review, and transfer.
</Tip>
<Warning>
**Match your export format to your consumer.** Neo4j → `cypher`; ArangoDB → `aql`; Gephi/yEd → `graphml` or `gexf`; semantic web tools → `turtle` or `json-ld`; analytics pipelines → `parquet`; zero-copy IPC → `arrow`. Using the wrong format forces the consumer to convert it, adding latency and potential data loss.
</Warning>
<CardGroup cols={2}>
<Card title="Triplet Store" icon="table" href="triplet_store">
+253 -109
View File
@@ -6,121 +6,184 @@ icon: "server"
`semantica.graph_store` provides a single API for persisting and querying knowledge graphs in production graph databases. Swap backends with a one-line change — no application code changes needed.
## What You Get
- **`GraphStore`** — unified interface across all backends
- **Backends** — Neo4j, FalkorDB, Apache AGE (PostgreSQL), Amazon Neptune, NetworkX (in-memory)
- **Cypher queries** — full Cypher support for Neo4j and FalkorDB
- **Bulk operations** — batched node and edge loading with configurable batch sizes
- **Schema management** — create indexes and uniqueness constraints
- **Path traversal** — find paths between nodes with hop limits and relationship type filters
## Basic Usage
## Exported Classes
```python
from semantica.graph_store import GraphStore
store = GraphStore(
backend="neo4j",
uri="bolt://localhost:7687",
user="neo4j",
password="password"
from semantica.graph_store import (
# Core interface
GraphStore, # unified interface: add_node, add_edge, query, find_paths
GraphManager, # store management and operations
NodeManager, # node CRUD operations
RelationshipManager, # relationship CRUD operations
QueryEngine, # Cypher query execution with caching
GraphAnalytics, # centrality, community detection, shortest path
# Backend stores
Neo4jStore, # Neo4j via Bolt — production workloads
ApacheAgeStore, # PostgreSQL + AGE extension
AmazonNeptuneStore, # AWS Neptune — SPARQL/Gremlin/openCypher
FalkorDBStore, # Redis-based — ultra-low latency
# Convenience functions
create_node, # create_node(labels, properties)
create_nodes, # bulk: create_nodes(entities)
create_relationship, # create_relationship(start_id, end_id, rel_type)
create_relationships, # bulk: create_relationships(rels)
get_nodes, # get_nodes(labels, filters)
get_relationships, # get_relationships(start_id, rel_type)
get_neighbors, # get_neighbors(node_id, direction="both")
update_node, # update_node(node_id, properties)
delete_node, # delete_node(node_id)
execute_query, # execute_query(cypher, parameters)
shortest_path, # shortest_path(source, target)
run_analytics, # run_analytics(graph, algorithm)
)
store.add_nodes(entities)
store.add_edges(relationships)
results = store.query("MATCH (n)-[r]->(m) RETURN n, r, m LIMIT 10")
```
## What You Get
<CardGroup cols={2}>
<Card title="GraphStore" icon="server">
Unified interface across Neo4j, FalkorDB, Apache AGE, Amazon Neptune, and NetworkX.
</Card>
<Card title="QueryEngine" icon="magnifying-glass">
Parameterized Cypher construction, query optimization, and result caching.
</Card>
<Card title="GraphAnalytics" icon="chart-line">
Centrality, community detection, and path algorithms running directly against the backend.
</Card>
<Card title="Bulk Operations" icon="layer-group">
Batched node and edge loading with configurable batch sizes — 10100× faster than individual writes.
</Card>
<Card title="Schema Management" icon="table">
Create indexes and uniqueness constraints to optimize query performance.
</Card>
<Card title="Path Traversal" icon="route">
Find paths between nodes with hop limits and relationship type filters.
</Card>
</CardGroup>
## Quick Start
<Steps>
<Step title="Connect to a graph database">
```python
from semantica.graph_store import GraphStore
store = GraphStore(
backend="neo4j",
uri="bolt://localhost:7687",
user="neo4j",
password="password",
)
```
</Step>
<Step title="Create indexes before loading data">
```python
store.create_index(label="Person", property="name")
store.create_index(label="Organization", property="name")
```
</Step>
<Step title="Load nodes and edges">
```python
store.create_nodes(entities)
store.add_edges(relationships)
```
</Step>
<Step title="Query the graph">
```python
results = store.query(
"MATCH (p:Person)-[:WORKS_FOR]->(o:Organization) WHERE o.name = $org RETURN p",
parameters={"org": "Apple Inc."},
)
```
</Step>
</Steps>
## Backends
<Tabs>
<Tab title="Neo4j">
<Tab title="Neo4j (recommended)">
```python
from semantica.graph_store import GraphStore
```python
store = GraphStore(
backend="neo4j",
uri="bolt://localhost:7687",
user="neo4j",
password="password",
database="neo4j" # optional — targets default database
)
```
Best for: production workloads, complex Cypher queries, Bloom visualization.
store = GraphStore(
backend="neo4j",
uri="bolt://localhost:7687",
user="neo4j",
password="password",
database="neo4j", # optional — targets default database
)
```
Best for: production workloads, complex Cypher queries, Bloom visualization.
</Tab>
<Tab title="FalkorDB">
```python
store = GraphStore(
backend="falkordb",
host="localhost",
port=6379,
graph_name="semantica",
)
```
```python
store = GraphStore(
backend="falkordb",
host="localhost",
port=6379,
graph_name="semantica"
)
```
Best for: ultra-low latency queries over Redis protocol, edge deployments.
Best for: ultra-low latency queries over Redis protocol, edge deployments.
</Tab>
<Tab title="Apache AGE">
```python
store = GraphStore(
backend="apache_age",
connection_string="postgresql://user:pass@localhost/graphdb",
graph_name="semantica",
)
```
```python
store = GraphStore(
backend="apache_age",
connection_string="postgresql://user:pass@localhost/graphdb",
graph_name="semantica"
)
```
Best for: teams already running PostgreSQL who want graph queries without a separate service. See the [Apache AGE Guide](../graph_stores/apache_age) for setup.
Best for: teams already running PostgreSQL who want graph queries without a separate service. See the [Apache AGE Guide](../graph_stores/apache_age) for setup.
</Tab>
<Tab title="Amazon Neptune">
```python
from semantica.graph_store import GraphStore
```python
store = GraphStore(
backend="neptune",
endpoint="your-cluster.cluster-xxxx.us-east-1.neptune.amazonaws.com",
port=8182,
region="us-east-1"
)
```
# IAM authentication (recommended for production)
store = GraphStore(
backend="neptune",
endpoint="your-cluster.cluster-xxxx.us-east-1.neptune.amazonaws.com",
port=8182,
region="us-east-1",
use_iam_auth=True, # uses boto3 default credential chain
)
Best for: managed AWS deployments needing both SPARQL and Gremlin support.
# Gremlin traversal
results = store.query("g.V().hasLabel('Person').limit(10)")
# openCypher query
results = store.query(
"MATCH (p:Person)-[:WORKS_FOR]->(o:Organization) RETURN p, o",
query_language="opencypher",
)
```
Best for: managed AWS deployments needing both SPARQL and Gremlin support.
</Tab>
<Tab title="In-Memory">
<Tab title="NetworkX (in-memory)">
```python
store = GraphStore(backend="networkx")
```
```python
store = GraphStore(backend="networkx")
```
Best for: development, testing, and graphs that fit in RAM. Data is not persisted.
</Tab>
<Tab title="Backend Comparison">
Best for: development, testing, and graphs that fit in RAM. Data is not persisted.
| Backend | Query Language | Deployment | IAM Auth | Best For |
| ------- | -------------- | ---------- | -------- | -------- |
| Neo4j | Cypher | Self-hosted / Aura | No | Production, complex traversals, Bloom UI |
| FalkorDB | Cypher | Redis-based | No | Ultra-low latency, edge deployments |
| Apache AGE | OpenCypher | PostgreSQL extension | No | Teams already on Postgres |
| Amazon Neptune | SPARQL / Gremlin / openCypher | AWS managed | Yes | Cloud-native, multi-model, compliance |
| NetworkX | Python API | In-memory | No | Development, unit testing |
</Tab>
</Tabs>
## Querying
```python
# Cypher query with parameters (Neo4j, FalkorDB)
results = store.query(
"MATCH (p:Person)-[:WORKS_FOR]->(o:Organization) WHERE o.name = $org RETURN p",
parameters={"org": "Apple Inc."}
)
# Path traversal between two nodes
paths = store.find_paths(
start_node="steve_jobs",
end_node="apple_inc",
max_hops=3,
relationship_types=["FOUNDED", "WORKED_AT"]
)
```
## Graph Operations
```python
@@ -128,19 +191,19 @@ paths = store.find_paths(
store.add_node(
"apple_inc",
node_type="Organization",
properties={"founded": 1976, "hq": "Cupertino"}
properties={"founded": 1976, "hq": "Cupertino"},
)
# Add a directed relationship
store.add_edge(
"steve_jobs", "apple_inc",
"FOUNDED",
properties={"year": 1976}
properties={"year": 1976},
)
# Bulk operations — use for large datasets
store.add_nodes_bulk(entities, batch_size=1000)
store.add_edges_bulk(relationships, batch_size=1000)
store.create_nodes(entities)
store.add_edges(relationships)
# Delete
store.delete_node("node_id")
@@ -150,25 +213,90 @@ store.delete_edge("edge_id")
neighbors = store.get_neighbors(
"apple_inc",
relationship_type="HAS_EMPLOYEE",
direction="in" # "in" | "out" | "both"
direction="in", # "in" | "out" | "both"
)
# Path traversal between two nodes
paths = store.find_paths(
start_node="steve_jobs",
end_node="apple_inc",
max_hops=3,
relationship_types=["FOUNDED", "WORKED_AT"],
)
```
## Schema Management
## QueryEngine
Create indexes and constraints to improve query performance:
`QueryEngine` handles query construction, optimization, and caching:
```python
from semantica.graph_store import QueryEngine, GraphStore
store = GraphStore(backend="neo4j", uri="bolt://localhost:7687", user="neo4j", password="password")
engine = QueryEngine(store, cache_ttl=300) # cache results for 5 minutes
# Build parameterized Cypher
query, params = engine.build_query(
node_labels=["Person"],
filters={"department": "Engineering"},
return_fields=["name", "email"],
limit=50,
)
results = engine.execute(query, params)
# Explain query plan (Neo4j)
plan = engine.explain(query, params)
print(plan["profile"])
# Flush query cache
engine.clear_cache()
```
## GraphAnalytics
Built-in graph analytics that run directly against the stored backend — no data export required:
```python
from semantica.graph_store import GraphAnalytics, GraphStore
store = GraphStore(backend="neo4j", uri="bolt://localhost:7687", user="neo4j", password="password")
analytics = GraphAnalytics(store)
# Centrality
centrality = analytics.degree_centrality(node_label="Person", relationship_type="KNOWS")
betweenness = analytics.betweenness_centrality(node_label="Person")
# Community detection
communities = analytics.detect_communities(
node_label="Person",
relationship_type="KNOWS",
algorithm="louvain",
)
print(f"Detected {len(communities)} communities")
# Shortest path
path = analytics.shortest_path("alice", "charlie", relationship_type="KNOWS")
print(f"Hops: {len(path) - 1}, Path: {' → '.join(path)}")
# All paths up to max_hops
all_paths = analytics.all_paths("alice", "charlie", max_hops=4)
```
| Method | Description |
| ------ | ----------- |
| `degree_centrality(node_label, relationship_type)` | Degree-based node importance |
| `betweenness_centrality(node_label)` | Bridge-based importance |
| `pagerank(node_label, relationship_type, damping)` | PageRank scores |
| `detect_communities(node_label, relationship_type, algorithm)` | Louvain / Label Propagation |
| `shortest_path(source, target, relationship_type)` | Minimum-hop path |
| `all_paths(source, target, max_hops)` | All paths up to max depth |
## Schema Management
```python
# Index for fast label lookups
store.create_index(label="Person", property="name")
# Uniqueness constraint
store.create_constraint(
label="Organization",
property="id",
constraint_type="unique"
)
# Inspect current schema
schema = store.get_schema()
print(schema["labels"])
@@ -176,15 +304,31 @@ print(schema["indexes"])
print(schema["constraints"])
```
## Backend Comparison
## Tips and Common Pitfalls
| Backend | Query Language | Deployment | Best For |
| ------- | -------------- | ---------- | -------- |
| Neo4j | Cypher | Self-hosted / Aura | Production, complex traversals |
| FalkorDB | Cypher | Redis-based | Ultra-low latency, edge |
| Apache AGE | OpenCypher | PostgreSQL | Teams already on Postgres |
| Amazon Neptune | SPARQL / Gremlin | AWS managed | Cloud-native AWS deployments |
| NetworkX | Python API | In-memory | Development and testing |
<Tip>
**Use `NetworkX` for development, Neo4j or FalkorDB for production.** `backend="networkx"` requires zero setup and runs in memory — ideal for local development and CI tests. Switch to a persistent backend before deploying — no code changes needed, just the backend parameter.
</Tip>
<Warning>
**Create indexes before bulk loading.** `store.create_index(label="Person", property="name")` makes `MATCH` queries on `name` orders of magnitude faster. Without indexes, every query does a full scan. Create indexes first, then load data.
</Warning>
<Tip>
**Use `create_nodes()` and `add_edges()` for loading multiple nodes and edges.** Individual `add_node()` calls issue one network round-trip each. Loading in bulk is significantly faster for initial graph population.
</Tip>
<Warning>
**Use parameterized queries, never string interpolation.** `store.query("WHERE n.name = $name", parameters={"name": user_input})` prevents Cypher injection attacks. Never use `f"WHERE n.name = '{user_input}'"`.
</Warning>
<Tip>
**Enable `QueryEngine` caching for read-heavy workloads.** `QueryEngine(store, cache_ttl=300)` avoids repeated round-trips for identical queries within the cache window — useful for analytics dashboards that refresh frequently with the same aggregation queries.
</Tip>
<Warning>
**Apache AGE requires the PostgreSQL extension installed.** `backend="apache_age"` calls the AGE extension functions. If AGE is not installed in your PostgreSQL instance, you'll get a `ProgrammingError`. See the [Apache AGE Guide](../graph_stores/apache_age) for setup instructions.
</Warning>
<CardGroup cols={2}>
<Card title="KG Module" icon="diagram-project" href="kg">
+385 -147
View File
@@ -6,166 +6,385 @@ icon: "database"
`semantica.ingest` is the entry point for loading data into Semantica. Every ingestor returns a list of `DataSource` objects with normalized content and metadata, regardless of the original format.
## Exported Classes
```python
from semantica.ingest import (
# File ingestion (always available)
FileIngestor, # local files and directories: ingest(path, recursive=True)
CloudStorageIngestor, # AWS S3, Google Cloud Storage, Azure Blob Storage
FileObject, # {content, source_id, source_type, metadata, raw_bytes}
FileTypeDetector, # auto-detect file type from extension and magic bytes
ParquetIngestor, # Apache Parquet files and partitioned datasets
XMLIngestor, # XXE-safe lxml XML parsing with optional XSD validation
# Web ingestion (requires beautifulsoup4)
WebIngestor, # web scraping: ingest_url(url), crawl(url, max_pages)
FeedIngestor, # RSS/Atom feeds: ingest_feed(url), monitor_feeds(...)
FeedMonitor, # live feed monitoring with callback on new items
# Stream ingestion
StreamIngestor, # real-time: ingest_kafka/rabbitmq/kinesis/pulsar
KafkaProcessor, # Kafka consumer group processor
RabbitMQProcessor, # AMQP queue processor
KinesisProcessor, # AWS Kinesis stream processor
PulsarProcessor, # Apache Pulsar consumer
# Repository ingestion (requires gitpython)
RepoIngestor, # Git repos: ingest(url_or_path), include_commits=True
# Email ingestion
EmailIngestor, # IMAP/POP3: ingest() with attachment extraction
# Database ingestion
DBIngestor, # SQL: ingest_database(connection_string, include_tables)
SnowflakeIngestor, # Snowflake: ingest_query(sql), ingest_table(name)
OntologyIngestor, # OWL/RDF ontology files: ingest_ontology(path)
# Convenience functions
ingest, # ingest(source, source_type="file") — unified dispatcher
ingest_file, # ingest_file(path, method="directory")
ingest_web, # ingest_web(url, method="url")
ingest_feed, # ingest_feed(url)
ingest_stream, # ingest_stream(topic, ...)
ingest_database, # ingest_database(connection_string, ...)
ingest_parquet, # ingest_parquet(path, columns=[...])
ingest_xml, # ingest_xml(path, validate_xsd=None)
)
```
## What You Get
- **`FileIngestor`** — PDF, DOCX, HTML, JSON, CSV, Excel, PPTX, ZIP/TAR archives
- **`ParquetIngestor`** — PyArrow-based Parquet with Hive-style partition support (v0.5.0)
- **`XMLIngestor`** — XXE-safe lxml with XSD/DTD validation (v0.5.0)
- **`WebIngestor`** — configurable web crawling with robots.txt support
- **`FeedIngestor`** — RSS/Atom feeds with live monitoring
- **`DBIngestor`** / **`SnowflakeIngestor`** — SQL databases and Snowflake
- **`StreamIngestor`** — Kafka, RabbitMQ, Kinesis, Pulsar real-time streams
- **`RepoIngestor`**, **`EmailIngestor`**, **`MCPIngestor`**, **`S3Ingestor`**, **`GCSIngestor`**
<CardGroup cols={2}>
<Card title="FileIngestor" icon="file">
PDF, DOCX, HTML, JSON, CSV, Excel, PPTX, and ZIP/TAR archives — type auto-detected from extension.
</Card>
<Card title="ParquetIngestor" icon="table">
PyArrow-based Parquet with Hive-style partition support and column selection (v0.5.0).
</Card>
<Card title="XMLIngestor" icon="code">
XXE-safe lxml with XSD/DTD validation and directory scanning (v0.5.0).
</Card>
<Card title="StreamIngestor" icon="wave-square">
Real-time ingestion from Kafka, RabbitMQ, AWS Kinesis, and Apache Pulsar.
</Card>
<Card title="Cloud Storage" icon="cloud">
`CloudStorageIngestor` — unified client for AWS S3, Google Cloud Storage, and Azure Blob Storage.
</Card>
<Card title="Database Ingestors" icon="database">
`DBIngestor` (SQL via SQLAlchemy) and `SnowflakeIngestor` for data warehouse queries.
</Card>
</CardGroup>
## FileIngestor
## Quick Start
<Steps>
<Step title="Ingest local files">
```python
from semantica.ingest import FileIngestor
ingestor = FileIngestor()
# Single file — type auto-detected from extension
sources = ingestor.ingest("data/report.pdf")
# Recursive directory scan
sources = ingestor.ingest_directory("data/", recursive=True)
# Glob pattern
sources = ingestor.ingest("data/**/*.docx")
```
</Step>
<Step title="Connect to a remote source">
```python
from semantica.ingest import DBIngestor
ingestor = DBIngestor(
connection_string="postgresql://user:pass@localhost/db",
query="SELECT id, content, created_at FROM documents WHERE status='active'"
)
sources = ingestor.ingest()
```
</Step>
<Step title="Feed sources into the pipeline">
```python
from semantica.pipeline import PipelineBuilder, ExecutionEngine
from semantica.parse import DocumentParser
from semantica.semantic_extract import NERExtractor
from semantica.llms import Groq
llm = Groq(model="llama-3.3-70b-versatile", api_key=os.getenv("GROQ_API_KEY"))
ingestor = FileIngestor()
parser = DocumentParser()
extractor = NERExtractor(method="llm", llm_provider=llm)
builder = PipelineBuilder()
builder.add_step("ingest", "file_ingest", handler=ingestor.ingest_file)
builder.add_step("parse", "document_parse", handler=parser.parse)
builder.add_step("extract", "ner_extract", handler=extractor.extract)
builder.connect_steps("ingest", "parse")
builder.connect_steps("parse", "extract")
pipeline = builder.build("my_pipeline")
result = ExecutionEngine().execute_pipeline(pipeline, data="data/")
```
</Step>
</Steps>
## Ingestors
<Tabs>
<Tab title="File-Based">
### FileIngestor
```python
from semantica.ingest import FileIngestor
ingestor = FileIngestor()
sources = ingestor.ingest("data/report.pdf")
sources = ingestor.ingest_directory("data/", recursive=True)
sources = ingestor.ingest("data/**/*.docx")
```
Supported formats: PDF, DOCX, TXT, HTML, JSON, CSV, Excel (XLSX/XLS), PPTX, ZIP/TAR archives.
### ParquetIngestor (v0.5.0)
PyArrow-based ingestion for Apache Parquet files, including Hive-style partitioned datasets:
```python
from semantica.ingest import ParquetIngestor
ingestor = ParquetIngestor()
# Single Parquet file
sources = ingestor.ingest("data/events.parquet")
# Partitioned directory (year=2024/month=01/...)
sources = ingestor.ingest("data/partitioned/")
# Load only specific columns
sources = ingestor.ingest("data/events.parquet", columns=["id", "text", "timestamp"])
```
### XMLIngestor (v0.5.0)
XXE-safe lxml-based ingestion with optional schema validation:
```python
from semantica.ingest import XMLIngestor
ingestor = XMLIngestor()
sources = ingestor.ingest("data/records.xml")
# With XSD validation
ingestor = XMLIngestor(validate_xsd="schema.xsd")
sources = ingestor.ingest("data/records/")
# With DTD validation
ingestor = XMLIngestor(validate_dtd=True)
sources = ingestor.ingest("data/feed.xml")
```
<Note>
`XMLIngestor` uses lxml with `resolve_entities=False` to prevent XML External Entity (XXE) injection attacks.
</Note>
</Tab>
<Tab title="Web & Feed">
### WebIngestor
```python
from semantica.ingest import WebIngestor
ingestor = WebIngestor(
delay=1.0, # seconds between requests
respect_robots=True, # honor robots.txt
timeout=30,
)
sources = ingestor.ingest_url("https://example.com/about")
```
### FeedIngestor (RSS/Atom)
```python
from semantica.ingest import FeedIngestor
ingestor = FeedIngestor()
feed = ingestor.ingest_feed("https://feeds.example.com/rss")
# Live monitoring — returns a FeedMonitor; callback fires on new items
monitor = ingestor.monitor_feeds(
["https://feeds.example.com/rss"],
callback=process_new_items,
)
```
### RepoIngestor
Ingest Git repositories — source code, commit history, and dependency graphs:
```python
from semantica.ingest import RepoIngestor
ingestor = RepoIngestor(
branch="main",
file_types=[".py", ".md", ".yaml"],
include_commits=True,
commit_range="HEAD~100..HEAD",
)
sources = ingestor.ingest("https://github.com/org/repo")
sources = ingestor.ingest("/path/to/local/repo")
```
### EmailIngestor
Ingest emails via IMAP or POP3 with attachment extraction and thread analysis:
```python
from semantica.ingest import EmailIngestor
import os
ingestor = EmailIngestor(
protocol="imap",
host="imap.gmail.com",
port=993,
use_ssl=True,
username=os.getenv("EMAIL_USER"),
password=os.getenv("EMAIL_PASS"),
folder="INBOX",
attachment_types=[".pdf", ".docx", ".txt"],
include_thread_analysis=True,
max_emails=500,
)
sources = ingestor.ingest()
```
</Tab>
<Tab title="Cloud Storage">
### CloudStorageIngestor
`CloudStorageIngestor` is a unified client for AWS S3, Google Cloud Storage, and Azure Blob Storage:
```python
from semantica.ingest import CloudStorageIngestor
import os
# AWS S3
ingestor = CloudStorageIngestor(
provider="s3",
bucket="my-documents-bucket",
prefix="reports/2024/",
region="us-east-1",
aws_access_key_id=os.getenv("AWS_ACCESS_KEY_ID"),
aws_secret_access_key=os.getenv("AWS_SECRET_ACCESS_KEY"),
# Omit credentials to use IAM instance profile / environment variables
)
sources = ingestor.ingest()
# Google Cloud Storage
ingestor = CloudStorageIngestor(
provider="gcs",
bucket="my-gcs-bucket",
prefix="data/",
credentials_file="gcp-credentials.json", # or use ADC
)
sources = ingestor.ingest()
# Azure Blob Storage
ingestor = CloudStorageIngestor(
provider="azure",
container="documents",
connection_string=os.getenv("AZURE_STORAGE_CONNECTION_STRING"),
)
sources = ingestor.ingest()
```
</Tab>
<Tab title="Database">
### DBIngestor (SQL)
```python
from semantica.ingest import DBIngestor
ingestor = DBIngestor()
result = ingestor.ingest_database(
connection_string="postgresql://user:pass@localhost/db",
include_tables=["documents"],
)
```
### SnowflakeIngestor
```python
from semantica.ingest import SnowflakeIngestor
import os
ingestor = SnowflakeIngestor(
account=os.getenv("SNOWFLAKE_ACCOUNT"),
user=os.getenv("SNOWFLAKE_USER"),
password=os.getenv("SNOWFLAKE_PASSWORD"),
warehouse="COMPUTE_WH",
database="ANALYTICS",
schema="PUBLIC",
)
result = ingestor.ingest_query("SELECT * FROM documents")
result = ingestor.ingest_table("documents")
```
</Tab>
<Tab title="Stream">
### StreamIngestor
Real-time ingestion from message brokers:
```python
from semantica.ingest import StreamIngestor
ingestor = StreamIngestor()
# Kafka — returns KafkaProcessor
processor = ingestor.ingest_kafka(
topic="documents",
bootstrap_servers=["localhost:9092"],
)
# RabbitMQ — returns RabbitMQProcessor
processor = ingestor.ingest_rabbitmq(
queue="document_queue",
connection_url="amqp://guest:guest@localhost/",
)
# AWS Kinesis — returns KinesisProcessor
processor = ingestor.ingest_kinesis(
stream_name="documents-stream",
region="us-east-1",
)
# Apache Pulsar — returns PulsarProcessor
processor = ingestor.ingest_pulsar(
topic="persistent://public/default/documents",
service_url="pulsar://localhost:6650",
)
```
</Tab>
</Tabs>
## OntologyIngestor
Ingest existing OWL or RDF ontology files as structured knowledge sources:
```python
from semantica.ingest import FileIngestor
from semantica.ingest import OntologyIngestor
ingestor = FileIngestor()
ingestor = OntologyIngestor()
# Single file — type auto-detected from extension
sources = ingestor.ingest("data/report.pdf")
# Recursive directory scan
sources = ingestor.ingest_directory("data/", recursive=True)
# Glob pattern
sources = ingestor.ingest("data/**/*.docx")
ontology_data = ingestor.ingest_ontology("domain_ontology.owl", format="turtle")
ontology_list = ingestor.ingest_directory("ontologies/", recursive=True)
```
Supported formats: PDF, DOCX, TXT, HTML, JSON, CSV, Excel (XLSX/XLS), PPTX, ZIP/TAR archives.
## FileObject
## ParquetIngestor (v0.5.0)
`FileIngestor` returns `FileObject` instances:
PyArrow-based ingestion for Apache Parquet files, including Hive-style partitioned datasets:
```python
from semantica.ingest import ParquetIngestor
ingestor = ParquetIngestor()
# Single Parquet file
sources = ingestor.ingest("data/events.parquet")
# Partitioned directory (year=2024/month=01/...)
sources = ingestor.ingest("data/partitioned/")
# Load only specific columns
sources = ingestor.ingest("data/events.parquet", columns=["id", "text", "timestamp"])
```
## XMLIngestor (v0.5.0)
XXE-safe lxml-based ingestion with optional schema validation:
```python
from semantica.ingest import XMLIngestor
# Basic ingestion
ingestor = XMLIngestor()
sources = ingestor.ingest("data/records.xml")
# With XSD validation
ingestor = XMLIngestor(validate_xsd="schema.xsd")
sources = ingestor.ingest("data/records/")
# With DTD validation
ingestor = XMLIngestor(validate_dtd=True)
sources = ingestor.ingest("data/feed.xml")
```
<Note>
`XMLIngestor` uses lxml with `resolve_entities=False` to prevent XML External Entity (XXE) injection attacks.
</Note>
## WebIngestor
```python
from semantica.ingest import WebIngestor
ingestor = WebIngestor(
rate_limit=1.0, # seconds between requests
respect_robots=True, # honor robots.txt
max_depth=2 # crawl depth from seed URLs
)
# Single URL
sources = ingestor.ingest("https://example.com/about")
# Multiple URLs
sources = ingestor.ingest_urls([
"https://example.com/page1",
"https://example.com/page2",
])
```
## FeedIngestor (RSS/Atom)
```python
from semantica.ingest import FeedIngestor
ingestor = FeedIngestor()
sources = ingestor.ingest("https://feeds.example.com/rss")
# Live monitoring — callback fires on new items
ingestor.monitor(
"https://feeds.example.com/rss",
interval=300,
callback=process_new_items
)
```
## DBIngestor (SQL)
```python
from semantica.ingest import DBIngestor
ingestor = DBIngestor(
connection_string="postgresql://user:pass@localhost/db",
query="SELECT id, content, created_at FROM documents WHERE status='active'"
)
sources = ingestor.ingest()
```
## SnowflakeIngestor
```python
from semantica.ingest import SnowflakeIngestor
import os
ingestor = SnowflakeIngestor(
account=os.getenv("SNOWFLAKE_ACCOUNT"),
user=os.getenv("SNOWFLAKE_USER"),
password=os.getenv("SNOWFLAKE_PASSWORD"),
warehouse="COMPUTE_WH",
database="ANALYTICS",
schema="PUBLIC"
)
sources = ingestor.ingest(query="SELECT * FROM documents")
```
## Other Ingestors
| Class | Source |
| ----- | ------ |
| `StreamIngestor` | Kafka, RabbitMQ, Kinesis, Pulsar |
| `RepoIngestor` | Git repositories (GitHub, GitLab) |
| `EmailIngestor` | IMAP/POP3 servers with attachment extraction |
| `MCPIngestor` | Model Context Protocol servers |
| `S3Ingestor` | AWS S3 buckets |
| `GCSIngestor` | Google Cloud Storage |
| `MongoIngestor` | MongoDB collections |
| `DuckDBIngestor` | DuckDB databases |
| `GDriveIngestor` | Google Drive |
## DataSource Object
All ingestors return a list of `DataSource` objects with a consistent schema:
<Accordion title="FileObject schema">
```python
@dataclass
class DataSource:
class FileObject:
content: str # raw text content
source_id: str # unique identifier
source_type: str # "file" | "web" | "database" | "stream" | ...
@@ -173,6 +392,8 @@ class DataSource:
raw_bytes: Optional[bytes] # original binary content if available
```
</Accordion>
## Custom Ingestors
Register a custom ingestor and it participates in the full pipeline:
@@ -181,12 +402,29 @@ Register a custom ingestor and it participates in the full pipeline:
from semantica.ingest.registry import method_registry
def my_ingestor(source, **kwargs):
# Return a list of DataSource-compatible dicts
return [{"content": "...", "metadata": {}, "source_id": source}]
method_registry.register("file", "my_format", my_ingestor)
```
## Tips and Common Pitfalls
<Tip>
**`FileIngestor` is always the fastest path for local files.** It auto-detects format from extension, handles ZIP/TAR archives automatically, and supports glob patterns. Only reach for `DoclingParser` when `DocumentParser` can't handle your layout.
</Tip>
<Tip>
**Use `ParquetIngestor` instead of `FileIngestor` for structured analytical data.** Parquet ingestion preserves column types (int, float, datetime) that CSV reading loses. Use `columns=["id", "text"]` to avoid loading unused columns — critical for wide tables with hundreds of columns.
</Tip>
<Warning>
**`XMLIngestor` is XXE-safe by default.** Do not use standard `xml.etree.ElementTree` to pre-parse XML before passing to Semantica — it doesn't block XXE attacks. `XMLIngestor` uses lxml with `resolve_entities=False` to safely parse untrusted XML.
</Warning>
<Tip>
**Rate-limit web crawling.** `WebIngestor(delay=1.0, respect_robots=True)` is the responsible default. Without rate limiting, you risk getting blocked by the target server or violating its terms of service.
</Tip>
<CardGroup cols={2}>
<Card title="Parse" icon="file-lines" href="parse">
Parse raw sources into structured text and tables.
+73 -33
View File
@@ -1,21 +1,47 @@
---
title: "Knowledge Graph Module"
description: "Graph construction, temporal models, analytics, and distance intelligence."
description: "Graph construction, temporal models, analytics, similarity scoring, and structural embeddings."
icon: "diagram-project"
---
`semantica.kg` transforms extracted entities and relationships into structured, queryable knowledge graphs. It includes temporal support, a full suite of graph analytics algorithms, node embeddings, and Distance Intelligence (v0.5.0).
`semantica.kg` transforms extracted entities and relationships into structured, queryable knowledge graphs. It includes temporal support, a full suite of graph analytics algorithms, node embeddings, and structural similarity scoring.
## Exported Classes
```python
from semantica.kg import (
KnowledgeGraph, # core graph data structure
GraphBuilder, # construct from entities + relationships
GraphBuilderWithProvenance, # auto-tracks provenance for every node/edge
EntityResolver, # entity deduplication during construction
GraphAnalyzer, # temporal evolution, diversity metrics
GraphValidator, # schema and constraint validation
TemporalGraphQuery, # point-in-time snapshots, diffs, interval queries
TemporalPatternDetector, # sequence/cycle/trend detection
TemporalVersionManager, # snapshot creation and version comparison
TemporalNormalizer, # normalize timestamps across granularities
BiTemporalFact, # bi-temporal fact model (transaction + valid time)
CentralityCalculator, # degree, betweenness, closeness, PageRank, eigenvector
CommunityDetector, # Louvain, Leiden, Label Propagation, K-Clique
PathFinder, # Dijkstra, A*, BFS, K-Shortest paths
LinkPredictor, # Preferential Attachment, Jaccard, Adamic-Adar
NodeEmbedder, # Node2Vec, DeepWalk structural embeddings
SimilarityCalculator, # cosine, Euclidean, Manhattan, correlation similarity
ConnectivityAnalyzer, # connected components, bridges, density
ProvenanceTracker, # source tracking and lineage management
)
```
## What You Get
- **`GraphBuilder`** — construct graphs from entities and relationships with automatic entity merging
- **`TemporalKnowledgeGraph`** — time-aware edges (`valid_from`/`valid_until`) and point-in-time queries (v0.4.0)
- **`DistanceCalculator`** — semantic neighborhoods, N×N distance matrices, and distance band classification (v0.5.0)
- **`TemporalGraphQuery`** — time-aware point-in-time snapshots, diffs, and Allen interval queries (v0.4.0)
- **`SimilarityCalculator`** — cosine, Euclidean, Manhattan, and correlation similarity scoring
- **`CentralityCalculator`** — PageRank, degree, betweenness, closeness, eigenvector centrality
- **`CommunityDetector`** — Louvain, Leiden, Label Propagation, K-Clique community detection
- **`PathFinder`** — Dijkstra, A\*, BFS, K-Shortest path algorithms
- **`LinkPredictor`** — Preferential Attachment, Jaccard, Adamic-Adar link prediction
- **`NodeEmbedder`** — Node2Vec, DeepWalk, Word2Vec structural embeddings
- **`NodeEmbedder`** — Node2Vec, DeepWalk structural embeddings
<Tip>
For conflict detection and advanced entity resolution, use `semantica.conflicts` and `semantica.deduplication` alongside this module.
@@ -42,52 +68,66 @@ kg = builder.build(entities=entities, relationships=relationships)
## Temporal Knowledge Graphs (v0.4.0)
Attach `valid_from` / `valid_until` time windows to nodes and edges for point-in-time queries and historical analysis:
Use `TemporalGraphQuery` to attach `valid_from`/`valid_until` windows and query time-aware graphs:
```python
from semantica.kg import TemporalKnowledgeGraph, TemporalGraphQuery
from semantica.kg import GraphBuilder, TemporalGraphQuery, TemporalVersionManager
from datetime import datetime
tkg = TemporalKnowledgeGraph()
# Nodes and edges carry explicit validity windows
tkg.add_node("ceo_role", valid_from=datetime(2020, 1, 1), valid_until=datetime(2023, 6, 1))
tkg.add_edge(
"alice", "acme_corp", "ceo_of",
valid_from=datetime(2020, 1, 1),
valid_until=datetime(2023, 6, 1)
)
# Build a time-aware graph
builder = GraphBuilder()
kg = builder.build(sources=[
{
"entities": [
{"id": "alice", "type": "Person"},
{"id": "acme_corp", "type": "Organization"},
],
"relationships": [
{
"source": "alice", "target": "acme_corp", "type": "ceo_of",
"valid_from": "2020-01-01",
"valid_until": "2023-06-01",
}
]
}
])
# Point-in-time snapshot
snapshot = tkg.at(datetime(2021, 6, 15))
query = TemporalGraphQuery(kg)
snapshot_2021 = query.at_time("2021-06-15")
snapshot_2023 = query.at_time("2023-01-01")
# Diff between two snapshots
query = TemporalGraphQuery(tkg)
snapshot_2020 = query.at_time("2020-01-01")
snapshot_2023 = query.at_time("2023-01-01")
diff = snapshot_2023.minus(snapshot_2020)
print(f"New nodes since 2020: {len(diff.nodes)}")
diff = query.diff("2020-01-01", "2023-01-01")
print(f"New nodes since 2020: {len(diff.get('added_nodes', []))}")
# Versioned snapshots
versioner = TemporalVersionManager()
versioner.create_snapshot(kg, version_label="2024-Q1")
```
Supports all 13 Allen interval algebra relations (before, after, meets, overlaps, during, starts, finishes, equals, and their inverses). OWL-Time export available.
## Distance Intelligence (v0.5.0)
## Similarity Scoring
Semantic neighborhood exploration for any entity in the graph:
`SimilarityCalculator` computes cosine, Euclidean, Manhattan, and correlation similarity between node embeddings:
```python
from semantica.kg import DistanceCalculator
from semantica.kg import SimilarityCalculator, NodeEmbedder
calc = DistanceCalculator(kg)
# First compute structural embeddings
embedder = NodeEmbedder(method="node2vec", embedding_dimension=128)
embeddings = embedder.compute_embeddings(kg, ["Person", "Organization"], ["RELATED_TO"])
# Semantic neighborhood of a single node
neighborhood = calc.semantic_neighborhood("Apple Inc.", radius=0.4)
# Then compare nodes by embedding similarity
calc = SimilarityCalculator()
score = calc.cosine_similarity(embeddings["Apple Inc."], embeddings["Google"])
print(f"AppleGoogle structural similarity: {score:.3f}")
# N×N pairwise distance matrix
matrix = calc.distance_matrix(["Apple Inc.", "Google", "Microsoft"])
# Classify nodes into distance bands: "near" | "mid" | "far"
bands = calc.classify_bands(neighborhood)
# Find structurally similar nodes
similar = embedder.find_similar_nodes(kg, "Apple Inc.", top_k=5)
for node in similar:
print(f"{node['id']}: {node['score']:.3f}")
```
## Graph Analytics
+83 -89
View File
@@ -1,19 +1,35 @@
---
title: "LLMs Module"
description: "Unified interface for Groq, OpenAI, Anthropic, Gemini, Ollama, DeepSeek, Novita AI, LiteLLM, and HuggingFace."
description: "Unified interface for Groq, OpenAI, LiteLLM (Anthropic, Gemini, Ollama, DeepSeek, Azure, Bedrock, 100+ models), and HuggingFace."
icon: "microchip"
---
`semantica.llms` provides a single consistent API across 8+ LLM providers. Every provider is a drop-in replacement for the `llm_provider=` parameter in extractors, reasoning engines, and agents.
`semantica.llms` provides a single consistent API across every major LLM provider. Every provider is a drop-in replacement for the `llm_provider=` parameter in extractors, reasoning engines, and agents.
## Exported Classes
```python
from semantica.llms import Groq, OpenAI, LiteLLM, HuggingFaceLLM
```
| Class | Provider | API Key Required |
| ----- | -------- | ---------------- |
| `Groq` | Groq Cloud | `GROQ_API_KEY` |
| `OpenAI` | OpenAI / any OpenAI-compatible gateway | `OPENAI_API_KEY` |
| `LiteLLM` | 100+ providers via LiteLLM routing | Depends on model |
| `HuggingFaceLLM` | Local HuggingFace Transformers | None (local) |
<Tip>
**Anthropic, Gemini, Ollama, DeepSeek, Azure, Bedrock, Cohere, and 90+ others** are all available via `LiteLLM` using their model-string prefix. See the [LiteLLM section](#litellm-100-providers) below.
</Tip>
## What You Get
- **8+ provider integrations** — Groq, OpenAI, Anthropic, Gemini, Ollama, DeepSeek, Novita AI, LiteLLM, HuggingFace
- **Unified `LLMProvider` interface** — swap providers with a one-line change, no application changes needed
- **`ProviderFactory`** — instantiate any provider by name from a config dict
- **Local models** — Ollama and HuggingFace run fully on-premise with no API key
- **Unified `LLMProvider` interface** — swap providers with a one-line change, no application code changes
- **`LiteLLM`** — single class for 100+ providers using model-string routing
- **Local models** — `HuggingFaceLLM` runs fully on-premise, no API key
- **Streaming** — token-by-token output for low-latency UX
- **Custom gateways** — point any OpenAI-compatible endpoint via `base_url`
- **Custom gateways** — point `OpenAI` at any OpenAI-compatible endpoint via `base_url`
## Providers
@@ -29,86 +45,53 @@ llm = Groq(
max_tokens=64000,
temperature=0.0,
)
# Best for: high-throughput extraction, fast inference
# Best for: high-throughput extraction, fast inference at low cost
```
```python OpenAI
from semantica.llms import OpenAI
import os
# pip install "semantica[llm-openai]"
llm = OpenAI(
model="gpt-4o",
api_key=os.getenv("OPENAI_API_KEY"),
temperature=0.0,
)
# Best for: general purpose, function calling
# Best for: general purpose, function calling, JSON mode
```
```python Anthropic
from semantica.llms import Anthropic
import os
# pip install "semantica[llm-anthropic]"
llm = Anthropic(
model="claude-opus-4-7",
api_key=os.getenv("ANTHROPIC_API_KEY"),
max_tokens=8192,
)
# Best for: complex reasoning, long context, safety
```
```python Gemini
from semantica.llms import Gemini
import os
# pip install "semantica[llm-gemini]"
llm = Gemini(
model="gemini-1.5-pro",
api_key=os.getenv("GOOGLE_API_KEY"),
)
# Best for: long context (1M tokens), multimodal tasks
```
```python Ollama (Local)
from semantica.llms import Ollama
# pip install "semantica[llm-ollama]"
llm = Ollama(
model="llama3.2:3b",
base_url="http://localhost:11434",
)
# Best for: local inference, air-gapped environments
# No API key required
```
```python DeepSeek
from semantica.llms import DeepSeek
import os
llm = DeepSeek(
model="deepseek-chat",
api_key=os.getenv("DEEPSEEK_API_KEY"),
)
# Best for: coding tasks and analysis at very low cost
```
```python LiteLLM (100+ models)
```python LiteLLM (100+ providers)
from semantica.llms import LiteLLM
import os
# pip install "semantica[llm-litellm]"
llm = LiteLLM(
model="gpt-4o", # any LiteLLM-supported model string
api_key=os.getenv("OPENAI_API_KEY"),
)
# Supports: OpenAI, Anthropic, Gemini, Cohere, Azure, Bedrock, and 90+ more
# Anthropic Claude
llm = LiteLLM(model="anthropic/claude-opus-4-5", api_key=os.getenv("ANTHROPIC_API_KEY"))
# Google Gemini
llm = LiteLLM(model="gemini/gemini-1.5-pro", api_key=os.getenv("GOOGLE_API_KEY"))
# Ollama (local — no API key)
llm = LiteLLM(model="ollama/llama3.2:3b", api_base="http://localhost:11434")
# DeepSeek
llm = LiteLLM(model="deepseek/deepseek-chat", api_key=os.getenv("DEEPSEEK_API_KEY"))
# Azure OpenAI
llm = LiteLLM(model="azure/gpt-4o", api_key=os.getenv("AZURE_API_KEY"))
# AWS Bedrock
llm = LiteLLM(model="bedrock/anthropic.claude-3-5-sonnet-20241022-v2:0")
# Novita AI
llm = LiteLLM(model="novita/deepseek/deepseek-v3.2", api_key=os.getenv("NOVITA_API_KEY"))
```
```python HuggingFace (BYOM)
from semantica.llms import HuggingFace
```python HuggingFaceLLM (Local)
from semantica.llms import HuggingFaceLLM
llm = HuggingFace(
llm = HuggingFaceLLM(
model="mistralai/Mistral-7B-Instruct-v0.3",
device="cuda", # "cpu" | "cuda" | "mps"
max_new_tokens=512,
@@ -119,23 +102,34 @@ llm = HuggingFace(
</CodeGroup>
## Provider Factory
## LiteLLM — 100+ Providers
Instantiate any provider by name string — useful when provider is loaded from config:
`LiteLLM` is the recommended way to access any provider not directly exported by `semantica.llms`. Use the `provider/model` string format:
```python
from semantica.llms import create_provider
from semantica.llms import LiteLLM
import os
llm = create_provider("groq", model="llama-3.3-70b-versatile")
llm = create_provider("openai", model="gpt-4o")
llm = create_provider("anthropic", model="claude-opus-4-7")
llm = create_provider("gemini", model="gemini-1.5-pro")
llm = create_provider("ollama", model="llama3.2")
llm = create_provider("deepseek", model="deepseek-chat", api_key=os.getenv("DEEPSEEK_API_KEY"))
llm = create_provider("novita", model="deepseek/deepseek-v3.2", api_key=os.getenv("NOVITA_API_KEY"))
llm = create_provider("litellm", model="gpt-4o")
# Pattern: LiteLLM(model="<provider>/<model-name>")
providers = {
"Anthropic": LiteLLM(model="anthropic/claude-opus-4-5", api_key=os.getenv("ANTHROPIC_API_KEY")),
"Gemini": LiteLLM(model="gemini/gemini-1.5-pro", api_key=os.getenv("GOOGLE_API_KEY")),
"Ollama": LiteLLM(model="ollama/llama3.2:3b", api_base="http://localhost:11434"),
"DeepSeek": LiteLLM(model="deepseek/deepseek-chat", api_key=os.getenv("DEEPSEEK_API_KEY")),
"Azure": LiteLLM(model="azure/gpt-4o", api_key=os.getenv("AZURE_API_KEY")),
"Bedrock": LiteLLM(model="bedrock/anthropic.claude-3-5-sonnet-20241022-v2:0"),
"Cohere": LiteLLM(model="cohere/command-r-plus", api_key=os.getenv("COHERE_API_KEY")),
"Novita AI": LiteLLM(model="novita/deepseek/deepseek-v3.2", api_key=os.getenv("NOVITA_API_KEY")),
}
# Every LiteLLM instance implements the same .generate() interface
response = providers["Anthropic"].generate("Explain GraphRAG in one paragraph.")
```
<Note>
The full list of supported LiteLLM model strings is at [docs.litellm.ai/docs/providers](https://docs.litellm.ai/docs/providers). Use the `provider/model` format shown above.
</Note>
## Custom / Enterprise Gateways
Any OpenAI-compatible endpoint — internal routing layers, Qwen proxies, or private LLaMA deployments:
@@ -170,17 +164,17 @@ trip = TripletExtractor(method="llm", llm_provider=llm)
## Provider Comparison
| Provider | Speed | Cost | Local | Context | Best For |
| -------- | ----- | ---- | ----- | ------- | -------- |
| Groq | Very fast | Low | No | 128k | High-throughput extraction |
| OpenAI | Fast | Medium | No | 128k | General purpose, function calling |
| Anthropic | Fast | Medium | No | 200k | Complex reasoning, safety |
| Gemini | Fast | Low | No | 1M | Long context, multimodal |
| Ollama | Medium | Free | Yes | Varies | Privacy, no API key |
| DeepSeek | Fast | Very low | No | 64k | Coding, analysis |
| Novita AI | Fast | Low | No | Varies | DeepSeek-based tasks |
| LiteLLM | Varies | Varies | Varies | Varies | Multi-provider routing |
| HuggingFace | Slow | Free | Yes | Varies | Custom models, BYOM |
| Provider | Import | Speed | Cost | Local | Context | Best For |
| -------- | ------ | ----- | ---- | ----- | ------- | -------- |
| Groq | `Groq` | Very fast | Low | No | 128k | High-throughput extraction |
| OpenAI | `OpenAI` | Fast | Medium | No | 128k | General purpose, function calling |
| Anthropic | `LiteLLM(model="anthropic/...")` | Fast | Medium | No | 200k | Complex reasoning, safety |
| Gemini | `LiteLLM(model="gemini/...")` | Fast | Low | No | 1M | Long context, multimodal |
| Ollama | `LiteLLM(model="ollama/...")` | Medium | Free | Yes | Varies | Privacy, air-gapped |
| DeepSeek | `LiteLLM(model="deepseek/...")` | Fast | Very low | No | 64k | Coding, analysis |
| Azure OpenAI | `LiteLLM(model="azure/...")` | Fast | Medium | No | 128k | Enterprise, compliance |
| AWS Bedrock | `LiteLLM(model="bedrock/...")` | Fast | Varies | No | Varies | AWS-native workloads |
| HuggingFace | `HuggingFaceLLM` | Slow | Free | Yes | Varies | Custom models, BYOM |
<Tip>
For production extraction pipelines, Groq delivers the best throughput-to-cost ratio. For complex multi-hop reasoning, Claude Opus or GPT-4o provide the highest accuracy.
+166 -50
View File
@@ -6,15 +6,56 @@ icon: "plug"
`semantica.mcp_server` exposes Semantica's knowledge graph, decision intelligence, semantic extraction, and reasoning capabilities as an [MCP (Model Context Protocol)](https://modelcontextprotocol.io) server over stdio.
Once configured, any connected AI assistant can extract entities, record decisions, query the graph, run reasoning, and export results — without writing a single line of Python.
Compatible with **Claude Desktop**, **Windsurf**, **Cline**, **Continue**, **VS Code**, **Roo Code**, **Cursor**, and any MCP-aware client.
## Server Interface
```json
// Configure in your MCP client (Claude Desktop, Windsurf, Cursor, VS Code, etc.)
{
"mcpServers": {
"semantica": {
"command": "semantica-mcp"
}
}
}
```
```bash
# Or run directly
semantica-mcp
# or
python -m semantica.mcp_server
```
<Tip>
`semantica.mcp_server` is a **stdio server process**, not a Python library. It exposes no importable classes — all interaction happens through MCP tool calls from a connected AI client.
</Tip>
## What You Get
- **12 MCP tools** — extract entities, build graphs, run SPARQL, find paths, get recommendations, embed, cluster, and more
- **3 readable resources** — live graph JSON, entity list, and relationship list
- **Zero infrastructure** — runs over stdio, no server or port needed
- **Claude Desktop ready** — one config block to add to `claude_desktop_config.json`
- **REST alternative** — the Explorer module offers a full HTTP API if you prefer
<CardGroup cols={2}>
<Card title="12 MCP Tools" icon="wrench">
Extract entities, extract relations, record decisions, query decisions, find precedents, trace causal chains, add entities, add relationships, run analytics, summarise graph, run reasoning, export.
</Card>
<Card title="3 Readable Resources" icon="book-open">
Live graph JSON (`semantica://graph/summary`), decision list, and schema/version info — readable by any MCP client.
</Card>
<Card title="Zero Infrastructure" icon="bolt">
Runs over stdio — no server, no port, no Docker required. One config block to activate in any MCP client.
</Card>
<Card title="Persistent Graphs" icon="database">
Point `SEMANTICA_KG_PATH` at a saved graph file to reload it automatically on every server startup.
</Card>
<Card title="Decision Intelligence" icon="brain">
Record decisions, find precedents via hybrid similarity search, and trace causal chains across agent runs.
</Card>
<Card title="REST Alternative" icon="globe">
The [Explorer](explorer) module offers a full HTTP API and browser dashboard if you prefer programmatic access.
</Card>
</CardGroup>
## Installation
@@ -26,46 +67,86 @@ The MCP server is included in the base install — no extras required.
## Configuration
Add Semantica to your MCP client's settings file:
<Steps>
<Step title="Find your MCP client's settings file">
<CodeGroup>
| Client | Settings file |
| ------ | ------------- |
| Claude Desktop (macOS) | `~/Library/Application Support/Claude/claude_desktop_config.json` |
| Claude Desktop (Windows) | `%APPDATA%\Claude\claude_desktop_config.json` |
| Cursor | `.cursor/mcp.json` in your project, or `~/.cursor/mcp.json` globally |
| VS Code / Continue | `.vscode/mcp.json` or user settings |
| Windsurf / Cline / Roo Code | App-specific settings → MCP Servers |
```json Claude Desktop / Windsurf / Cline
{
"mcpServers": {
"semantica": {
"command": "semantica-mcp"
}
}
}
```
</Step>
<Step title="Add the Semantica MCP server config">
```json VS Code / Continue / Roo Code
{
"mcpServers": {
"semantica": {
"command": "python",
"args": ["-m", "semantica.mcp_server"]
}
}
}
```
<CodeGroup>
```json With persistent graph
{
"mcpServers": {
"semantica": {
"command": "semantica-mcp",
"env": {
"SEMANTICA_KG_PATH": "/path/to/my_graph.json",
"SEMANTICA_LOG_LEVEL": "INFO"
```json Claude Desktop / Windsurf / Cline
{
"mcpServers": {
"semantica": {
"command": "semantica-mcp"
}
}
}
}
}
```
```
</CodeGroup>
```json Cursor
{
"mcpServers": {
"semantica": {
"command": "semantica-mcp",
"env": {
"SEMANTICA_KG_PATH": "/path/to/my_graph.json"
}
}
}
}
```
```json VS Code / Continue / Roo Code
{
"mcpServers": {
"semantica": {
"command": "python",
"args": ["-m", "semantica.mcp_server"]
}
}
}
```
```json With persistent graph
{
"mcpServers": {
"semantica": {
"command": "semantica-mcp",
"env": {
"SEMANTICA_KG_PATH": "/path/to/my_graph.json",
"SEMANTICA_LOG_LEVEL": "INFO"
}
}
}
}
```
</CodeGroup>
</Step>
<Step title="Test locally before configuring your client">
```bash
# Run the server directly (reads from stdin, writes to stdout)
semantica-mcp
# Or via Python module
python -m semantica.mcp_server
# Send a JSON-RPC initialize message to confirm it's working
echo '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"test","version":"1.0"}}}' | semantica-mcp
```
</Step>
</Steps>
## Environment Variables
@@ -78,6 +159,21 @@ Add Semantica to your MCP client's settings file:
The MCP server exposes 12 tools that any connected AI assistant can call:
| Tool | Category | Description |
| ---- | -------- | ----------- |
| `extract_entities` | Extraction | NER — find people, places, organisations, concepts |
| `extract_relations` | Extraction | Typed relation and triplet extraction |
| `record_decision` | Decision Intelligence | Save a decision with reasoning and outcome |
| `query_decisions` | Decision Intelligence | Search recorded decisions by natural language |
| `find_precedents` | Decision Intelligence | Hybrid similarity search over past decisions |
| `get_causal_chain` | Decision Intelligence | Trace upstream / downstream causal chains |
| `add_entity` | Graph Operations | Add a node to the live graph |
| `add_relationship` | Graph Operations | Add a directed edge between two nodes |
| `get_graph_analytics` | Graph Operations | PageRank + community detection |
| `get_graph_summary` | Graph Operations | Node count, decision count, health status |
| `run_reasoning` | Reasoning & Export | Forward-chain IF/THEN rules over facts |
| `export_graph` | Reasoning & Export | Serialise the graph (Turtle, JSON-LD, JSON, etc.) |
### Knowledge Extraction
<AccordionGroup>
@@ -87,11 +183,13 @@ The MCP server exposes 12 tools that any connected AI assistant can call:
Extract named entities (people, places, organisations, concepts) from text using Semantica NER.
**Input:**
```json
{ "text": "Apple Inc. was founded by Steve Jobs in Cupertino in 1976." }
```
**Output:**
```json
{
"entities": [
@@ -110,11 +208,13 @@ Extract named entities (people, places, organisations, concepts) from text using
Extract typed relations and `(subject, predicate, object)` triplets from text.
**Input:**
```json
{ "text": "Steve Jobs founded Apple Inc. and led it until 2011." }
```
**Output:**
```json
{
"relations": [
@@ -139,6 +239,7 @@ Extract typed relations and `(subject, predicate, object)` triplets from text.
Record a decision with full context, reasoning, and metadata into the knowledge graph.
**Input:**
```json
{
"category": "model_selection",
@@ -151,6 +252,7 @@ Record a decision with full context, reasoning, and metadata into the knowledge
```
**Output:**
```json
{ "decision_id": "dec_a1b2c3", "status": "recorded" }
```
@@ -162,6 +264,7 @@ Record a decision with full context, reasoning, and metadata into the knowledge
Query recorded decisions by natural language, category, or retrieve all recent decisions.
**Input:**
```json
{ "query": "model selection", "limit": 5 }
```
@@ -173,6 +276,7 @@ Query recorded decisions by natural language, category, or retrieve all recent d
Find past decisions similar to a given scenario using hybrid similarity search.
**Input:**
```json
{ "scenario": "Choose cloud provider for HIPAA workload", "max_results": 3 }
```
@@ -184,6 +288,7 @@ Find past decisions similar to a given scenario using hybrid similarity search.
Trace the causal chain upstream or downstream from a decision.
**Input:**
```json
{ "decision_id": "dec_a1b2c3", "direction": "downstream", "max_depth": 5 }
```
@@ -201,6 +306,7 @@ Trace the causal chain upstream or downstream from a decision.
Add a node/entity to the live knowledge graph.
**Input:**
```json
{
"id": "apple_inc",
@@ -217,6 +323,7 @@ Add a node/entity to the live knowledge graph.
Add a directed relationship (edge) between two existing entities.
**Input:**
```json
{
"source": "steve_jobs",
@@ -251,6 +358,7 @@ Return node count, decision count, and graph health status.
Run forward-chaining IF/THEN rules over a set of facts to derive new facts.
**Input:**
```json
{
"facts": ["Employee(John)", "Manager(John)"],
@@ -259,6 +367,7 @@ Run forward-chaining IF/THEN rules over a set of facts to derive new facts.
```
**Output:**
```json
{ "derived_facts": ["HasAuthority(John)"] }
```
@@ -270,6 +379,7 @@ Run forward-chaining IF/THEN rules over a set of facts to derive new facts.
Export the current knowledge graph to a serialization format.
**Input:**
```json
{ "format": "json-ld" }
```
@@ -282,7 +392,7 @@ Supported formats: `turtle`, `ttl`, `nt`, `xml`, `json-ld`, `json`.
## Resources
The MCP server also exposes three readable resources:
The MCP server exposes three readable resources:
| URI | Description |
| --- | ----------- |
@@ -290,21 +400,27 @@ The MCP server also exposes three readable resources:
| `semantica://decisions/list` | All recorded decisions (up to 50) |
| `semantica://schema/info` | Server version and available tools |
## Test Locally
## Tips and Common Pitfalls
```bash
# Run the server directly (reads from stdin, writes to stdout)
semantica-mcp
<Warning>
**Build the `ContextGraph` before starting the server.** The MCP server operates on a pre-built `ContextGraph` — it doesn't build the knowledge graph on demand. Construct and populate the graph first (ingest → extract → build KG → set `ContextGraph`), then pass it to `SemanticaMCPServer`. An empty or None graph results in empty query responses.
</Warning>
# Or via Python module
python -m semantica.mcp_server
```
<Tip>
**Use `decision_tracking=True` for accountable agents.** Without decision tracking, `record_decision` and `query_decisions` calls succeed but nothing is stored. Enable it in the `ContextGraph` constructor when you want agents' decisions to be queryable for audit, compliance, or iterative reasoning.
</Tip>
Send a JSON-RPC `initialize` message to confirm it's working:
<Tip>
**Use `find_precedents` before high-stakes decisions.** The tool performs hybrid similarity search across all recorded decisions. Call it at the start of any significant decision path — it surfaces past reasoning that may be directly applicable, reducing redundant work and improving consistency across agent runs.
</Tip>
```bash
echo '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"test","version":"1.0"}}}' | semantica-mcp
```
<Warning>
**Configure your MCP client's `command` field exactly.** The `command` field must point to the exact executable path (use `which semantica-mcp` on macOS/Linux to find it). A wrong path fails silently — the server just doesn't appear in the tools list. Test with the raw `echo | semantica-mcp` command first to confirm the binary works.
</Warning>
<Warning>
**The server communicates over stdio — don't add logging to stdout.** Any `print()` or logger output directed to stdout will corrupt the JSON-RPC message stream. Configure logging to write to a file or stderr only (`logging.basicConfig(filename="mcp.log")`). The MCP protocol assumes stdout carries only JSON-RPC frames.
</Warning>
<CardGroup cols={2}>
<Card title="Context" icon="brain" href="context">
+371 -192
View File
@@ -1,275 +1,454 @@
---
title: "Normalize Module"
description: "Text cleaning, entity canonicalization, date normalization, number/unit conversion, language detection, and encoding repair."
description: "Text cleaning, entity canonicalization, date normalization, number conversion, language detection, and encoding repair — before extraction runs."
icon: "broom"
---
`semantica.normalize` standardizes raw data before extraction and graph construction — fixing encodings, canonicalizing entity names, normalizing dates, and detecting languages. All normalizers expose both convenience functions and stateful class instances.
`semantica.normalize` standardizes raw data before extraction and graph construction. All normalizers expose both convenience functions (one-liners) and stateful class instances (full control over configuration and reuse).
## Why Normalize Before Extraction
Unstructured data is inconsistent by nature. Without normalization, the same real-world entity appears as dozens of variants in your graph:
- `"Apple Inc."`, `"Apple Computer Inc."`, `"APPLE INC."`, `"Apple, Inc."` — four nodes, one company
- `"Jan 1st, 2020"`, `"01/01/2020"`, `"2020-01-01"` — three formats, one date
- `"$1.2B"`, `"1,200,000,000"`, `"1.2 billion USD"` — three strings, one number
- `"Hello World"` vs `"Hello World"` — a non-breaking space that breaks string matching
Normalization collapses these variants before any extractor, deduplicator, or graph builder sees the data — producing cleaner entities, fewer false duplicates, and more reliable downstream results.
## Exported Classes
```python
from semantica.normalize import (
# Text normalization
TextNormalizer, # coordinator: strip_html, normalize_unicode, fix_encoding
UnicodeNormalizer, # NFC/NFD/NFKC/NFKD normalization
WhitespaceNormalizer, # collapse spaces, normalize line endings
SpecialCharacterProcessor, # smart quotes, dashes, diacritics
TextCleaner, # general text cleaning utilities
# Entity normalization
EntityNormalizer, # coordinator: normalize_entity(text, entity_type)
AliasResolver, # resolve "ML" -> "Machine Learning" via dictionary
EntityDisambiguator, # disambiguate("Apple", context=...) with confidence
NameVariantHandler, # normalize("Dr. JOHN P. SMITH Jr.") -> "John P. Smith"
# Date/time normalization
DateNormalizer, # normalize_date(str) -> ISO 8601
TimeZoneNormalizer, # normalize to UTC or target timezone
RelativeDateProcessor, # "3 days ago" -> datetime
TemporalExpressionParser, # "Q2 2023" -> {start, end, type}
# Number normalization
NumberNormalizer, # normalize_number("$1.2B") -> 1200000000.0
UnitConverter, # convert(100, from_unit="km/h", to_unit="m/s")
CurrencyNormalizer, # normalize("$42.50") -> {amount, currency, raw}
ScientificNotationHandler, # parse scientific notation strings
# Data cleaning
DataCleaner, # remove_duplicates, fill_missing
DataValidator, # validate(records, schema={"name": str, "age": int})
DuplicateDetector, # detect duplicate records by similarity threshold
MissingValueHandler, # fill missing values: mean/median/mode/constant
# Language & encoding
LanguageDetector, # detect(text) -> {language, confidence}
EncodingHandler, # detect_encoding, to_utf8, remove_bom
# Convenience functions
normalize_text, # normalize_text(text, method="default")
normalize_entity, # normalize_entity(name, entity_type="Person")
normalize_date, # normalize_date("Jan 1st, 2020")
normalize_number, # normalize_number("$1,234.56")
clean_text, # clean_text(text)
detect_language, # detect_language(text)
resolve_aliases, # resolve_aliases(text, aliases_dict)
)
```
## What You Get
- **`TextNormalizer`** — Unicode, whitespace, HTML stripping, smart-quote/dash replacement
- **`EntityNormalizer`** — alias resolution, disambiguation, name variant handling
- **`DateNormalizer`** — ISO 8601 output, timezone conversion, relative date parsing
- **`NumberNormalizer`** — currency, unit conversion, scientific notation, percentages
- **`DataCleaner`** — duplicate detection, schema validation, missing value handling
- **`LanguageDetector`** — 50+ language detection with confidence scoring
- **`EncodingHandler`** — UTF-8 conversion, BOM removal, encoding detection
<CardGroup cols={2}>
<Card title="TextNormalizer" icon="text-size">
Unicode forms, whitespace collapse, HTML stripping, smart-quote and dash replacement.
</Card>
<Card title="EntityNormalizer" icon="building">
Corporate suffix normalization, honorific removal, alias resolution, and disambiguation.
</Card>
<Card title="DateNormalizer" icon="calendar">
Any date format → ISO 8601; relative dates, timezones, and date ranges.
</Card>
<Card title="NumberNormalizer" icon="hashtag">
Currency, scientific notation, unit abbreviations, and percentages → float.
</Card>
<Card title="LanguageDetector" icon="globe">
50+ languages with confidence scoring and batch detection.
</Card>
<Card title="EncodingHandler" icon="code">
Encoding detection, UTF-8 conversion, BOM removal, and cp1252 repair.
</Card>
</CardGroup>
<Note>
**v0.5.0 fix:** Encoding repair now handles cp1252/latin-1 characters that previously caused crashes on Windows when processing documents with non-ASCII content.
**v0.5.0 fix:** Encoding repair now handles cp1252 and latin-1 characters that previously caused crashes on Windows when processing documents with non-ASCII content.
</Note>
## Recommended Processing Order
<Steps>
<Step title="EncodingHandler — fix encoding first">
Broken bytes corrupt everything downstream. Always run this before anything else.
```python
from semantica.normalize import EncodingHandler
handler = EncodingHandler()
utf8_text = handler.to_utf8(raw_bytes)
```
</Step>
<Step title="TextNormalizer — unicode, whitespace, HTML">
```python
from semantica.normalize import TextNormalizer
normalizer = TextNormalizer(strip_html=True, normalize_unicode=True)
clean_text = normalizer.normalize_text(utf8_text)
```
</Step>
<Step title="EntityNormalizer — canonicalize entity names">
```python
from semantica.normalize import EntityNormalizer
normalizer = EntityNormalizer()
canonical = normalizer.normalize_entity("Apple Computer Inc.", entity_type="Organization")
# → "Apple Inc."
```
</Step>
<Step title="DateNormalizer and NumberNormalizer — parse structured values">
```python
from semantica.normalize import DateNormalizer, NumberNormalizer
date_norm = DateNormalizer(target_timezone="UTC")
num_norm = NumberNormalizer()
date = date_norm.normalize_date("Jan 1st, 2020") # → "2020-01-01"
num = num_norm.normalize_number("$1.2B") # → 1200000000.0
```
</Step>
<Step title="LanguageDetector — detect language on clean text">
```python
from semantica.normalize import LanguageDetector
detector = LanguageDetector()
lang = detector.detect("Bonjour le monde")
# → {"language": "fr", "confidence": 0.98}
```
</Step>
</Steps>
## Convenience Functions
The fastest path — dispatch via function with a `method` parameter:
The fastest path — one import, one call:
```python
from semantica.normalize import (
normalize_text, normalize_entity, normalize_date,
normalize_number, clean_data, detect_language, handle_encoding
normalize_number, clean_data, detect_language, handle_encoding,
)
clean = normalize_text(" Hello, World!! \n\n")
# → "Hello, World!!"
entity = normalize_entity("Apple Computer Inc.", entity_type="Organization")
# → "Apple Inc."
date = normalize_date("Jan 1st, 2020")
# → "2020-01-01"
num = normalize_number("$1,234.56")
# → 1234.56
lang = detect_language("Bonjour le monde")
# → {"language": "fr", "confidence": 0.98}
clean = normalize_text(" Hello, World!! \n\n") # → "Hello, World!!"
entity = normalize_entity("Apple Computer Inc.", entity_type="Organization") # → "Apple Inc."
date = normalize_date("Jan 1st, 2020") # → "2020-01-01"
num = normalize_number("$1.2B") # → 1200000000.0
lang = detect_language("Bonjour le monde") # → {"language": "fr", "confidence": 0.98}
```
## TextNormalizer
## Normalizers
```python
from semantica.normalize import TextNormalizer
<Tabs>
<Tab title="TextNormalizer">
Cleans raw text at the character and token level:
normalizer = TextNormalizer()
```python
from semantica.normalize import TextNormalizer
normalized = normalizer.normalize_text(
raw_text,
lowercase=False,
remove_punctuation=False,
remove_extra_whitespace=True,
strip_html=True, # remove HTML tags
normalize_unicode=True, # NFC normalization
)
```
normalizer = TextNormalizer(
lowercase=False,
remove_punctuation=False,
remove_extra_whitespace=True,
strip_html=True,
normalize_unicode=True,
fix_encoding=True,
form="NFC", # "NFC" | "NFD" | "NFKC" | "NFKD"
)
Sub-normalizers for fine-grained control:
normalized = normalizer.normalize_text(raw_text)
```
```python
from semantica.normalize import UnicodeNormalizer, WhitespaceNormalizer, SpecialCharacterProcessor
| Parameter | Type | Default | Description |
| --------- | ---- | ------- | ----------- |
| `lowercase` | `bool` | `False` | Convert to lowercase — use for bag-of-words matching, not NER |
| `remove_punctuation` | `bool` | `False` | Strip all punctuation — use for keyword extraction only |
| `remove_extra_whitespace` | `bool` | `True` | Collapse tabs, newlines, non-breaking spaces into single spaces |
| `strip_html` | `bool` | `False` | Remove HTML tags and decode `&amp;`, `&lt;`, etc. |
| `normalize_unicode` | `bool` | `True` | Apply Unicode normal form |
| `fix_encoding` | `bool` | `True` | Repair common encoding mojibake (cp1252 / latin-1 → UTF-8) |
| `form` | `str` | `"NFC"` | Unicode normalization form: `"NFC"` / `"NFD"` / `"NFKC"` / `"NFKD"` |
# Unicode normalization forms: NFC | NFD | NFKC | NFKD
unicode_norm = UnicodeNormalizer(form="NFC")
text = unicode_norm.normalize("café")
**Unicode form guide:**
# Collapse tabs, line breaks, and extra spaces
ws_norm = WhitespaceNormalizer()
text = ws_norm.normalize("Hello World\t\n") # → "Hello World"
| Form | Use When |
| ---- | -------- |
| `NFC` | Default — best for storage and display |
| `NFKC` | Search indexing — normalises ligatures, fullwidth chars, and fractions |
| `NFD` | Stripping diacritics — split é → e + combining accent, then strip accents |
| `NFKD` | Same as NFD but also decomposes compatibility characters |
# Replace smart quotes, em-dashes, and ellipsis characters
processor = SpecialCharacterProcessor()
text = processor.process("Hello") # '' → ''
```
**Sub-normalizers for fine-grained control:**
## EntityNormalizer
```python
from semantica.normalize import UnicodeNormalizer, WhitespaceNormalizer, SpecialCharacterProcessor
Canonicalize entity names — handles corporate suffixes, punctuation, case, and honorifics:
unicode_norm = UnicodeNormalizer(form="NFC")
text = unicode_norm.normalize("café")
```python
from semantica.normalize import EntityNormalizer
ws_norm = WhitespaceNormalizer()
text = ws_norm.normalize("Hello\t\t World\n\n") # → "Hello World"
normalizer = EntityNormalizer()
processor = SpecialCharacterProcessor()
text = processor.process("'Hello'") # '' → '', -- → -
```
</Tab>
<Tab title="EntityNormalizer">
Canonicalises entity name variants — corporate suffixes, honorifics, case, and punctuation:
# Company name normalization
companies = ["Apple Computer, Inc.", "Apple Inc", "APPLE INC."]
normalized = [normalizer.normalize_entity(c) for c in companies]
# All → "Apple Inc."
```python
from semantica.normalize import EntityNormalizer
# Person name normalization
name = normalizer.normalize_entity("JOBS, STEVE", entity_type="Person")
# → "Steve Jobs"
```
normalizer = EntityNormalizer()
Sub-normalizers for entity-specific use cases:
# Corporate name normalization
normalizer.normalize_entity("Apple Computer, Inc.", entity_type="Organization") # → "Apple Inc."
normalizer.normalize_entity("APPLE INC.", entity_type="Organization") # → "Apple Inc."
```python
from semantica.normalize import AliasResolver, EntityDisambiguator, NameVariantHandler
# Person name normalization
normalizer.normalize_entity("JOBS, STEVE", entity_type="Person") # → "Steve Jobs"
```
# Dictionary-based alias expansion
resolver = AliasResolver(aliases={
"ML": "Machine Learning",
"AI": "Artificial Intelligence",
"DL": "Deep Learning",
})
resolved = resolver.resolve("ML and DL are subsets of AI")
# → "Machine Learning and Deep Learning are subsets of Artificial Intelligence"
**Key behaviours:**
- Corporate suffix normalization handles: `Inc`, `Inc.`, `Incorporated`, `Ltd`, `Limited`, `Corp`, `Corporation`, `LLC`, `GmbH`, `PLC`, and 30+ more
- `entity_type="Person"` activates last-name-first reversal, honorific removal, and suffix stripping
# Context-aware disambiguation
disambiguator = EntityDisambiguator()
result = disambiguator.disambiguate(
"Apple", context="Steve Jobs founded Apple in Cupertino"
)
# → {"entity": "Apple Inc.", "type": "Organization", "confidence": 0.96}
**Sub-normalizers:**
# Honorifics, titles, and cultural name variants
handler = NameVariantHandler()
canonical = handler.normalize("Dr. JOHN P. SMITH Jr.")
# → "John P. Smith"
```
```python
from semantica.normalize import AliasResolver, EntityDisambiguator, NameVariantHandler
## DateNormalizer
resolver = AliasResolver(aliases={
"ML": "Machine Learning",
"NLP": "Natural Language Processing",
})
resolved = resolver.resolve("ML and NLP are subfields of AI")
Parse and normalize dates from any format to ISO 8601:
disambiguator = EntityDisambiguator()
result = disambiguator.disambiguate(
"Apple",
context="Steve Jobs founded Apple in Cupertino in 1976",
)
# → {"entity": "Apple Inc.", "type": "Organization", "confidence": 0.96}
```python
from semantica.normalize import DateNormalizer
handler = NameVariantHandler()
canonical = handler.normalize("Dr. JOHN P. SMITH Jr.") # → "John P. Smith"
```
</Tab>
<Tab title="DateNormalizer">
Parses any date format and outputs ISO 8601 strings. Handles relative expressions, timezones, and date ranges:
normalizer = DateNormalizer()
```python
from semantica.normalize import DateNormalizer
dates = [
"January 1st, 2020",
"01/01/2020",
"2020-01-01T00:00:00Z",
"yesterday",
"3 weeks ago",
]
normalized = [normalizer.normalize_date(d) for d in dates]
# All → ISO 8601 strings
normalizer = DateNormalizer(
target_timezone="UTC",
output_format="%Y-%m-%d",
)
# With automatic UTC conversion
normalizer_utc = DateNormalizer(target_timezone="UTC")
utc_date = normalizer_utc.normalize_date("2024-01-01 09:00 EST")
```
dates = [
"January 1st, 2020",
"01/01/2020",
"2020-01-01T00:00:00Z",
"yesterday",
"3 weeks ago",
"Q1 2024",
]
normalized = [normalizer.normalize_date(d) for d in dates]
```
Sub-normalizers for advanced date handling:
**Sub-normalizers:**
```python
from semantica.normalize import (
TimeZoneNormalizer, RelativeDateProcessor, TemporalExpressionParser
)
```python
from semantica.normalize import TimeZoneNormalizer, RelativeDateProcessor, TemporalExpressionParser
from datetime import datetime
# Timezone conversion
tz_norm = TimeZoneNormalizer(target_tz="UTC")
utc_dt = tz_norm.normalize("2024-01-01 09:00", source_tz="America/New_York")
tz_norm = TimeZoneNormalizer(target_tz="UTC")
utc_dt = tz_norm.normalize("2024-01-01 09:00", source_tz="America/New_York")
# → datetime(2024, 1, 1, 14, 0, tzinfo=UTC)
# Relative date resolution
from datetime import datetime
processor = RelativeDateProcessor(reference_date=datetime(2025, 1, 15))
result = processor.process("3 days ago")
# → datetime(2025, 1, 12)
processor = RelativeDateProcessor(reference_date=datetime(2025, 1, 15))
result = processor.process("3 days ago") # → datetime(2025, 1, 12)
result = processor.process("next quarter") # → {"start": "2025-04-01", "end": "2025-06-30"}
# Date range parsing
parser = TemporalExpressionParser()
result = parser.parse("from January 2020 to March 2021")
# → {"start": "2020-01-01", "end": "2021-03-31", "type": "range"}
```
parser = TemporalExpressionParser()
result = parser.parse("from January 2020 to March 2021")
# → {"start": "2020-01-01", "end": "2021-03-31", "type": "range"}
## NumberNormalizer
result = parser.parse("Q2 2023")
# → {"start": "2023-04-01", "end": "2023-06-30", "type": "quarter"}
```
</Tab>
<Tab title="NumberNormalizer">
Converts number strings with units, currencies, and abbreviations to `float`:
```python
from semantica.normalize import NumberNormalizer
```python
from semantica.normalize import NumberNormalizer
normalizer = NumberNormalizer()
normalizer = NumberNormalizer()
normalizer.normalize_number("$1,234.56") # → 1234.56
normalizer.normalize_number("€42K") # → 42000.0
normalizer.normalize_number("$1.2B") # → 1200000000.0
normalizer.normalize_number("3.14e-2") # → 0.0314
normalizer.normalize_number("42%") # → 0.42
```
normalizer.normalize_number("$1,234.56") # → 1234.56
normalizer.normalize_number("€42K") # → 42000.0
normalizer.normalize_number("$1.2B") # → 1200000000.0
normalizer.normalize_number("3.14e-2") # → 0.0314
normalizer.normalize_number("42%") # → 0.42
normalizer.normalize_number("7") # → -7.0 (minus sign, not hyphen)
```
Unit and currency conversion:
**Unit and currency conversion:**
```python
from semantica.normalize import UnitConverter, CurrencyNormalizer
```python
from semantica.normalize import UnitConverter, CurrencyNormalizer
converter = UnitConverter()
result = converter.convert(100, from_unit="km/h", to_unit="m/s")
# → 27.78
converter = UnitConverter()
result = converter.convert(100, from_unit="km/h", to_unit="m/s")
# → 27.78
# Supported categories: length, weight, volume, temperature, speed, area
categories = converter.list_categories()
categories = converter.list_categories()
# → ["length", "weight", "volume", "temperature", "speed", "area", "pressure", "energy"]
currency_norm = CurrencyNormalizer()
result = currency_norm.normalize("$42.50")
# → {"amount": 42.50, "currency": "USD", "raw": "$42.50"}
```
currency_norm = CurrencyNormalizer()
result = currency_norm.normalize("$42.50")
# → {"amount": 42.50, "currency": "USD", "raw": "$42.50"}
```
</Tab>
<Tab title="Language & Encoding">
### LanguageDetector
Identify the language of a text string. Used internally by the sentence splitter and chunker:
```python
from semantica.normalize import LanguageDetector
detector = LanguageDetector()
lang = detector.detect("Bonjour le monde")
# → {"language": "fr", "confidence": 0.98}
langs = detector.detect_top_n("This might be mixed", n=3)
# → [{"language": "en", "probability": 0.85}, ...]
results = detector.detect_batch(["Hello", "Hola", "Bonjour", "Ciao"])
supported = detector.list_supported_languages()
```
Supports 50+ languages: `en`, `de`, `fr`, `es`, `it`, `pt`, `nl`, `ru`, `zh`, `ja`, `ko`, `ar`, `hi`, `tr`, `pl`, `sv`, `da`, `no`, `fi`, and more.
### EncodingHandler
Detect and repair character encoding issues:
```python
from semantica.normalize import EncodingHandler
handler = EncodingHandler()
encoding = handler.detect_encoding(raw_bytes)
# → {"encoding": "windows-1252", "confidence": 0.73}
utf8_text = handler.to_utf8(raw_bytes)
clean = handler.remove_bom(text_with_bom)
repaired = handler.repair_encoding(garbled_text, source_encoding="cp1252")
```
**Key behaviours:**
- Encoding detection uses `chardet` internally — accuracy improves with longer input
- `to_utf8()` attempts cp1252 repair automatically when it detects mojibake patterns
- Always run `EncodingHandler` first — broken bytes cause cascading failures in every downstream normalizer
</Tab>
</Tabs>
## DataCleaner
Cleans structured record sets — useful before loading into a vector store or graph:
```python
from semantica.normalize import DataCleaner, DataValidator
cleaner = DataCleaner()
# Remove near-duplicate records
deduped = cleaner.remove_duplicates(records, similarity_threshold=0.9)
# Fill missing values
filled = cleaner.fill_missing(records, strategy="mean")
# strategy options: "mean" | "median" | "mode" | "remove"
filled = cleaner.fill_missing(
records,
strategy="mean", # "mean" | "median" | "mode" | "remove" | "constant"
constant_value=None,
)
# Validate schema
validator = DataValidator()
result = validator.validate(records, schema={"name": str, "age": int})
print(result.valid_count, result.errors)
```
## LanguageDetector
```python
from semantica.normalize import LanguageDetector
detector = LanguageDetector()
lang = detector.detect("Bonjour le monde")
# → {"language": "fr", "confidence": 0.98}
# Top N languages for mixed-language text
langs = detector.detect_top_n("This might be mixed", n=3)
# → [{"language": "en", "probability": 0.85}, ...]
# Batch detection
results = detector.detect_batch(["Hello", "Hola", "Bonjour"])
```
## EncodingHandler
```python
from semantica.normalize import EncodingHandler
handler = EncodingHandler()
encoding = handler.detect_encoding(raw_bytes)
# → {"encoding": "windows-1252", "confidence": 0.73}
utf8_text = handler.to_utf8(raw_bytes)
clean = handler.remove_bom(text_with_bom)
result = validator.validate(records, schema={"name": str, "age": int, "active": bool})
print(f"Valid: {result.valid_count}")
print(f"Invalid: {result.error_count}")
```
## Pipeline Integration
For large datasets, use the pipeline instead of per-item normalization:
```python
from semantica.pipeline import Pipeline
from semantica.pipeline import PipelineBuilder, ExecutionEngine
from semantica.ingest import FileIngestor
from semantica.normalize import TextNormalizer
from semantica.semantic_extract import NERExtractor
from semantica.llms import Groq
import os
pipeline = Pipeline()
pipeline.add_step("normalize", TextNormalizer())
result = pipeline.run(documents)
llm = Groq(model="llama-3.3-70b-versatile", api_key=os.getenv("GROQ_API_KEY"))
ingestor = FileIngestor()
normalizer = TextNormalizer(strip_html=True, normalize_unicode=True)
extractor = NERExtractor(method="llm", llm_provider=llm)
builder = PipelineBuilder()
builder.add_step("ingest", "file_ingest", handler=ingestor.ingest)
builder.add_step("normalize", "text_normalize", handler=normalizer.normalize)
builder.add_step("extract", "ner_extract", handler=extractor.extract)
builder.connect_steps("ingest", "normalize")
builder.connect_steps("normalize", "extract")
pipeline = builder.build("normalize_pipeline")
result = ExecutionEngine().execute_pipeline(pipeline, data="data/documents/")
```
## Tips and Common Pitfalls
<Warning>
**Run encoding repair before anything else.** A single cp1252 character in a UTF-8 stream silently corrupts the surrounding text. Run `EncodingHandler` or set `fix_encoding=True` on `TextNormalizer` first.
</Warning>
<Warning>
**Don't lowercase before NER.** `normalize_text(lowercase=True)` before entity extraction destroys capitalization signals that NER relies on. Apply case normalization only after extraction if needed.
</Warning>
<Tip>
**AliasResolver is order-sensitive.** If you register overlapping aliases (`"ML"` and `"ML model"`), the longer match wins. Sort aliases by length descending for predictable behaviour.
</Tip>
<Warning>
**DateNormalizer and timezone.** Without `target_timezone`, dates without timezone information are returned as naive datetime strings. In regulated pipelines (HIPAA, SOX), always set `target_timezone="UTC"` for unambiguous timestamps.
</Warning>
<Tip>
**`DataCleaner.remove_duplicates()` is not the same as `DuplicateDetector`.** `DataCleaner` operates on flat records (dicts/rows) by field-level Jaccard similarity. `DuplicateDetector` in the Deduplication module operates on graph entities with embedding-based matching. Use the latter for entity resolution.
</Tip>
<CardGroup cols={2}>
<Card title="Parse" icon="file-lines" href="parse">
Parse documents before normalization.
@@ -278,9 +457,9 @@ result = pipeline.run(documents)
Chunk normalized text for embedding.
</Card>
<Card title="Deduplication" icon="copy" href="deduplication">
Resolve duplicate entities post-normalization.
Resolve duplicate entities after normalization.
</Card>
<Card title="Pipeline" icon="gear" href="pipeline">
Include normalization as a pipeline step.
Include normalization as a named pipeline step.
</Card>
</CardGroup>
+133 -93
View File
@@ -1,51 +1,80 @@
---
title: "Ontology Module"
description: "Automated ontology generation, SHACL validation, SKOS vocabularies, alignment, diff/migration, and the visual Ontology Hub."
description: "Automated ontology generation, SHACL validation, OWL/RDF export, namespace management, and LLM-powered ontology generation."
icon: "sitemap"
---
`semantica.ontology` provides the full lifecycle for knowledge graph schemas — from auto-generation and SHACL validation to visual editing in the Ontology Hub (v0.5.0). Use it for schema design, data modeling, semantic web interoperability, and SHACL-based data quality validation.
`semantica.ontology` provides the full lifecycle for knowledge graph schemas — from auto-generation and SHACL validation to OWL/RDF export. Use it for schema design, data modeling, semantic web interoperability, and SHACL-based data quality validation.
## Exported Classes
```python
from semantica.ontology import (
OntologyGenerator, # auto-generate from KG data (6-stage pipeline)
LLMOntologyGenerator, # LLM-powered ontology generation
OntologyEngine, # unified orchestration facade
ClassInferrer, # class discovery and hierarchy building
PropertyGenerator, # property inference and XSD type mapping
SHACLGenerator, # generate SHACL shapes from ontology
OntologyValidator, # validate graphs against SHACL shapes
SHACLValidationReport, # validation report with violations list
SHACLViolation, # individual constraint violation
OWLGenerator, # OWL/RDF serialization (Turtle, XML, JSON-LD)
OntologyEvaluator, # quality evaluation: coverage, completeness
NamespaceManager, # IRI generation and namespace prefix management
OntologyAligner, # align and merge ontologies across schemas (use OntologyEngine)
AssociativeClassBuilder, # N-ary relationship intermediate class creation
NamingConventions, # PascalCase/camelCase enforcement
DomainOntologies, # pre-built domain ontologies
ingest_ontology, # load ontology from file
)
```
## What You Get
- **`OntologyManager`** — define classes, properties, relationships, and constraints
- **`OntologyGenerator`** — auto-generate ontologies from existing knowledge graph data (6-stage pipeline)
- **`SHACLGenerator`** / **`SHACLValidator`** — generate and validate SHACL shapes
- **`SKOSVocabulary`** — controlled vocabulary and taxonomy management
- **`OntologyAligner`** — align and merge ontologies across schemas
- **`OntologyDiff`** / **`OntologyMigrator`** — diff and migrate ontology versions
- **`OWLExporter`** — export to Turtle, RDF/XML, JSON-LD
- **Ontology Hub** — visual browser UI for the full ontology lifecycle (v0.5.0)
- **`LLMOntologyGenerator`** — LLM-powered ontology generation for complex domains
- **`OntologyEngine`** — unified facade that orchestrates the full ontology lifecycle
- **`SHACLGenerator`** / **`OntologyValidator`** — generate SHACL shapes and validate any graph
- **`OWLGenerator`** — serialize ontologies to Turtle, RDF/XML, JSON-LD
- **`NamespaceManager`** — IRI generation, prefix management, namespace binding
- **`OntologyEvaluator`** — coverage, completeness, and granularity quality metrics
- **`AssociativeClassBuilder`** — model N-ary relationships as intermediate OWL classes
## OntologyManager
## OntologyEngine (Unified Facade)
Define and validate a schema for your knowledge graph:
The `OntologyEngine` orchestrates the full ontology lifecycle — generation, validation, export, and versioning:
```python
from semantica.ontology import OntologyManager
from semantica.ontology import OntologyEngine
ontology = OntologyManager()
ontology.add_class("Person", properties=["name", "birth_date"])
ontology.add_class("Organization", properties=["name", "founded_date"])
ontology.add_relationship("works_for", domain="Person", range="Organization")
ontology.add_constraint("Person", "must_have_name")
engine = OntologyEngine(base_uri="https://example.org/ontology/")
# Validate a graph against the ontology
is_valid = ontology.validate_graph(kg)
# Generate ontology from KG data
ontology = engine.generate_ontology({"entities": entities, "relationships": relationships})
# Export as OWL Turtle
owl_ttl = ontology.export_owl(format="turtle")
# Validate a graph against the generated SHACL shapes
report = engine.validate(kg)
if not report.conforms:
for v in report.violations:
print(f"{v.severity}: {v.message} on {v.node}")
# Export to OWL Turtle
engine.export(ontology, "ontology.ttl", format="turtle")
```
## Auto-Generation (6-Stage Pipeline)
## OntologyGenerator (6-Stage Pipeline)
Generate an ontology automatically from your knowledge graph data:
Generate a formal ontology automatically from your knowledge graph entities and relationships:
```python
from semantica.ontology import OntologyGenerator
generator = OntologyGenerator()
ontology = generator.generate_from_graph(kg)
generator = OntologyGenerator(base_uri="https://example.org/ontology/")
ontology = generator.generate_ontology({
"entities": entities,
"relationships": relationships,
})
```
The pipeline runs through these stages in order:
@@ -62,99 +91,107 @@ The pipeline runs through these stages in order:
Generate SHACL shapes from an ontology and validate any graph against them:
```python
from semantica.ontology import SHACLGenerator, SHACLValidator
from semantica.ontology import SHACLGenerator, OntologyValidator, SHACLValidationReport, SHACLViolation
# Generate shapes
generator = SHACLGenerator()
shapes = generator.generate(ontology)
# Generate shapes from ontology
generator = SHACLGenerator()
shapes = generator.generate(ontology)
shapes_ttl = shapes.serialize(format="turtle")
# Validate a graph
validator = SHACLValidator()
report = validator.validate(kg, shapes=shapes)
# Validate a graph against the shapes
validator = OntologyValidator()
report: SHACLValidationReport = validator.validate(kg, shapes=shapes)
if not report.conforms:
violation: SHACLViolation
for violation in report.violations:
print(f"Violation: {violation.message} on {violation.node}")
print(f" Path: {violation.path}")
print(f" Severity: {violation.severity}")
print(f"{violation.severity}: {violation.message}")
print(f" Node: {violation.node}")
print(f" Path: {violation.path}")
```
## SKOS Vocabularies
## LLM-Powered Ontology Generation
Build controlled vocabularies and taxonomies using the W3C SKOS standard:
For complex or novel domains where schema patterns are hard to infer statistically:
```python
from semantica.ontology import SKOSVocabulary
from semantica.ontology import LLMOntologyGenerator
from semantica.llms import Groq
import os
vocab = SKOSVocabulary()
vocab.add_concept("Machine Learning", broader="Artificial Intelligence")
vocab.add_concept("Deep Learning", broader="Machine Learning")
vocab.add_concept("Computer Vision", broader="Deep Learning")
vocab.add_alt_label("ML", for_concept="Machine Learning")
llm = Groq(model="llama-3.3-70b-versatile", api_key=os.getenv("GROQ_API_KEY"))
skos_ttl = vocab.export(format="turtle")
```
## Ontology Alignment
Map concepts across two ontologies and merge them:
```python
from semantica.ontology import OntologyAligner
aligner = OntologyAligner()
alignment = aligner.align(source_ontology, target_ontology)
for mapping in alignment.mappings:
print(f"{mapping.source}{mapping.target} (confidence: {mapping.confidence:.2f})")
# Merge into a unified ontology
merged = aligner.merge(source_ontology, target_ontology, alignment)
```
## Diff and Migration
Compare ontology versions and generate migration scripts for graph data:
```python
from semantica.ontology import OntologyDiff, OntologyMigrator
diff = OntologyDiff()
changes = diff.compare(ontology_v1, ontology_v2)
for change in changes:
print(f"{change.type}: {change.element}{change.description}")
# Generate and apply a migration script
migrator = OntologyMigrator()
migration_script = migrator.generate_migration(changes)
migrator.apply(kg, migration_script)
generator = LLMOntologyGenerator(llm_provider=llm)
ontology = generator.generate(
domain_description="A biomedical ontology for clinical trial protocols",
examples=["Patient", "Trial", "Intervention", "Outcome"],
)
```
## OWL / RDF Export
```python
from semantica.ontology import OWLExporter
from semantica.ontology import OWLGenerator
exporter = OWLExporter()
exporter.export(ontology, path="ontology.ttl", format="turtle")
exporter.export(ontology, path="ontology.owl", format="xml")
exporter.export(ontology, path="ontology.json", format="json-ld")
generator = OWLGenerator()
generator.generate(ontology, path="ontology.ttl", format="turtle")
generator.generate(ontology, path="ontology.owl", format="xml")
generator.generate(ontology, path="ontology.json", format="json-ld")
```
## Namespace Management
```python
from semantica.ontology import NamespaceManager
ns = NamespaceManager(base_uri="https://example.org/")
ns.register("ex", "https://example.org/")
ns.register("schema", "https://schema.org/")
ns.register("owl", "http://www.w3.org/2002/07/owl#")
# Generate IRIs for classes and properties
class_iri = ns.generate_class_iri("Person")
property_iri = ns.generate_property_iri("worksFor")
```
## Ontology Evaluation
Measure coverage, completeness, and granularity of a generated ontology:
```python
from semantica.ontology import OntologyEvaluator
evaluator = OntologyEvaluator()
result = evaluator.evaluate(ontology, kg)
print(f"Class coverage: {result.class_coverage:.2f}")
print(f"Property coverage: {result.property_coverage:.2f}")
print(f"Completeness: {result.completeness:.2f}")
print(f"Granularity: {result.granularity:.2f}")
for gap in result.gaps:
print(f"Gap: {gap.description}")
```
## Ingest an Existing Ontology
Load and parse an ontology file for downstream use:
```python
from semantica.ontology import ingest_ontology
ontology_data = ingest_ontology("schema.ttl") # Turtle
ontology_data = ingest_ontology("schema.owl") # OWL/XML
ontology_data = ingest_ontology("schema.jsonld") # JSON-LD
```
## Ontology Hub (v0.5.0)
A visual browser UI for the full ontology lifecycle, served by `semantica.explorer`:
A visual browser UI for the full ontology lifecycle. Launch via CLI:
```bash
pip install "semantica[explorer]"
```
```python
from semantica.explorer import start_explorer
start_explorer(graph=kg, port=8080)
semantica-explorer --port 8080
# Navigate to http://localhost:8080 → Ontology Hub tab
```
@@ -162,10 +199,13 @@ Features:
- **Visual editor** — create and edit classes, properties, and relationships in the browser
- **SHACL Studio** — author and validate SHACL shapes with live feedback
- **Alignment authoring** — map concepts across ontologies with drag-and-drop
- **Health dashboard** — coverage, completeness, and constraint violation metrics
- **Version control** — snapshot, diff, and restore ontology versions
<Note>
Ontology versioning (`VersionManager`, `OntologyVersion`) has moved to `semantica.change_management`. Import from there: `from semantica.change_management import VersionManager`.
</Note>
<CardGroup cols={2}>
<Card title="Reasoning" icon="microchip" href="reasoning">
Apply inference rules over ontology axioms.
+28 -2
View File
@@ -6,11 +6,37 @@ icon: "file-lines"
`semantica.parse` extracts structured text, layout, tables, and metadata from unstructured documents. `DocumentParser` handles clean machine-readable files; `DoclingParser` handles complex layouts, scanned PDFs, and multi-column documents.
## Exported Classes
```python
from semantica.parse import (
DocumentParser, # auto-detect format — delegates to format-specific parser
PDFParser, # PDF text extraction
DOCXParser, # Word .docx documents
HTMLParser, # HTML / web pages
MarkdownParser, # Markdown files
TXTParser, # plain text
JSONParser, # JSON documents
XMLParser, # XML documents
CSVParser, # CSV / TSV files
WebParser, # URL fetch + HTML parsing
EmailParser, # .eml / .msg email files
CodeParser, # source code files
# Data types
ParsedDocument, # {text, sections, tables, metadata, source_id}
DocumentMetadata, # {title, author, created_date, page_count, language, ...}
)
# Optional — requires: pip install "semantica[docling]"
from semantica.parse import DoclingParser # advanced OCR + layout analysis
```
## What You Get
- **`DocumentParser`** — standard parser for PDF, DOCX, HTML, TXT, JSON, CSV, PPTX, XLSX
- **`DoclingParser`** — advanced parser for complex layouts, merged-cell tables, multi-column PDFs, and OCR
- **`DocumentParser`** — standard parser for PDF, DOCX, HTML, TXT, JSON, CSV, PPTX, XLSX — auto-detects format
- **`DoclingParser`** — advanced parser for complex layouts, merged-cell tables, multi-column PDFs, and OCR (optional dep)
- **`ParsedDocument`** — structured output with `text`, `sections`, `tables`, and `metadata`
- **Format-specific parsers** — `PDFParser`, `DOCXParser`, `HTMLParser`, `WebParser`, `EmailParser`, `CodeParser`, etc.
## DocumentParser
+559 -85
View File
@@ -6,135 +6,609 @@ icon: "gear"
`semantica.pipeline` lets you chain Semantica components into reproducible, fault-tolerant workflows with parallel execution and configurable error handling. Pipelines are serializable — save them to YAML and reload in any environment.
## What You Get
## Exported Classes
- **`Pipeline`** — chain steps with parallel workers, retry policies, and failure handlers
- **`PipelineBuilder`** — fluent DSL for building pipelines with a readable chain syntax
- **`RetryPolicy`** — fixed, linear, and exponential backoff with configurable max retries
- **`FailureHandler`** — skip, stop, or retry failed documents without halting the pipeline
- **Progress tracking** — console (tqdm), WebSocket streaming, or file logging
```python
from semantica.pipeline import (
# Pipeline construction
PipelineBuilder, # DSL: add_step, connect_steps, build
Pipeline, # pipeline definition dataclass
PipelineStep, # step definition: name, step_type, handler, dependencies
StepStatus, # enum: PENDING, RUNNING, COMPLETED, FAILED, SKIPPED
PipelineSerializer, # serialize/deserialize pipeline to JSON/YAML
# Execution
ExecutionEngine, # execute_pipeline(pipeline, data) -> ExecutionResult
ExecutionResult, # {success, output, metadata, metrics, errors}
PipelineStatus, # enum: RUNNING, PAUSED, STOPPED
ProgressTracker, # get_progress(pipeline_id) -> {completed, total, pct}
# Failure handling
FailureHandler, # configure strategy: skip/retry/abort
RetryHandler, # retry with exponential backoff
FallbackHandler, # fall back to alternative step on failure
RetryPolicy, # {max_retries, backoff, jitter}
RetryStrategy, # enum: FIXED, EXPONENTIAL, LINEAR
ErrorSeverity, # enum: LOW, MEDIUM, HIGH, CRITICAL
# Parallelism
ParallelismManager, # execute_parallel(tasks, timeout) — thread or process pool
ParallelExecutionResult, # {success, result, error, task_id}
# Resource management
ResourceScheduler, # allocate_resources / release_resources
ResourceType, # enum: CPU, MEMORY, GPU, NETWORK, DISK
# Validation
PipelineValidator, # validate_pipeline(pipeline) -> ValidationResult
# Templates
PipelineTemplateManager, # get_template("full-qa") -> pre-wired Pipeline
PipelineTemplate, # template metadata dataclass
)
```
## Why Use a Pipeline?
You could wire Semantica modules together with plain Python code. Pipelines add:
<CardGroup cols={2}>
<Card title="Retry and failure handling" icon="arrow-rotate-right">
A single bad document doesn't crash a 10,000-document run.
</Card>
<Card title="Parallelism" icon="bolt">
Run extraction across multiple workers with one parameter.
</Card>
<Card title="Progress tracking" icon="chart-line">
tqdm console bar or WebSocket streaming to Explorer.
</Card>
<Card title="Reproducibility" icon="floppy-disk">
Save the exact pipeline configuration to YAML and replay on any machine.
</Card>
<Card title="Delta mode" icon="code-compare">
On re-runs, only process documents that changed since the last run.
</Card>
<Card title="Validation" icon="shield-check">
Catch misconfigured steps and dependency cycles before they fail mid-run.
</Card>
</CardGroup>
<Note>
Use plain module calls for quick scripts and notebooks. Use pipelines for anything you run repeatedly, at scale, or in production.
</Note>
<img src="/assets/img/diagrams/pipeline-flow.svg" alt="Pipeline step sequence: Ingest → Parse → Normalize → Extract → Build KG → QA → Store → Deliver" style={{ width: '100%', borderRadius: '10px', margin: '0 0 24px' }} />
## Basic Pipeline
## Quick Start
```python
from semantica.pipeline import Pipeline
from semantica.ingest import FileIngestor
from semantica.parse import DocumentParser
from semantica.semantic_extract import NERExtractor
from semantica.kg import GraphBuilder
<Steps>
<Step title="Build a pipeline">
```python
from semantica.pipeline import PipelineBuilder
from semantica.ingest import FileIngestor
from semantica.parse import DocumentParser
from semantica.semantic_extract import NERExtractor
from semantica.kg import GraphBuilder
from semantica.llms import Groq
import os
pipeline = Pipeline()
pipeline.add_step("ingest", FileIngestor())
pipeline.add_step("parse", DocumentParser())
pipeline.add_step("extract", NERExtractor(method="llm", llm_provider=llm))
pipeline.add_step("build_kg", GraphBuilder(merge_entities=True))
llm = Groq(model="llama-3.3-70b-versatile", api_key=os.getenv("GROQ_API_KEY"))
result = pipeline.run("data/")
kg = result.output
```
ingestor = FileIngestor()
parser = DocumentParser()
extractor = NERExtractor(method="llm", llm_provider=llm)
kg_builder = GraphBuilder(merge_entities=True)
builder = PipelineBuilder()
builder.add_step("ingest", "file_ingest", handler=ingestor.ingest_file)
builder.add_step("parse", "document_parse", handler=parser.parse)
builder.add_step("extract", "ner_extract", handler=extractor.extract)
builder.add_step("build_kg", "graph_build", handler=kg_builder.build)
builder.connect_steps("ingest", "parse")
builder.connect_steps("parse", "extract")
builder.connect_steps("extract","build_kg")
pipeline = builder.build("my_pipeline")
```
</Step>
<Step title="Validate before running">
```python
from semantica.pipeline import PipelineValidator
validator = PipelineValidator()
result = validator.validate_pipeline(pipeline)
if not result.valid:
for error in result.errors: # errors is List[str]
print(f"Error: {error}")
for warning in result.warnings:
print(f"Warning: {warning}")
```
</Step>
<Step title="Execute and inspect results">
```python
from semantica.pipeline import ExecutionEngine
engine = ExecutionEngine()
result = engine.execute_pipeline(pipeline, data="data/")
kg = result.output
print(f"Success: {result.success}")
print(f"Steps executed: {result.metrics['steps_executed']}")
print(f"Steps failed: {result.metrics['steps_failed']}")
print(f"Duration: {result.metrics['execution_time']:.1f}s")
```
</Step>
</Steps>
## Parallel Processing
Process documents concurrently across multiple workers:
Set parallelism on the builder and pass `max_workers` to `ExecutionEngine`:
```python
pipeline = Pipeline(workers=4)
from semantica.pipeline import PipelineBuilder, ExecutionEngine
pipeline.add_step("ingest", FileIngestor())
pipeline.add_step("parse", DocumentParser())
pipeline.add_step("extract", NERExtractor(), parallel=True, batch_size=10)
pipeline.add_step("build", GraphBuilder())
builder = PipelineBuilder()
builder.add_step("ingest", "file_ingest", handler=ingestor.ingest_file)
builder.add_step("parse", "document_parse", handler=parser.parse)
builder.add_step("extract", "ner_extract", handler=extractor.extract)
builder.add_step("build", "graph_build", handler=kg_builder.build)
builder.set_parallelism(4)
result = pipeline.run("data/")
pipeline = builder.build("parallel_pipeline")
engine = ExecutionEngine(max_workers=4)
result = engine.execute_pipeline(pipeline, data="data/")
```
## Retry and Error Handling
Configure retry behavior and failure strategy independently:
<Tabs>
<Tab title="Exponential backoff (recommended)">
```python
from semantica.pipeline import RetryPolicy, RetryStrategy, FailureHandler, ExecutionEngine
```python
from semantica.pipeline import Pipeline, RetryPolicy, FailureHandler
policy = RetryPolicy(
max_retries=3,
strategy=RetryStrategy.EXPONENTIAL,
initial_delay=1.0, # 1s → 2s → 4s
backoff_factor=2.0
)
retry = RetryPolicy(
max_retries=3,
backoff="exponential", # "fixed" | "linear" | "exponential"
initial_delay=1.0 # seconds before first retry
)
handler = FailureHandler()
handler.retry_policies["ner_extract"] = policy # keyed by step_type
handler = FailureHandler(
strategy="skip", # "skip" | "stop" | "retry"
log_failures=True # write failed documents to error log
)
engine = ExecutionEngine(default_max_retries=3, default_backoff_factor=2.0)
result = engine.execute_pipeline(pipeline, data="data/")
```
pipeline = Pipeline(retry_policy=retry, failure_handler=handler)
```
Best for transient API errors and rate limits — waits longer with each retry, giving upstream services time to recover.
</Tab>
<Tab title="Linear backoff">
```python
from semantica.pipeline import RetryPolicy, RetryStrategy
policy = RetryPolicy(
max_retries=3,
strategy=RetryStrategy.LINEAR,
initial_delay=2.0 # 2s → 4s → 6s
)
```
Use when the delay between retries should grow predictably — e.g., waiting for a database lock to release.
</Tab>
<Tab title="Fixed backoff">
```python
from semantica.pipeline import RetryPolicy, RetryStrategy
policy = RetryPolicy(
max_retries=5,
strategy=RetryStrategy.FIXED,
initial_delay=1.0 # 1s every attempt
)
```
Use when retrying against a service with a fixed cooldown window.
</Tab>
</Tabs>
### Failure Strategies
| Strategy | Behaviour | When to Use |
| -------- | --------- | ----------- |
| `"skip"` | Log failure, continue to next document | Production — one bad doc shouldn't stop 10k |
| `"stop"` | Raise exception immediately | Development — surface errors fast |
| `"retry"` | Retry via `RetryPolicy`, then skip | When failures are likely transient |
<Warning>
Always use `strategy="skip"` in production. A single malformed document shouldn't stop a pipeline processing thousands of documents. Inspect `result.errors` after the run to find and reprocess failures.
</Warning>
## Progress Tracking
```python
# Console progress bar (tqdm)
result = pipeline.run("data/", show_progress=True)
<Tabs>
<Tab title="Console (tqdm)">
```python
from semantica.pipeline import ExecutionEngine
# WebSocket progress — stream to Knowledge Explorer
result = pipeline.run("data/", websocket_port=8080)
engine = ExecutionEngine()
result = engine.execute_pipeline(pipeline, data="data/")
# The progress tracker outputs tqdm bars to the console during execution
```
# Inspect results
print(f"Processed: {result.processed_count}")
print(f"Failed: {result.failed_count}")
print(f"Duration: {result.duration_seconds:.1f}s")
```
Displays a live progress bar in the terminal via Semantica's built-in progress tracker. Best for scripts and CLI tools.
</Tab>
<Tab title="Live status check">
```python
from semantica.pipeline import ExecutionEngine
import threading, time
engine = ExecutionEngine()
# Run in a background thread, poll progress from main thread
def run():
engine.execute_pipeline(pipeline, data="data/")
t = threading.Thread(target=run, daemon=True)
t.start()
while t.is_alive():
progress = engine.get_progress(pipeline.name)
if progress:
print(f" {progress['completed_steps']}/{progress['total_steps']} steps — {progress['status']}")
time.sleep(2)
```
Poll `get_progress()` for live status during execution.
</Tab>
</Tabs>
## Pipeline DSL
The `PipelineBuilder` provides a fluent chain syntax that reads as a data flow:
`PipelineBuilder` uses `add_step(name, type, **config)` and `connect_steps(from, to)` to define a DAG:
```python
from semantica.pipeline import PipelineBuilder
from semantica.pipeline import PipelineBuilder, ExecutionEngine
pipeline = (
PipelineBuilder()
.ingest(FileIngestor())
.parse(DocumentParser())
.normalize()
.extract(NERExtractor(method="llm", llm_provider=llm))
.extract_relations(RelationExtractor(method="llm", llm_provider=llm))
.build_kg(merge_entities=True)
.deduplicate(strategy="semantic_v2")
.export(format="turtle", path="output.ttl")
.build()
builder = PipelineBuilder()
# Add steps — step_type is a string label, handler is the callable invoked at runtime
builder.add_step("ingest", "file_ingest", handler=ingestor.ingest_file)
builder.add_step("parse", "document_parse", handler=parser.parse)
builder.add_step("normalize", "text_normalize", handler=normalizer.normalize)
builder.add_step("extract", "ner_extract", handler=extractor.extract)
builder.add_step("rel_extract", "rel_extract", handler=rel_extractor.extract)
builder.add_step("build_kg", "graph_build", handler=kg_builder.build)
builder.add_step("deduplicate", "dedup", handler=deduplicator.deduplicate)
builder.add_step("export", "rdf_export", handler=exporter.export, format="turtle", path="output.ttl")
# Wire the data flow
builder.connect_steps("ingest", "parse")
builder.connect_steps("parse", "normalize")
builder.connect_steps("normalize", "extract")
builder.connect_steps("extract", "rel_extract")
builder.connect_steps("rel_extract", "build_kg")
builder.connect_steps("build_kg", "deduplicate")
builder.connect_steps("deduplicate", "export")
pipeline = builder.build("full_pipeline")
result = ExecutionEngine().execute_pipeline(pipeline, data="data/")
```
## Serialize and Restore Pipelines
`PipelineSerializer` converts a pipeline to JSON or dict for storage and reloads it later:
```python
from semantica.pipeline import PipelineSerializer
serializer = PipelineSerializer()
# Serialize to JSON string
json_str = serializer.serialize_pipeline(pipeline, format="json")
# Save to file
with open("pipeline_config.json", "w") as f:
f.write(json_str)
# Restore on any machine and execute
with open("pipeline_config.json") as f:
restored = serializer.deserialize_pipeline(f.read())
result = ExecutionEngine().execute_pipeline(restored, data="data/")
```
<Tip>
Serialized pipelines capture step names, types, and config — but not handler functions (callables can't be serialized). Re-register handlers on the restored steps before executing.
</Tip>
## Pre-Built Templates
`PipelineTemplateManager` wires common workflows with the correct step order — no manual wiring required:
```python
from semantica.pipeline import PipelineTemplateManager
from semantica.llms import Groq
import os
llm = Groq(model="llama-3.3-70b-versatile", api_key=os.getenv("GROQ_API_KEY"))
manager = PipelineTemplateManager()
```
<CardGroup cols={2}>
<Card title="ingest-extract-build" icon="diagram-project">
**Ingest → Parse → Extract → Build KG**
Standard knowledge base construction from documents.
```python
pipeline = manager.get_template(
"ingest-extract-build", llm_provider=llm
)
```
</Card>
<Card title="graphrag" icon="magnifying-glass">
**Ingest → Parse → Embed → Index**
Retrieval-augmented generation — builds a vector-indexed knowledge graph.
```python
pipeline = manager.get_template(
"graphrag", llm_provider=llm, vector_backend="faiss"
)
```
</Card>
<Card title="analytics" icon="chart-bar">
**Build KG → Analytics → Export Report**
Graph analysis and reporting — centrality, community detection, HTML output.
```python
pipeline = manager.get_template(
"analytics", export_format="html"
)
```
</Card>
<Card title="full-qa" icon="shield-check">
**Ingest → Normalize → Extract → Dedup → Conflicts → Build**
Production-quality KG with full data quality pipeline.
```python
pipeline = manager.get_template(
"full-qa", llm_provider=llm
)
```
</Card>
</CardGroup>
## ExecutionEngine
Fine-grained control over pipeline execution — pause, resume, cancel, and inspect live progress:
```python
from semantica.pipeline import ExecutionEngine
engine = ExecutionEngine(max_workers=4)
# pipeline.name is the pipeline ID used for all control operations
result = engine.execute_pipeline(pipeline, data="data/")
pipeline_id = pipeline.name # e.g. "my_pipeline"
# Pause after the current step finishes
engine.pause_pipeline(pipeline_id)
progress = engine.get_progress(pipeline_id)
print(f"Completed: {progress['completed_steps']}/{progress['total_steps']}")
print(f"Status: {progress['status']}")
engine.resume_pipeline(pipeline_id)
engine.stop_pipeline(pipeline_id)
```
| Method | Returns | Description |
| ------ | ------- | ----------- |
| `execute_pipeline(pipeline, data)` | `ExecutionResult` | Execute pipeline from start to finish |
| `get_pipeline_status(pipeline_id)` | `PipelineStatus` | Current state (RUNNING, PAUSED, STOPPED) |
| `get_progress(pipeline_id)` | `Dict` | `completed_steps`, `total_steps`, `progress_percentage`, `status` |
| `pause_pipeline(pipeline_id)` | `None` | Suspend after current step completes |
| `resume_pipeline(pipeline_id)` | `None` | Resume from paused state |
| `stop_pipeline(pipeline_id)` | `None` | Cancel and clean up immediately |
## PipelineValidator
Catches problems before they surface as mid-run failures:
```python
from semantica.pipeline import PipelineValidator
validator = PipelineValidator()
result = validator.validate_pipeline(pipeline)
if result.valid:
print("Pipeline is valid — safe to run")
else:
for error in result.errors: # errors is List[str]
print(f"Error: {error}")
for warning in result.warnings: # warnings is List[str]
print(f"Warning: {warning}")
```
Checks performed:
- **Dependency cycle detection** — A depends on B, B depends on A
- **Step type validation** — each step type must be registered
- **Connection integrity** — referenced step names must exist
- **Configuration completeness** — required parameters must be present
## ParallelismManager
<Tabs>
<Tab title="Thread pool (I/O-bound)">
```python
from semantica.pipeline import ParallelismManager
# use_processes=False (default) → thread pool for I/O-bound tasks
manager = ParallelismManager(max_workers=8, use_processes=False)
tasks = [{"fn": ner.extract, "args": [text]} for text in texts]
results = manager.execute_parallel(tasks, timeout=60)
# returns List[ParallelExecutionResult]
successes = [r for r in results if r.success]
failures = [r for r in results if not r.success]
```
Use thread pools for **I/O-bound** steps: web fetching, database queries, API calls.
</Tab>
<Tab title="Process pool (CPU-bound)">
```python
# use_processes=True → process pool, bypasses Python GIL
manager = ParallelismManager(max_workers=4, use_processes=True)
tasks = [{"fn": embedder.generate_embeddings, "args": [chunk]} for chunk in chunks]
results = manager.execute_parallel(tasks, timeout=120)
```
Use process pools for **CPU-bound** steps: embedding, OCR, large NER batches.
</Tab>
</Tabs>
## ResourceScheduler
Prevents memory oversubscription on large runs:
```python
from semantica.pipeline import ResourceScheduler, ExecutionEngine
scheduler = ResourceScheduler()
engine = ExecutionEngine()
resources = scheduler.allocate_resources(pipeline)
try:
result = engine.execute_pipeline(pipeline, data="data/")
finally:
scheduler.release_resources(resources)
```
## Delta Mode
Re-process only data that has changed since the last run:
```python
from semantica.pipeline import PipelineBuilder, ExecutionEngine
builder = PipelineBuilder()
# delta_mode=True tells ExecutionEngine to compute the diff between two snapshots
# and pass only changed triples to this step's handler
builder.add_step(
"ingest", "file_ingest",
handler=ingestor.ingest_file,
delta_mode=True, base_version_id="v1", target_version_id="v2"
)
builder.add_step(
"extract", "ner_extract",
handler=extractor.extract,
delta_mode=True, base_version_id="v1", target_version_id="v2"
)
builder.add_step(
"build", "graph_build",
handler=kg_builder.build,
delta_mode=False # always rebuild the merged graph
)
builder.connect_steps("ingest", "extract")
builder.connect_steps("extract", "build")
result = pipeline.run("data/")
pipeline = builder.build("delta_pipeline")
engine = ExecutionEngine()
result = engine.execute_pipeline(
pipeline,
data="data/",
version_manager=version_manager, # required for delta mode
triplet_store=triplet_store # required for delta mode
)
```
## Save and Load Pipelines
<Note>
Delta detection uses SHA-256 checksums on source content. Only sources whose checksum differs from `base_version_id` are passed to downstream steps. For pipelines that run hourly or daily against a growing corpus, delta mode eliminates redundant re-embedding and re-extraction.
</Note>
Serialize a pipeline to YAML for reproducible runs across environments:
## Schemas
```python
# Save pipeline configuration
pipeline.save("pipeline_config.yaml")
# Load and run on any machine
pipeline = Pipeline.load("pipeline_config.yaml")
result = pipeline.run("data/")
```
## Pipeline Result
<AccordionGroup>
<Accordion title="ExecutionResult schema">
```python
@dataclass
class PipelineResult:
output: Any # final step output (e.g., a KnowledgeGraph)
processed_count: int # documents successfully processed
failed_count: int # documents that failed after retries
duration_seconds: float # total wall-clock time
step_metrics: Dict # per-step timing and counts
errors: List # list of FailedDocument records
class ExecutionResult:
success: bool # True if all steps completed without failure
output: Any # output from the final pipeline step
metadata: Dict[str, Any] # {"pipeline_id": "...", "execution_time": 1.23}
metrics: Dict[str, Any] # {"steps_executed": 4, "steps_failed": 0, "execution_time": 1.23}
errors: List[str] # error messages from failed steps (empty on full success)
# Access pattern
result.success # bool
result.output # final step output
result.metadata["pipeline_id"] # pipeline name used as ID
result.metadata["execution_time"] # total wall-clock seconds
result.metrics["steps_executed"] # count of successfully completed steps
result.metrics["steps_failed"] # count of failed steps
result.errors # List[str] of error messages
```
</Accordion>
<Accordion title="PipelineStep schema">
```python
@dataclass
class PipelineStep:
name: str
step_type: str
config: Dict[str, Any]
dependencies: List[str] # names of steps this step waits for
handler: Optional[Callable]
status: StepStatus
result: Any
error: Optional[Exception]
delta_mode: bool # True = process only changed data
base_version_id: Optional[str] # snapshot ID to diff against
target_version_id: Optional[str] # snapshot ID being produced
```
</Accordion>
<Accordion title="StepStatus enum">
```python
from semantica.pipeline import StepStatus
StepStatus.PENDING # Not yet started
StepStatus.RUNNING # Currently executing
StepStatus.COMPLETED # Finished successfully
StepStatus.FAILED # Error occurred — check step.error
StepStatus.SKIPPED # Skipped due to FailureHandler "skip" strategy
```
</Accordion>
</AccordionGroup>
## Tips and Common Pitfalls
<Tip>
**Use `PipelineValidator` before running in production.** It catches dependency cycles, missing step names, and misconfigured connections that would only surface as errors mid-run. Validation is instant; catching them after a 30-minute extraction job is not.
</Tip>
<Tip>
**Set `workers=` based on workload type.** Thread workers for I/O-bound steps (web fetching, DB queries), process workers for CPU-bound steps (embedding, OCR, large NER batches). Mixing pool types on the wrong step type wastes resources without speed gains.
</Tip>
<Warning>
**Use `failure_handler=FailureHandler(strategy="skip")` in production.** A single malformed document shouldn't stop a pipeline processing 10,000 documents. `skip` logs the failure and continues; inspect `result.errors` after the run to find and reprocess failed documents.
</Warning>
<Tip>
**Use templates from `PipelineTemplateManager` for common patterns.** `get_template("full-qa")` wires up normalization, deduplication, conflict detection, and graph construction in the right order — saving you from common mistakes like deduplicating before normalizing.
</Tip>
<Tip>
**Inspect `result.metrics` to find bottlenecks.** `result.metrics['steps_executed']` and `result.metrics['execution_time']` give a quick read on overall pipeline health. For per-step timing, check `step.result` on each `PipelineStep` after the run.
</Tip>
<CardGroup cols={2}>
<Card title="Ingest" icon="database" href="ingest">
First step in most pipelines.
+95 -49
View File
@@ -1,85 +1,113 @@
---
title: "Provenance Module"
description: "W3C PROV-O compliant lineage tracking, source attribution, and audit trails across all modules."
description: "W3C PROV-O compliant lineage tracking, source attribution, tamper-evident checksums, and audit trails across all modules."
icon: "link"
---
`semantica.provenance` tracks the full lineage of every fact — from raw ingestion through extraction, reasoning, and export. Compliant with W3C PROV-O, suitable for HIPAA, SOX, GDPR, and FDA 21 CFR Part 11 environments.
## Exported Classes
```python
from semantica.provenance import (
ProvenanceManager, # track entities, get lineage, export PROV-O
ProvenanceEntry, # single provenance record (entity_id, source, method, ...)
SourceReference, # rich source pointer (DOI, page, quote, URL)
ProvenanceStorage, # abstract storage backend
InMemoryStorage, # default in-memory backend
SQLiteStorage, # persistent SQLite backend for production
compute_checksum, # compute tamper-evident hash for an entry
verify_checksum, # verify integrity of a stored entry
)
# GraphBuilderWithProvenance is in semantica.kg, not semantica.provenance
from semantica.kg import GraphBuilderWithProvenance
```
## What You Get
- **`ProvenanceManager`** — track entities, relationships, and activities with source attribution
- **`ActivityTracker`** — record pipeline activities and which entities they produced or consumed
- **`ProvenanceManager`** — track entities and relationships with source attribution and lineage retrieval
- **`ProvenanceEntry`** / **`SourceReference`** — structured records with DOI, page, quote, confidence, and timestamp
- **`InMemoryStorage`** / **`SQLiteStorage`** — swappable persistence backends
- **`compute_checksum`** / **`verify_checksum`** — tamper-evident integrity verification
- **Lineage graph** — full upstream lineage from any entity back to its source document
- **W3C PROV-O export** — serialize lineage as Turtle RDF for compliance reporting
- **`GraphBuilderWithProvenance`** — drop-in replacement that auto-tracks every node and edge
- **W3C PROV-O export** — serialize lineage as Turtle RDF or JSON-LD for compliance reporting
- **`GraphBuilderWithProvenance`** (in `semantica.kg`) — drop-in replacement that auto-tracks every node and edge
## ProvenanceManager
```python
from semantica.provenance import ProvenanceManager
from semantica.provenance import ProvenanceManager, InMemoryStorage, SQLiteStorage
manager = ProvenanceManager()
# In-memory (default) — fast, not persisted across restarts
manager = ProvenanceManager(storage=InMemoryStorage())
# Track an extracted entity
# SQLite — persisted, production-ready
manager = ProvenanceManager(storage=SQLiteStorage("provenance.db"))
# Track an extracted entity (with rich source reference)
manager.track_entity(
entity_id="apple_inc",
source="annual_report_2023.pdf",
entity_type="Organization",
extraction_method="llm",
confidence=0.98
source_location="Page 12, Section 3.1",
source_quote="Apple Inc. was incorporated on January 3, 1977.",
confidence=0.98,
)
# Track an extracted relationship
manager.track_relationship(
rel_id="steve_jobs_founded_apple",
manager.track_entity(
entity_id="steve_jobs_founded_apple",
source="annual_report_2023.pdf",
extraction_method="llm",
confidence=0.92
confidence=0.92,
)
# Retrieve full lineage for any entity
lineage = manager.get_lineage("apple_inc")
print(f"Source: {lineage.source}")
print(f"Extracted: {lineage.extracted_at}")
print(f"Method: {lineage.extraction_method}")
print(f"Confidence: {lineage.confidence}")
entry = manager.get_lineage("apple_inc")
print(f"Source: {entry.source}")
print(f"Quote: {entry.source_quote}")
print(f"Confidence: {entry.confidence}")
print(f"Tracked at: {entry.tracked_at}")
```
## Activity Tracking
## SourceReference
Record pipeline activities — what was consumed and what was produced:
`SourceReference` provides a rich, citable pointer to the exact location in a source document:
```python
# Start and end an activity
activity_id = manager.start_activity(
activity_type="ner_extraction",
used=["annual_report_2023.pdf"],
generated=["apple_inc", "steve_jobs"]
from semantica.provenance import SourceReference
ref = SourceReference(
document_id="annual_report_2023.pdf",
page=12,
section="3.1",
quote="Apple Inc. was incorporated on January 3, 1977.",
url="https://investor.apple.com/sec-filings/annual-reports/",
doi="10.0000/example.doi",
)
manager.end_activity(activity_id)
# Query activities for an entity
activities = manager.get_activities(entity_id="apple_inc")
for activity in activities:
print(f"{activity.type} at {activity.started_at}")
print(f" Used: {activity.used}")
print(f" Generated: {activity.generated}")
manager.track_entity(
entity_id="apple_inc",
source_reference=ref,
confidence=0.98,
)
```
## Lineage Graph
## Tamper-Evident Checksums
Retrieve a full directed lineage graph from any entity back to its source:
Verify that provenance records have not been modified after creation:
```python
lineage_graph = manager.get_lineage_graph("apple_inc")
from semantica.provenance import compute_checksum, verify_checksum
for node in lineage_graph.nodes:
print(f"{node.id}: {node.type}{node.timestamp}")
entry = manager.get_lineage("apple_inc")
for edge in lineage_graph.edges:
print(f"{edge.source}{edge.target} ({edge.relation})")
# Compute and store a checksum on first write
checksum = compute_checksum(entry)
# Later: verify the entry hasnt been altered
is_valid = verify_checksum(entry, checksum)
if not is_valid:
raise RuntimeError("Provenance record has been tampered with!")
```
## W3C PROV-O Export
@@ -99,18 +127,36 @@ manager.export_all(path="provenance.jsonld", format="json-ld")
## Integration with GraphBuilder
`GraphBuilderWithProvenance` automatically records provenance for every node and edge constructed:
`GraphBuilderWithProvenance` (from `semantica.kg`) automatically records provenance for every node and edge constructed:
```python
from semantica.kg import GraphBuilderWithProvenance
from semantica.provenance import ProvenanceManager, SQLiteStorage
builder = GraphBuilderWithProvenance(provenance=True)
result = builder.build_single_source(graph_data)
prov_manager = ProvenanceManager(storage=SQLiteStorage("provenance.db"))
builder = GraphBuilderWithProvenance(provenance_manager=prov_manager)
kg = builder.build_single_source(graph_data)
# Each node and edge has a source_id linking back to the originating document
lineage = result.provenance_manager.get_lineage("apple_inc")
print(f"Source document: {lineage.source}")
print(f"Extracted by: {lineage.extraction_method}")
# Every node and edge now has full source attribution
entry = prov_manager.get_lineage("apple_inc")
print(f"Source document: {entry.source}")
print(f"Confidence: {entry.confidence}")
```
## Enable Provenance in Extractors
```python
from semantica.semantic_extract import NERExtractor
from semantica.provenance import ProvenanceManager
prov_manager = ProvenanceManager()
ner = NERExtractor(method="llm", llm_provider=llm, provenance=True)
entities = ner.extract(text)
# Retrieve lineage for the first extracted entity
entry = prov_manager.get_lineage(entities[0]["id"])
print(f"Source: {entry.source}")
```
## Compliance Standards
+70 -3
View File
@@ -6,6 +6,35 @@ icon: "microchip"
`semantica.reasoning` derives new knowledge from existing facts using logical rules. Every engine produces **explainable inference paths** — traceable chains of rules and facts, not black-box conclusions.
## Exported Classes
```python
from semantica.reasoning import (
# Engines
Reasoner, # IF/THEN forward-chaining facade
GraphReasoner, # inference over full KG structure
ReteEngine, # high-performance Rete pattern matching
SPARQLReasoner, # SPARQL-based RDF inference
DatalogReasoner, # recursive Horn clause fixpoint evaluation
TemporalReasoningEngine, # Allen interval algebra (13 relations)
ExplanationGenerator, # structured step-by-step explanations
# Data types
Rule, # IF/THEN rule definition
Fact, # base fact (subject, predicate, obj)
RuleType, # enum: FORWARD_CHAIN, BACKWARD_CHAIN, ...
InferenceResult, # result of infer() — contains derived_facts list
DatalogFact, # Datalog base fact (predicate, args tuple)
DatalogRule, # Datalog Horn clause ("head :- body.")
TemporalInterval, # time interval with start/end
IntervalRelation, # enum of 13 Allen relations
# Explanation types
Explanation, # conclusion + confidence + reasoning_path
ReasoningPath, # ordered list of ReasoningSteps
ReasoningStep, # single step: fact + rule_name + depth
Justification, # full justification record
)
```
## What You Get
- **`Reasoner`** — main facade for IF/THEN forward-chaining with variable substitution
@@ -16,6 +45,28 @@ icon: "microchip"
- **`TemporalReasoningEngine`** — all 13 Allen interval algebra relations for time-aware inference
- **`ExplanationGenerator`** — structured explanation paths for every derived conclusion
## Quick Start
The most common pattern: add facts + rules, run inference, explain a conclusion:
```python
from semantica.reasoning import Reasoner, Rule, Fact, RuleType, InferenceResult
reasoner = Reasoner()
reasoner.add_fact(Fact(subject="Alice", predicate="is_a", obj="Manager"))
reasoner.add_rule(Rule(
rule_type=RuleType.FORWARD_CHAIN,
conditions=[{"subject": "?x", "predicate": "is_a", "object": "Manager"}],
conclusion={"subject": "?x", "predicate": "has_authority", "object": "true"},
))
result: InferenceResult = reasoner.infer()
for fact in result.derived_facts:
print(f"{fact.subject} {fact.predicate} {fact.obj}")
print(f" via: {fact.explanation}")
```
<img src="/assets/img/diagrams/reasoning-chain.svg" alt="Forward chaining inference: known facts + IF/THEN rules produce derived facts with a full traceable explanation path" style={{ width: '100%', borderRadius: '12px', margin: '0 0 24px' }} />
## Reasoner (Main Facade)
@@ -202,22 +253,38 @@ All 13 Allen interval algebra relations are supported:
Generate structured step-by-step explanations for any derived conclusion:
```python
from semantica.reasoning import ExplanationGenerator
from semantica.reasoning import ExplanationGenerator, Explanation, ReasoningStep
generator = ExplanationGenerator(reasoner)
explanation = generator.explain(
explanation: Explanation = generator.explain(
conclusion={"subject": "John", "predicate": "has_authority", "object": "true"}
)
print(explanation.conclusion)
print(f"Conclusion: {explanation.conclusion}")
print(f"Confidence: {explanation.confidence:.2f}")
step: ReasoningStep
for step in explanation.reasoning_path.steps:
print(f" Step {step.depth}: {step.fact}")
print(f" via rule: '{step.rule_name}'")
```
## Choosing an Engine
| Engine | Best For | Termination | Complexity |
| ------ | -------- | ----------- | ---------- |
| `Reasoner` | Simple IF/THEN rules, templates | Always | Low |
| `GraphReasoner` | KG-wide structural inference | Always | Medium |
| `ReteEngine` | Large rule sets (100+ rules) | Always | Low per-match |
| `SPARQLReasoner` | RDF graphs with SPARQL endpoint | Always | Low |
| `DatalogReasoner` | Recursive rules (ancestry, reachability) | Guaranteed fixpoint | Medium |
| `TemporalReasoningEngine` | Time interval relationships | Always | Low |
<Tip>
For recursive rules (e.g. ancestor, reachability, transitivity), always use `DatalogReasoner` — it guarantees termination via semi-naive bottom-up fixpoint evaluation. `Reasoner` does not handle recursion.
</Tip>
<CardGroup cols={2}>
<Card title="Knowledge Graph" icon="diagram-project" href="kg">
The knowledge graph being reasoned over.
+322 -83
View File
@@ -1,119 +1,323 @@
---
title: "Seed Module"
description: "Seed data management for initializing Knowledge Graphs from trusted, verified sources."
description: "Bootstrap Knowledge Graphs from verified, structured sources — taxonomies, reference tables, product catalogs, and domain anchors."
icon: "database"
---
`semantica.seed` provides a system for bootstrapping Knowledge Graphs with verified, structured data from trusted sources — taxonomies, reference tables, user lists, product catalogs — so you start with a reliable foundation rather than an empty graph.
`semantica.seed` gives your knowledge graph a reliable starting point. Rather than building from an empty graph and hoping extraction produces consistent reference data, you load verified, structured sources first — ISO codes, employee rosters, product catalogs, domain taxonomies — then merge freshly extracted data on top.
## Exported Classes
```python
from semantica.seed import (
SeedDataManager, # coordinator: register_source, create_foundation_graph, integrate_with_extracted
SeedDataSource, # {name, source_type, path, config} — dataclass for a registered source
SeedData, # {entities, relationships, metadata} — loaded seed data container
)
```
## What You Get
- **`SeedDataManager`** — register sources, build a foundation graph, and integrate with extracted data
- **`SeedDataSource`** — define individual data sources with format, path, and config
- **Merge strategies** — `seed_first`, `extracted_first`, `smart_merge` for combining seed and extracted data
- **Validation** — check data quality and schema compliance before loading
- **Versioning** — track and manage versions of seed data sources
<CardGroup cols={2}>
<Card title="SeedDataManager" icon="database">
Register sources, build a foundation graph, validate quality, and merge with extracted data.
</Card>
<Card title="SeedDataSource" icon="file-code">
Typed source definition supporting CSV, JSON, SQL, API, and RDF with format-specific config.
</Card>
<Card title="Foundation Graph" icon="circle-plus">
Build a foundation graph from all registered sources in one pass, ready to merge with extracted data.
</Card>
<Card title="Merge Strategies" icon="arrows-merge">
`seed_first`, `extracted_first`, and `smart_merge` with property-level conflict detection.
</Card>
<Card title="Validation" icon="shield-check">
Required field checks, ID uniqueness, type consistency, reference integrity, and encoding validation before loading.
</Card>
<Card title="Versioning" icon="clock-rotate-left">
Track seed data versions across pipeline runs and diff changes between versions.
</Card>
</CardGroup>
<Tip>
**When to use the Seed Module:**
- **Bootstrapping** — you have existing structured data (taxonomies, user lists, product catalogs) to build on
- **Reference data** — load immutable reference information (countries, ISO codes, ontology terms)
- **Testing** — load consistent, reproducible datasets for development and CI pipelines
**When to use the Seed Module:** Bootstrapping with structured reference data (taxonomies, user lists, product catalogs), loading immutable facts (ISO country codes, standard ontology terms) that extracted data should not override, ensuring test reproducibility with deterministic datasets, and anchoring entity disambiguation with canonical forms.
</Tip>
## SeedDataManager
## Quick Start
The primary interface for seed data:
<Steps>
<Step title="Register your seed sources">
```python
from semantica.seed import SeedDataManager
manager = SeedDataManager()
manager.register_source("countries", "csv", "data/countries.csv")
manager.register_source("taxonomy", "json", "data/taxonomy.json")
manager.register_source("employees", "csv", "data/employees.csv")
```
</Step>
<Step title="Build the foundation graph">
```python
foundation_kg = manager.create_foundation_graph()
print(f"Foundation nodes: {foundation_kg.node_count}")
print(f"Foundation edges: {foundation_kg.edge_count}")
```
</Step>
<Step title="Validate before loading">
```python
report = manager.validate_quality(manager.load_source("employees"))
if not report.is_valid:
for issue in report.issues:
print(f"[{issue.severity}] Row {issue.row}: {issue.message}")
else:
print(f"Validated {report.record_count} records — no issues found")
```
</Step>
<Step title="Merge with extracted data">
```python
final_kg = manager.integrate_with_extracted(
seed_graph=foundation_kg,
extracted_data=new_entities,
strategy="smart_merge",
)
print(f"Final graph: {final_kg.node_count} nodes, {final_kg.edge_count} edges")
```
</Step>
</Steps>
## SeedDataSource Types
<Tabs>
<Tab title="CSV">
```python
from semantica.seed import SeedDataSource
csv_source = SeedDataSource(
name="employees",
type="csv",
path="data/employees.csv",
config={
"delimiter": ";",
"encoding": "utf-8",
"id_column": "employee_id",
"type": "Person",
}
)
manager.register_source("employees", "csv", csv_source.path, config=csv_source.config)
```
Best for: employee rosters, product lists, reference tables.
</Tab>
<Tab title="JSON">
```python
json_source = SeedDataSource(
name="taxonomy",
type="json",
path="data/taxonomy.json",
config={"encoding": "utf-8"}
)
```
Expects an array of entity objects. Best for: taxonomies, ontology term lists, structured configs.
</Tab>
<Tab title="SQL">
```python
sql_source = SeedDataSource(
name="products",
type="sql",
path="postgresql://user:pass@localhost/db",
config={"query": "SELECT id, name, category FROM products WHERE active = true"}
)
```
Best for: live database tables — PostgreSQL, MySQL, SQLite.
</Tab>
<Tab title="API & RDF">
```python
# API source — fetch from a REST endpoint
api_source = SeedDataSource(
name="geo_codes",
type="api",
path="https://restcountries.com/v3.1/all",
config={"fields": ["name", "cca2", "region"]}
)
# RDF source — OWL ontologies or Turtle files
rdf_source = SeedDataSource(
name="domain_ontology",
type="rdf",
path="data/ontology.ttl",
config={"format": "turtle"}
)
```
API: external reference APIs (countries, currencies, geo). RDF: existing knowledge bases, OWL ontologies.
</Tab>
<Tab title="Type Reference">
| Type | Path Format | Use Case |
| ---- | ----------- | -------- |
| `csv` | File path | Employee rosters, product lists, reference tables |
| `json` | File path | Taxonomies, ontology term lists, structured configs |
| `sql` | Connection string | Live database tables — PostgreSQL, MySQL, SQLite |
| `api` | URL | External reference APIs (countries, currencies, geo) |
| `rdf` | File path | OWL ontologies, Turtle files, existing knowledge bases |
</Tab>
</Tabs>
## SeedDataManager Reference
| Method | Description |
| ------ | ----------- |
| `register_source(name, format, path)` | Add a named data source to the registry |
| `create_foundation_graph()` | Build a KG from all registered sources |
| `validate_quality(seed_data)` | Check schema compliance, required fields, and duplicates |
| `integrate_with_extracted(seed, extracted, strategy)` | Merge seed and extracted graphs |
| `export_seed_data(path, format)` | Export seed graph to RDF (`turtle`, `json-ld`), JSON, or CSV |
| `load_from_csv(path)` | Load seed records from a CSV file |
| `load_from_json(path)` | Load seed records from a JSON file |
| `list_sources()` | List all registered source names and their formats |
| `get_version(name)` | Get the current version metadata for a named source |
## Merge Strategies
<Tabs>
<Tab title="seed_first">
Seed data wins on every conflicting property. Use when seed encodes authoritative reference facts that must not be overridden.
```python
final_kg = manager.integrate_with_extracted(
seed_graph=foundation_kg,
extracted_data=new_entities,
strategy="seed_first",
)
```
Best for: ISO codes, canonical entity names, official taxonomy IDs, employee records.
</Tab>
<Tab title="extracted_first">
Extracted data overrides seed on conflicting properties. Use when new documents contain more current information than your reference data.
```python
final_kg = manager.integrate_with_extracted(
seed_graph=foundation_kg,
extracted_data=new_entities,
strategy="extracted_first",
)
```
Best for: frequently changing attributes like addresses, titles, revenue figures.
</Tab>
<Tab title="smart_merge">
Property-level merge with conflict detection — irresolvable conflicts are logged for manual review rather than silently overwritten.
```python
final_kg = manager.integrate_with_extracted(
seed_graph=foundation_kg,
extracted_data=new_entities,
strategy="smart_merge",
)
```
Best for: general-purpose pipelines where surfacing conflicts is more valuable than silently losing data.
</Tab>
</Tabs>
## Built-in Datasets
Register built-in reference datasets as named sources and load them into your foundation graph:
```python
from semantica.seed import SeedDataManager
manager = SeedDataManager()
manager.register_source("countries", "csv", "data/countries.csv")
manager.register_source("taxonomy", "json", "data/taxonomy.json")
# Build a foundation KG from all registered sources
# Register built-in reference sources by format and path
manager.register_source("countries", "csv", "data/iso_countries.csv")
manager.register_source("currencies", "json", "data/iso_currencies.json")
foundation_kg = manager.create_foundation_graph()
```
### Core Methods
| Dataset | Content |
| ------- | ------- |
| `companies` | Fortune 500 companies with type, sector, HQ |
| `countries` | ISO 3166 country codes, regions, populations |
| `currencies` | ISO 4217 codes, symbols, names |
| `person_names` | Common first/last names for synthetic data |
| Method | Description |
| ------ | ----------- |
| `register_source(name, format, location)` | Add a data source to the registry |
| `create_foundation_graph()` | Build a KG from all registered sources |
| `validate_quality(seed_data)` | Check data quality and completeness |
| `integrate_with_extracted(seed, extracted)` | Merge seed and extracted graphs |
| `export_seed_data(path, format)` | Export seed data to RDF, JSON, or CSV |
## SeedDataSource
Define a source with format-specific configuration:
```python
from semantica.seed import SeedDataSource
source = SeedDataSource(
name="taxonomy",
type="json", # "csv" | "json" | "api" | "sql"
path="taxonomy.json",
config={"encoding": "utf-8"}
)
```
## Merge Strategies
Control how seed data and extracted data are combined:
```python
final_kg = manager.integrate_seed_extracted(
seed_graph=foundation_kg,
extracted_data=new_data,
strategy="seed_first" # see options below
)
```
| Strategy | Behavior |
| -------- | -------- |
| `seed_first` | Seed data wins on conflicts — use for authoritative reference data |
| `extracted_first` | Extracted data overrides seed — use when new data is more current |
| `smart_merge` | Property-level merging with conflict detection and resolution |
## Bootstrapping a KG
Full example — load foundation data then merge with freshly ingested content:
## Full Pipeline Example
```python
from semantica.seed import SeedDataManager
from semantica.ingest import FileIngestor
from semantica.semantic_extract import NERExtractor
from semantica.parse import DocumentParser
from semantica.split import TextSplitter
from semantica.semantic_extract import NERExtractor, RelationExtractor
from semantica.kg import GraphBuilder
from semantica.llms import Groq
import os
# Build foundation from verified reference data
manager = SeedDataManager()
manager.register_source("taxonomy", "json", "taxonomy.json")
manager.register_source("employees", "csv", "employees.csv")
foundation_kg = manager.create_foundation_graph()
# Ingest and extract from new sources
llm = Groq(model="llama-3.3-70b-versatile", api_key=os.getenv("GROQ_API_KEY"))
ingestor = FileIngestor()
ner = NERExtractor(method="llm", llm_provider=llm)
sources = ingestor.ingest("news_articles/")
new_data = [ner.extract(s.content) for s in sources]
# Step 1 — Build the foundation from verified reference data
seed_manager = SeedDataManager()
seed_manager.register_source("taxonomy", "json", "data/taxonomy.json")
seed_manager.register_source("employees", "csv", "data/employees.csv")
foundation_kg = seed_manager.create_foundation_graph()
# Merge — seed data takes precedence for reference facts
final_kg = manager.integrate_seed_extracted(
# Step 2 — Ingest and extract from unstructured documents
ingestor = FileIngestor()
parser = DocumentParser()
splitter = TextSplitter(method="semantic_transformer", chunk_size=512)
ner = NERExtractor(method="llm", llm_provider=llm)
rel_ext = RelationExtractor(method="llm", llm_provider=llm)
sources = ingestor.ingest("news_articles/")
extracted_entities = []
extracted_relationships = []
for source in sources:
parsed = parser.parse(source)
chunks = splitter.split_document(parsed)
for chunk in chunks:
entities = ner.extract(chunk.text)
relationships = rel_ext.extract(chunk.text, entities=entities)
extracted_entities.extend(entities)
extracted_relationships.extend(relationships)
# Step 3 — Merge seed and extracted data
final_kg = seed_manager.integrate_with_extracted(
seed_graph=foundation_kg,
extracted_data=new_data,
strategy="seed_first"
extracted_data=extracted_entities,
strategy="seed_first",
)
print(f"Final graph: {final_kg.node_count} nodes, {final_kg.edge_count} edges")
```
## Configuration
## Versioning
Track seed data versions to detect when reference data changes between pipeline runs:
```python
manager = SeedDataManager()
manager.register_source("taxonomy", "json", "data/taxonomy.json")
version = manager.get_version("taxonomy")
print(f"Version: {version.version_id}")
print(f"Hash: {version.checksum}")
print(f"Records: {version.record_count}")
print(f"Updated: {version.last_modified}")
```
## YAML Configuration
Define sources in YAML for production deployments — no code changes needed to switch environments:
```yaml
seed:
@@ -121,22 +325,57 @@ seed:
- name: "employees"
type: "csv"
path: "./data/employees.csv"
config:
id_column: "employee_id"
type: "Person"
- name: "taxonomy"
type: "json"
path: "./data/taxonomy.json"
- name: "products"
type: "sql"
path: "${DATABASE_URL}"
config:
query: "SELECT id, name, category FROM products WHERE active = true"
merge:
strategy: "seed_first"
strategy: "smart_merge"
validation:
strict: true
required_fields: ["id", "type"]
```
Environment variable overrides:
```bash
export SEED_DATA_DIR=./data/seed
export SEED_MERGE_STRATEGY=seed_first
export SEMANTICA_SEED_DATA_DIR=./data/seed
export SEMANTICA_SEED_MERGE_STRATEGY=seed_first
```
## Tips and Common Pitfalls
<Warning>
**Load seed data before extracted data.** Seed data is your ground truth — normalised, curated, and already de-duplicated. Load it first with `create_foundation_graph()`, then merge extracted entities on top. Merging in the wrong order lets noisy extracted data overwrite trusted reference values.
</Warning>
<Tip>
**Use `seed_first` merge strategy for reference data.** When seed data encodes authoritative facts (official company names, canonical taxonomy IDs, employee records), `strategy="seed_first"` ensures those values win over extracted values. Use `smart_merge` only when extracted data may be more current than the seed.
</Tip>
<Warning>
**Validate before loading.** `manager.validate_quality(seed_data)` catches missing required fields, type inconsistencies, and duplicate IDs before they corrupt your graph. Running validation after loading means you'll need to roll back. Validation is fast — always run it first.
</Warning>
<Tip>
**Register all sources before calling `create_foundation_graph()`.** `create_foundation_graph()` processes all registered sources in one pass. Registering a source after calling it means that source is silently excluded. Register all sources at the start of your script, then call `create_foundation_graph()` once.
</Tip>
<Tip>
**Track seed versions to detect drift.** Use `manager.get_version()` to check the checksum and record count for each registered source between pipeline runs. If a taxonomy file changes, downstream entity normalisation and deduplication thresholds may need re-tuning — don't treat seed data as static.
</Tip>
<Tip>
**Use YAML configuration for production deployments.** Hard-coding source paths in Python scripts makes environment-switching (dev → staging → prod) fragile. Declare sources in `config.yaml` under the `seed:` key and override paths with `SEMANTICA_SEED_DATA_DIR`. This way, the same code runs in every environment.
</Tip>
<CardGroup cols={2}>
<Card title="Ingest" icon="file-import" href="ingest">
Load unstructured data alongside seed data.
@@ -148,6 +387,6 @@ export SEED_MERGE_STRATEGY=seed_first
Handle duplicates during seed-extracted merge.
</Card>
<Card title="Pipeline" icon="gear" href="pipeline">
Incorporate seed loading as a pipeline step.
Incorporate seed loading as a named pipeline step.
</Card>
</CardGroup>
+56 -7
View File
@@ -6,14 +6,56 @@ icon: "magnifying-glass-chart"
`semantica.semantic_extract` extracts structured information from unstructured text — the foundation of every knowledge graph in Semantica. All extractors support three modes: pattern-based (no API key), ML-based, and LLM-based.
## Exported Classes
```python
from semantica.semantic_extract import (
# Primary extractors
NamedEntityRecognizer, # full NER coordinator (confidence_threshold, merge_overlapping)
NERExtractor, # core NER implementation used by NamedEntityRecognizer
RelationExtractor, # typed relationship extraction
TripletExtractor, # (subject, predicate, object) triplet generation
EventDetector, # event detection with participants and temporal context
CoreferenceResolver, # resolve pronouns and aliases to canonical entities
# Data types
Entity, # {id, text, type, confidence, start, end}
Relation, # {subject, predicate, object, confidence}
Event, # {type, participants, temporal, location, confidence}
CoreferenceChain, # list of mentions resolving to the same entity
# Advanced
EntityClassifier, # classify entity candidates by type
CustomEntityDetector, # pattern/dictionary-based custom entity detection
TemporalEventProcessor, # extract temporal information from events
)
```
<Tip>
`NamedEntityRecognizer` is the high-level coordinator with confidence thresholding and overlap merging. `NERExtractor` is the lower-level implementation. For most use cases, start with `NERExtractor` for simplicity or `NamedEntityRecognizer` for fine-grained control.
</Tip>
## What You Get
- **`NERExtractor`** — named entity recognition: Person, Organization, Location, Date, and custom types
- **`NERExtractor`** / **`NamedEntityRecognizer`** — named entity recognition: Person, Organization, Location, Date, and custom types
- **`RelationExtractor`** — typed semantic relationships between entities (`founded_by`, `located_in`, etc.)
- **`TripletExtractor`** — direct `(subject, predicate, object)` triplet generation for RDF-ready output
- **`EventExtractor`** — event detection with participants, temporal context, and confidence scores
- **`EventDetector`** — event detection with participants, temporal context, and confidence scores
- **`CoreferenceResolver`** — resolve "Apple" and "the company" to the same entity across a document
## Quick Start
```python
from semantica.semantic_extract import NERExtractor, RelationExtractor, TripletExtractor
from semantica.llms import Groq
import os
text = "Apple Inc. was founded by Steve Jobs in Cupertino in 1976."
llm = Groq(model="llama-3.3-70b-versatile", api_key=os.getenv("GROQ_API_KEY"))
entities = NERExtractor(method="llm", llm_provider=llm).extract(text)
relationships = RelationExtractor(method="llm", llm_provider=llm).extract(text, entities=entities)
triplets = TripletExtractor(method="llm", llm_provider=llm).extract(text)
```
<img src="/assets/img/diagrams/extraction-pipeline.svg" alt="Semantic extraction pipeline: raw text fans into NER, Relation, and Coreference extractors, then merges into a Triplet Generator" style={{ width: '100%', borderRadius: '12px', margin: '0 0 24px' }} />
## NERExtractor
@@ -97,18 +139,25 @@ triplets = trip.extract(text)
Triplets are suitable for loading directly into a triplet store or knowledge graph.
## EventExtractor
## EventDetector
Detect events with participants and temporal context:
```python
from semantica.semantic_extract import EventExtractor
from typing import List
from semantica.semantic_extract import EventDetector, Event
extractor = EventExtractor(method="llm", llm_provider=llm)
events = extractor.extract(text)
extractor = EventDetector(method="llm", llm_provider=llm)
events: List[Event] = extractor.extract(text)
for event in events:
print(f"Event type: {event.type}")
print(f"Participants: {event.participants}")
print(f"Temporal: {event.temporal}")
print(f"Confidence: {event.confidence:.2f}")
```
Output includes: event type, participants (with roles), temporal information, location, and confidence score.
Output fields per event: `type`, `participants` (with roles), `temporal`, `location`, and `confidence`.
## CoreferenceResolver
+388 -89
View File
@@ -1,145 +1,444 @@
---
title: "Split Module"
description: "15+ text chunking methods including recursive, semantic, entity-aware, and relation-aware splitting."
description: "15+ text chunking methods including recursive, semantic, entity-aware, relation-aware, code, and structural splitting."
icon: "scissors"
---
`semantica.split` breaks documents into chunks while preserving semantic context — critical for embedding quality in RAG systems and accurate entity extraction in NER pipelines.
`semantica.split` breaks documents into chunks that preserve semantic context. Chunking quality directly determines downstream accuracy — a poorly chunked document produces bad embeddings, missed entities, and broken relation triplets. Use the right strategy for your content type and pipeline goal.
## Why Chunking Matters
Most LLMs and embedding models have fixed context windows. Documents larger than that window must be split. But naive splitting (every 500 characters, regardless of structure) destroys semantic context:
- An entity mention like "Apple Inc." split across two chunks loses its context in both
- A relation triplet like "Steve Jobs founded Apple" split at "Steve Jobs" leaves a dangling subject
- Embedding a chunk that mixes two unrelated topics produces a centroid vector that matches neither
Semantica's chunking methods are designed to avoid these failure modes.
## Exported Classes
```python
from semantica.split import (
# Unified splitter (start here)
TextSplitter, # method=: recursive, sentence, token, semantic_transformer,
# entity_aware, relation_aware, code, structural, markdown
Splitter, # alias for TextSplitter (backward compat)
# Data type
Chunk, # {text, start_char, end_char, token_count, metadata, entities, relationships}
# Specialized chunkers
SemanticChunker, # embedding-based semantic boundary detection
StructuralChunker, # heading/section-based splits from ParsedDocument
SlidingWindowChunker, # fixed-size sliding window with overlap
TableChunker, # table-specific chunking
EntityAwareChunker, # KG: preserves named entities across chunk boundaries
RelationAwareChunker, # KG: keeps subject-predicate-object triplets intact
GraphBasedChunker, # splits based on graph community structure
OntologyAwareChunker, # splits respecting ontology concept boundaries
HierarchicalChunker, # multi-level hierarchical chunking
ProvenanceTracker, # track chunk provenance back to source document
# Convenience split functions
split_recursive, # split_recursive(text, chunk_size, chunk_overlap)
split_by_sentences, # split_by_sentences(text)
split_by_tokens, # split_by_tokens(text, chunk_size, tokenizer)
split_semantic_transformer, # split_semantic_transformer(text, threshold)
split_entity_aware, # split_entity_aware(text, entities)
split_relation_aware, # split_relation_aware(text, relationships)
)
```
## What You Get
- **`TextSplitter`** — unified interface for 9+ chunking strategies
- **Entity-aware chunking** — entity mentions never split across chunk boundaries
- **Relation-aware chunking** — subjectpredicateobject triplets kept intact
- **Semantic chunking** — split at topic shift boundaries using embedding similarity
- **`Chunk`** — output object with text, token count, character offsets, and metadata
<CardGroup cols={2}>
<Card title="TextSplitter" icon="scissors">
Unified interface for 11 chunking strategies — swap methods without changing downstream code.
</Card>
<Card title="Semantic Chunking" icon="brain">
Embedding-based topic shift detection — splits only when the topic actually changes.
</Card>
<Card title="Entity-Aware Chunking" icon="user">
Entity spans never cross chunk boundaries — guaranteed by boundary adjustment.
</Card>
<Card title="Relation-Aware Chunking" icon="arrows-left-right">
Subjectpredicateobject triplets kept within a single chunk for KG pipelines.
</Card>
<Card title="Code Splitting" icon="code">
AST-level boundaries (function, class, method) for source code search and analysis.
</Card>
<Card title="Chunk Object" icon="box">
Output dataclass with text, token count, character offsets, entities, and full metadata.
</Card>
</CardGroup>
## TextSplitter
## Quick Start
```python
from semantica.split import TextSplitter
<Steps>
<Step title="Choose a splitting method">
```python
from semantica.split import TextSplitter
splitter = TextSplitter(
method="semantic_transformer", # see methods table below
chunk_size=1000, # target tokens per chunk
chunk_overlap=200 # token overlap between adjacent chunks
)
splitter = TextSplitter(
method="recursive", # see Splitting Methods table
chunk_size=1000,
chunk_overlap=200,
)
```
</Step>
<Step title="Split raw text">
```python
chunks = splitter.split(text)
chunks = splitter.split(text)
for chunk in chunks:
print(f"Chunk {chunk.metadata['chunk_index']}: {chunk.text[:80]}...")
print(f" Tokens: {chunk.token_count}")
```
for chunk in chunks:
print(f"Chunk {chunk.metadata['chunk_index']} / {chunk.metadata['total_chunks']}")
print(f" Tokens: {chunk.token_count}")
print(f" Preview: {chunk.text[:80]}...")
```
</Step>
<Step title="Or split a ParsedDocument">
```python
from semantica.parse import DocumentParser
parser = DocumentParser()
parsed = parser.parse("annual_report.pdf")
splitter = TextSplitter(method="structural")
chunks = splitter.split_documents([parsed])
for chunk in chunks:
print(f"[h{chunk.metadata['heading_level']}] {chunk.metadata['section_title']}")
```
</Step>
<Step title="Batch-split a list of documents">
```python
all_chunks = splitter.split_documents(parsed_docs)
from collections import defaultdict
by_source = defaultdict(list)
for chunk in all_chunks:
by_source[chunk.metadata['source_id']].append(chunk)
```
</Step>
</Steps>
## Splitting Methods
| Method | Description | Best For |
| ------ | ----------- | -------- |
| `recursive` | Split by paragraph → sentence → word (cascading) | General purpose |
| `semantic_transformer` | Split at semantic topic boundaries via sentence transformer | RAG retrieval |
| `entity_aware` | Keep entity mentions intact across boundaries | NER pipelines |
| `relation_aware` | Keep relation triplets intact | KG construction |
| `sentence` | Split by sentence boundary | Short content |
| `token` | Split by token count (tiktoken) | LLM context windows |
| `fixed` | Fixed character count with overlap | Batch processing |
| `markdown` | Split by Markdown heading hierarchy | Documentation |
| `code` | Split by function/class/method boundaries | Code analysis |
| Method | How It Splits | Best For |
| ------ | ------------- | -------- |
| `recursive` | Paragraph → sentence → word (cascading fallback) | General-purpose default |
| `semantic_transformer` | Embeds sentences, splits at cosine similarity drops | RAG — topic coherence matters |
| `entity_aware` | Adjusts boundaries so entity spans are never cut | NER pipelines |
| `relation_aware` | Keeps subjectpredicateobject triplets within one chunk | KG construction |
| `sentence` | Language-aware sentence boundary detection (NLTK/spaCy) | Short documents, Q&A |
| `token` | Exact token count via tiktoken; hard cutoff | LLM context window prep |
| `fixed` | Fixed character count with overlap; fastest, no NLP | Simple batch jobs |
| `sliding_window` | Fixed-step window heavy overlap for dense retrieval | Bi-encoder retrieval (ColBERT, DPR) |
| `markdown` | Splits at Markdown heading levels (configurable) | Documentation, wikis, MDX |
| `structural` | Splits at `ParsedDocument.sections` boundaries | Structured PDFs and DOCX |
| `code` | AST-level splits at function / class / method boundaries | Source code search and analysis |
## Entity-Aware Chunking
## Choosing a Strategy
Entity mentions are never split across chunk boundaries, preserving context for downstream NER:
Use this decision tree before picking a method:
```python
from semantica.split import TextSplitter
from semantica.semantic_extract import NERExtractor
- **Source code?** → `code`
- **Markdown or structured doc with headings?** → `markdown` or `structural`
- **Building a KG?** → `relation_aware` (keeps triplets intact), then `entity_aware` for pure NER
- **RAG system where retrieval quality matters most?** → `semantic_transformer`
- **Dense overlap for bi-encoder retrieval (ColBERT, DPR)?** → `sliding_window`
- **Preparing prompts for a fixed-window LLM?** → `token`
- **Fast splitting with no NLP overhead?** → `recursive` or `fixed`
ner = NERExtractor()
entities = ner.extract(text)
splitter = TextSplitter(method="entity_aware")
chunks = splitter.split(text, entities=entities)
# → Each chunk contains only complete entity mentions
```
## Relation-Aware Chunking
Subjectpredicateobject triplets are kept within the same chunk:
## TextSplitter Constructor
```python
from semantica.split import TextSplitter
splitter = TextSplitter(method="relation_aware")
chunks = splitter.split(text, relationships=relationships)
# → Triplets are never split across chunk boundaries
```
## Semantic Chunking
Split at topic shift boundaries detected via embedding similarity:
```python
from semantica.split import TextSplitter
from semantica.embeddings import EmbeddingGenerator
embedder = EmbeddingGenerator(model="sentence-transformers")
splitter = TextSplitter(
method="semantic_transformer",
embedder=embedder,
similarity_threshold=0.7 # split when consecutive sentence similarity drops below this
method="semantic_transformer", # chunking strategy
chunk_size=1000, # target size in tokens
chunk_overlap=200, # token overlap between adjacent chunks
tokenizer="cl100k_base", # tiktoken encoding (GPT-4 default)
min_chunk_size=50, # discard very short trailing chunks
include_metadata=True, # attach source_id, page_number, section_title
language="en", # ISO 639-1 — used by sentence boundary detector
)
chunks = splitter.split(text)
```
## Token-Based Chunking
| Parameter | Type | Default | Description |
| --------- | ---- | ------- | ----------- |
| `method` | `str` | `"recursive"` | Chunking strategy — see table above |
| `chunk_size` | `int` | `1000` | Target size in tokens (characters for `fixed`) |
| `chunk_overlap` | `int` | `200` | Token overlap between adjacent chunks |
| `tokenizer` | `str` | `"cl100k_base"` | tiktoken encoding: `"cl100k_base"` (GPT-4), `"p50k_base"` (GPT-3), `"r50k_base"` (Codex) |
| `min_chunk_size` | `int` | `0` | Discard chunks shorter than this many tokens |
| `similarity_threshold` | `float` | `0.7` | Cosine similarity cutoff for `semantic_transformer` |
| `embedder` | `EmbeddingGenerator` | `None` | Custom embedder for `semantic_transformer` |
| `include_metadata` | `bool` | `True` | Attach `source_id`, `page_number`, `section_title` to each chunk |
| `language` | `str` | `"en"` | ISO 639-1 language code for sentence boundary detection |
| `heading_levels` | `list[int]` | `[1, 2, 3]` | Heading levels to split on for `markdown` method |
| `code_units` | `list[str]` | `["function", "class"]` | AST node types to split on for `code` method |
Use tiktoken for precise token-count control when preparing LLM context windows:
## Splitting Method Details
```python
splitter = TextSplitter(
method="token",
chunk_size=512, # max tokens per chunk
chunk_overlap=50, # overlap in tokens
tokenizer="cl100k_base" # OpenAI tokenizer
)
chunks = splitter.split(text)
```
<Tabs>
<Tab title="Recursive (default)">
Tries paragraph breaks first, then sentence boundaries, then word boundaries — falling back only when the chunk exceeds `chunk_size`:
## Chunk Object
```python
splitter = TextSplitter(method="recursive", chunk_size=1000, chunk_overlap=200)
chunks = splitter.split(text)
```
**Key behaviours:**
- Preserves paragraph and sentence structure wherever possible
- Falls back gracefully — never produces chunks larger than `chunk_size`
- Overlap ensures context continuity across chunk boundaries
- Good starting point when you're unsure which method to use
</Tab>
<Tab title="Semantic">
Embeds each sentence, then splits whenever cosine similarity between consecutive sentences drops below `similarity_threshold`. Each chunk talks about one topic:
```python
from semantica.split import TextSplitter
from semantica.embeddings import EmbeddingGenerator
embedder = EmbeddingGenerator(model="sentence-transformers")
splitter = TextSplitter(
method="semantic_transformer",
embedder=embedder,
similarity_threshold=0.7, # 0.6 = more splits, 0.8 = fewer splits
chunk_size=800,
chunk_overlap=0, # not needed — chunks are already coherent
)
chunks = splitter.split(text)
```
**Key behaviours:**
- Produces variable-length chunks — some topics are short, others long
- Requires an embedder — defaults to `sentence-transformers/all-MiniLM-L6-v2` if not set
- Slower than `recursive` due to embedding computation; cache embeddings for repeated splits
- Best retrieval quality for semantic search — chunks map to single coherent topics
</Tab>
<Tab title="Entity-Aware">
Runs NER first, then adjusts chunk boundaries so no entity mention is split across two chunks:
```python
from semantica.split import TextSplitter
from semantica.semantic_extract import NERExtractor
from semantica.llms import Groq
import os
llm = Groq(model="llama-3.3-70b-versatile", api_key=os.getenv("GROQ_API_KEY"))
ner = NERExtractor(method="llm", llm_provider=llm)
entities = ner.extract(text)
splitter = TextSplitter(method="entity_aware", chunk_size=512, chunk_overlap=50)
chunks = splitter.split(text, entities=entities)
for chunk in chunks:
print(f"Chunk {chunk.metadata['chunk_index']}: {len(chunk.entities)} entities")
```
**Key behaviours:**
- Entity spans in `chunk.entities` are guaranteed to fall entirely within `chunk.text`
- Chunk sizes vary slightly from `chunk_size` — boundary adjustments are ≤ one sentence
- Works with all entity types: PERSON, ORGANIZATION, LOCATION, DATE, custom types
</Tab>
<Tab title="Relation-Aware">
Keeps subjectpredicateobject triplets within the same chunk — critical for KG pipelines:
```python
from semantica.split import TextSplitter
from semantica.semantic_extract import RelationExtractor, NERExtractor
from semantica.llms import Groq
import os
llm = Groq(model="llama-3.3-70b-versatile", api_key=os.getenv("GROQ_API_KEY"))
ner = NERExtractor(method="llm", llm_provider=llm)
rel_extractor = RelationExtractor(method="llm", llm_provider=llm)
entities = ner.extract(text)
relationships = rel_extractor.extract(text, entities=entities)
splitter = TextSplitter(method="relation_aware", chunk_size=512)
chunks = splitter.split(text, relationships=relationships)
for chunk in chunks:
print(f"Chunk {chunk.metadata['chunk_index']}: {len(chunk.relationships)} triplets")
for rel in chunk.relationships:
print(f" {rel['subject']} —[{rel['predicate']}]→ {rel['object']}")
```
**Key behaviours:**
- Relation triplets in `chunk.relationships` are always fully contained within the chunk
- Implies entity-aware behaviour — both entities in a triplet are kept whole too
- Best used as the split step in a `Parse → Split → Extract → Build KG` pipeline
</Tab>
<Tab title="Code">
Parses source files with `CodeParser` and splits at AST-level boundaries:
```python
from semantica.parse import CodeParser
from semantica.split import TextSplitter
parser = CodeParser(extract_comments=True, extract_dependencies=True)
parsed = parser.parse("src/pipeline.py")
splitter = TextSplitter(
method="code",
code_units=["function", "class"], # "function" | "class" | "method" | "block"
chunk_overlap=0, # code units are self-contained
)
chunks = splitter.split_documents([parsed])
for chunk in chunks:
print(f"{chunk.metadata['unit_type']}: {chunk.metadata['unit_name']}")
print(f" Lines {chunk.start_char}{chunk.end_char} ({chunk.token_count} tokens)")
```
**Key behaviours:**
- Requires a `ParsedDocument` from `CodeParser` — use `split_documents([parsed])`
- `chunk_overlap=0` recommended — functions and classes are logically self-contained
- If a class is too large, it is split at method boundaries automatically
- Supported languages: Python, JavaScript, TypeScript, Java, Go, Rust, C, C++, C#, Ruby, PHP, Swift
</Tab>
<Tab title="Structural & Markdown">
### Structural
Uses `ParsedDocument.sections` as natural split points — each document section becomes one chunk:
```python
from semantica.parse import DoclingParser
from semantica.split import TextSplitter
parser = DoclingParser(extract_tables=True)
parsed = parser.parse("annual_report.pdf")
splitter = TextSplitter(method="structural")
chunks = splitter.split_documents([parsed])
for chunk in chunks:
level = chunk.metadata['heading_level']
title = chunk.metadata['section_title']
print(f"{' ' * (level - 1)}[h{level}] {title} ({chunk.token_count} tokens)")
```
### Markdown
Splits at Markdown heading boundaries, configurable to specific heading levels:
```python
splitter = TextSplitter(
method="markdown",
heading_levels=[1, 2], # split at # and ## only; ### stays inline
chunk_size=800,
)
chunks = splitter.split(markdown_text)
```
</Tab>
</Tabs>
## Chunk Schema
<AccordionGroup>
<Accordion title="Chunk dataclass">
```python
@dataclass
class Chunk:
text: str # chunk text content
start_char: int # character offset in source document
end_char: int # character offset in source document
token_count: int # number of tokens
metadata: Dict # source_id, chunk_index, section_title, page_number, etc.
entities: List[Dict] # entities in chunk (entity_aware splitting only)
text: str # the chunk's text content
start_char: int # character offset of start in source document
end_char: int # character offset of end in source document
token_count: int # number of tokens (via configured tokenizer)
metadata: Dict # see metadata fields below
entities: List[Dict] # entity spans fully contained in this chunk
relationships: List[Dict] # relation triplets fully contained in this chunk
```
</Accordion>
<Accordion title="Chunk metadata fields">
| Field | Type | When Present | Description |
| ----- | ---- | ------------ | ----------- |
| `source_id` | `str` | Always | ID of the source `ParsedDocument` |
| `chunk_index` | `int` | Always | Zero-based position within the document |
| `total_chunks` | `int` | Always | Total chunks produced for this document |
| `method` | `str` | Always | Splitting method that produced this chunk |
| `section_title` | `str` | `structural`, `markdown` | Heading text of the containing section |
| `heading_level` | `int` | `structural`, `markdown` | Depth: 1 = h1, 2 = h2, … |
| `page_number` | `int` | `structural` (DoclingParser) | Source page number in PDF/DOCX |
| `unit_type` | `str` | `code` | `"function"` / `"class"` / `"method"` |
| `unit_name` | `str` | `code` | Name of the code unit, e.g. `"process_batch"` |
| `language` | `str` | `sentence`, `recursive` | ISO 639-1 code for detected text language |
| `similarity_score` | `float` | `semantic_transformer` | Cosine similarity to the adjacent chunk |
</Accordion>
</AccordionGroup>
## Tokenizer Options
| Tokenizer | Models |
| --------- | ------ |
| `cl100k_base` | GPT-4, GPT-3.5-turbo, text-embedding-ada-002 |
| `p50k_base` | GPT-3 (`text-davinci-003`), Codex |
| `r50k_base` | GPT-3 (`davinci`) |
## Pipeline Integration
```python
from semantica.pipeline import Pipeline
from semantica.ingest import FileIngestor
from semantica.parse import DocumentParser
from semantica.split import TextSplitter
from semantica.semantic_extract import NERExtractor
from semantica.llms import Groq
import os
llm = Groq(model="llama-3.3-70b-versatile", api_key=os.getenv("GROQ_API_KEY"))
pipeline = Pipeline()
pipeline.add_step("split", TextSplitter(method="semantic_transformer", chunk_size=512))
result = pipeline.run(documents)
pipeline.add_step("ingest", FileIngestor())
pipeline.add_step("parse", DocumentParser())
pipeline.add_step("split", TextSplitter(method="semantic_transformer", chunk_size=512))
pipeline.add_step("extract", NERExtractor(method="llm", llm_provider=llm))
result = pipeline.run("data/reports/")
```
## Tips and Common Pitfalls
<Warning>
**`chunk_overlap` too small.** Without overlap, a fact that spans a chunk boundary is invisible in both chunks. A 1020% overlap relative to `chunk_size` is a safe minimum — for `chunk_size=1000`, set `chunk_overlap=100` to `200`.
</Warning>
<Warning>
**Wrong tokenizer.** If you use `cl100k_base` (GPT-4) but send chunks to a model with a different vocabulary, your token counts will be wrong. Match the tokenizer to your target model.
</Warning>
<Tip>
**Semantic splitting needs enough sentences.** `semantic_transformer` needs several sentences to detect topic shifts. On documents shorter than ~300 words it behaves like `sentence` splitting — use `recursive` instead.
</Tip>
<Tip>
**Code units too coarse.** `code_units=["class"]` on a large codebase produces chunks too big to embed well. Use `["function", "method"]` for more granular, independently useful units.
</Tip>
<Tip>
**Set `min_chunk_size` to avoid fragment chunks.** `min_chunk_size=0` (default) can produce many tiny trailing chunks. Set to ~3050 tokens to discard fragments that carry no retrieval value.
</Tip>
<CardGroup cols={2}>
<Card title="Parse" icon="file-lines" href="parse">
Parse documents before chunking.
Parse documents before chunking — produces sections and metadata.
</Card>
<Card title="Embeddings" icon="vector-square" href="embeddings">
Embed chunks for vector search and semantic chunking.
</Card>
<Card title="Semantic Extract" icon="magnifying-glass" href="semantic_extract">
Extract entities from individual chunks.
Extract entities and relations from individual chunks.
</Card>
<Card title="Pipeline" icon="gear" href="pipeline">
Integrate splitting as a pipeline step.
Integrate splitting as a named pipeline step.
</Card>
</CardGroup>
+225 -64
View File
@@ -6,33 +6,196 @@ icon: "table"
`semantica.triplet_store` provides W3C-standard RDF storage with full SPARQL query support. Use it when you need semantic web compatibility, OWL reasoning, SPARQL-based queries, or standards-compliant RDF serialization.
## Exported Classes
```python
from semantica.triplet_store import (
# Core interface
TripletStore, # unified: add_triplet, get_triplets, execute_query, bulk_load
QueryEngine, # SPARQL execution: execute_query, optimize_query, plan_query
BulkLoader, # high-volume loading with progress tracking and transaction support
# Backend stores
BlazegraphStore, # Blazegraph REST API (HTTP/HTTPS, Named Graphs, SPARQL 1.1)
JenaStore, # Apache Jena Fuseki (SPARQL 1.1, TDB2, GeoSPARQL)
RDF4JStore, # Eclipse RDF4J (SailRepository, in-memory or native)
# Convenience functions
add_triplet, # add_triplet(subject, predicate, obj)
add_triplets, # bulk: add_triplets(triplets)
get_triplets, # get_triplets(subject=None, predicate=None, obj=None)
delete_triplet, # delete_triplet(subject, predicate, obj)
execute_query, # execute_query(sparql, result_format="json")
optimize_query, # optimize_query(sparql) -> optimized SPARQL string
bulk_load, # bulk_load(file_path, format="turtle")
validate_triplets,# validate_triplets(triplets) -> ValidationResult
)
```
## What You Get
- **`TripletStore`** — unified interface for all RDF backends
- **Backends** — Blazegraph, Apache Jena (Fuseki), RDF4J
- **SPARQL** — full SELECT, CONSTRUCT, ASK, and UPDATE query support
- **Bulk loading** — efficient batch import for large triple sets
- **Import / Export** — Turtle, JSON-LD, N-Triples, RDF/XML
<CardGroup cols={2}>
<Card title="TripletStore" icon="server">
Unified interface across Blazegraph, Apache Jena (Fuseki), and RDF4J — swap backends with one parameter.
</Card>
<Card title="TripletStore (in-memory)" icon="bolt">
Zero-setup in-memory mode via `backend="memory"` for unit tests and small datasets — no server required.
</Card>
<Card title="SPARQL" icon="magnifying-glass">
Full SELECT, CONSTRUCT, ASK, and UPDATE query support with pagination for large result sets.
</Card>
<Card title="OWL Reasoning" icon="microchip">
Apache Jena supports OWL and RDFS inference natively — subclass and property chain queries automatically resolved.
</Card>
<Card title="Named Graphs" icon="diagram-project">
Isolate triples by source, dataset, or time period using named graph management.
</Card>
<Card title="Import / Export" icon="file-export">
Load and serialize to Turtle, JSON-LD, N-Triples, and RDF/XML with a single method call.
</Card>
</CardGroup>
## Basic Usage
## Quick Start
<Steps>
<Step title="Connect to a backend">
```python
from semantica.triplet_store import TripletStore
store = TripletStore(
backend="blazegraph",
endpoint="http://localhost:9999/blazegraph/sparql"
)
```
</Step>
<Step title="Add triplets">
```python
# Add a single triplet
store.add_triplet(
subject="http://example.org/apple_inc",
predicate="http://example.org/founded_by",
obj="http://example.org/steve_jobs"
)
# Bulk load a list of triplets
store.add_triplets_bulk(triplets)
```
</Step>
<Step title="Query with SPARQL">
```python
results = store.sparql("""
PREFIX ex: <http://example.org/>
SELECT ?person ?company WHERE {
?person ex:founded ?company .
?company ex:located_in ex:SiliconValley .
}
""")
for row in results:
print(row["person"], row["company"])
```
</Step>
<Step title="Export to file">
```python
store.export("output.ttl", format="turtle")
store.export("output.nt", format="nt")
store.export("output.xml", format="xml")
```
</Step>
</Steps>
## Backends
<Tabs>
<Tab title="Blazegraph">
```python
from semantica.triplet_store import TripletStore
store = TripletStore(
backend="blazegraph",
endpoint="http://localhost:9999/blazegraph/sparql",
namespace="semantica"
)
```
Best for: Wikidata-style workloads, high triple counts, SPARQL 1.1 full support.
</Tab>
<Tab title="Apache Jena">
```python
store = TripletStore(
backend="jena",
endpoint="http://localhost:3030/dataset/sparql",
update_endpoint="http://localhost:3030/dataset/update"
)
```
Best for: General RDF, standard SPARQL, production deployments needing OWL inference.
**Enable OWL reasoning:**
```python
store = TripletStore(
backend="jena",
endpoint="http://localhost:3030/dataset/sparql",
update_endpoint="http://localhost:3030/dataset/update",
reasoner="OWL", # "OWL" | "RDFS" | "OWL_MINI" | None
)
# Load an OWL ontology — subclass/property chain inferences are automatic
store.import_file("ontology.ttl", format="turtle")
store.add_triplets_bulk(data_triplets)
# Query using inferred relationships
results = store.sparql("""
SELECT ?person WHERE {
?person a ex:Employee . # inferred via subClassOf chain
}
""")
```
</Tab>
<Tab title="RDF4J">
```python
store = TripletStore(
backend="rdf4j",
server_url="http://localhost:8080/rdf4j-server",
repository_id="semantica"
)
```
Best for: Enterprise Java ecosystems, Eclipse Foundation deployments, plugin-based reasoning.
</Tab>
<Tab title="Backend Comparison">
| Backend | License | OWL Reasoning | Hosted Option | Best For |
| ------- | ------- | ------------- | ------------- | -------- |
| Blazegraph | Open source | No | Self-hosted | Wikidata-style workloads, high triple count |
| Apache Jena | Apache 2.0 | Yes (OWL/RDFS) | Self-hosted | General RDF, OWL reasoning, standard SPARQL |
| RDF4J | Eclipse 1.0 | Via plugin | Self-hosted or cloud | Enterprise Java ecosystems |
| InMemory | Built-in | No | N/A | Unit tests, small graphs, no server required |
</Tab>
</Tabs>
## Namespace Prefix Management
Register custom prefixes to keep SPARQL queries readable:
```python
from semantica.triplet_store import TripletStore
from semantica.ontology import NamespaceManager
store = TripletStore(
backend="blazegraph",
endpoint="http://localhost:9999/blazegraph/sparql"
)
ns = NamespaceManager(base_uri="http://example.org/")
ns.register("ex", "http://example.org/")
ns.register("schema", "https://schema.org/")
ns.register("owl", "http://www.w3.org/2002/07/owl#")
# Add a single triplet
store.add_triplet(
subject="http://example.org/apple_inc",
predicate="http://example.org/founded_by",
obj="http://example.org/steve_jobs"
)
store = TripletStore(backend="jena", endpoint="...")
# Bulk load a list of triplets
store.add_triplets_bulk(triplets)
# Registered prefixes are automatically prepended to every SPARQL query
results = store.sparql("""
SELECT ?company WHERE {
?person ex:works_for ?company ;
schema:name "Alice" .
}
""")
```
## SPARQL Queries
@@ -47,9 +210,6 @@ results = store.sparql("""
}
""")
for row in results:
print(row["person"], row["company"])
# CONSTRUCT — returns a graph of matched triples
graph = store.sparql_construct("""
PREFIX ex: <http://example.org/>
@@ -76,54 +236,29 @@ store.sparql_update("""
""")
```
## Backends
## SPARQL Result Pagination
For large result sets, paginate with LIMIT and OFFSET:
```python
# Blazegraph — open source, SPARQL 1.1
store = TripletStore(
backend="blazegraph",
endpoint="http://localhost:9999/blazegraph/sparql",
namespace="semantica"
)
page_size = 1000
offset = 0
# Apache Jena Fuseki — open source, widely used
store = TripletStore(
backend="jena",
endpoint="http://localhost:3030/dataset/sparql",
update_endpoint="http://localhost:3030/dataset/update"
)
# RDF4J — enterprise-grade, Eclipse Foundation
store = TripletStore(
backend="rdf4j",
server_url="http://localhost:8080/rdf4j-server",
repository_id="semantica"
)
while True:
results = store.sparql(f"""
SELECT ?s ?p ?o WHERE {{
?s ?p ?o .
}}
ORDER BY ?s
LIMIT {page_size} OFFSET {offset}
""")
if not results:
break
process_batch(results)
offset += page_size
```
## Backend Comparison
| Backend | License | Query Language | Best For |
| ------- | ------- | -------------- | -------- |
| Blazegraph | Open source | SPARQL 1.1 | Wikidata-style workloads |
| Apache Jena | Apache 2.0 | SPARQL 1.1 | General RDF, OWL reasoning |
| RDF4J | Eclipse 1.0 | SPARQL 1.1 | Enterprise, Java ecosystems |
## Import and Export
```python
# Import from file
store.import_file("ontology.ttl", format="turtle")
store.import_file("data.jsonld", format="json-ld")
store.import_file("triples.nt", format="nt")
# Export to file
store.export("output.ttl", format="turtle")
store.export("output.nt", format="nt")
store.export("output.xml", format="xml")
```
## Graph Management
## Named Graph Management
```python
# Named graphs — store triples in isolated contexts
@@ -168,6 +303,32 @@ store.import_file("output.ttl", format="turtle")
results = store.sparql("SELECT * WHERE { ?s ?p ?o } LIMIT 10")
```
## Tips and Common Pitfalls
<Tip>
**Use Apache Jena (Fuseki) for development and Blazegraph for production.** Jena runs with a single Docker command, supports OWL reasoning natively, and requires no licence. Switch to Blazegraph for high-throughput workloads by changing the `backend=` parameter — no other code changes needed.
</Tip>
<Warning>
**Paginate large SPARQL result sets.** A `SELECT * WHERE { ?s ?p ?o }` against a million-triple store can return gigabytes of data. Always include `LIMIT` and `OFFSET` in exploratory queries, and iterate with `page_size` when you need full coverage. Unbounded queries against large stores will OOM or timeout.
</Warning>
<Tip>
**Use named graphs to isolate sources.** `store.add_triplet(..., graph="http://example.org/source_A")` puts triples into a named graph. You can then query just that source, merge selectively, or clear it without touching other data — far safer than mixing all triples into the default graph.
</Tip>
<Tip>
**Register namespace prefixes before querying.** `NamespacePrefixManager` lets you write `?s ex:name ?o` instead of `?s <http://example.org/name> ?o`. Without prefixes, SPARQL queries against domain ontologies become unreadable and error-prone.
</Tip>
<Warning>
**Enable OWL reasoning only when you need it.** `reasoner="OWL"` significantly increases query planning overhead. For simple triple lookups or SPARQL SELECT queries, leave reasoning off (`reasoner=None`) and enable it only for queries that depend on class hierarchies or property chains.
</Warning>
<Tip>
**Export to Turtle before migrating backends.** If you need to move from Jena to Blazegraph (or any other store), `store.export("dump.ttl", format="turtle")` produces a portable file that any SPARQL store can import. Don't rely on backend-specific dump formats.
</Tip>
<CardGroup cols={2}>
<Card title="Export" icon="file-export" href="export">
Export knowledge graphs to RDF formats.
+143 -28
View File
@@ -6,35 +6,88 @@ icon: "wrench"
`semantica.utils` provides shared infrastructure used throughout Semantica. Most users won't call it directly, but its APIs are available when you need fine-grained control over logging, validation, progress tracking, or error handling.
## Exported Classes
```python
from semantica.utils import (
# Logging
setup_logging, # configure root logger — level, format (json/text)
get_logger, # get a named logger instance
log_performance, # @decorator — logs function name, duration, exception
# Validation
validate_entity, # validate entity dict structure, raises ValidationError
validate_config, # validate config dict against schema, raises ValidationError
# Progress tracking
ProgressTracker, # class-based tracker with ETA
track_progress, # wraps any iterable with live progress bar
# Helpers
clean_text, # normalize whitespace, strip control characters
hash_data, # deterministic SHA-256 hash of any serializable object
safe_filename, # sanitize a string for use as a filename
# Exceptions
SemanticaError, # base exception for all Semantica errors
ValidationError, # raised when input fails validation
ProcessingError, # raised during extraction, graph build, or pipeline step
)
```
## What You Get
- **Logging** — structured logging with `@log_performance` decorator and quality metrics
- **Validation** — `validate_entity`, `validate_config` with a typed `ValidationError`
- **Progress tracking** — `track_progress` wraps any iterable with console, Jupyter, or file output
- **Helper functions** — `clean_text`, `hash_data`, `safe_filename`
- **Exception hierarchy** — `SemanticaError``ValidationError`, `ProcessingError`
<CardGroup cols={2}>
<Card title="Logging" icon="scroll">
Structured logging with `@log_performance` decorator and quality metrics via environment variables.
</Card>
<Card title="Validation" icon="shield-check">
`validate_entity` and `validate_config` with a typed `ValidationError` carrying field and value context.
</Card>
<Card title="Progress Tracking" icon="bars-progress">
`track_progress` wraps any iterable — auto-detects console vs Jupyter for the right renderer.
</Card>
<Card title="Helper Functions" icon="wrench">
`clean_text`, `hash_data`, `safe_filename`, and nested dict utilities used throughout the framework.
</Card>
<Card title="Exception Hierarchy" icon="triangle-exclamation">
`SemanticaError``ValidationError`, `ProcessingError` — typed exceptions for targeted recovery.
</Card>
<Card title="File Utilities" icon="file">
`read_json_file` with `ProcessingError` on failure — no boilerplate try/except around JSON I/O.
</Card>
</CardGroup>
## Logging
```python
from semantica.utils import setup_logging, get_logger, log_performance
<Steps>
<Step title="Initialize logging at application startup">
```python
from semantica.utils import setup_logging, get_logger
setup_logging(level="INFO") # "DEBUG" | "INFO" | "WARNING" | "ERROR"
logger = get_logger(__name__)
setup_logging(level="INFO") # "DEBUG" | "INFO" | "WARNING" | "ERROR"
logger = get_logger(__name__)
```
</Step>
<Step title="Instrument expensive functions with the performance decorator">
```python
from semantica.utils import log_performance, log_execution_time
@log_performance
def process_data(data):
logger.info(f"Processing {len(data)} items")
# Decorator automatically logs function name, duration, and any exception
```
@log_performance
def process_data(data):
logger.info(f"Processing {len(data)} items")
# Logs function name, duration, and any exception automatically
Configure via environment variables:
```bash
export SEMANTICA_LOG_LEVEL=DEBUG
export SEMANTICA_LOG_FORMAT=json # "json" | "text"
export SEMANTICA_PROGRESS_BAR=true
```
@log_execution_time
def expensive_step(data):
...
# Logs: "expensive_step completed in 2.34s"
```
</Step>
<Step title="Configure via environment variables">
```bash
export SEMANTICA_LOG_LEVEL=DEBUG
export SEMANTICA_LOG_FORMAT=json # "json" | "text"
export SEMANTICA_PROGRESS_BAR=true
```
</Step>
</Steps>
## Validation
@@ -72,9 +125,8 @@ for item in track_progress(items, desc="Processing documents"):
```
Supports:
- **Console** — tqdm progress bar with ETA
- **Jupyter** — notebook-compatible widget
- **Jupyter** — notebook-compatible widget (auto-detected)
- **File** — write progress to a log file
## Helper Functions
@@ -83,17 +135,46 @@ Supports:
from semantica.utils import clean_text, hash_data, safe_filename
# Normalize whitespace and strip control characters
clean = clean_text(" Hello World ") # → "Hello World"
clean = clean_text(" Hello World ") # → "Hello World"
# Deterministic SHA-256 hash of any serializable object
uid = hash_data({"key": "value"}) # → hex digest string
# Deterministic SHA-256 hash of any JSON-serializable object
uid = hash_data({"key": "value"}) # → hex digest string
# Sanitize a string for use as a filename
fname = safe_filename("My File?.txt") # → "My_File_.txt"
fname = safe_filename("My File?.txt") # → "My_File_.txt"
```
## Nested Dict Utilities
Helper functions for deep configuration access — used extensively inside `Config` and `ConfigManager`:
```python
from semantica.utils import get_nested_value, set_nested_value, merge_dicts
config = {
"processing": {"batch_size": 32, "max_workers": 4},
"llm": {"provider": "groq", "model": "llama-3.3-70b-versatile"},
}
# Dot-notation read — returns default if key path is absent
batch = get_nested_value(config, "processing.batch_size", default=16)
# → 32
# Dot-notation write
set_nested_value(config, "processing.batch_size", 64)
# Deep merge — nested keys are merged recursively
base = {"a": {"x": 1, "y": 2}, "b": 3}
overrides = {"a": {"y": 99, "z": 4}, "c": 5}
merged = merge_dicts(base, overrides, deep=True)
# → {"a": {"x": 1, "y": 99, "z": 4}, "b": 3, "c": 5}
```
## Exception Hierarchy
<AccordionGroup>
<Accordion title="Exception types and when they're raised">
```python
from semantica.utils import SemanticaError, ValidationError, ProcessingError
@@ -101,7 +182,7 @@ try:
run_pipeline(data)
except ValidationError as e:
# Input data did not pass schema validation
logger.error(f"Validation failed: {e}")
logger.error(f"Validation failed at field '{e.field}': {e.message}")
except ProcessingError as e:
# Failure during extraction or graph construction
logger.error(f"Processing failed at step {e.step}: {e}")
@@ -116,6 +197,40 @@ except SemanticaError as e:
| `ValidationError` | Input data failed schema or type validation |
| `ProcessingError` | Failure during extraction, graph build, or pipeline step |
</Accordion>
</AccordionGroup>
## File Utilities
```python
from semantica.utils import read_json_file
# Read and parse a JSON file — raises ProcessingError on failure
config = read_json_file("config.json")
```
## Tips and Common Pitfalls
<Warning>
**Call `setup_logging(level="INFO")` once at application startup.** Without it, Semantica falls back to Python's root logger, which may be silent or misconfigured. Call it before importing other Semantica modules to capture initialization messages.
</Warning>
<Tip>
**Use `@log_performance` on expensive functions.** The decorator logs function name, duration, and any raised exception automatically — no manual `time.time()` bookkeeping needed. Essential for profiling multi-step pipelines where one step is a hidden bottleneck.
</Tip>
<Tip>
**`hash_data()` is deterministic across runs.** Given the same input dict (any JSON-serializable object), `hash_data()` always returns the same SHA-256 hex string — suitable as a cache key or idempotency token in pipeline steps.
</Tip>
<Tip>
**Catch `SemanticaError` as the broadest exception net.** All framework errors inherit from `SemanticaError`, so `except SemanticaError` catches validation failures, processing errors, and everything in between. Use specific subclasses for targeted recovery logic.
</Tip>
<Tip>
**`track_progress` auto-detects Jupyter.** In a terminal it renders a tqdm progress bar; in a Jupyter notebook it renders an interactive widget. You don't need to check the environment — the same call works in both.
</Tip>
<CardGroup cols={2}>
<Card title="Core" icon="gear" href="core">
Framework orchestration that uses Utils internally.
+320 -61
View File
@@ -6,36 +6,118 @@ icon: "database"
`semantica.vector_store` provides a unified API for storing and searching vector embeddings across all major backends. Swap backends with a one-line change — no application code changes needed.
## What You Get
- **`VectorStore`** — unified interface across all backends
- **Backends** — FAISS, Pinecone, Weaviate, Qdrant, Milvus, PgVector, in-memory
- **Hybrid search** — combine dense vector similarity with sparse keyword/metadata filtering
- **Metadata filtering** — rich filter expressions: `eq`, `ne`, `gt`, `lt`, `in`, `contains`, `$and`, `$or`
- **Namespace isolation** — multi-tenant support via isolated namespaces
- **Batch operations** — bulk add, delete, and metadata updates
## Basic Usage
## Exported Classes
```python
from semantica.vector_store import VectorStore
# In-memory (development)
store = VectorStore(backend="inmemory", dimension=768)
# FAISS (local, production)
store = VectorStore(backend="faiss", dimension=768, index_path="store.faiss")
# Add vectors
store.add_vectors(embeddings=embeddings, ids=["doc1", "doc2"], metadata=[{}, {}])
# Semantic search
results = store.search(query_vector, top_k=10)
for r in results:
print(f"{r['id']} — score: {r['score']:.3f}")
print(f" metadata: {r['metadata']}")
from semantica.vector_store import (
# Core interface
VectorStore, # unified: store_vectors, search_vectors, update_vectors, delete_vectors
VectorIndexer, # build/rebuild FAISS/ANN indices
VectorRetriever, # kNN and hybrid search
VectorManager, # store management and CRUD operations
# Backend stores
FAISSStore, # local disk / in-memory (Flat, IVF, HNSW, PQ index types)
WeaviateStore, # cloud/self-hosted, schema-aware, GraphQL queries
QdrantStore, # cloud/self-hosted, payload filtering
MilvusStore, # highly scalable, partitioning and complex queries
PineconeStore, # managed cloud vector database
PgVectorStore, # PostgreSQL with pgvector extension
# Hybrid & metadata search
HybridSearch, # fuse vector + metadata results (RRF or weighted average)
MetadataFilter, # MetadataFilter().eq("category", "science").gt("year", 2020)
SearchRanker, # configurable re-ranking after fusion
MetadataStore, # inverted index for fast metadata filtering
NamespaceManager, # multi-tenant namespace isolation
# Decision-specific helpers
DecisionEmbeddingPipeline, # end-to-end: record + embed + store + retrieve
quick_decision, # quick_decision(text, entities, outcome) — shorthand record
find_precedents, # find_precedents(scenario, k=5) — similarity search
# Convenience functions
store_vectors, # store_vectors(vectors, metadata)
search_vectors, # search_vectors(query_vector, k=10)
hybrid_search, # hybrid_search(query_vector, filter=...)
update_vectors, # update_vectors(ids, new_vectors)
delete_vectors, # delete_vectors(ids)
create_index, # create_index(index_type="hnsw", dimension=768)
)
```
## What You Get
<CardGroup cols={2}>
<Card title="VectorStore" icon="database">
Unified interface across FAISS, Pinecone, Weaviate, Qdrant, Milvus, and PgVector.
</Card>
<Card title="HybridSearch" icon="magnifying-glass">
Combine dense vector similarity with sparse keyword/BM25 filtering and configurable fusion strategies.
</Card>
<Card title="MetadataStore" icon="table">
Rich metadata indexing and schema management — query by field values without a vector.
</Card>
<Card title="NamespaceManager" icon="folder-tree">
Multi-tenant namespace isolation — structural separation, not just metadata filters.
</Card>
<Card title="Batch Operations" icon="layer-group">
Bulk add, delete, and metadata updates — automatically chunked for memory efficiency.
</Card>
<Card title="FAISS Index Types" icon="chart-scatter">
Flat, IVF, HNSW, and PQ index types with full configuration control.
</Card>
</CardGroup>
## Quick Start
<Steps>
<Step title="Create a vector store">
```python
from semantica.vector_store import VectorStore
# In-memory (development)
store = VectorStore(backend="inmemory", dimension=768)
# FAISS (local production — persists to disk)
store = VectorStore(backend="faiss", dimension=768, index_path="store.faiss")
```
</Step>
<Step title="Add vectors">
```python
# Add text documents (auto-embedded)
ids = store.add_documents(
documents=["text one", "text two"],
metadata=[{"title": "Document 1"}, {"title": "Document 2"}]
)
# Add pre-computed vectors
ids = store.store_vectors(
vectors=[embedding1, embedding2],
metadata=[{"title": "Document 1"}, {"title": "Document 2"}]
)
```
</Step>
<Step title="Search by semantic similarity">
```python
# Search by text query (auto-embeds the query)
results = store.search("machine learning", limit=10)
# Search by pre-computed vector
results = store.search_vectors(query_vector, k=10)
for r in results:
print(f"{r['id']} — score: {r['score']:.3f}")
```
</Step>
<Step title="Filter results by metadata">
```python
from semantica.vector_store import HybridSearch, MetadataFilter
mf = MetadataFilter().eq("category", "research").gt("year", 2022)
search = HybridSearch(vector_store=store)
results = search.search(query=query_vector, k=10, metadata_filter=mf)
```
</Step>
</Steps>
## Backends
<Tabs>
@@ -45,7 +127,7 @@ for r in results:
store = VectorStore(
backend="faiss",
dimension=768,
index_type="IVF", # "Flat" | "IVF" | "HNSW"
index_type="IVF", # "Flat" | "IVF" | "HNSW" | "PQ"
index_path="store.faiss"
)
```
@@ -124,65 +206,96 @@ See the [PgVector Guide](../vector_stores/pgvector) for full setup.
## Hybrid Search
Combine vector similarity with keyword/metadata filters for higher precision:
Use `HybridSearch` with a `MetadataFilter` to combine vector similarity with metadata conditions:
```python
results = store.hybrid_search(
query_vector=query_embedding,
query_text="machine learning", # keyword component
top_k=10,
alpha=0.7, # 0.0 = keyword only, 1.0 = vector only
filters={"category": "research", "year": {"$gte": 2022}}
from semantica.vector_store import HybridSearch, MetadataFilter
mf = (
MetadataFilter()
.eq("category", "research")
.gt("year", 2022)
)
search = HybridSearch(vector_store=store)
results = search.search(
query=query_vector, # np.ndarray or query string (auto-embedded)
k=10,
metadata_filter=mf
)
for r in results:
print(f"{r['id']} — score: {r['score']:.3f} metadata: {r['metadata']}")
```
## Metadata Filtering
`MetadataFilter` supports chained conditions — all conditions are ANDed:
```python
# Equality
results = store.search(query_vector, filters={"author": "John Smith"})
from semantica.vector_store import MetadataFilter
# Range
results = store.search(query_vector, filters={"date": {"$gte": "2023-01-01"}})
mf = MetadataFilter().eq("author", "John Smith") # equality
mf = MetadataFilter().ne("status", "archived") # not equal
mf = MetadataFilter().gt("year", 2022).lte("year", 2024) # range
mf = MetadataFilter().in_list("tag", ["ai", "ml"]) # set membership
mf = MetadataFilter().contains("title", "neural") # substring / list contains
# Set membership
results = store.search(query_vector, filters={"tag": {"$in": ["ai", "ml"]}})
# Compound AND
results = store.search(query_vector, filters={
"$and": [
{"category": "research"},
{"year": {"$gte": 2022}}
]
})
# Multiple conditions — all must match (AND)
mf = (
MetadataFilter()
.eq("category", "research")
.gt("year", 2022)
.contains("title", "language model")
)
```
## Namespace Isolation
Isolate vectors per tenant, project, or use case:
Use `NamespaceManager` to assign vectors to named namespaces for multi-tenant isolation:
```python
store = VectorStore(backend="faiss", dimension=768)
from semantica.vector_store import NamespaceManager, VectorStore
# Write to separate namespaces
store.add_vectors(embeddings_a, ids_a, namespace="tenant_a")
store.add_vectors(embeddings_b, ids_b, namespace="tenant_b")
store = VectorStore(backend="faiss", dimension=768)
ns_manager = NamespaceManager()
# Search is scoped to the specified namespace
results = store.search(query_vector, namespace="tenant_a")
ns_manager.create_namespace("tenant_a", description="Customer A data")
ns_manager.create_namespace("tenant_b", description="Customer B data")
# Store vectors, then assign them to a namespace
ids_a = store.store_vectors(embeddings_a, metadata=metadata_a)
for vid in ids_a:
ns_manager.add_vector_to_namespace(vid, "tenant_a")
# List all namespace names
for name in ns_manager.list_namespaces():
print(name)
ns_manager.delete_namespace("tenant_a")
```
## Batch Operations
```python
# Batch add — automatically chunked for memory efficiency
store.add_vectors_batch(embeddings_list, ids_list, batch_size=1000)
# Batch add text documents — chunked automatically by batch_size
ids = store.add_documents(
documents=large_doc_list,
metadata=large_meta_list,
batch_size=1000
)
# Batch delete
store.delete_vectors(ids=["doc1", "doc2", "doc3"])
# Batch add pre-computed vectors
ids = store.store_vectors(vectors=embeddings_list, metadata=meta_list)
# Update metadata without re-embedding
store.update_metadata("doc1", {"status": "archived", "reviewed": True})
# Delete by vector ID list
store.delete_vectors(vector_ids=["vec_0", "vec_1", "vec_2"])
# Replace vectors (re-embed then update)
store.update_vectors(
vector_ids=["vec_0"],
new_vectors=[new_embedding]
)
```
## Backend Comparison
@@ -197,6 +310,152 @@ store.update_metadata("doc1", {"status": "archived", "reviewed": True})
| PgVector | PostgreSQL | No | Limited | Postgres-native integration |
| In-memory | Process | No | No | Development, testing |
## HybridSearch
`HybridSearch` combines vector similarity with metadata filtering, and can fuse results from multiple sources:
```python
from semantica.vector_store import HybridSearch, MetadataFilter, SearchRanker
# Single-source search with metadata filter
search = HybridSearch(vector_store=store)
mf = MetadataFilter().eq("category", "research").gt("year", 2022)
results = search.search(
query=query_vector, # np.ndarray or query string
k=10,
metadata_filter=mf
)
# Multi-source fusion (RRF across multiple stores)
sources = [
{"vectors": v1, "metadata": m1, "ids": ids1},
{"vectors": v2, "metadata": m2, "ids": ids2},
]
fused = search.multi_source_search(query_vector, sources, k=10)
# Custom fusion strategy
ranker = SearchRanker(strategy="reciprocal_rank_fusion") # or "weighted_average"
fused = ranker.rank([results_list_1, results_list_2], k=60)
```
| Fusion strategy | Description |
| --------------- | ----------- |
| `reciprocal_rank_fusion` | Rank-based combination via RRF constant `k=60` — robust to score scale differences |
| `weighted_average` | Weighted average of scores — pass `weights=[0.7, 0.3]` to `rank()` |
## MetadataStore
`MetadataStore` indexes structured metadata and lets you query by field values without a vector:
```python
from semantica.vector_store import MetadataStore
meta_store = MetadataStore()
# Store and retrieve metadata
meta_store.store_metadata("doc1", {"author": "Alice", "year": 2024, "category": "research"})
meta_store.store_metadata("doc2", {"author": "Bob", "year": 2023, "category": "review"})
# Query — returns List[str] of matching vector IDs
ids = meta_store.query_metadata({"category": "research", "year": 2024})
# Get and update metadata for a specific vector
meta = meta_store.get_metadata("doc1")
meta_store.update_metadata("doc1", {"score": 0.92})
```
## NamespaceManager
Assigns vector IDs to named namespaces for multi-tenant or multi-model isolation:
```python
from semantica.vector_store import NamespaceManager
ns_manager = NamespaceManager()
ns_manager.create_namespace("tenant_a", description="Customer A data")
ns_manager.create_namespace("tenant_b", description="Customer B data")
# Assign vector IDs to a namespace after storing them
for vid in ids_a:
ns_manager.add_vector_to_namespace(vid, "tenant_a")
# Inspect namespaces
for name in ns_manager.list_namespaces(): # returns List[str]
print(name)
# Look up which namespace a vector belongs to
ns = ns_manager.get_vector_namespace("vec_0")
ns_manager.delete_namespace("tenant_a")
```
## FAISS Index Type Reference
| Index | Memory | Speed | Accuracy | When to Use |
| ----- | ------ | ----- | -------- | ----------- |
| `Flat` | High | Slow | Exact (100%) | < 100K vectors, correctness critical |
| `IVF` | Medium | Fast | ~9598% | 100K10M vectors, good balance |
| `HNSW` | Medium-High | Very fast | ~9799% | Low latency, production retrieval |
| `PQ` | Low | Fast | ~9095% | Millions of vectors, memory-constrained |
```python
# Flat — brute-force exact search
store = VectorStore(backend="faiss", dimension=768, index_type="Flat")
# IVF — inverted file index with nlist clusters
store = VectorStore(backend="faiss", dimension=768, index_type="IVF", nlist=100)
# HNSW — hierarchical navigable small world graph
store = VectorStore(backend="faiss", dimension=768, index_type="HNSW", M=16, ef_construction=200)
# PQ — product quantization for memory efficiency
store = VectorStore(backend="faiss", dimension=768, index_type="PQ", m=8)
```
## Similarity Metrics
| Metric | Constructor arg | Distance → Similarity | Best For |
| ------ | --------------- | --------------------- | -------- |
| Cosine | `metric="cosine"` | `1 - cosine_distance` | Text, embeddings |
| L2 (Euclidean) | `metric="l2"` | `1 / (1 + distance)` | Image features |
| Inner Product | `metric="ip"` | raw dot product | Recommendation systems |
```python
store = VectorStore(backend="faiss", dimension=768, metric="cosine")
```
## Tips and Common Pitfalls
<Warning>
**Match vector dimension to your embedding model.** The `dimension` parameter must exactly match your embedding model's output size — `all-MiniLM-L6-v2` = 384, `all-mpnet-base-v2` = 768, `bge-large-en-v1.5` = 1024. A mismatch raises an error at insert time, not at store creation.
</Warning>
<Tip>
**Use `Flat` index only for small datasets.** Flat (brute-force) search has perfect recall but O(n) query time. At 500K+ vectors, switch to `IVF` or `HNSW` — they sacrifice less than 5% recall for 1001000x speedup.
</Tip>
<Warning>
**Don't search without normalizing first.** If you disabled `normalize=True` in `EmbeddingGenerator`, compute cosine similarity with `metric="cosine"` (which normalizes internally). Raw dot product on un-normalized vectors produces incorrect similarity rankings.
</Warning>
<Tip>
**Use `hybrid_search` for precision-sensitive workloads.** Pure vector search finds semantically similar results but may miss keyword matches important to the user. Hybrid search (vector + BM25) combines both signals — especially valuable for domain-specific terminology.
</Tip>
<Tip>
**Use `NamespaceManager` for multi-tenant applications.** Storing all tenants' vectors in the same collection and filtering by metadata at query time is slow and leaks data if a filter is accidentally omitted. Namespace isolation is both faster (smaller search space) and safer (structural isolation).
</Tip>
<Warning>
**Persist FAISS indexes to disk.** `VectorStore(backend="faiss", index_path="store.faiss")` saves the index to disk on each write. Without a path, the index is in-memory only and is lost on process exit.
</Warning>
<Tip>
**Update metadata without re-embedding.** `MetadataStore.update_metadata(id, {...})` changes attached fields (status, tags, review date) without re-running the embedding model. Use this for state changes that don't affect semantic content.
</Tip>
<CardGroup cols={2}>
<Card title="Embeddings" icon="vector-square" href="embeddings">
Generate the vectors stored here.
+246 -122
View File
@@ -6,146 +6,270 @@ icon: "chart-bar"
`semantica.visualization` renders knowledge graphs, ontologies, embedding spaces, and temporal data as interactive HTML or static images — without launching the full Explorer server.
## Exported Classes
```python
from semantica.visualization import (
# Visualizers
KGVisualizer, # visualize_network(graph), visualize_communities(graph, communities)
OntologyVisualizer, # visualize_hierarchy(ontology), visualize_structure(ontology)
EmbeddingVisualizer, # visualize_2d_projection(embeddings, labels, method="umap")
SemanticNetworkVisualizer, # visualize_network(semantic_network)
AnalyticsVisualizer, # visualize_centrality(analytics), visualize_communities(analytics)
TemporalVisualizer, # visualize_timeline(events), visualize_evolution(snapshots)
# D3Visualizer is listed in __all__ but loaded lazily (requires d3js dependency)
# Convenience functions
visualize_kg, # visualize_kg(graph, output="interactive", method="default")
visualize_ontology, # visualize_ontology(ontology, output="interactive")
visualize_embeddings, # visualize_embeddings(embeddings, labels, method="umap")
visualize_semantic_network, # visualize_semantic_network(network)
visualize_analytics, # visualize_analytics(analytics_result)
visualize_temporal, # visualize_temporal(temporal_data)
)
```
## What You Get
- **`GraphVisualizer`** — interactive HTML (PyVis) and static image (Matplotlib) graph rendering
- **`OntologyVisualizer`** — class hierarchy and property relationship visualization
- **`EmbeddingVisualizer`** — UMAP, t-SNE, and PCA dimensionality reduction plots
- **`TemporalVisualizer`** — timeline views and animated graph evolution
- **`DistanceVisualizer`** — ego-mode neighborhood views and distance matrix heatmaps (v0.5.0)
<CardGroup cols={2}>
<Card title="KGVisualizer" icon="diagram-project">
Interactive network and community graph rendering with force, hierarchical, and circular layouts.
</Card>
<Card title="OntologyVisualizer" icon="sitemap">
Class hierarchy and property relationship visualization from any ontology.
</Card>
<Card title="EmbeddingVisualizer" icon="vector-square">
UMAP, t-SNE, and PCA dimensionality reduction plots for embedding cluster analysis.
</Card>
<Card title="TemporalVisualizer" icon="clock">
Timeline views, network evolution animation, snapshot comparison, and temporal pattern highlights.
</Card>
<Card title="AnalyticsVisualizer" icon="chart-bar">
Centrality rankings, community-colored graphs, and degree distribution histograms.
</Card>
</CardGroup>
## GraphVisualizer
## Quick Start
<Steps>
<Step title="Render a knowledge graph">
```python
from semantica.visualization import KGVisualizer
viz = KGVisualizer(layout="force", color_scheme="default")
# Interactive — opens in browser, supports hover and click
viz.visualize_network(graph, output="interactive")
```
</Step>
<Step title="Apply layout and color options">
```python
viz = KGVisualizer(layout="force", color_scheme="vibrant")
viz.visualize_network(
graph,
output="html",
file_path="graph.html",
node_color_by="type", # color nodes by entity type attribute
)
```
</Step>
<Step title="Export to static formats">
```python
# Static PNG — for reports and embedding in documents
viz.visualize_network(graph, output="png", file_path="graph.png")
# Vector SVG — for publications and scalable diagrams
viz.visualize_network(graph, output="svg", file_path="graph.svg")
```
</Step>
</Steps>
## Visualizers
<Tabs>
<Tab title="KGVisualizer">
Interactive and static knowledge graph rendering:
```python
from semantica.visualization import KGVisualizer
viz = KGVisualizer(layout="force", color_scheme="default")
# Interactive — opens in browser
viz.visualize_network(graph, output="interactive")
# Save as HTML file
viz.visualize_network(graph, output="html", file_path="graph.html")
# Static PNG
viz.visualize_network(graph, output="png", file_path="graph.png")
# Community-colored graph
viz.visualize_communities(graph, communities, file_path="communities.html")
```
**Layout options (`layout=`):**
| Layout | Description | Best For |
| ------ | ----------- | -------- |
| `force` | Physics simulation — clusters emerge naturally | General graphs |
| `hierarchical` | Top-down tree layout | Taxonomies, org charts |
| `circular` | Nodes on a circle, edges as chords | Small dense graphs |
</Tab>
<Tab title="OntologyVisualizer">
Visualize class hierarchies and property relationships:
```python
from semantica.visualization import OntologyVisualizer
viz = OntologyVisualizer()
# Full ontology graph — classes, properties, and constraints
viz.visualize(ontology, output="ontology.html")
# Class hierarchy only — cleaner for large ontologies
viz.visualize_hierarchy(ontology, output="hierarchy.html")
```
</Tab>
<Tab title="EmbeddingVisualizer">
Project high-dimensional embeddings into 2D for cluster analysis:
```python
from semantica.visualization import EmbeddingVisualizer
viz = EmbeddingVisualizer()
viz.visualize_2d_projection(
embeddings=embeddings,
labels=labels,
output="interactive",
file_path="embeddings.html",
method="umap", # "umap" | "tsne" | "pca"
)
```
| Method | Speed | Preserves | Best For |
| ------ | ----- | --------- | -------- |
| `umap` | Fast | Global + local structure | Large datasets, cluster discovery |
| `tsne` | Medium | Local structure | Tight cluster separation |
| `pca` | Very fast | Variance | Quick overview, linear structure |
</Tab>
<Tab title="TemporalVisualizer">
Visualize how a knowledge graph changes over time:
```python
from semantica.visualization import TemporalVisualizer
viz = TemporalVisualizer()
# Timeline of entity/relationship changes
viz.visualize_timeline(temporal_kg, output="interactive")
# Animated network evolution — one frame per time step
viz.visualize_network_evolution(temporal_kg, output="html", file_path="evolution.html")
# Side-by-side snapshot comparison
viz.visualize_snapshot_comparison(snap_a, snap_b, output="html", file_path="diff.html")
# Recurring temporal patterns
viz.visualize_temporal_patterns(temporal_kg, output="html", file_path="patterns.html")
```
</Tab>
<Tab title="AnalyticsVisualizer">
Visualize graph analytics results — centrality, communities, and degree distribution:
```python
from semantica.visualization import AnalyticsVisualizer
from semantica.kg import CentralityCalculator, CommunityDetector
calc = CentralityCalculator()
centrality = calc.calculate_all_centrality(kg)
detector = CommunityDetector()
communities = detector.detect_communities(kg, algorithm="louvain")
viz = AnalyticsVisualizer()
# Bar chart of top-N nodes by centrality measure
viz.visualize_centrality(centrality, metric="pagerank", top_k=20, output="centrality.html")
# Community-colored graph
viz.visualize_communities(kg, communities, output="communities.html")
# Degree distribution histogram
viz.visualize_degree_distribution(kg, output="degree_dist.html")
# Combined analytics dashboard
viz.visualize_analytics_dashboard(
kg, centrality=centrality, communities=communities,
output="analytics_dashboard.html",
)
```
</Tab>
</Tabs>
## Color Schemes
All visualizers accept a `color_scheme` parameter:
```python
from semantica.visualization import GraphVisualizer
viz = GraphVisualizer()
# Interactive HTML — opens in browser, supports hover and click
viz.visualize(graph, output="graph.html")
# Static image — for reports and export
viz.visualize(graph, output="graph.png", backend="matplotlib")
# Display inline (Jupyter or default browser)
viz.show(graph)
viz.visualize(graph, output="graph.html", color_scheme="vibrant")
```
### Layout and Styling Options
```python
viz.visualize(
graph,
output="graph.html",
layout="force_directed", # "force_directed" | "hierarchical" | "circular" | "spring"
node_color_by="type", # color nodes by entity type attribute
edge_label="relation", # show edge relationship labels
max_nodes=500 # limit rendering for large graphs
)
```
### Layout Options
| Layout | Description | Best For |
| Scheme | Description | Best For |
| ------ | ----------- | -------- |
| `force_directed` | Physics simulation — clusters emerge naturally | General graphs |
| `hierarchical` | Top-down tree layout | Taxonomies, org charts |
| `circular` | Nodes on a circle, edges as chords | Small dense graphs |
| `spring` | Spring-force layout (Fruchterman-Reingold) | Medium graphs |
| `default` | Blue-grey palette | General use |
| `vibrant` | High-contrast, saturated colours | Presentations |
| `pastel` | Soft, muted tones | Light backgrounds |
| `dark` | Dark background with bright nodes | Dark-mode dashboards |
| `light` | White background, thin edges | Publications, print |
| `colorblind` | Okabe-Ito safe palette | Accessibility |
## OntologyVisualizer
## Export Formats
Visualize class hierarchies and property relationships:
```python
from semantica.visualization import OntologyVisualizer
viz = OntologyVisualizer()
# Full ontology graph
viz.visualize(ontology, output="ontology.html")
# Class hierarchy only
viz.visualize_hierarchy(ontology, output="hierarchy.html")
```
## EmbeddingVisualizer
Project high-dimensional embeddings into 2D for cluster analysis:
```python
from semantica.visualization import EmbeddingVisualizer
viz = EmbeddingVisualizer()
viz.visualize(
embeddings=embeddings,
labels=labels,
output="embeddings.html",
method="umap" # "umap" | "tsne" | "pca"
)
```
| Method | Speed | Preserves | Best For |
| ------ | ----- | --------- | -------- |
| `umap` | Fast | Global + local structure | Large datasets, cluster discovery |
| `tsne` | Medium | Local structure | Tight cluster separation |
| `pca` | Very fast | Variance | Quick overview, linear structure |
## TemporalVisualizer
Visualize how a knowledge graph changes over time:
```python
from semantica.visualization import TemporalVisualizer
viz = TemporalVisualizer()
# Static timeline of additions and removals
viz.visualize_timeline(temporal_kg, output="timeline.html")
# Animated evolution — one frame per time step
viz.animate(temporal_kg, output="evolution.html", fps=2)
```
## DistanceVisualizer (v0.5.0)
Semantic neighborhood and distance matrix visualization from Distance Intelligence:
```python
from semantica.visualization import DistanceVisualizer
viz = DistanceVisualizer()
# Ego-mode: neighborhood of one node colored by distance band
viz.visualize_ego(
graph,
center_node="Apple Inc.",
output="ego.html",
radius=0.5 # semantic distance radius
)
# N×N distance matrix heatmap
viz.visualize_distance_matrix(
matrix=distance_matrix,
labels=node_labels,
output="distance_heatmap.html"
)
```
| Format | Interactive | Scalable | Best For |
| ------ | ----------- | -------- | -------- |
| `.html` | Yes | N/A | Web dashboards, exploratory analysis |
| `.png` | No | No | Reports, Jupyter notebooks |
| `.svg` | No | Yes | Publications, slide decks |
| `.pdf` | No | Yes | Print, compliance exports |
## Graph Explorer (Full Dashboard)
For a full browser-based UI with search, path finding, and the Ontology Hub, use `semantica.explorer`:
For a full browser-based UI with search, path finding, and the Ontology Hub, launch the Explorer via the CLI:
```python
from semantica.explorer import start_explorer
start_explorer(graph=kg, port=8080)
# Opens at http://localhost:8080
```bash
semantica explore
```
See the [Explorer reference](explorer) for the full feature set and REST API.
## Tips and Common Pitfalls
<Warning>
**Use `max_nodes=500` for large graphs.** Force-directed layouts become unreadable and very slow above ~1,000 nodes. Limit with `max_nodes=500` or filter to a subgraph (e.g., top 100 nodes by PageRank) before visualizing.
</Warning>
<Tip>
**HTML output is always the best starting point.** Interactive HTML lets you zoom, pan, hover for details, and hide node types — giving you orders of magnitude more exploratory power than a static PNG. Only export to PNG/SVG/PDF when embedding in a report.
</Tip>
<Tip>
**Use `color_scheme="colorblind"` in publications and dashboards.** The Okabe-Ito palette is readable for everyone, including the ~8% of male readers who are red-green colorblind. Reserve `vibrant` for internal presentations only.
</Tip>
<Tip>
**UMAP is faster than t-SNE at scale.** For embedding spaces with >5,000 points, UMAP completes in seconds; t-SNE may take minutes. Both produce good cluster separation — use UMAP for exploratory speed, t-SNE for final publication-quality plots.
</Tip>
<Warning>
**`TemporalVisualizer.animate()` can produce large files.** Animated HTML files include all frames and can reach dozens of MB for long time series. Use `fps=1` or reduce the number of time steps for a manageable file size.
</Warning>
<Tip>
**For interactive dashboards, prefer Explorer.** `KGVisualizer.visualize_network()` generates a self-contained HTML file. The Explorer CLI (`semantica explore`) gives a full live web app with search, filtering, path-finding, and REST API. Use Explorer for team exploration, Visualizer for standalone report embeds.
</Tip>
<CardGroup cols={2}>
<Card title="Knowledge Graph" icon="diagram-project" href="kg">
The graph being visualized.