mirror of
https://github.com/semantica-agi/semantica.git
synced 2026-09-04 04:01:07 +00:00
Compare commits
30
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d0d6b9ab5c | ||
|
|
b177fa7556 | ||
|
|
4559ac6536 | ||
|
|
b17ce71f56 | ||
|
|
39c35549d2 | ||
|
|
d54d74c810 | ||
|
|
2086a21615 | ||
|
|
b4af22d724 | ||
|
|
6a2173027e | ||
|
|
66632a9437 | ||
|
|
a59688c6f9 | ||
|
|
40466269b8 | ||
|
|
38ae5b580b | ||
|
|
279fdbf15b | ||
|
|
45915e50a3 | ||
|
|
b7b60d4a17 | ||
|
|
25d2ea5fe9 | ||
|
|
3c68cd12ad | ||
|
|
798a7455e4 | ||
|
|
f83d2a8b12 | ||
|
|
bd584b7402 | ||
|
|
a4500f5b20 | ||
|
|
bdd12e8ac6 | ||
|
|
48204d4e02 | ||
|
|
1bc873cbbd | ||
|
|
4dd88375e1 | ||
|
|
fc899c6966 | ||
|
|
b8299b1427 | ||
|
|
bbd423c50a | ||
|
|
6b36379f15 |
@@ -12,13 +12,63 @@ on:
|
||||
- '**/*.md'
|
||||
pull_request:
|
||||
branches: [main]
|
||||
paths-ignore:
|
||||
- 'docs/**'
|
||||
- 'docs_check.py'
|
||||
- '**/*.md'
|
||||
|
||||
jobs:
|
||||
# Detect whether this PR touches any source files (non-docs/non-markdown).
|
||||
# The result drives the `build` job's `if:` condition so that:
|
||||
# - docs-only PRs: `build` is skipped (satisfies the required check).
|
||||
# - code PRs: `build` runs exactly as before.
|
||||
# Push events (to main) keep their own paths-ignore above and never reach
|
||||
# this job, so the push optimization is unaffected.
|
||||
changes:
|
||||
runs-on: ubuntu-latest
|
||||
# Only needed for pull_request events; push events are pre-filtered above.
|
||||
if: github.event_name == 'pull_request'
|
||||
outputs:
|
||||
src: ${{ steps.filter.outputs.src }}
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
|
||||
with:
|
||||
# Fetch enough history to compute the merge base against the PR base.
|
||||
fetch-depth: 0
|
||||
- name: Check for source changes
|
||||
id: filter
|
||||
run: |
|
||||
# List files changed in this PR relative to the true merge base.
|
||||
# Using three-dot merge-base diff so changes on the base branch that
|
||||
# are not part of this PR do not appear in the file list.
|
||||
# If every changed file matches docs/** or *.md (any depth) or
|
||||
# docs_check.py, this is a docs-only PR and src=false; otherwise
|
||||
# src=true.
|
||||
BASE="${{ github.event.pull_request.base.sha }}"
|
||||
HEAD="${{ github.event.pull_request.head.sha }}"
|
||||
MERGE_BASE=$(git merge-base "$BASE" "$HEAD")
|
||||
CHANGED=$(git diff --name-only "$MERGE_BASE" "$HEAD")
|
||||
echo "Changed files:"
|
||||
echo "$CHANGED"
|
||||
NON_DOCS=$(echo "$CHANGED" | grep -Ev '^(docs/|docs_check\.py|.*\.md$)' || true)
|
||||
if [ -n "$NON_DOCS" ]; then
|
||||
echo "src=true" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "src=false" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
build:
|
||||
needs: [changes]
|
||||
# For pull_request events:
|
||||
# - skip only when changes ran successfully and explicitly set src=false
|
||||
# (i.e. a confirmed docs-only PR).
|
||||
# - run when changes succeeded with src=true (source changes present).
|
||||
# - run when changes failed or was cancelled (fail-closed: missing output
|
||||
# must not silently skip the build).
|
||||
# For push/non-PR events: changes is skipped; always() prevents the build
|
||||
# from being skipped due to a skipped needs dependency.
|
||||
if: >-
|
||||
always() && (
|
||||
github.event_name != 'pull_request' ||
|
||||
needs.changes.result != 'success' ||
|
||||
needs.changes.outputs.src == 'true'
|
||||
)
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
|
||||
|
||||
@@ -13,17 +13,65 @@ on:
|
||||
- '**/*.md'
|
||||
pull_request:
|
||||
branches: [main]
|
||||
paths-ignore:
|
||||
- 'docs/**'
|
||||
- 'mkdocs.yml'
|
||||
- 'requirements-docs.txt'
|
||||
- '**/*.md'
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
# Detect whether this PR touches any source files (non-docs/non-markdown).
|
||||
# The result drives the `security-scan` job's `if:` condition so that:
|
||||
# - docs-only PRs: `security-scan` is skipped (satisfies the required check).
|
||||
# - code PRs: the full scan runs exactly as before.
|
||||
# Schedule and workflow_dispatch runs always skip this job and run the scan
|
||||
# unconditionally (the security-scan job's if: accounts for that below).
|
||||
# Push events (to main) keep their own paths-ignore above.
|
||||
changes:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event_name == 'pull_request'
|
||||
outputs:
|
||||
src: ${{ steps.filter.outputs.src }}
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Check for source changes
|
||||
id: filter
|
||||
run: |
|
||||
# List files changed in this PR relative to the true merge base.
|
||||
# Using three-dot merge-base diff so changes on the base branch that
|
||||
# are not part of this PR do not appear in the file list.
|
||||
# If every changed file matches the docs/markdown paths-ignore list
|
||||
# (at any directory depth), this is a docs-only PR and src=false;
|
||||
# otherwise src=true.
|
||||
BASE="${{ github.event.pull_request.base.sha }}"
|
||||
HEAD="${{ github.event.pull_request.head.sha }}"
|
||||
MERGE_BASE=$(git merge-base "$BASE" "$HEAD")
|
||||
CHANGED=$(git diff --name-only "$MERGE_BASE" "$HEAD")
|
||||
echo "Changed files:"
|
||||
echo "$CHANGED"
|
||||
NON_DOCS=$(echo "$CHANGED" | grep -Ev '^(docs/|mkdocs\.yml$|requirements-docs\.txt$|.*\.md$)' || true)
|
||||
if [ -n "$NON_DOCS" ]; then
|
||||
echo "src=true" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "src=false" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
security-scan:
|
||||
# For pull_request events:
|
||||
# - skip only when changes ran successfully and explicitly set src=false
|
||||
# (i.e. a confirmed docs-only PR).
|
||||
# - run when changes succeeded with src=true (source changes present).
|
||||
# - run when changes failed or was cancelled (fail-closed: missing output
|
||||
# must not silently skip the security scan).
|
||||
# For schedule/workflow_dispatch/push: changes is skipped; always() ensures
|
||||
# the scan still runs unconditionally for those triggers.
|
||||
needs: [changes]
|
||||
if: >-
|
||||
always() && (
|
||||
github.event_name != 'pull_request' ||
|
||||
needs.changes.result != 'success' ||
|
||||
needs.changes.outputs.src == 'true'
|
||||
)
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
@@ -11,6 +11,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
### Added
|
||||
|
||||
- **Salesforce ingestor** (#1240) by @Sameer6305
|
||||
- New `SalesforceConnector` / `SalesforceData` / `SalesforceIngestor` (`semantica.ingest`, lazy export), following the same Connector + Data + Ingestor pattern already used for Snowflake/Databricks/SAP
|
||||
- Auth covers both landscapes Salesforce actually uses: username + password + security token (SOAP login), session_id + instance_url (reusing an existing session), and username + consumer_key + private key (JWT Bearer); production and sandbox are selected via `domain`, and credentials can come from environment variables. Credential material is never intentionally written to logs, exceptions, or `repr()`
|
||||
- `ingest_sobject()`, `ingest_query()`, `list_sobjects()`, `get_sobject_schema()`, `export_as_documents()` against standard sObjects, custom objects (`__c`), custom metadata (`__mdt`), platform events (`__e`), namespaced objects, and relationship-field traversal (e.g. `Owner.Name`); pagination follows `nextRecordsUrl`/`query_more()` and stops once a caller's `limit` is satisfied
|
||||
- New `pip install semantica[db-salesforce]` extra (`simple-salesforce>=1.12.0`)
|
||||
- New `tests/test_salesforce_ingestor.py`
|
||||
- Docs: `docs/integrations/salesforce.md`
|
||||
|
||||
- **`ErasureCoordinator` completes the erasure workflow `purge_node()` only starts — the graph node was removed while the same content survived verbatim in `AgentMemory` and as an embedding** (closes #1018) by @pravit-amp
|
||||
- New `semantica/context/erasure.py`, exporting `ErasureCoordinator` and `ErasureReceipt` from `semantica.context`. `purge_node()`/`purge_edge()` (#957) are graph-scope by design and their changelog entry documents this gap explicitly; the changelog also names GDPR Article 17 as the motivation, and an Article 17 erasure that removes the node while the content stays retrievable by similarity search is not an erasure — it is worse than not offering one, because `purge_node()` returns `True` and writes a tombstone attesting the content is gone
|
||||
- The coordinator **composes** the existing public APIs — nothing in `context_graph.py` or `agent_memory.py` changes behaviorally, and `ContextGraph` keeps its documented graph-scope contract rather than acquiring references to `AgentMemory`/`vector_store` that would invert the dependency
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
|
||||
**Context Management · Knowledge Modeling · Deterministic Reasoning · Ontology Management · Decision Intelligence · End-to-End Traceability**
|
||||
|
||||
**Open Source · Self-Hostable · Auditable · Governed · Zero Vendor Lock-In**
|
||||
**Open Source · Governed · Zero Vendor Lock-In**
|
||||
|
||||
**Polyglot Graph Storage · RDF & LPG Support · W3C Standards · Interoperable**
|
||||
|
||||
@@ -62,12 +62,12 @@ Most AI agents run on embeddings, not meaning: similarity scores with no structu
|
||||
|
||||
**Who it's for:**
|
||||
|
||||
- **AI/ML platform teams** shipping agents that make consequential decisions and need structured, queryable context built from fragmented raw data, not just a vector index
|
||||
- **Data platform teams on Databricks or Snowflake** who need to turn tables already sitting in Unity Catalog or a Snowflake warehouse into a governed, lineage-tracked knowledge graph, without exporting that data to a third-party SaaS first
|
||||
- **Compliance, risk, and audit teams** who need a straight answer to "why did the AI do that?" in a format a regulator will actually accept
|
||||
- **Regulated enterprises** (finance, healthcare, legal, government, defense) that can't ship a black box, and can't send their data to someone else's SaaS to get one
|
||||
- **AI/ML platform teams** shipping agents that make consequential decisions and need structured, queryable context, not just a vector index
|
||||
- **Data platform teams on Databricks or Snowflake** turning tables already in Unity Catalog or a warehouse into a governed, lineage-tracked knowledge graph, without exporting to a third-party SaaS
|
||||
- **Compliance, risk, and audit teams** who need a straight answer to "why did the AI do that?" in a format a regulator accepts
|
||||
- **Regulated enterprises** (finance, healthcare, legal, government, defense) that can't ship a black box or send their data to someone else's SaaS to get one
|
||||
- **Platform and infra engineers** who want the KG, reasoning, and provenance stack self-hosted and swappable, not locked to one vendor's backend
|
||||
- **Data and knowledge engineers** building a KG from messy, multi-source data: entities and relationships get extracted, conflicting or contradictory facts are flagged instead of silently overwritten, and duplicates are merged before they turn into noise
|
||||
- **Data and knowledge engineers** building a KG from messy, multi-source data, where conflicting facts get flagged and duplicates get merged, not silently overwritten
|
||||
|
||||
**[Quick Start](#quick-start)** · **[Architecture](#architecture)** · **[What You Get](#what-semantica-gives-you)** · **[Why Semantica](#why-semantica)** · **[Decision Intelligence](#decision-intelligence)** · **[Context Graphs](#context-graphs)** · **[Recipe: Audit Trail](#recipe-audit-trail-for-a-regulated-decision)** · **[Module Reference](#module-reference)** · **[Integrations](#integrations)** · **[CLI](#cli)** · **[Performance](#performance)** · **[Install](#installation)**
|
||||
|
||||
@@ -81,7 +81,7 @@ Most AI agents run on embeddings, not meaning: similarity scores with no structu
|
||||
- **Full Auditability:** W3C PROV-O provenance on every fact, with audit trails exportable to JSON, CSV, or RDF
|
||||
- **Deterministic Reasoning:** Forward chaining, Rete network, Datalog, and SPARQL with fully explainable paths, not black boxes
|
||||
- **Knowledge Pipeline:** Multi-source ingestion, entity-aware chunking, NER/relation/event extraction, and knowledge graph construction, with semantic deduplication and provenance-preserving merges throughout
|
||||
- **Enterprise Data Platforms:** Native connectors for Databricks (Unity Catalog + Delta Lake, PAT/OAuth M2M auth, catalog/schema/table/lineage introspection) and Snowflake (warehouse/database/schema, key-pair and OAuth auth), so tables already living in your lakehouse or warehouse become graph nodes with provenance, not another export/import hop
|
||||
- **Enterprise Data Platforms:** Native connectors for Databricks (Unity Catalog + Delta Lake, PAT/OAuth M2M auth, catalog/schema/table/lineage introspection), Snowflake (warehouse/database/schema, key-pair and OAuth auth), and SAP OData (Business Partners, Sales Orders, OAuth2/Basic auth), so data already living in your lakehouse or warehouse becomes graph nodes with provenance, not another export/import hop
|
||||
- **Graph Analytics:** Centrality, community detection, link prediction, and shortest-path queries over the graph you just built
|
||||
- **Polyglot Graph Storage:** Native RDF (embedded Oxigraph, Blazegraph, Apache Jena, Eclipse RDF4J via SPARQL) and Labeled Property Graphs (Neo4j, FalkorDB, Apache AGE, AWS Neptune via Cypher), plus vector stores, all swappable without touching your code
|
||||
- **Visualization:** Explore any graph, ontology, or timeline in an interactive browser workbench
|
||||
@@ -139,10 +139,6 @@ compliant = graph.check_decision_rules({"category": "vendor_selection"}) # poli
|
||||
|
||||
```bash
|
||||
semantica doctor
|
||||
# Python 3.11.9 pass
|
||||
# semantica 0.6.7 pass
|
||||
# faiss vector store pass
|
||||
# Config file pass ~/.semantica/config.yaml
|
||||
```
|
||||
|
||||
**Running in a script or CI?** Progress bars are written only when stdout is an interactive terminal (or a Jupyter notebook), so piping and redirecting stay clean by default. Override with `SEMANTICA_DISABLE_PROGRESS=1` to silence progress everywhere, or `SEMANTICA_FORCE_PROGRESS=1` to keep it when stdout is redirected. `SEMANTICA_DISABLE_PROGRESS` takes precedence.
|
||||
@@ -167,7 +163,7 @@ Sources → Ingest → Parse → Normalize → Split → Extract → Conflict De
|
||||
→ Vector Store + Polyglot Graph Store (RDF & LPG) → Export / Visualize / REST · MCP · CLI
|
||||
```
|
||||
|
||||
- **Ingest:** files, web, databases, enterprise data platforms (Databricks, Snowflake), cloud (Google Drive, Elasticsearch), streams (Kafka, Kinesis), Git, email, MCP
|
||||
- **Ingest:** files, web, databases, enterprise data platforms (Databricks, Snowflake, SAP), cloud (Google Drive, Elasticsearch), streams (Kafka, Kinesis), Git, email, MCP
|
||||
- **Parse → Normalize → Split:** document parsing, text/entity/date normalization, GraphRAG-native entity-aware chunking
|
||||
- **Extract → Conflict Detection → Deduplication:** NER, relations, events, triplets; conflicting facts flagged and resolved before they merge
|
||||
- **Knowledge Graph:** `GraphBuilder` constructs the graph; bi-temporal facts and full graph analytics (centrality, communities, link prediction) run on top of it
|
||||
@@ -320,7 +316,7 @@ Every module below is independently importable, with working code samples verifi
|
||||
|
||||
| Module | What it does |
|
||||
| --- | --- |
|
||||
| [`semantica.ingest`](#semanticaingest-multi-source-ingestion) | Files, web, databases, APIs, streams, email, Git, Parquet, Databricks, Snowflake, MCP |
|
||||
| [`semantica.ingest`](#semanticaingest-multi-source-ingestion) | Files, web, databases, APIs, streams, email, Git, Parquet, Databricks, Snowflake, SAP, MCP |
|
||||
| [`semantica.semantic_extract`](#semanticasemantic_extract-ner-relations-events-triplets) | NER, relation extraction, event detection, triplet generation |
|
||||
| [`semantica.kg`](#semanticakg-knowledge-graph-construction--analysis) | Graph construction, centrality, communities, link prediction |
|
||||
| [`semantica.reasoning`](#semanticareasoning-forward-chaining-rete-datalog-sparql) | Forward chaining, Rete, Datalog, SPARQL, fully explainable |
|
||||
@@ -349,7 +345,7 @@ Expand any module below for its runnable example.
|
||||
<summary><b><code>semantica.ingest</code></b>: Multi-Source Ingestion</summary>
|
||||
<a id="semanticaingest-multi-source-ingestion"></a>
|
||||
|
||||
Ingest from files, web, databases, APIs, streams, email, Git repos, Parquet, Databricks, Snowflake, or MCP servers, all through a unified interface.
|
||||
Ingest from files, web, databases, APIs, streams, email, Git repos, Parquet, Databricks, Snowflake, SAP, or MCP servers, all through a unified interface.
|
||||
|
||||
```python
|
||||
from semantica.ingest import FileIngestor, WebIngestor, ParquetIngestor, DBIngestor
|
||||
@@ -400,7 +396,7 @@ orders = snowflake.ingest_table("ORDERS", limit=10_000)
|
||||
|
||||
> **Security Note:** Never hardcode credentials (`token`, `password`, `private_key`) in production code; pass them via environment variables (e.g., `DATABRICKS_TOKEN`, `SNOWFLAKE_PASSWORD`) or a secrets manager.
|
||||
|
||||
**Supported sources:** Local files (PDF, DOCX, PPTX, HTML, TXT, CSV, JSON, YAML, Excel, XML) · Web pages · RSS/Atom feeds · REST APIs · Databases (PostgreSQL, MySQL, SQLite, Oracle, SQL Server) · Parquet datasets · Databricks (Unity Catalog + Delta Lake) · Snowflake · Git repositories · Email (IMAP/POP3) · Message streams (Kafka, RabbitMQ, Kinesis, Pulsar) · MCP resources · Apache Arrow/Feather/IPC (`ArrowIngestor`)
|
||||
**Supported sources:** Local files (PDF, DOCX, PPTX, HTML, TXT, CSV, JSON, YAML, Excel, XML) · Web pages · RSS/Atom feeds · REST APIs · Databases (PostgreSQL, MySQL, SQLite, Oracle, SQL Server) · Parquet datasets · Databricks (Unity Catalog + Delta Lake) · Snowflake · SAP (OData v2/v4) · Git repositories · Email (IMAP/POP3) · Message streams (Kafka, RabbitMQ, Kinesis, Pulsar) · MCP resources · Apache Arrow/Feather/IPC (`ArrowIngestor`)
|
||||
|
||||
DuckDB, Elasticsearch, Google Drive, HuggingFace, MongoDB, and Pandas ingestion also ship (`DuckDBIngestor`, `ElasticIngestor`, `GDriveIngestor`, `HuggingFaceIngestor`, `MongoIngestor`, `PandasIngestor`) but aren't re-exported from the top-level `semantica.ingest` namespace yet — import them directly: `from semantica.ingest.duckdb_ingestor import DuckDBIngestor`.
|
||||
|
||||
@@ -1145,7 +1141,7 @@ if report.valid:
|
||||
| **Vector Store** | FAISS · Pinecone · Weaviate · Qdrant · Milvus · PgVector · hybrid + filtered search |
|
||||
| **Graph Databases (LPG)** | Neo4j · FalkorDB · Apache AGE · AWS Neptune |
|
||||
| **Triple Stores (RDF)** | Oxigraph (embedded) · Blazegraph · Apache Jena · Eclipse RDF4J · unified `TripletStore` interface · SPARQL query & bulk load |
|
||||
| **Enterprise Data Platforms** | Databricks (`DatabricksIngestor`: Unity Catalog + Delta Lake, PAT/OAuth M2M, table/query ingestion, catalog/schema/table/lineage introspection) · Snowflake (`SnowflakeIngestor`: warehouse/database/schema, password/key-pair/OAuth auth) |
|
||||
| **Enterprise Data Platforms** | Databricks (`DatabricksIngestor`: Unity Catalog + Delta Lake, PAT/OAuth M2M, table/query ingestion, catalog/schema/table/lineage introspection) · Snowflake (`SnowflakeIngestor`: warehouse/database/schema, password/key-pair/OAuth auth) · SAP (`SAPIngestor`: OData v2/v4, OAuth2/Basic auth, Business Partners/Sales Orders) |
|
||||
| **LLM Providers** | **All already supported today:** OpenAI (GPT-4o, o1, o3) · Anthropic (Claude) · Google Gemini · Mistral · Meta Llama · Groq · Cohere · Azure OpenAI · AWS Bedrock · Ollama · DeepSeek · Perplexity · Together AI · Fireworks AI · Replicate · HuggingFace · via `semantica.llms` and LiteLLM |
|
||||
|
||||
---
|
||||
@@ -1517,6 +1513,7 @@ pip install semantica[vectorstore-qdrant] # Qdrant vector store
|
||||
pip install semantica[vectorstore-pinecone] # Pinecone vector store
|
||||
pip install semantica[db-snowflake] # Snowflake
|
||||
pip install semantica[db-databricks] # Databricks (SDK + SQL connector)
|
||||
pip install semantica[ingest-sap] # SAP OData
|
||||
pip install semantica[ingest-parquet] # Parquet / PyArrow
|
||||
pip install semantica[ingest-arrow] # Apache Arrow, Feather, IPC
|
||||
pip install semantica[viz] # HTML interactive visualization
|
||||
|
||||
@@ -185,7 +185,7 @@ Centralized `ConfigManager` with environment variable overrides. No magic defaul
|
||||
| **Deduplication v2** | `blocking_v2`, `hybrid_v2`, `semantic_v2`: up to 7x faster than v1 |
|
||||
| **Indexed search** | Explorer search at 0.004ms on 118k nodes (v0.5.0) |
|
||||
|
||||
- [Modules](modules) — Full module documentation with code examples.
|
||||
- [Learning More](learning-more) — Configuration reference, performance guide, and troubleshooting.
|
||||
- [Pipeline Reference](reference/pipeline) — Pipeline orchestration, workers, and retry policies.
|
||||
- [Core Reference](reference/core) — Framework lifecycle, plugin registry, and configuration.
|
||||
- [Modules](/modules) — Full module documentation with code examples.
|
||||
- [Learning More](/learning-more) — Configuration reference, performance guide, and troubleshooting.
|
||||
- [Pipeline Reference](/reference/pipeline) — Pipeline orchestration, workers, and retry policies.
|
||||
- [Core Reference](/reference/core) — Framework lifecycle, plugin registry, and configuration.
|
||||
|
||||
+14
-14
@@ -5,7 +5,7 @@ icon: "compass"
|
||||
---
|
||||
|
||||
<Info>
|
||||
Every module works independently — import only what you need. This page maps developer goals to starting points. The [Module Reference](modules) covers every module in depth.
|
||||
Every module works independently — import only what you need. This page maps developer goals to starting points. The [Module Reference](/modules) covers every module in depth.
|
||||
</Info>
|
||||
|
||||
## Quick Reference
|
||||
@@ -89,7 +89,7 @@ Pick your goal to see the minimum imports and a working skeleton.
|
||||
Pass `method="pattern"` to `NERExtractor` for zero-cost, zero-API-key extraction. Switch to `method="llm"` with any of the supported providers for higher recall.
|
||||
</Tip>
|
||||
|
||||
**Next:** [Quickstart →](quickstart) — full pipeline with visualization and export.
|
||||
**Next:** [Quickstart →](/quickstart) — full pipeline with visualization and export.
|
||||
</Tab>
|
||||
|
||||
<Tab title="Build GraphRAG">
|
||||
@@ -122,7 +122,7 @@ Pick your goal to see the minimum imports and a working skeleton.
|
||||
print(result["reasoning_path"]) # multi-hop trace
|
||||
```
|
||||
|
||||
**Next:** [Context module reference →](reference/context)
|
||||
**Next:** [Context module reference →](/reference/context)
|
||||
</Tab>
|
||||
|
||||
<Tab title="Add Agent Memory">
|
||||
@@ -163,7 +163,7 @@ Pick your goal to see the minimum imports and a working skeleton.
|
||||
`decision_tracking=True` is required. Without it, `record_decision()` raises `RuntimeError`.
|
||||
</Note>
|
||||
|
||||
**Next:** [Context module reference →](reference/context)
|
||||
**Next:** [Context module reference →](/reference/context)
|
||||
</Tab>
|
||||
|
||||
<Tab title="Track Provenance">
|
||||
@@ -195,7 +195,7 @@ Pick your goal to see the minimum imports and a working skeleton.
|
||||
diff = manager.diff("v1.0", "v1.1")
|
||||
```
|
||||
|
||||
**Next:** [Provenance reference →](reference/provenance) · [Change Management reference →](reference/change_management)
|
||||
**Next:** [Provenance reference →](/reference/provenance) · [Change Management reference →](/reference/change_management)
|
||||
</Tab>
|
||||
|
||||
<Tab title="Export">
|
||||
@@ -222,11 +222,11 @@ Pick your goal to see the minimum imports and a working skeleton.
|
||||
|
||||
**Formats:** Turtle · JSON-LD · N-Triples · RDF/XML · Parquet · Cypher · Arrow · OWL · CSV · ArangoDB AQL
|
||||
|
||||
**Next:** [Export module reference →](reference/export)
|
||||
**Next:** [Export module reference →](/reference/export)
|
||||
</Tab>
|
||||
|
||||
<Tab title="MCP — Claude / Cursor">
|
||||
Use Semantica from Claude Desktop, Cursor, VS Code, or any MCP-aware tool — no Python code required after setup. 12 tools available instantly.
|
||||
Use Semantica from Claude Desktop, Cursor, VS Code, or any MCP-aware tool — no Python code required after setup. 15 tools available instantly.
|
||||
|
||||
**Step 1 — Install:**
|
||||
```bash
|
||||
@@ -268,7 +268,7 @@ Pick your goal to see the minimum imports and a working skeleton.
|
||||
Set `SEMANTICA_KG_PATH` to persist your graph across restarts. Without it, all data is lost when the server process exits.
|
||||
</Warning>
|
||||
|
||||
**Next:** [MCP Server reference →](reference/mcp_server)
|
||||
**Next:** [MCP Server reference →](/reference/mcp_server)
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
@@ -283,11 +283,11 @@ Pick your goal to see the minimum imports and a working skeleton.
|
||||
|
||||
Use **both together** via `AgentContext` (GraphRAG) to get grounded LLM responses where every claim traces back to a source node.
|
||||
|
||||
See also: [Core Concepts](concepts)
|
||||
See also: [Core Concepts](/concepts)
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="I just want to run something quickly." icon="rocket">
|
||||
Start with the [Quickstart](quickstart). It builds a complete pipeline (ingest → parse → extract → graph → visualize → export) with no API key required.
|
||||
Start with the [Quickstart](/quickstart). It builds a complete pipeline (ingest → parse → extract → graph → visualize → export) with no API key required.
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="I'm adding Semantica to an existing agent — what's the minimum?" icon="plug">
|
||||
@@ -304,7 +304,7 @@ Pick your goal to see the minimum imports and a working skeleton.
|
||||
)
|
||||
```
|
||||
|
||||
[Context module reference →](reference/context)
|
||||
[Context module reference →](/reference/context)
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="I need a compliance-ready pipeline — what's the minimum stack?" icon="shield-check">
|
||||
@@ -322,6 +322,6 @@ Pick your goal to see the minimum imports and a working skeleton.
|
||||
|
||||
---
|
||||
|
||||
- [Quickstart](quickstart) — Full pipeline in 5 minutes.
|
||||
- [Module Reference](modules) — Every module with examples and common chains.
|
||||
- [API Reference](reference/context) — Complete class and method documentation.
|
||||
- [Quickstart](/quickstart) — Full pipeline in 5 minutes.
|
||||
- [Module Reference](/modules) — Every module with examples and common chains.
|
||||
- [API Reference](/reference/context) — Complete class and method documentation.
|
||||
|
||||
+2
-2
@@ -48,5 +48,5 @@ Published research using Semantica? [Let us know](https://github.com/semantica-a
|
||||
|
||||
## See Also
|
||||
|
||||
- [License](project-license) — MIT License details.
|
||||
- [Community](community) — Connect with the Semantica community.
|
||||
- [License](/project-license) — MIT License details.
|
||||
- [Community](/community) — Connect with the Semantica community.
|
||||
|
||||
+9
-9
@@ -24,7 +24,7 @@ After installation the following commands are available:
|
||||
| `semantica-mcp` | `semantica.mcp_server:main` | MCP server (stdio) for Claude Desktop, Cursor, Windsurf, and other MCP clients |
|
||||
|
||||
<Note>
|
||||
`semantica-explorer` requires `pip install semantica[explorer]`. Running it without that extra will immediately print an error and exit. See [Explorer Setup](explorer-setup) for the full walkthrough.
|
||||
`semantica-explorer` requires `pip install semantica[explorer]`. Running it without that extra will immediately print an error and exit. See [Explorer Setup](/explorer-setup) for the full walkthrough.
|
||||
</Note>
|
||||
|
||||
|
||||
@@ -52,8 +52,8 @@ python -c "import semantica; print(semantica.__version__)"
|
||||
- **semantica** — The general-purpose CLI. Use it for one-off pipeline runs, entity extraction, and graph operations from a shell script or CI job.
|
||||
- **semantica-server** — Starts the REST API server. Binds to `0.0.0.0:8000`. Use this when another service or application needs programmatic access to Semantica over HTTP.
|
||||
- **semantica-worker** — Background task processor. Run alongside `semantica-server` when you need async pipeline execution outside the request cycle. Start the server first, then start one or more workers pointing at the same backend.
|
||||
- **semantica-explorer** — Launches the browser dashboard. Requires `pip install semantica[explorer]`. Use this to explore a saved knowledge graph interactively. See [Explorer Setup](explorer-setup).
|
||||
- **semantica-mcp** — Runs the MCP server over stdio. Configure it in your MCP client's settings file to expose all 12 tools and 3 resources to Claude Desktop, Cursor, Windsurf, or any MCP-aware client. See [MCP Server](reference/mcp_server).
|
||||
- **semantica-explorer** — Launches the browser dashboard. Requires `pip install semantica[explorer]`. Use this to explore a saved knowledge graph interactively. See [Explorer Setup](/explorer-setup).
|
||||
- **semantica-mcp** — Runs the MCP server over stdio. Configure it in your MCP client's settings file to expose all 15 tools and 3 resources to Claude Desktop, Cursor, Windsurf, or any MCP-aware client. See [MCP Server](/reference/mcp_server).
|
||||
|
||||
|
||||
## Usage Examples
|
||||
@@ -116,7 +116,7 @@ python -c "import semantica; print(semantica.__version__)"
|
||||
echo '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"test","version":"1.0"}}}' | semantica-mcp
|
||||
```
|
||||
|
||||
You should receive a JSON-RPC response. See [MCP Server](reference/mcp_server) for the full list of tools and resources.
|
||||
You should receive a JSON-RPC response. See [MCP Server](/reference/mcp_server) for the full list of tools and resources.
|
||||
</Tab>
|
||||
<Tab title="Explorer">
|
||||
```bash
|
||||
@@ -124,7 +124,7 @@ python -c "import semantica; print(semantica.__version__)"
|
||||
semantica-explorer --graph my_graph.json
|
||||
```
|
||||
|
||||
See [Explorer Setup](explorer-setup) for the full walkthrough including how to build and save a graph file.
|
||||
See [Explorer Setup](/explorer-setup) for the full walkthrough including how to build and save a graph file.
|
||||
</Tab>
|
||||
<Tab title="Python module form">
|
||||
Every command also runs as a Python module: useful when the script directory is not on `PATH`:
|
||||
@@ -228,7 +228,7 @@ Install the [Microsoft Visual C++ Redistributable](https://aka.ms/vs/17/release/
|
||||
|
||||
## Next Steps
|
||||
|
||||
- [Explorer Setup](explorer-setup) — Build a graph, save it, and launch the browser dashboard.
|
||||
- [MCP Server](reference/mcp_server) — All 12 tools and 3 resources exposed over the MCP protocol.
|
||||
- [Installation](installation) — Virtual environments, optional extras, and platform-specific notes.
|
||||
- [Quickstart](quickstart) — End-to-end pipeline walkthrough with working code.
|
||||
- [Explorer Setup](/explorer-setup) — Build a graph, save it, and launch the browser dashboard.
|
||||
- [MCP Server](/reference/mcp_server) — All 15 tools and 3 resources exposed over the MCP protocol.
|
||||
- [Installation](/installation) — Virtual environments, optional extras, and platform-specific notes.
|
||||
- [Quickstart](/quickstart) — End-to-end pipeline walkthrough with working code.
|
||||
|
||||
@@ -109,12 +109,12 @@ def my_ingestor(source):
|
||||
method_registry.register("file", "my_format", my_ingestor)
|
||||
```
|
||||
|
||||
See [Architecture](architecture#extension-points) for the full extension guide.
|
||||
See [Architecture](/architecture#extension-points) for the full extension guide.
|
||||
|
||||
|
||||
## How to Contribute
|
||||
|
||||
- [Contributing Guide](contributing-guide) — Submit code, documentation, tests, or cookbook notebooks.
|
||||
- [Contributing Guide](/contributing-guide) — Submit code, documentation, tests, or cookbook notebooks.
|
||||
- [GitHub Issues](https://github.com/semantica-agi/semantica/issues) — Report bugs, request features, or propose integrations.
|
||||
- [Discord](https://discord.gg/sV34vps5hH) — Share what you're building with the community.
|
||||
- [GitHub Discussions](https://github.com/semantica-agi/semantica/discussions) — Long-form questions, design discussions, and ideas.
|
||||
|
||||
+5
-5
@@ -55,7 +55,7 @@ There's no single right way to contribute. Pick the path that fits your skills a
|
||||
- Review open pull requests
|
||||
- Share your Semantica projects in GitHub Discussions
|
||||
|
||||
See the [Contributing Guide](contributing-guide) for the full development workflow.
|
||||
See the [Contributing Guide](/contributing-guide) for the full development workflow.
|
||||
|
||||
|
||||
## Stay Connected
|
||||
@@ -68,7 +68,7 @@ See the [Contributing Guide](contributing-guide) for the full development workfl
|
||||
|
||||
## See Also
|
||||
|
||||
- [Contributing Guide](contributing-guide) — Step-by-step guide for submitting PRs and setting up your dev environment.
|
||||
- [Community Projects](community-projects) — Projects and integrations built by the community.
|
||||
- [FAQ](faq) — Common questions answered.
|
||||
- [Governance](governance) — How the project is run and decisions are made.
|
||||
- [Contributing Guide](/contributing-guide) — Step-by-step guide for submitting PRs and setting up your dev environment.
|
||||
- [Community Projects](/community-projects) — Projects and integrations built by the community.
|
||||
- [FAQ](/faq) — Common questions answered.
|
||||
- [Governance](/governance) — How the project is run and decisions are made.
|
||||
|
||||
+7
-7
@@ -5,7 +5,7 @@ icon: "book-open"
|
||||
---
|
||||
|
||||
<Info>
|
||||
New here? Start with [Getting Started](getting-started) for hands-on examples, then return here for deeper understanding.
|
||||
New here? Start with [Getting Started](/getting-started) for hands-on examples, then return here for deeper understanding.
|
||||
</Info>
|
||||
|
||||
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.
|
||||
@@ -203,7 +203,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. See the [Ontology reference](reference/ontology) for the full 6-stage generation pipeline.
|
||||
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
|
||||
@@ -319,7 +319,7 @@ scores = calc.calculate_similarity(entity_a, entity_b)
|
||||
|
||||
**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.
|
||||
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
|
||||
@@ -413,7 +413,7 @@ When multiple sources disagree on the same fact, Semantica flags and resolves th
|
||||
- **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`.
|
||||
See the [Conflicts reference](/reference/conflicts) for `ConflictResolver`, `SourceTracker`, and `InvestigationGuideGenerator`.
|
||||
|
||||
|
||||
## Custom Plugin Development
|
||||
@@ -482,6 +482,6 @@ Semantica is designed for extension. Any component: ingestor, extractor, graph b
|
||||
</Accordion>
|
||||
</AccordionGroup>
|
||||
|
||||
- [Quickstart Tutorial](quickstart) — Build a full pipeline with code.
|
||||
- [Modules Guide](modules) — Every module explained with examples.
|
||||
- [API Reference](reference/context) — Complete technical reference.
|
||||
- [Quickstart Tutorial](/quickstart) — Build a full pipeline with code.
|
||||
- [Modules Guide](/modules) — Every module explained with examples.
|
||||
- [API Reference](/reference/context) — Complete technical reference.
|
||||
|
||||
@@ -85,5 +85,5 @@ All contributors are expected to follow the [Contributor Covenant Code of Conduc
|
||||
- [GitHub Discussions](https://github.com/semantica-agi/semantica/discussions)
|
||||
- [Discord](https://discord.gg/sV34vps5hH)
|
||||
|
||||
- [Community](community) — Community guidelines and values.
|
||||
- [Governance](governance) — How decisions are made and the project is run.
|
||||
- [Community](/community) — Community guidelines and values.
|
||||
- [Governance](/governance) — How decisions are made and the project is run.
|
||||
|
||||
+1
-1
@@ -8,7 +8,7 @@ icon: "flask"
|
||||
**Where to start:**
|
||||
- **New to Semantica**: begin with [Core Tutorials](#core-tutorials)
|
||||
- **Building an application**: see [Advanced Concepts](#advanced-concepts)
|
||||
- **Need installation help**: see the [Installation Guide](installation)
|
||||
- **Need installation help**: see the [Installation Guide](/installation)
|
||||
</Tip>
|
||||
|
||||
<Note>
|
||||
|
||||
@@ -6,7 +6,7 @@ icon: "map"
|
||||
|
||||
**`semantica-explorer`** is an **interactive browser dashboard** for knowledge graph exploration. You give it a graph file, it starts a local server, and opens a browser tab where you can search nodes, find paths, inspect provenance, and run analytics: no code required after launch.
|
||||
|
||||
This page covers everything needed to go from zero to a running Explorer. For the full REST API reference and endpoint catalogue, see [Explorer Reference](reference/explorer).
|
||||
This page covers everything needed to go from zero to a running Explorer. For the full REST API reference and endpoint catalogue, see [Explorer Reference](/reference/explorer).
|
||||
|
||||
|
||||
## Prerequisites
|
||||
@@ -27,7 +27,7 @@ Verify:
|
||||
semantica-explorer --help
|
||||
```
|
||||
|
||||
You should see the usage message with the four available flags. If you see `command not found`, activate your virtual environment first. See [CLI Setup](cli-setup#troubleshooting) for PATH help.
|
||||
You should see the usage message with the four available flags. If you see `command not found`, activate your virtual environment first. See [CLI Setup](/cli-setup#troubleshooting) for PATH help.
|
||||
|
||||
|
||||
## Minimal End-to-End Example
|
||||
@@ -264,7 +264,7 @@ Once running, Explorer exposes a REST API and dashboard for:
|
||||
|
||||
The full endpoint catalogue is documented in the Swagger UI at `/docs` and in the reference page below.
|
||||
|
||||
- [Explorer Reference](reference/explorer) — Every REST endpoint, WebSocket events, analytics, and all supported flags.
|
||||
- [CLI Setup](cli-setup) — All five Semantica executables and when to use each one.
|
||||
- [Context Module](reference/context) — Full documentation for ContextGraph: build, query, save, and load.
|
||||
- [Quickstart](quickstart) — End-to-end pipeline: ingest → extract → build graph → export.
|
||||
- [Explorer Reference](/reference/explorer) — Every REST endpoint, WebSocket events, analytics, and all supported flags.
|
||||
- [CLI Setup](/cli-setup) — All five Semantica executables and when to use each one.
|
||||
- [Context Module](/reference/context) — Full documentation for ContextGraph: build, query, save, and load.
|
||||
- [Quickstart](/quickstart) — End-to-end pipeline: ingest → extract → build graph → export.
|
||||
|
||||
+8
-8
@@ -16,7 +16,7 @@ icon: "circle-question"
|
||||
| Python version? | 3.8+ (3.11+ recommended) |
|
||||
| API key required? | Optional: pattern extraction works with no keys |
|
||||
| Works with LangChain / LlamaIndex? | Yes: Semantica is a layer on top, not a replacement |
|
||||
| Production-ready? | Yes: 1,000+ tests, v0.5.0 ships with 12 security fixes |
|
||||
| Production-ready? | Yes: 1,000+ tests, security fixes shipped in every release (see [CHANGELOG](https://github.com/semantica-agi/semantica/blob/main/CHANGELOG.md)) |
|
||||
| Latest version? | **v0.6.7** (August 2026) |
|
||||
| Local LLMs? | Yes: Ollama via LiteLLM, HuggingFaceLLM for air-gapped |
|
||||
|
||||
@@ -70,9 +70,9 @@ Yes: MIT licensed, no vendor lock-in, no paywalled features. Some capabilities r
|
||||
|
||||
<Accordion title="What's the latest version?" icon="star">
|
||||
|
||||
**v0.5.0**: released May 2026.
|
||||
**v0.6.7**: released August 2026.
|
||||
|
||||
Highlights: Ontology Hub, Distance Intelligence, Parquet/XML ingestion, 12 security fixes, Graph Explorer redesign, NER gateway fix.
|
||||
Highlights: first-class LangChain integration, SAP OData ingestor, human-editable Markdown round-trip persistence for `ContextGraph`, a structured Action layer for the reasoning engine, and a public `run_shacl_validation` entry point. The 0.6.x line also added first-class CrewAI support and the Semantica RDF vocabulary with deterministic IRIs. See the [CHANGELOG](https://github.com/semantica-agi/semantica/blob/main/CHANGELOG.md) for the full history.
|
||||
|
||||
```bash
|
||||
pip install --upgrade semantica
|
||||
@@ -93,7 +93,7 @@ pip install --upgrade semantica
|
||||
pip install semantica
|
||||
```
|
||||
|
||||
See [Installation](installation) for virtual environment setup, optional extras (`[gpu]`, `[all]`, provider-specific), and platform-specific troubleshooting.
|
||||
See [Installation](/installation) for virtual environment setup, optional extras (`[gpu]`, `[all]`, provider-specific), and platform-specific troubleshooting.
|
||||
|
||||
</Accordion>
|
||||
|
||||
@@ -173,7 +173,7 @@ This includes PyTorch with CUDA, FAISS GPU, and CuPy.
|
||||
<Accordion title="How does Semantica handle large datasets?" icon="layer-group">
|
||||
|
||||
- **Batching**: process documents in configurable chunks to control memory usage
|
||||
- **Parallel processing**: `Pipeline(workers=N)` runs extraction steps concurrently
|
||||
- **Parallel processing**: the `semantica.pipeline` module can run independent, parallel-safe steps in the same dependency layer concurrently (see the [Pipeline guide](/guides/pipeline))
|
||||
- **Delta processing**: update graphs incrementally without full recompute on new data
|
||||
- **Persistent backends**: swap in-memory NetworkX for Neo4j, FalkorDB, or Apache AGE for large-scale production graphs
|
||||
|
||||
@@ -269,13 +269,13 @@ Groq, OpenAI, Anthropic, Google Gemini, Ollama (fully local), DeepSeek, Novita A
|
||||
|
||||
<Accordion title="Is Semantica production-ready?" icon="shield-check">
|
||||
|
||||
Yes. v0.5.0 ships with:
|
||||
Yes. Every release ships with:
|
||||
|
||||
- 1,000+ passing tests across Python 3.8–3.12
|
||||
- `PipelineValidator` and `FailureHandler` with exponential backoff and configurable retry policies
|
||||
- W3C PROV-O provenance tracking across all modules
|
||||
- Change management with SHA-256 checksums and full audit trails
|
||||
- 12 security vulnerability fixes: eval injection, pickle deserialization, SQL injection, XXE, SSRF, ReDoS, path traversal, and more
|
||||
- Ongoing security hardening: eval injection, pickle deserialization, SQL injection, XXE, SSRF, ReDoS, and path traversal fixes have all landed across recent releases (see the [CHANGELOG](https://github.com/semantica-agi/semantica/blob/main/CHANGELOG.md) security sections)
|
||||
|
||||
</Accordion>
|
||||
|
||||
@@ -350,4 +350,4 @@ set PYTHONIOENCODING=utf-8
|
||||
|
||||
- [Discord](https://discord.gg/sV34vps5hH) — Community chat and live support.
|
||||
- [GitHub Issues](https://github.com/semantica-agi/semantica/issues) — Bug reports and feature requests.
|
||||
- [Contributing](contributing-guide) — Help improve Semantica.
|
||||
- [Contributing](/contributing-guide) — Help improve Semantica.
|
||||
|
||||
+23
-23
@@ -5,7 +5,7 @@ icon: "rocket"
|
||||
---
|
||||
|
||||
<Tip>
|
||||
Already installed? Jump straight to [Quickstart](quickstart). Need setup help first? See [Installation](installation).
|
||||
Already installed? Jump straight to [Quickstart](/quickstart). Need setup help first? See [Installation](/installation).
|
||||
</Tip>
|
||||
|
||||
## What You Can Build
|
||||
@@ -52,15 +52,15 @@ icon: "rocket"
|
||||
|
||||
| Track | You want to... | Start with |
|
||||
| :----- | :-------------- | :--------- |
|
||||
| **Knowledge Graph** | Turn documents into structured, queryable graphs | [Quickstart → Step 1](quickstart) |
|
||||
| **Agent Context** | Give your AI agent persistent memory and decision tracking | [Context reference](reference/context) |
|
||||
| **GraphRAG** | Ground LLM answers in structured knowledge | [Concepts → GraphRAG](concepts#graphrag) |
|
||||
| **MCP Integration** | Use Semantica from Claude Desktop or VS Code | [MCP Server](reference/mcp_server) |
|
||||
| **Knowledge Graph** | Turn documents into structured, queryable graphs | [Quickstart → Step 1](/quickstart) |
|
||||
| **Agent Context** | Give your AI agent persistent memory and decision tracking | [Context reference](/reference/context) |
|
||||
| **GraphRAG** | Ground LLM answers in structured knowledge | [Concepts → GraphRAG](/concepts#graphrag) |
|
||||
| **MCP Integration** | Use Semantica from Claude Desktop or VS Code | [MCP Server](/reference/mcp_server) |
|
||||
|
||||
</Step>
|
||||
|
||||
<Step title="Run the pipeline">
|
||||
The full 6-step pipeline: ingest, parse, extract, build, visualize, export: is in the [Quickstart](quickstart). Takes under 5 minutes with pattern-based extraction (no API key required).
|
||||
The full 6-step pipeline: ingest, parse, extract, build, visualize, export: is in the [Quickstart](/quickstart). Takes under 5 minutes with pattern-based extraction (no API key required).
|
||||
|
||||
<Note>
|
||||
An LLM API key is **optional** for the quickstart. Pattern-based extraction works out of the box: upgrade to LLM extraction for higher accuracy when you're ready.
|
||||
@@ -99,7 +99,7 @@ icon: "rocket"
|
||||
print(f"{len(graph['entities'])} nodes, {len(graph['relationships'])} edges")
|
||||
```
|
||||
|
||||
**Next:** [Full pipeline walkthrough →](quickstart)
|
||||
**Next:** [Full pipeline walkthrough →](/quickstart)
|
||||
</Tab>
|
||||
|
||||
<Tab title="Agent Context">
|
||||
@@ -131,7 +131,7 @@ icon: "rocket"
|
||||
precedents = context.find_precedents("model selection", limit=5)
|
||||
```
|
||||
|
||||
**Next:** [Context module reference →](reference/context)
|
||||
**Next:** [Context module reference →](/reference/context)
|
||||
</Tab>
|
||||
|
||||
<Tab title="GraphRAG">
|
||||
@@ -161,7 +161,7 @@ icon: "rocket"
|
||||
print(f"{claim.text} → source: {claim.source_node}")
|
||||
```
|
||||
|
||||
**Next:** [GraphRAG concepts →](concepts#graphrag)
|
||||
**Next:** [GraphRAG concepts →](/concepts#graphrag)
|
||||
</Tab>
|
||||
|
||||
<Tab title="MCP Integration">
|
||||
@@ -183,9 +183,9 @@ icon: "rocket"
|
||||
}
|
||||
```
|
||||
|
||||
12 tools available instantly: extract entities, query graph, record decisions, run reasoning, export results.
|
||||
15 tools available instantly: extract entities, query graph, record decisions, run reasoning, export results.
|
||||
|
||||
**Next:** [MCP Server reference →](reference/mcp_server)
|
||||
**Next:** [MCP Server reference →](/reference/mcp_server)
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
@@ -194,29 +194,29 @@ icon: "rocket"
|
||||
|
||||
Semantica uses a modular, layered architecture: import only what you need.
|
||||
|
||||
- **[Input Layer](reference/ingest)** — Load and prepare data from any source. Modules: `ingest`, `parse`, `split`, `normalize`
|
||||
- **[Semantic Layer](reference/semantic_extract)** — Extract meaning from raw text. Modules: `semantic_extract`, `kg`, `ontology`, `reasoning`
|
||||
- **[Storage Layer](reference/vector_store)** — Persist knowledge for retrieval. Modules: `embeddings`, `vector_store`, `graph_store`, `triplet_store`
|
||||
- **[Quality Layer](reference/deduplication)** — Validate and deduplicate. Modules: `deduplication`, `conflicts`
|
||||
- **[Context Layer](reference/context)** — Track decisions and lineage. Modules: `context`, `provenance`, `change_management`
|
||||
- **[Output Layer](reference/export)** — Deliver results downstream. Modules: `export`, `visualization`, `pipeline`, `explorer`
|
||||
- **[Input Layer](/reference/ingest)** — Load and prepare data from any source. Modules: `ingest`, `parse`, `split`, `normalize`
|
||||
- **[Semantic Layer](/reference/semantic_extract)** — Extract meaning from raw text. Modules: `semantic_extract`, `kg`, `ontology`, `reasoning`
|
||||
- **[Storage Layer](/reference/vector_store)** — Persist knowledge for retrieval. Modules: `embeddings`, `vector_store`, `graph_store`, `triplet_store`
|
||||
- **[Quality Layer](/reference/deduplication)** — Validate and deduplicate. Modules: `deduplication`, `conflicts`
|
||||
- **[Context Layer](/reference/context)** — Track decisions and lineage. Modules: `context`, `provenance`, `change_management`
|
||||
- **[Output Layer](/reference/export)** — Deliver results downstream. Modules: `export`, `visualization`, `pipeline`, `explorer`
|
||||
|
||||
|
||||
## Which Module Do I Need?
|
||||
|
||||
See the [Choose the Right Module](choose-your-module) guide — it maps 35+ developer goals to the right starting point across all 27 modules, with working code for the most common paths.
|
||||
See the [Choose the Right Module](/choose-your-module) guide — it maps 35+ developer goals to the right starting point across all 27 modules, with working code for the most common paths.
|
||||
|
||||
|
||||
## Next Steps
|
||||
|
||||
- [Core Concepts](concepts) — Knowledge graphs, ontologies, and reasoning explained in depth.
|
||||
- [Quickstart Tutorial](quickstart) — Full 6-step pipeline walkthrough with working code.
|
||||
- [Module Reference](modules) — Every module, class, and common chain explained.
|
||||
- [API Reference](reference/context) — Complete module documentation for every class and method.
|
||||
- [Core Concepts](/concepts) — Knowledge graphs, ontologies, and reasoning explained in depth.
|
||||
- [Quickstart Tutorial](/quickstart) — Full 6-step pipeline walkthrough with working code.
|
||||
- [Module Reference](/modules) — Every module, class, and common chain explained.
|
||||
- [API Reference](/reference/context) — Complete module documentation for every class and method.
|
||||
|
||||
|
||||
## Help
|
||||
|
||||
- [Discord](https://discord.gg/sV34vps5hH) — Ask questions, share projects, get community support.
|
||||
- [GitHub Issues](https://github.com/semantica-agi/semantica/issues) — Report bugs or request features.
|
||||
- [FAQ](faq) — Common questions answered.
|
||||
- [FAQ](/faq) — Common questions answered.
|
||||
|
||||
+4
-4
@@ -214,7 +214,7 @@ A vulnerability in XML parsers that allows attackers to read arbitrary files or
|
||||
|
||||
## See Also
|
||||
|
||||
- [Core Concepts](concepts) — Deeper explanation of key ideas with code examples.
|
||||
- [Getting Started](getting-started) — First working examples: no prior graph experience required.
|
||||
- [Modules Guide](modules) — All 27 modules explained with code and pipeline chains.
|
||||
- [API Reference](reference/context) — Complete technical reference for every class and method.
|
||||
- [Core Concepts](/concepts) — Deeper explanation of key ideas with code examples.
|
||||
- [Getting Started](/getting-started) — First working examples: no prior graph experience required.
|
||||
- [Modules Guide](/modules) — All 27 modules explained with code and pipeline chains.
|
||||
- [API Reference](/reference/context) — Complete technical reference for every class and method.
|
||||
|
||||
+3
-3
@@ -74,10 +74,10 @@ Semantica follows **Semantic Versioning** (`MAJOR.MINOR.PATCH`):
|
||||
|
||||
## License
|
||||
|
||||
MIT License: see [LICENSE](https://github.com/semantica-agi/semantica/blob/main/LICENSE) and the [License page](project-license).
|
||||
MIT License: see [LICENSE](https://github.com/semantica-agi/semantica/blob/main/LICENSE) and the [License page](/project-license).
|
||||
|
||||
|
||||
## See Also
|
||||
|
||||
- [Contributing](contributing-guide) — How to submit changes.
|
||||
- [Community](community) — Community guidelines and channels.
|
||||
- [Contributing](/contributing-guide) — How to submit changes.
|
||||
- [Community](/community) — Community guidelines and channels.
|
||||
|
||||
@@ -46,7 +46,7 @@ Agent Memory provides persistent storage and intelligent retrieval of informatio
|
||||
- Simple retrieval tasks where relationships between entities don't matter
|
||||
|
||||
<Info>
|
||||
This guide covers the memory layer. For graph-enriched traversal and entity linking, see [Context Graphs](context-graphs). For decision accountability — recording, auditing, and causally tracing what the agent chose — see [Decision Intelligence](decision-intelligence).
|
||||
This guide covers the memory layer. For graph-enriched traversal and entity linking, see [Context Graphs](/guides/context-graphs). For decision accountability — recording, auditing, and causally tracing what the agent chose — see [Decision Intelligence](/guides/decision-intelligence).
|
||||
</Info>
|
||||
|
||||
## Setting Up a Persistent Memory Context
|
||||
@@ -657,10 +657,10 @@ print("Total memories: {}".format(s.get("total_items", 0)))
|
||||
|
||||
## Related Guides
|
||||
|
||||
- [Context Graphs](context-graphs) — How the underlying `ContextGraph` stores entity nodes and decision nodes; temporal interval reasoning; deduplication before node insertion; ontology from graph.
|
||||
- [Decision Intelligence](decision-intelligence) — Recording decisions as graph nodes with causal chains and policy gating.
|
||||
- [Multi-Agent Systems](multi-agent) — Coordinating multiple agents through a shared `AgentContext` and save/load handoffs.
|
||||
- [LLM Integrations](llm-integrations) — Configuring the LLM provider passed to `query_with_reasoning()`.
|
||||
- [Context Graphs](/guides/context-graphs) — How the underlying `ContextGraph` stores entity nodes and decision nodes; temporal interval reasoning; deduplication before node insertion; ontology from graph.
|
||||
- [Decision Intelligence](/guides/decision-intelligence) — Recording decisions as graph nodes with causal chains and policy gating.
|
||||
- [Multi-Agent Systems](/guides/multi-agent) — Coordinating multiple agents through a shared `AgentContext` and save/load handoffs.
|
||||
- [LLM Integrations](/guides/llm-integrations) — Configuring the LLM provider passed to `query_with_reasoning()`.
|
||||
- [Deduplication Guide](deduplication) — Full reference for `DuplicateDetector`, `EntityMerger`, similarity methods, and cluster strategies.
|
||||
- [Ontology Management](ontology) — Generate and validate OWL ontologies from the knowledge graph; export to Turtle, OWL/XML, JSON-LD.
|
||||
- [Context Module Reference](../reference/context) — Full API: `AgentContext`, `AgentMemory`, `MemoryItem`, `ContextRetriever`.
|
||||
|
||||
@@ -496,8 +496,8 @@ print("Model v1.1 verified and approved for production.")
|
||||
|
||||
## Related Guides
|
||||
|
||||
- [Context Graphs](context-graphs) — `ContextGraph.to_dict()` feeds `create_snapshot()`
|
||||
- [Context Graphs](/guides/context-graphs) — `ContextGraph.to_dict()` feeds `create_snapshot()`
|
||||
- [Ontology Management](ontology) — pair ontology versioning with graph versioning for a complete schema + data audit trail
|
||||
- [SHACL Validation](shacl-validation) — validate graph data at each version gate before snapshotting
|
||||
- [SHACL Validation](/guides/shacl-validation) — validate graph data at each version gate before snapshotting
|
||||
- [Provenance](provenance) — combine change management with W3C PROV-O lineage for a full audit trail
|
||||
- [Visualization](visualization) — `TemporalVisualizer.visualize_snapshot_comparison()` and `visualize_metrics_evolution()` render version diffs as interactive charts
|
||||
|
||||
@@ -69,7 +69,7 @@ flowchart TD
|
||||
2. **Conflict Detection** — Call `detect_entity_conflicts()` to surface all property disagreements at once, or `detect_value_conflicts()` to target a specific property.
|
||||
3. **Resolution** — For each conflict, apply a strategy (`CREDIBILITY_WEIGHTED`, `MOST_RECENT`, `VOTING`, etc.) or route it for expert review (`EXPERT_REVIEW`).
|
||||
4. **Persist Canonical Values** — Write resolved values back to your canonical entities or graph store. See [Persisting resolved values](#persisting-resolved-values).
|
||||
5. **SHACL Validation** — Enforce structural constraints on the resolved graph to confirm it satisfies your ontology. See [SHACL Validation](shacl-validation).
|
||||
5. **SHACL Validation** — Enforce structural constraints on the resolved graph to confirm it satisfies your ontology. See [SHACL Validation](/guides/shacl-validation).
|
||||
|
||||
## Quick Start: A Beginner Example
|
||||
|
||||
@@ -698,6 +698,6 @@ Calling `set_resolution_rule()` for every entity-property pair just to apply the
|
||||
|
||||
- [Deduplication](deduplication) — remove duplicate nodes before running conflict detection
|
||||
- [Provenance](provenance) — track which source each resolved value came from, and verify the audit trail cryptographically
|
||||
- [SHACL Validation](shacl-validation) — enforce structural constraints after conflicts are resolved
|
||||
- [Change Management](change-management) — snapshot the graph before and after conflict resolution runs
|
||||
- [SHACL Validation](/guides/shacl-validation) — enforce structural constraints after conflicts are resolved
|
||||
- [Change Management](/guides/change-management) — snapshot the graph before and after conflict resolution runs
|
||||
- [Ontology Management](ontology) — align entity types to a shared vocabulary to reduce type conflicts at the schema level
|
||||
|
||||
@@ -50,7 +50,7 @@ A context graph is a property graph that stores entities as **nodes** and relati
|
||||
- Cases where setup complexity exceeds the relationship complexity
|
||||
|
||||
<Info>
|
||||
ContextGraph is an **in-memory data structure**. All nodes, edges, and metadata are stored in Python dictionaries and lists. For standalone graphs, persist state with `save_to_file()`. When using `AgentContext`, call `AgentContext.save()` instead — it saves the graph, the FAISS vector index, and memory in one step. For analytical operations on top of a populated graph — centrality rankings, community detection, node embeddings, link prediction — see the [Graph Analytics guide](graph-analytics). For recording and querying decisions stored as nodes, see the [Decision Intelligence guide](decision-intelligence).
|
||||
ContextGraph is an **in-memory data structure**. All nodes, edges, and metadata are stored in Python dictionaries and lists. For standalone graphs, persist state with `save_to_file()`. When using `AgentContext`, call `AgentContext.save()` instead — it saves the graph, the FAISS vector index, and memory in one step. For analytical operations on top of a populated graph — centrality rankings, community detection, node embeddings, link prediction — see the [Graph Analytics guide](/guides/graph-analytics). For recording and querying decisions stored as nodes, see the [Decision Intelligence guide](/guides/decision-intelligence).
|
||||
</Info>
|
||||
|
||||
## Constructing the Graph
|
||||
@@ -704,8 +704,8 @@ for n in stress_reach:
|
||||
|
||||
## Related Guides
|
||||
|
||||
- [Graph Analytics](graph-analytics) — centrality rankings, community detection, node embeddings, and link prediction on a populated `ContextGraph`
|
||||
- [Decision Intelligence](decision-intelligence) — recording decisions as typed nodes, causal chain analysis, precedent search, and policy enforcement
|
||||
- [Graph Analytics](/guides/graph-analytics) — centrality rankings, community detection, node embeddings, and link prediction on a populated `ContextGraph`
|
||||
- [Decision Intelligence](/guides/decision-intelligence) — recording decisions as typed nodes, causal chain analysis, precedent search, and policy enforcement
|
||||
- [Ingest](ingest) — loading data from PDFs, APIs, databases, STIX bundles, and RSS feeds into the graph
|
||||
- [Deduplication](deduplication) — detecting and merging near-duplicate nodes before insertion to prevent graph fragmentation
|
||||
- [Reasoning](reasoning) — temporal interval algebra (Allen relations), forward/backward chaining, and SPARQL over the knowledge graph
|
||||
|
||||
@@ -638,8 +638,8 @@ results = context.find_precedents("APT29 infrastructure attribution", limit=5)
|
||||
|
||||
## Related Guides
|
||||
|
||||
- [Context Graphs](context-graphs) — how `ContextGraph` stores decision nodes and causal edges
|
||||
- [Distance Intelligence](distance-intelligence) — `trace_decision_causality()` annotates causal chains with confidence decay and distance bands
|
||||
- [Context Graphs](/guides/context-graphs) — how `ContextGraph` stores decision nodes and causal edges
|
||||
- [Distance Intelligence](/guides/distance-intelligence) — `trace_decision_causality()` annotates causal chains with confidence decay and distance bands
|
||||
- [Provenance](provenance) — W3C PROV-O audit trail that wraps decision records in standards-compliant provenance
|
||||
- [MCP Server](mcp-server) — expose decision recording and precedent search to LLM agents via the `record_decision` and `find_precedents` tools
|
||||
- [Change Management](change-management) — checkpoint decision state with `flush_checkpoint()` for versioned snapshots
|
||||
- [MCP Server](/guides/mcp-server) — expose decision recording and precedent search to LLM agents via the `record_decision` and `find_precedents` tools
|
||||
- [Change Management](/guides/change-management) — checkpoint decision state with `flush_checkpoint()` for versioned snapshots
|
||||
|
||||
@@ -612,7 +612,7 @@ The similarity threshold controls sensitivity. Start at 0.7 and examine false po
|
||||
## Related Guides
|
||||
|
||||
- [Ingest Anything](ingest) — multi-source ingestion creates the duplicates this module resolves
|
||||
- [Context Graphs](context-graphs) — store deduplicated entities directly in the knowledge graph
|
||||
- [Conflict Resolution](conflict-resolution) — after merging, reconcile disagreeing property values on the canonical entity
|
||||
- [Context Graphs](/guides/context-graphs) — store deduplicated entities directly in the knowledge graph
|
||||
- [Conflict Resolution](/guides/conflict-resolution) — after merging, reconcile disagreeing property values on the canonical entity
|
||||
- [Provenance](provenance) — track merge lineage so every canonical entity traces back to its original sources
|
||||
- [Pipeline](pipeline) — chain ingest, deduplicate, and store as a `PipelineBuilder` workflow
|
||||
|
||||
@@ -557,8 +557,8 @@ for chain in chains:
|
||||
|
||||
## Related Guides
|
||||
|
||||
- [Context Graphs](context-graphs) — `ContextGraph` node and edge model; `add_edge(weight=...)` feeds confidence decay
|
||||
- [Graph Analytics](graph-analytics) — centrality, community detection, Node2Vec embeddings, link prediction
|
||||
- [Agent Memory](agent-memory) — proximity-blended retrieval (`proximity_weight`) integrates distance intelligence into memory search
|
||||
- [Decision Intelligence](decision-intelligence) — `trace_decision_causality()` for causal chains with distance annotations
|
||||
- [Context Graphs](/guides/context-graphs) — `ContextGraph` node and edge model; `add_edge(weight=...)` feeds confidence decay
|
||||
- [Graph Analytics](/guides/graph-analytics) — centrality, community detection, Node2Vec embeddings, link prediction
|
||||
- [Agent Memory](/guides/agent-memory) — proximity-blended retrieval (`proximity_weight`) integrates distance intelligence into memory search
|
||||
- [Decision Intelligence](/guides/decision-intelligence) — `trace_decision_causality()` for causal chains with distance annotations
|
||||
- [Reasoning & Rules](reasoning) — `TemporalReasoningEngine` for Allen interval algebra over time-bounded graph nodes
|
||||
|
||||
@@ -443,8 +443,8 @@ For semantic reasoning and ontology work, OWL/XML is the format — it is the on
|
||||
|
||||
## Related Guides
|
||||
|
||||
- [Context Graphs](context-graphs) — the `ContextGraph` object whose `to_dict()` feeds all exports
|
||||
- [Context Graphs](/guides/context-graphs) — the `ContextGraph` object whose `to_dict()` feeds all exports
|
||||
- [Ontology Management](ontology) — export OWL ontologies generated from your graph
|
||||
- [Reasoning & Rules](reasoning) — reasoning results can be exported as RDF triples
|
||||
- [Change Management](change-management) — snapshot a graph before exporting to prove the export was made from a verified state
|
||||
- [Change Management](/guides/change-management) — snapshot a graph before exporting to prove the export was made from a verified state
|
||||
- [Pipeline](pipeline) — chain ingest, extract, and export in a single `PipelineBuilder`
|
||||
|
||||
@@ -310,7 +310,7 @@ for node1, node2, score in predictions:
|
||||
A score above 0.8 is worth analyst review — these aren't random; they're edges the topology of the existing graph strongly implies. Scores below 0.5 are noise. The sweet spot for human review is 0.6–0.8: plausible but not yet confirmed.
|
||||
|
||||
<Info>
|
||||
Link prediction is also available on `Decision` nodes through `DecisionQuery.predict_decision_relationships(decision_id, top_k)`. See the [Decision Intelligence guide](decision-intelligence) for how to surface causal relationships between past decisions.
|
||||
Link prediction is also available on `Decision` nodes through `DecisionQuery.predict_decision_relationships(decision_id, top_k)`. See the [Decision Intelligence guide](/guides/decision-intelligence) for how to surface causal relationships between past decisions.
|
||||
</Info>
|
||||
|
||||
## Understanding Your Decision History
|
||||
@@ -538,7 +538,7 @@ print(f"\n{len(result['communities'])} exposure clusters "
|
||||
|
||||
## Related Guides
|
||||
|
||||
- [Context Graphs](context-graphs) — building and querying the underlying `ContextGraph`
|
||||
- [Context Graphs](/guides/context-graphs) — building and querying the underlying `ContextGraph`
|
||||
- [Visualization](visualization) — render centrality rankings and community clusters as interactive dashboards
|
||||
- [Decision Intelligence](decision-intelligence) — link prediction and structural similarity applied to decision nodes
|
||||
- [GraphRAG](graphrag) — using analytics results to ground LLM generation in the most contextually relevant subgraph
|
||||
- [Decision Intelligence](/guides/decision-intelligence) — link prediction and structural similarity applied to decision nodes
|
||||
- [GraphRAG](/guides/graphrag) — using analytics results to ground LLM generation in the most contextually relevant subgraph
|
||||
|
||||
@@ -576,9 +576,9 @@ The vector search and graph traversal run independently, then their scores are f
|
||||
|
||||
## Related Guides
|
||||
|
||||
- [Semantic Extraction](semantic-extraction) — build the graph from raw unstructured text
|
||||
- [Agent Memory](agent-memory) — store, retrieve, and persist agent memories
|
||||
- [Context Graphs](context-graphs) — build and traverse the knowledge graph directly
|
||||
- [Semantic Extraction](/guides/semantic-extraction) — build the graph from raw unstructured text
|
||||
- [Agent Memory](/guides/agent-memory) — store, retrieve, and persist agent memories
|
||||
- [Context Graphs](/guides/context-graphs) — build and traverse the knowledge graph directly
|
||||
- [Reasoning](reasoning) — derive new facts and run inference rules over the graph
|
||||
- [Decision Intelligence](decision-intelligence) — causal chains, policy enforcement, decision tracking
|
||||
- [LLM Integrations](llm-integrations) — connect Groq, OpenAI, Anthropic, HuggingFace, and 100+ more
|
||||
- [Decision Intelligence](/guides/decision-intelligence) — causal chains, policy enforcement, decision tracking
|
||||
- [LLM Integrations](/guides/llm-integrations) — connect Groq, OpenAI, Anthropic, HuggingFace, and 100+ more
|
||||
|
||||
@@ -951,8 +951,8 @@ print(f"Compliance graph: {graph.stats()['node_count']} nodes, "
|
||||
## Related Guides
|
||||
|
||||
- [Pipeline](pipeline) — chain ingest steps with `PipelineBuilder` for automated, retryable, parallelised workflows
|
||||
- [Context Graphs](context-graphs) — storing and querying the entities you ingest as a typed property graph
|
||||
- [Semantic Extraction](semantic-extraction) — NER, relation extraction, and triplet extraction from ingested text
|
||||
- [Context Graphs](/guides/context-graphs) — storing and querying the entities you ingest as a typed property graph
|
||||
- [Semantic Extraction](/guides/semantic-extraction) — NER, relation extraction, and triplet extraction from ingested text
|
||||
- [Provenance](provenance) — tracking the origin document, confidence score, and ingestion timestamp for every extracted entity
|
||||
- [Databricks Integration](../integrations/databricks) — Unity Catalog setup, PAT/OAuth M2M authentication, and lineage introspection
|
||||
- [Snowflake Integration](../integrations/snowflake) — warehouse setup and password/key-pair/OAuth authentication
|
||||
|
||||
@@ -719,7 +719,7 @@ for src in best["sources"]:
|
||||
|
||||
## Related Guides
|
||||
|
||||
- [Agent Memory](agent-memory) — using `query_with_reasoning()` with any LLM provider for graph-grounded retrieval
|
||||
- [Multi-Agent Systems](multi-agent) — wiring different LLM providers to different agent tiers in a shared-graph pipeline
|
||||
- [Semantic Extraction](semantic-extraction) — LLM-powered NER, relation extraction, event detection, and triplet extraction
|
||||
- [GraphRAG](graphrag) — multi-hop graph reasoning with `query_with_reasoning()`
|
||||
- [Agent Memory](/guides/agent-memory) — using `query_with_reasoning()` with any LLM provider for graph-grounded retrieval
|
||||
- [Multi-Agent Systems](/guides/multi-agent) — wiring different LLM providers to different agent tiers in a shared-graph pipeline
|
||||
- [Semantic Extraction](/guides/semantic-extraction) — LLM-powered NER, relation extraction, event detection, and triplet extraction
|
||||
- [GraphRAG](/guides/graphrag) — multi-hop graph reasoning with `query_with_reasoning()`
|
||||
|
||||
@@ -11,7 +11,7 @@ MCP stands for the Model Context Protocol. It is an open standard that allows ex
|
||||
The Semantica MCP server exposes your knowledge graph as 12 callable tools. By connecting it, any compatible AI client can traverse the graph live, record decisions, run analytics, and export results during a conversation — without you having to write custom tool wrappers.
|
||||
|
||||
<Info>
|
||||
The Semantica MCP server exposes 12 tools and 3 read-only resources. All tools accept and return JSON. No configuration beyond an optional environment variable for graph persistence is required.
|
||||
The Semantica MCP server exposes 15 tools and 3 read-only resources. All tools accept and return JSON. No configuration beyond an optional environment variable for graph persistence is required.
|
||||
</Info>
|
||||
|
||||
## Architecture & Communication
|
||||
@@ -132,7 +132,7 @@ docker run --rm -i \
|
||||
ghcr.io/semantica-agi/semantica-mcp:latest
|
||||
```
|
||||
|
||||
## What the Agent Can Do: The 12 Tools
|
||||
## What the Agent Can Do: The 15 Tools
|
||||
|
||||
Once connected, the LLM can call any of these tools during a conversation. The agent chains them automatically — you do not orchestrate the sequence, you just describe what you want.
|
||||
|
||||
@@ -140,6 +140,8 @@ Once connected, the LLM can call any of these tools during a conversation. The a
|
||||
|
||||
**Knowledge graph manipulation** — `add_entity` adds a node, `add_relationship` adds a directed edge. After extraction, the agent calls these to persist what it found into the live graph.
|
||||
|
||||
**Live graph queries and edits** — `query_graph` reads the graph without exporting it: fetch one node, walk its neighbours up to five hops, or keyword-search nodes. `update_node` merges properties onto an existing node (for example marking a task node `done`), and `delete_node` archives a node it no longer tracks. When `SEMANTICA_KG_PATH` is set, `update_node` and `delete_node` write their changes back to that file so they survive a restart.
|
||||
|
||||
**Decision intelligence** — `record_decision` writes a decision as a provenance node with confidence score, reasoning, and decision maker identity. `query_decisions` retrieves past decisions by query or category. `find_precedents` finds the most similar past decisions by semantic similarity. `get_causal_chain` traces decision causality upstream or downstream.
|
||||
|
||||
**Reasoning** — `run_reasoning` applies forward-chaining IF/THEN rules over a set of facts and returns derived conclusions.
|
||||
@@ -341,7 +343,7 @@ The result is a fully auditable credit decision trail with precedent links, read
|
||||
## Related Guides
|
||||
|
||||
- [Reasoning & Rules](reasoning) — the engine behind the `run_reasoning` tool
|
||||
- [Decision Intelligence](decision-intelligence) — how decisions are stored as causal graph nodes
|
||||
- [Context Graphs](context-graphs) — the graph that `add_entity` and `add_relationship` write to
|
||||
- [Decision Intelligence](/guides/decision-intelligence) — how decisions are stored as causal graph nodes
|
||||
- [Context Graphs](/guides/context-graphs) — the graph that `add_entity` and `add_relationship` write to
|
||||
- [Export & Serialization](export) — all export formats available via `export_graph`
|
||||
- [Ontology Management](ontology) — generate OWL ontologies from the graph built via MCP
|
||||
|
||||
@@ -55,7 +55,7 @@ Semantica coordinates agents through shared context (memory and knowledge graphs
|
||||
Semantica coordinates multiple agents through a shared `ContextGraph` — agents read and write to the same graph, or hand off serialized state via `save()` and `load()`, with no message broker required. Use this pattern when splitting work across ingestion, enrichment, reasoning, and reporting roles that must share a single evidence base.
|
||||
|
||||
<Info>
|
||||
This guide covers multi-agent coordination. For the memory layer each agent uses internally, see [Agent Memory](agent-memory). For graph traversal and entity linking, see [Context Graphs](context-graphs). For decision recording and precedent matching, see [Decision Intelligence](decision-intelligence).
|
||||
This guide covers multi-agent coordination. For the memory layer each agent uses internally, see [Agent Memory](/guides/agent-memory). For graph traversal and entity linking, see [Context Graphs](/guides/context-graphs). For decision recording and precedent matching, see [Decision Intelligence](/guides/decision-intelligence).
|
||||
</Info>
|
||||
|
||||
## The Three Coordination Patterns
|
||||
@@ -679,7 +679,7 @@ context.retrieve("...", user_id="analyst-jsmith")
|
||||
|
||||
## Related Guides
|
||||
|
||||
- [Agent Memory](agent-memory) — memory storage, retrieval, persistence, and the working memory window each agent uses internally
|
||||
- [Context Graphs](context-graphs) — build and traverse the shared `ContextGraph` directly; temporal interval reasoning; entity deduplication before node insertion
|
||||
- [Decision Intelligence](decision-intelligence) — record and trace decisions across agent handoffs with causal chain analysis
|
||||
- [LLM Integrations](llm-integrations) — configure the LLM provider passed to `query_with_reasoning()` in each agent
|
||||
- [Agent Memory](/guides/agent-memory) — memory storage, retrieval, persistence, and the working memory window each agent uses internally
|
||||
- [Context Graphs](/guides/context-graphs) — build and traverse the shared `ContextGraph` directly; temporal interval reasoning; entity deduplication before node insertion
|
||||
- [Decision Intelligence](/guides/decision-intelligence) — record and trace decisions across agent handoffs with causal chain analysis
|
||||
- [LLM Integrations](/guides/llm-integrations) — configure the LLM provider passed to `query_with_reasoning()` in each agent
|
||||
|
||||
@@ -297,7 +297,7 @@ export_rdf(ontology, "cyber_threat.jsonld", format="jsonld")
|
||||
export_rdf(ontology, "cyber_threat.nt", format="ntriples")
|
||||
```
|
||||
|
||||
The exported Turtle file is the input to Semantica's SHACL validation pipeline. See the [SHACL Validation](shacl-validation) guide for how to generate constraint shapes from this ontology and run them against live graph data.
|
||||
The exported Turtle file is the input to Semantica's SHACL validation pipeline. See the [SHACL Validation](/guides/shacl-validation) guide for how to generate constraint shapes from this ontology and run them against live graph data.
|
||||
|
||||
---
|
||||
|
||||
@@ -503,8 +503,8 @@ else:
|
||||
|
||||
## Related Guides
|
||||
|
||||
- [SHACL Validation](shacl-validation) — generate W3C SHACL constraint shapes from your ontology and validate live graph data against them
|
||||
- [SHACL Validation](/guides/shacl-validation) — generate W3C SHACL constraint shapes from your ontology and validate live graph data against them
|
||||
- [Reasoning & Rules](reasoning) — apply forward/backward-chaining rules over your ontology to derive new facts
|
||||
- [Export & Serialization](export) — export graphs to RDF, GraphML, CSV, and Neo4j Cypher
|
||||
- [Semantic Extraction](semantic-extraction) — extract entities and relationships that feed ontology generation
|
||||
- [Context Graphs](context-graphs) — the knowledge graph that ontology generation reads from
|
||||
- [Semantic Extraction](/guides/semantic-extraction) — extract entities and relationships that feed ontology generation
|
||||
- [Context Graphs](/guides/context-graphs) — the knowledge graph that ontology generation reads from
|
||||
|
||||
@@ -717,6 +717,6 @@ print(f"Compliance delta update: {result.output}")
|
||||
## Related Guides
|
||||
|
||||
- [Ingest](ingest) — all source types for the ingest step: PDFs, APIs, databases, RSS feeds, STIX directories, and streams
|
||||
- [Semantic Extraction](semantic-extraction) — NER, relation extraction, triplet extraction, and event detection for the extract step
|
||||
- [Context Graphs](context-graphs) — building and querying the `ContextGraph` that the store step populates
|
||||
- [Semantic Extraction](/guides/semantic-extraction) — NER, relation extraction, triplet extraction, and event detection for the extract step
|
||||
- [Context Graphs](/guides/context-graphs) — building and querying the `ContextGraph` that the store step populates
|
||||
- [Provenance](provenance) — tracking the origin document, confidence score, and pipeline run ID for every extracted entity
|
||||
|
||||
@@ -662,9 +662,9 @@ print("Policy updated to v2.4.0")
|
||||
|
||||
## Related Guides
|
||||
|
||||
- [Decision Intelligence](decision-intelligence) — `record_decision()`, causal chains, and precedent search — the decisions that `check_compliance()` evaluates
|
||||
- [Decision Intelligence](/guides/decision-intelligence) — `record_decision()`, causal chains, and precedent search — the decisions that `check_compliance()` evaluates
|
||||
- [Reasoning & Rules](reasoning) — complement policy rules with formal inference for logical conflict detection
|
||||
- [SHACL Validation](shacl-validation) — enforce structural constraints on policy nodes themselves
|
||||
- [Change Management](change-management) — version-snapshot the policy graph alongside the knowledge graph
|
||||
- [SHACL Validation](/guides/shacl-validation) — enforce structural constraints on policy nodes themselves
|
||||
- [Change Management](/guides/change-management) — version-snapshot the policy graph alongside the knowledge graph
|
||||
- [Provenance](provenance) — W3C PROV-O lineage for every policy decision and exception
|
||||
- [MCP Server](mcp-server) — expose `record_decision` and `find_precedents` as MCP tools for AI agents
|
||||
- [MCP Server](/guides/mcp-server) — expose `record_decision` and `find_precedents` as MCP tools for AI agents
|
||||
|
||||
@@ -659,7 +659,7 @@ Note: the banking example above passes `agent_id="credit_data_service_v2"` to `t
|
||||
|
||||
## Related Guides
|
||||
|
||||
- [Semantic Extraction](semantic-extraction) — the NER and relation extraction pipeline that auto-generates provenance entries for every extracted entity
|
||||
- [Conflict Resolution](conflict-resolution) — provenance property sources feed directly into conflict detection; every resolved value is traceable to its source
|
||||
- [Semantic Extraction](/guides/semantic-extraction) — the NER and relation extraction pipeline that auto-generates provenance entries for every extracted entity
|
||||
- [Conflict Resolution](/guides/conflict-resolution) — provenance property sources feed directly into conflict detection; every resolved value is traceable to its source
|
||||
- [Deduplication](deduplication) — merge operations are recorded in merge history; pair with provenance for a complete lineage from source to canonical entity
|
||||
- [Provenance Reference](../reference/provenance) — full storage backend API, `InMemoryStorage`, `SQLiteStorage`, and `ProvenanceEntry` schema
|
||||
|
||||
@@ -838,9 +838,9 @@ if proof:
|
||||
|
||||
## Related Guides
|
||||
|
||||
- [Semantic Extraction](semantic-extraction) — extract the entities and relationships that populate the graph facts you reason over
|
||||
- [GraphRAG](graphrag) — retrieve graph-grounded context for LLM responses
|
||||
- [Semantic Extraction](/guides/semantic-extraction) — extract the entities and relationships that populate the graph facts you reason over
|
||||
- [GraphRAG](/guides/graphrag) — retrieve graph-grounded context for LLM responses
|
||||
- [Ontology Management](ontology) — generate OWL ontologies to give your rules formal semantics
|
||||
- [Decision Intelligence](decision-intelligence) — record and trace inferred decisions through the full causal chain
|
||||
- [Context Graphs](context-graphs) — the knowledge graph that reasoning operates over
|
||||
- [MCP Server](mcp-server) — expose `run_reasoning` as a tool for Claude and other agents
|
||||
- [Decision Intelligence](/guides/decision-intelligence) — record and trace inferred decisions through the full causal chain
|
||||
- [Context Graphs](/guides/context-graphs) — the knowledge graph that reasoning operates over
|
||||
- [MCP Server](/guides/mcp-server) — expose `run_reasoning` as a tool for Claude and other agents
|
||||
|
||||
@@ -71,7 +71,7 @@ This pipeline transforms documents like "APT29 deployed HAMMERTOSS malware targe
|
||||
`semantica.semantic_extract` turns unstructured text into structured graph-ready output: it identifies named entities, extracts relationships between them, detects time-anchored events, resolves coreferences, and serialises everything as RDF triplets. Use it to populate a `ContextGraph` from raw documents — intelligence reports, clinical notes, regulatory filings, or any free-text corpus.
|
||||
|
||||
<Info>
|
||||
Extracted entities and relationships feed into `ContextGraph` via `AgentContext.store()`. For how they are attributed back to source documents, see the [Provenance Guide](provenance). For how the populated graph is queried and traversed, see [Context Graphs](context-graphs).
|
||||
Extracted entities and relationships feed into `ContextGraph` via `AgentContext.store()`. For how they are attributed back to source documents, see the [Provenance Guide](provenance). For how the populated graph is queried and traversed, see [Context Graphs](/guides/context-graphs).
|
||||
</Info>
|
||||
|
||||
## Step 1 — Named Entity Recognition: who and what is in the text
|
||||
@@ -664,8 +664,8 @@ The fallback behaviour is automatic: if the primary method returns an empty list
|
||||
## Related Guides
|
||||
|
||||
- [Provenance Guide](provenance) — track every extracted entity and chunk back to its source document
|
||||
- [Agent Memory Guide](agent-memory) — store extracted knowledge as searchable agent memories with graph enrichment
|
||||
- [Context Graphs Guide](context-graphs) — how extracted entities populate `ContextGraph` nodes and edges
|
||||
- [GraphRAG Guide](graphrag) — retrieve facts from the populated graph to ground LLM responses
|
||||
- [Agent Memory Guide](/guides/agent-memory) — store extracted knowledge as searchable agent memories with graph enrichment
|
||||
- [Context Graphs Guide](/guides/context-graphs) — how extracted entities populate `ContextGraph` nodes and edges
|
||||
- [GraphRAG Guide](/guides/graphrag) — retrieve facts from the populated graph to ground LLM responses
|
||||
- [Reasoning Guide](reasoning) — derive new facts, run SPARQL queries, and apply inference rules over the extracted graph
|
||||
- [Semantic Extract Reference](../reference/semantic_extract) — full API for all extractor classes, providers, and validators
|
||||
|
||||
@@ -740,5 +740,5 @@ def validate_before_publish(data_graph_str: str, ontology: dict) -> None:
|
||||
- [Ontology Management](ontology) — generate the OWL ontology that SHACL shapes are derived from
|
||||
- [Reasoning & Rules](reasoning) — complement SHACL structural constraints with logical inference rules
|
||||
- [Export & Serialization](export) — serialize graph data to Turtle/RDF/XML for `run_shacl_validation` input
|
||||
- [Conflict Resolution](conflict-resolution) — detect and resolve data conflicts before SHACL validation
|
||||
- [Change Management](change-management) — version-gate SHACL shapes alongside ontology versions
|
||||
- [Conflict Resolution](/guides/conflict-resolution) — detect and resolve data conflicts before SHACL validation
|
||||
- [Change Management](/guides/change-management) — version-gate SHACL shapes alongside ontology versions
|
||||
|
||||
@@ -614,8 +614,8 @@ fig.write_html("out.html") # manual export
|
||||
|
||||
## Related Guides
|
||||
|
||||
- [Context Graphs](context-graphs) — `graph.to_dict()` is the primary input for `KGVisualizer`
|
||||
- [Context Graphs](/guides/context-graphs) — `graph.to_dict()` is the primary input for `KGVisualizer`
|
||||
- [Ontology Management](ontology) — `OntologyVisualizer` renders ontologies produced by `OntologyGenerator`
|
||||
- [Change Management](change-management) — `TemporalVersionManager` snapshots feed `visualize_metrics_evolution()` and `visualize_snapshot_comparison()`
|
||||
- [Graph Analytics](graph-analytics) — centrality scores, community dicts, and connectivity results that feed the `AnalyticsVisualizer`
|
||||
- [Change Management](/guides/change-management) — `TemporalVersionManager` snapshots feed `visualize_metrics_evolution()` and `visualize_snapshot_comparison()`
|
||||
- [Graph Analytics](/guides/graph-analytics) — centrality scores, community dicts, and connectivity results that feed the `AnalyticsVisualizer`
|
||||
- [Export & Serialization](export) — export the same graph to GraphML, GEXF, or DOT for Gephi and Graphviz
|
||||
|
||||
+14
-14
@@ -185,8 +185,8 @@ decision_id = context.record_decision(
|
||||
|
||||
</CodeGroup>
|
||||
|
||||
- [Full Quickstart](quickstart) — Step-by-step pipeline walkthrough
|
||||
- [Cookbook](cookbook) — 40+ real-world Jupyter notebooks
|
||||
- [Full Quickstart](/quickstart) — Step-by-step pipeline walkthrough
|
||||
- [Cookbook](/cookbook) — 40+ real-world Jupyter notebooks
|
||||
- [Join Discord](https://discord.gg/sV34vps5hH) — Community chat and support
|
||||
|
||||
|
||||
@@ -195,7 +195,7 @@ decision_id = context.record_decision(
|
||||
Semantica was designed for domains where every decision must be explainable and every fact must be traceable.
|
||||
|
||||
<Warning>
|
||||
**This is system-level explainability, not foundation-model explainability.** Semantica does not expose, reconstruct, or explain what happens *inside* the LLM/foundation model — its internal reasoning or chain-of-thought stays opaque, as it does for any external system. What Semantica explains is *outside* the model: the context and data fed in, the decision produced, its provenance, the relevant relationships, the policies applied, and the full execution trail. See [Core Concepts](concepts) for the full scope note.
|
||||
**This is system-level explainability, not foundation-model explainability.** Semantica does not expose, reconstruct, or explain what happens *inside* the LLM/foundation model — its internal reasoning or chain-of-thought stays opaque, as it does for any external system. What Semantica explains is *outside* the model: the context and data fed in, the decision produced, its provenance, the relevant relationships, the policies applied, and the full execution trail. See [Core Concepts](/concepts) for the full scope note.
|
||||
</Warning>
|
||||
|
||||
**Healthcare & Life Sciences**
|
||||
@@ -242,35 +242,35 @@ Semantica was designed for domains where every decision must be explainable and
|
||||
```bash
|
||||
pip install semantica
|
||||
```
|
||||
See [Installation](installation) for optional extras (`[all]`, `[neo4j]`, `[pinecone]`) and environment setup.
|
||||
See [Installation](/installation) for optional extras (`[all]`, `[neo4j]`, `[pinecone]`) and environment setup.
|
||||
</Step>
|
||||
<Step title="Run the Quickstart">
|
||||
Build a complete knowledge graph pipeline in [5 minutes](quickstart):
|
||||
Build a complete knowledge graph pipeline in [5 minutes](/quickstart):
|
||||
- Ingest documents from any source
|
||||
- Extract entities and relationships
|
||||
- Build and query the graph
|
||||
- Record and trace a decision
|
||||
</Step>
|
||||
<Step title="Learn the mental model">
|
||||
[Core Concepts](concepts) covers:
|
||||
[Core Concepts](/concepts) covers:
|
||||
- Knowledge graphs vs. vector stores: when to use each
|
||||
- What GraphRAG is and how Semantica implements it
|
||||
- How provenance and decision tracking work together
|
||||
- The accountability layer architecture
|
||||
</Step>
|
||||
<Step title="Go deep on any module">
|
||||
Every module has a dedicated [reference page](reference/context) with:
|
||||
Every module has a dedicated [reference page](/reference/context) with:
|
||||
- Full class and method documentation
|
||||
- Parameter tables with types and defaults
|
||||
- Runnable code examples for each feature
|
||||
</Step>
|
||||
</Steps>
|
||||
|
||||
- [Installation](installation) — Get Semantica installed in under a minute
|
||||
- [Quickstart](quickstart) — Build a complete knowledge graph pipeline in 5 minutes
|
||||
- [Core Concepts](concepts) — The mental model behind the API
|
||||
- [API Reference](reference/context) — Exact module, class, and method details
|
||||
- [Cookbook](cookbook) — Domain notebooks for real-world use cases
|
||||
- [Installation](/installation) — Get Semantica installed in under a minute
|
||||
- [Quickstart](/quickstart) — Build a complete knowledge graph pipeline in 5 minutes
|
||||
- [Core Concepts](/concepts) — The mental model behind the API
|
||||
- [API Reference](/reference/context) — Exact module, class, and method details
|
||||
- [Cookbook](/cookbook) — Domain notebooks for real-world use cases
|
||||
- [Changelog](https://github.com/semantica-agi/semantica/releases) — Release history
|
||||
|
||||
|
||||
@@ -369,7 +369,7 @@ Semantica was designed for domains where every decision must be explainable and
|
||||
| `semantica.reasoning` | Forward chaining, Rete, deductive, abductive, SPARQL, Datalog |
|
||||
| `semantica.ontology` | SHACL, SKOS, alignments, diff/migration, auto-generation, OWL/RDF |
|
||||
| `semantica.explorer` | FastAPI Knowledge Explorer, Ontology Hub, Distance Intelligence, SHACL Studio |
|
||||
| `semantica.mcp_server` | MCP stdio server: 12 tools for Claude Desktop, VS Code, Cursor, Windsurf, Cline |
|
||||
| `semantica.mcp_server` | MCP stdio server: 15 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 with SPARQL |
|
||||
@@ -404,7 +404,7 @@ Semantica was designed for domains where every decision must be explainable and
|
||||
- 1,000+ passing tests with full regression coverage
|
||||
- `PipelineValidator` catches configuration errors at startup
|
||||
- `FailureHandler` with exponential backoff and dead-letter queues
|
||||
- 12 security vulnerabilities fixed in v0.5.0
|
||||
- Ongoing security hardening: fixes shipped in every release ([CHANGELOG](https://github.com/semantica-agi/semantica/blob/main/CHANGELOG.md))
|
||||
|
||||
**Modular by Design** — Import only what you need.
|
||||
- Use `NERExtractor` without a graph store
|
||||
|
||||
@@ -183,6 +183,6 @@ Install the [Microsoft Visual C++ Redistributable](https://aka.ms/vs/17/release/
|
||||
|
||||
## Next Steps
|
||||
|
||||
- [Getting Started](getting-started) — Understand what Semantica does before you build.
|
||||
- [Build the Pipeline](quickstart) — Follow the end-to-end workflow with code.
|
||||
- [Browse Examples](cookbook) — See notebook examples organized by use case.
|
||||
- [Getting Started](/getting-started) — Understand what Semantica does before you build.
|
||||
- [Build the Pipeline](/quickstart) — Follow the end-to-end workflow with code.
|
||||
- [Browse Examples](/cookbook) — See notebook examples organized by use case.
|
||||
|
||||
@@ -193,7 +193,7 @@ if not connector.test_connection():
|
||||
## See Also
|
||||
|
||||
- [Ingest Module](../reference/ingest) — Full DatabricksIngestor and all other ingestors.
|
||||
- [Snowflake Integration](snowflake) — Companion connector for a Snowflake + Databricks hybrid estate.
|
||||
- [Snowflake Integration](/integrations/snowflake) — Companion connector for a Snowflake + Databricks hybrid estate.
|
||||
- [Pipeline](../reference/pipeline) — Use Databricks ingestion as a pipeline step.
|
||||
- [Installation](../installation) — All optional dependency extras.
|
||||
- [Knowledge Graph](../reference/kg) — Build a KG from ingested Databricks data.
|
||||
|
||||
@@ -370,7 +370,7 @@ Common causes of authentication failures:
|
||||
## See Also
|
||||
|
||||
- [Ingest Module](../reference/ingest) — Full `SalesforceIngestor` API and all other ingestors.
|
||||
- [Snowflake Integration](snowflake) — Relational warehouse connector with a similar design.
|
||||
- [Databricks Integration](databricks) — Lakehouse connector.
|
||||
- [Snowflake Integration](/integrations/snowflake) — Relational warehouse connector with a similar design.
|
||||
- [Databricks Integration](/integrations/databricks) — Lakehouse connector.
|
||||
- [Installation](../installation) — All optional dependency extras.
|
||||
- [Knowledge Graph](../reference/kg) — Build a KG from ingested Salesforce data.
|
||||
|
||||
@@ -172,7 +172,7 @@ if not connector.test_connection():
|
||||
## See Also
|
||||
|
||||
- [Ingest Module](../reference/ingest) — Full SnowflakeIngestor and all other ingestors.
|
||||
- [Databricks Integration](databricks) — Companion connector for a Snowflake + Databricks hybrid estate.
|
||||
- [Databricks Integration](/integrations/databricks) — Companion connector for a Snowflake + Databricks hybrid estate.
|
||||
- [Pipeline](../reference/pipeline) — Use Snowflake ingestion as a pipeline step.
|
||||
- [Installation](../installation) — All optional dependency extras.
|
||||
- [Knowledge Graph](../reference/kg) — Build a KG from ingested Snowflake data.
|
||||
|
||||
+13
-13
@@ -9,9 +9,9 @@ Whether you're running your first pipeline or deploying Semantica in production,
|
||||
|
||||
## Learning Paths
|
||||
|
||||
- **Beginner (1–2 hrs)** — New to Semantica and knowledge graphs. [Start with Installation →](installation)
|
||||
- **Intermediate (4–6 hrs)** — Comfortable with basics, building real applications. [Start with Modules →](modules)
|
||||
- **Advanced (8+ hrs)** — Enterprise deployments, customization, and extension. [Start with Architecture →](architecture)
|
||||
- **Beginner (1–2 hrs)** — New to Semantica and knowledge graphs. [Start with Installation →](/installation)
|
||||
- **Intermediate (4–6 hrs)** — Comfortable with basics, building real applications. [Start with Modules →](/modules)
|
||||
- **Advanced (8+ hrs)** — Enterprise deployments, customization, and extension. [Start with Architecture →](/architecture)
|
||||
|
||||
<Tabs>
|
||||
<Tab title="Beginner (1–2 hrs)">
|
||||
@@ -19,16 +19,16 @@ Whether you're running your first pipeline or deploying Semantica in production,
|
||||
|
||||
<Steps>
|
||||
<Step title="Set up your environment">
|
||||
[Installation Guide](installation): virtual environments, optional extras, platform-specific fixes.
|
||||
[Installation Guide](/installation): virtual environments, optional extras, platform-specific fixes.
|
||||
</Step>
|
||||
<Step title="Understand the core ideas">
|
||||
[Core Concepts](concepts): what knowledge graphs are, how embeddings work, what extraction does.
|
||||
[Core Concepts](/concepts): what knowledge graphs are, how embeddings work, what extraction does.
|
||||
</Step>
|
||||
<Step title="Run your first example">
|
||||
[Getting Started](getting-started): 5-minute code walkthrough with pattern-based extraction (no API key needed).
|
||||
[Getting Started](/getting-started): 5-minute code walkthrough with pattern-based extraction (no API key needed).
|
||||
</Step>
|
||||
<Step title="Build your first knowledge graph">
|
||||
[Quickstart Tutorial](quickstart): full 6-step pipeline from ingestion to visualization.
|
||||
[Quickstart Tutorial](/quickstart): full 6-step pipeline from ingestion to visualization.
|
||||
</Step>
|
||||
<Step title="Explore interactively">
|
||||
[Welcome to Semantica notebook](https://github.com/semantica-agi/semantica/blob/main/cookbook/introduction/01_Welcome_to_Semantica.ipynb): Jupyter walkthrough of every module.
|
||||
@@ -40,13 +40,13 @@ Whether you're running your first pipeline or deploying Semantica in production,
|
||||
|
||||
<Steps>
|
||||
<Step title="Learn every module">
|
||||
[Modules Guide](modules): all 27 modules with code examples and common pipeline chains.
|
||||
[Modules Guide](/modules): all 27 modules with code examples and common pipeline chains.
|
||||
</Step>
|
||||
<Step title="Build production knowledge graphs">
|
||||
[Building Knowledge Graphs notebook](https://github.com/semantica-agi/semantica/blob/main/cookbook/introduction/07_Building_Knowledge_Graphs.ipynb): multi-source, deduplication, conflict resolution.
|
||||
</Step>
|
||||
<Step title="Add semantic search">
|
||||
[Embeddings notebook](https://github.com/semantica-agi/semantica/blob/main/cookbook/introduction/09_Embeddings.ipynb): providers, pooling strategies, vector stores.
|
||||
[Embedding Generation notebook](https://github.com/semantica-agi/semantica/blob/main/cookbook/introduction/12_Embedding_Generation.ipynb): generating embeddings, provider and model switching, dimensions. Then [Vector Store notebook](https://github.com/semantica-agi/semantica/blob/main/cookbook/introduction/13_Vector_Store.ipynb): storing and searching vectors for retrieval.
|
||||
</Step>
|
||||
<Step title="Multi-source integration">
|
||||
[Multi-Source Data Integration notebook](https://github.com/semantica-agi/semantica/blob/main/cookbook/advanced/06_Multi_Source_Data_Integration.ipynb) for multi-source patterns.
|
||||
@@ -58,7 +58,7 @@ Whether you're running your first pipeline or deploying Semantica in production,
|
||||
|
||||
<Steps>
|
||||
<Step title="Understand the architecture">
|
||||
[Architecture Guide](architecture): four-layer design, extension points, and design decisions.
|
||||
[Architecture Guide](/architecture): four-layer design, extension points, and design decisions.
|
||||
</Step>
|
||||
<Step title="Temporal intelligence">
|
||||
[Temporal Graphs notebook](https://github.com/semantica-agi/semantica/blob/main/cookbook/advanced/10_Temporal_Knowledge_Graphs.ipynb): `valid_from`/`valid_until`, Allen interval algebra, point-in-time queries.
|
||||
@@ -236,6 +236,6 @@ The `blocking_v2`, `hybrid_v2`, and `semantic_v2` strategies reduce O(n²) compa
|
||||
- **Graph exports**: encrypt sensitive exports at rest; use the v0.5.0 SSRF-safe `base_url` validation when configuring custom LLM gateways
|
||||
- **XML ingestion**: always use `XMLIngestor` (v0.5.0), which uses the XXE-safe lxml backend; never parse untrusted XML with the standard library parser
|
||||
|
||||
- [Cookbook](cookbook) — Interactive Jupyter notebooks from beginner to advanced.
|
||||
- [FAQ](faq) — Common questions answered.
|
||||
- [API Reference](reference/core) — Complete technical documentation.
|
||||
- [Cookbook](/cookbook) — Interactive Jupyter notebooks from beginner to advanced.
|
||||
- [FAQ](/faq) — Common questions answered.
|
||||
- [API Reference](/reference/core) — Complete technical documentation.
|
||||
|
||||
+32
-32
@@ -9,7 +9,7 @@ icon: "puzzle-piece"
|
||||
</Info>
|
||||
|
||||
<Tip>
|
||||
Not sure which module to use? The [Choose the Right Module](choose-your-module) guide maps 35+ developer goals to modules with code examples — start there if you're orienting for the first time.
|
||||
Not sure which module to use? The [Choose the Right Module](/choose-your-module) guide maps 35+ developer goals to modules with code examples — start there if you're orienting for the first time.
|
||||
</Tip>
|
||||
|
||||
Semantica is organized into **27 modules** across six logical layers. Each module is independently importable: you never pay for what you don't use.
|
||||
@@ -438,7 +438,7 @@ Exposes Semantica as an MCP stdio server for IDE and agent integrations.
|
||||
python -m semantica.mcp_server
|
||||
```
|
||||
|
||||
**Integrations:** Claude Desktop, VS Code, Cursor, Windsurf, Cline: 12 MCP tools exposed
|
||||
**Integrations:** Claude Desktop, VS Code, Cursor, Windsurf, Cline: 15 MCP tools exposed
|
||||
|
||||
### Seed
|
||||
|
||||
@@ -680,34 +680,34 @@ versioner.create_snapshot(kg, "2024-Q1", author="user@example.com", description=
|
||||
|
||||
| Module | Purpose | Key Classes |
|
||||
| :------ | :------- | :----------- |
|
||||
| [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 | `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`, `ClusterBuilder`, `MergeStrategyManager` |
|
||||
| [conflicts](reference/conflicts) | Conflict resolution | `ConflictDetector` |
|
||||
| [context](reference/context) | Agent context & decisions | `AgentContext`, `ContextGraph` |
|
||||
| [provenance](reference/provenance) | W3C PROV-O lineage | `ProvenanceManager` |
|
||||
| [change_management](reference/change_management) | Version control | `TemporalVersionManager` |
|
||||
| [export](reference/export) | Data export | `RDFExporter`, `ParquetExporter` |
|
||||
| [visualization](reference/visualization) | Graph visualization | `KGVisualizer` |
|
||||
| [pipeline](reference/pipeline) | Workflow orchestration | `Pipeline`, `PipelineBuilder` |
|
||||
| [explorer](reference/explorer) | Knowledge Explorer UI | `semantica-explorer --graph <file>` |
|
||||
| [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) | 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` |
|
||||
| [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 | `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`, `ClusterBuilder`, `MergeStrategyManager` |
|
||||
| [conflicts](/reference/conflicts) | Conflict resolution | `ConflictDetector` |
|
||||
| [context](/reference/context) | Agent context & decisions | `AgentContext`, `ContextGraph` |
|
||||
| [provenance](/reference/provenance) | W3C PROV-O lineage | `ProvenanceManager` |
|
||||
| [change_management](/reference/change_management) | Version control | `TemporalVersionManager` |
|
||||
| [export](/reference/export) | Data export | `RDFExporter`, `ParquetExporter` |
|
||||
| [visualization](/reference/visualization) | Graph visualization | `KGVisualizer` |
|
||||
| [pipeline](/reference/pipeline) | Workflow orchestration | `Pipeline`, `PipelineBuilder` |
|
||||
| [explorer](/reference/explorer) | Knowledge Explorer UI | `semantica-explorer --graph <file>` |
|
||||
| [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) | 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` |
|
||||
|
||||
- [Getting Started](getting-started) — Your first knowledge graph in 5 minutes.
|
||||
- [Cookbook](cookbook) — 40+ domain notebooks with real-world examples.
|
||||
- [API Reference](reference/context) — Full technical documentation.
|
||||
- [Getting Started](/getting-started) — Your first knowledge graph in 5 minutes.
|
||||
- [Cookbook](/cookbook) — 40+ domain notebooks with real-world examples.
|
||||
- [API Reference](/reference/context) — Full technical documentation.
|
||||
|
||||
@@ -76,5 +76,5 @@ By contributing to Semantica, you agree that your contributions will be licensed
|
||||
|
||||
## See Also
|
||||
|
||||
- [Contributing](contributing-guide) — How to contribute to the project.
|
||||
- [Citation](citation) — How to cite Semantica in research.
|
||||
- [Contributing](/contributing-guide) — How to contribute to the project.
|
||||
- [Citation](/citation) — How to cite Semantica in research.
|
||||
|
||||
+89
-66
@@ -5,7 +5,7 @@ icon: "rocket"
|
||||
---
|
||||
|
||||
<Info>
|
||||
**v0.5.0** — Ontology Hub, Distance Intelligence, Parquet & XML ingestion, 12 security fixes. <a href="https://github.com/semantica-agi/semantica/releases" style={{color:"#10B981",fontWeight:600,textDecoration:"none"}}>What's new →</a>
|
||||
**v0.6.7** — first-class LangChain integration, SAP OData ingestor, human-editable Markdown persistence for `ContextGraph`, and a structured Action layer for the reasoning engine. <a href="https://github.com/semantica-agi/semantica/releases" style={{color:"#10B981",fontWeight:600,textDecoration:"none"}}>What's new →</a>
|
||||
</Info>
|
||||
|
||||
This guide walks you through the end-to-end pipeline for building your first knowledge graph. Start here after installation. An LLM API key is optional: pattern-based extraction works out of the box.
|
||||
@@ -35,7 +35,7 @@ Verify:
|
||||
|
||||
```bash
|
||||
python -c "import semantica; print(semantica.__version__)"
|
||||
# 0.5.0
|
||||
# 0.6.7
|
||||
```
|
||||
|
||||
|
||||
@@ -47,36 +47,24 @@ python -c "import semantica; print(semantica.__version__)"
|
||||
|
||||
<Step title="Ingest">
|
||||
|
||||
Load a document from a file, directory, URL, or database.
|
||||
Load a document from a file or directory. The rest of this walkthrough follows
|
||||
the file path; other sources are shown afterwards.
|
||||
|
||||
<CodeGroup>
|
||||
|
||||
```python File
|
||||
```python
|
||||
from semantica.ingest import FileIngestor
|
||||
|
||||
ingestor = FileIngestor()
|
||||
sources = ingestor.ingest("data/report.pdf")
|
||||
# Also accepts: .docx, .html, .json, .csv, .xlsx, .pptx, .parquet, .xml
|
||||
# Also accepts a directory, .docx, .html, .json, .csv, .xlsx, .pptx, .parquet, .xml
|
||||
```
|
||||
|
||||
```python Web
|
||||
from semantica.ingest import WebIngestor
|
||||
|
||||
ingestor = WebIngestor(max_depth=2)
|
||||
sources = ingestor.ingest("https://example.com/article")
|
||||
```
|
||||
|
||||
```python Parquet / XML (v0.5.0)
|
||||
from semantica.ingest import ParquetIngestor, XMLIngestor
|
||||
|
||||
# Single file or Hive-partitioned directory
|
||||
sources = ParquetIngestor().ingest("data/events.parquet")
|
||||
|
||||
# XML with XSD schema validation
|
||||
sources = XMLIngestor(validate_xsd="schema.xsd").ingest("data/records/")
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
<Tip>
|
||||
**Other sources.** `WebIngestor().ingest_url(url)` returns a `WebContent` whose
|
||||
`.text` you can feed straight into the Extract step (no parsing needed).
|
||||
`ParquetIngestor().ingest(path)` and `XMLIngestor().ingest(path, schema_path=...)`
|
||||
return structured records rather than documents; build a graph from those with
|
||||
`GraphBuilder().build({"entities": [...], "relationships": [...]})` directly.
|
||||
</Tip>
|
||||
|
||||
</Step>
|
||||
|
||||
@@ -88,22 +76,24 @@ Extract structured text and layout from raw documents.
|
||||
from semantica.parse import DocumentParser
|
||||
|
||||
parser = DocumentParser()
|
||||
parsed = parser.parse(sources[0])
|
||||
parsed = parser.parse(sources[0].path) # parse() takes a path string
|
||||
|
||||
print(parsed.text[:200]) # extracted text
|
||||
print(parsed.metadata) # title, author, date, source
|
||||
print(parsed["text"][:200]) # extracted text
|
||||
print(parsed["metadata"]) # file_path, encoding, size, and format-specific keys
|
||||
```
|
||||
|
||||
`parse()` returns a `dict` with `text`, `full_text`, and `metadata` keys.
|
||||
|
||||
<Tip>
|
||||
For PDFs with tables, charts, or multi-column layouts, use `DoclingParser`: it applies advanced layout analysis and returns structured table data alongside text.
|
||||
For PDFs with tables, charts, or multi-column layouts, use `DoclingParser` (`pip install semantica[parse-docling]`): it applies advanced layout analysis and returns structured table data alongside text.
|
||||
</Tip>
|
||||
|
||||
```python
|
||||
from semantica.parse import DoclingParser
|
||||
|
||||
parser = DoclingParser()
|
||||
parsed = parser.parse(sources[0])
|
||||
print(parsed.tables) # structured table objects
|
||||
parsed = parser.parse(sources[0].path)
|
||||
print(parsed["tables"]) # structured table data
|
||||
```
|
||||
|
||||
</Step>
|
||||
@@ -117,26 +107,28 @@ Identify named entities and extract typed relationships between them.
|
||||
```python Pattern-based (fast, no API key)
|
||||
from semantica.semantic_extract import NERExtractor, RelationExtractor
|
||||
|
||||
ner = NERExtractor(method="pattern")
|
||||
entities = ner.extract(parsed)
|
||||
# Returns: [{"text": "Apple Inc.", "type": "ORGANIZATION", "confidence": 0.98}, ...]
|
||||
text = parsed["text"]
|
||||
|
||||
rel = RelationExtractor(method="rule")
|
||||
relationships = rel.extract(parsed, entities=entities)
|
||||
# Returns: [{"subject": "Steve Jobs", "predicate": "founded", "object": "Apple Inc."}, ...]
|
||||
ner = NERExtractor(method="pattern")
|
||||
entities = ner.extract(text)
|
||||
# Returns: [Entity(text="Apple Inc.", label="ORG", start_char=0, end_char=10, confidence=0.7), ...]
|
||||
|
||||
rel = RelationExtractor(method="pattern")
|
||||
relationships = rel.extract(text, entities=entities)
|
||||
# Returns: [Relation(subject=Entity(...), predicate="founded_by", object=Entity(...), confidence=0.7), ...]
|
||||
```
|
||||
|
||||
```python LLM-powered (higher accuracy)
|
||||
from semantica.semantic_extract import NERExtractor, RelationExtractor
|
||||
from semantica.llms import Groq
|
||||
|
||||
llm = Groq(model="llama-3.3-70b-versatile")
|
||||
# Reads GROQ_API_KEY from the environment; provider/llm_model select the backend
|
||||
text = parsed["text"]
|
||||
|
||||
ner = NERExtractor(method="llm", llm_provider=llm)
|
||||
entities = ner.extract(parsed)
|
||||
ner = NERExtractor(method="llm", provider="groq", llm_model="llama-3.3-70b-versatile")
|
||||
entities = ner.extract(text)
|
||||
|
||||
rel = RelationExtractor(method="llm", llm_provider=llm)
|
||||
relationships = rel.extract(parsed, entities=entities)
|
||||
rel = RelationExtractor(method="llm", provider="groq", llm_model="llama-3.3-70b-versatile")
|
||||
relationships = rel.extract(text, entities=entities)
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
@@ -198,16 +190,17 @@ exporter.export(graph, file_path="graph.nt", format="nt")
|
||||
from semantica.export import ParquetExporter
|
||||
|
||||
exporter = ParquetExporter()
|
||||
exporter.export(graph, file_path="output/graph.parquet")
|
||||
# Writes nodes.parquet + edges.parquet: ready for Spark, BigQuery, Databricks
|
||||
exporter.export(graph, file_path="output/graph")
|
||||
# Dict input writes one file per key: output/graph_entities.parquet and
|
||||
# output/graph_relationships.parquet: ready for Spark, BigQuery, Databricks
|
||||
```
|
||||
|
||||
```python ArangoDB
|
||||
from semantica.export import ArangoAQLExporter
|
||||
|
||||
exporter = ArangoAQLExporter()
|
||||
aql = exporter.export(graph)
|
||||
# Returns ready-to-run AQL INSERT statements
|
||||
exporter.export(graph, file_path="graph.aql")
|
||||
# Writes ready-to-run AQL INSERT statements to graph.aql
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
@@ -272,14 +265,21 @@ relationships = rel.extract(text, entities=entities)
|
||||
<Accordion title="Multi-source incremental graph build" icon="layer-group">
|
||||
|
||||
```python
|
||||
from semantica.ingest import FileIngestor
|
||||
from semantica.parse import DocumentParser
|
||||
from semantica.semantic_extract import NERExtractor, RelationExtractor
|
||||
from semantica.kg import GraphBuilder
|
||||
|
||||
builder = GraphBuilder(merge_entities=True)
|
||||
all_entities, all_rels = [], []
|
||||
parser = DocumentParser()
|
||||
ner = NERExtractor(method="pattern")
|
||||
rel = RelationExtractor(method="pattern")
|
||||
builder = GraphBuilder(merge_entities=True)
|
||||
|
||||
for doc in parsed_docs:
|
||||
entities = ner.extract(doc)
|
||||
rels = rel.extract(doc, entities=entities)
|
||||
all_entities, all_rels = [], []
|
||||
for source in FileIngestor().ingest("data/reports/"):
|
||||
text = parser.parse(source.path)["text"]
|
||||
entities = ner.extract(text)
|
||||
rels = rel.extract(text, entities=entities)
|
||||
all_entities.extend(entities)
|
||||
all_rels.extend(rels)
|
||||
|
||||
@@ -359,7 +359,8 @@ graph = builder.build({"entities": entities, "relationships": relationships})
|
||||
# Retrieve full lineage for any entity
|
||||
sources = prov.get_all_sources("Apple Inc.")
|
||||
print(sources[0])
|
||||
# {"source": "data/report.pdf", "location": None, "timestamp": "...", "confidence": 0.98}
|
||||
# {"source": "data/report.pdf", "location": None, "timestamp": "...",
|
||||
# "confidence": 1.0, "metadata": {"confidence": 0.98}}
|
||||
```
|
||||
|
||||
</Accordion>
|
||||
@@ -373,32 +374,54 @@ print(sources[0])
|
||||
|
||||
<Accordion title="No entities extracted" icon="magnifying-glass">
|
||||
|
||||
The document likely contains scanned images rather than machine-readable text. Enable OCR:
|
||||
The document likely contains scanned images rather than machine-readable text. `DocumentParser` warns when a PDF has no text layer; switch to `DoclingParser` with OCR enabled:
|
||||
|
||||
```python
|
||||
from semantica.parse import DocumentParser
|
||||
from semantica.parse import DoclingParser # pip install semantica[parse-docling]
|
||||
|
||||
parser = DocumentParser(ocr=True) # enables Tesseract OCR
|
||||
parsed = parser.parse(sources[0])
|
||||
parser = DoclingParser(enable_ocr=True)
|
||||
parsed = parser.parse(sources[0].path)
|
||||
```
|
||||
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Slow processing on large corpora" icon="gauge">
|
||||
|
||||
Enable parallel processing and GPU acceleration:
|
||||
Install the GPU extras so embedding and ML inference run on CUDA:
|
||||
|
||||
```bash
|
||||
pip install semantica[gpu]
|
||||
```
|
||||
|
||||
```python
|
||||
from semantica.pipeline import Pipeline
|
||||
Scan the directory for paths first (no file contents are read), then handle one
|
||||
document at a time and write to a persistent graph backend instead of the
|
||||
in-memory graph:
|
||||
|
||||
pipeline = Pipeline(workers=8, batch_size=32)
|
||||
pipeline.run(sources)
|
||||
```python
|
||||
from semantica.ingest import FileIngestor
|
||||
from semantica.parse import DocumentParser
|
||||
from semantica.semantic_extract import NERExtractor, RelationExtractor
|
||||
from semantica.graph_store import GraphStore
|
||||
from semantica.kg import GraphBuilder
|
||||
|
||||
ingestor = FileIngestor()
|
||||
parser = DocumentParser()
|
||||
ner = NERExtractor(method="pattern")
|
||||
rel = RelationExtractor(method="pattern")
|
||||
store = GraphStore(backend="neo4j", uri="bolt://localhost:7687",
|
||||
user="neo4j", password="password")
|
||||
builder = GraphBuilder(merge_entities=True, graph_store=store)
|
||||
|
||||
for info in ingestor.scan_directory("data/reports/", recursive=True):
|
||||
text = parser.parse(info["path"])["text"] # one document loaded at a time
|
||||
entities = ner.extract(text)
|
||||
rels = rel.extract(text, entities=entities)
|
||||
builder.build({"entities": entities, "relationships": rels})
|
||||
```
|
||||
|
||||
For multi-step orchestration with configurable parallelism, see the
|
||||
[Pipeline guide](/guides/pipeline).
|
||||
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Memory errors on large graphs" icon="memory">
|
||||
@@ -429,7 +452,7 @@ pip install --upgrade semantica
|
||||
|
||||
## Next Steps
|
||||
|
||||
- [Core Concepts](concepts) — Knowledge graphs, ontologies, reasoning engines: the mental model behind Semantica.
|
||||
- [Module Reference](modules) — Every module explained with key classes and common chains.
|
||||
- [API Reference](reference/context) — Complete documentation for every module, class, and parameter.
|
||||
- [Cookbook](cookbook) — 40+ interactive Jupyter notebooks with real-world datasets.
|
||||
- [Core Concepts](/concepts) — Knowledge graphs, ontologies, reasoning engines: the mental model behind Semantica.
|
||||
- [Module Reference](/modules) — Every module explained with key classes and common chains.
|
||||
- [API Reference](/reference/context) — Complete documentation for every module, class, and parameter.
|
||||
- [Cookbook](/cookbook) — 40+ interactive Jupyter notebooks with real-world datasets.
|
||||
|
||||
@@ -351,6 +351,6 @@ for record in history:
|
||||
</AccordionGroup>
|
||||
|
||||
- [Provenance](provenance) — W3C PROV-O lineage tracking.
|
||||
- [Knowledge Graph](kg) — The graph being versioned.
|
||||
- [Knowledge Graph](/reference/kg) — The graph being versioned.
|
||||
- [Export](export) — Export versioned snapshots.
|
||||
- [Conflicts](conflicts) — Detect conflicts introduced between versions.
|
||||
- [Conflicts](/reference/conflicts) — Detect conflicts introduced between versions.
|
||||
|
||||
@@ -453,4 +453,4 @@ class InvestigationStep:
|
||||
- [Deduplication](deduplication) — Resolve duplicate entities before conflict detection.
|
||||
- [Ontology](ontology) — Logical conflicts use SHACL shapes and ontology axioms.
|
||||
- [Provenance](provenance) — Track which source each conflicting fact came from.
|
||||
- [Knowledge Graph](kg) — The graph being checked for conflicts.
|
||||
- [Knowledge Graph](/reference/kg) — The graph being checked for conflicts.
|
||||
|
||||
@@ -449,7 +449,7 @@ print("Nodes: {}, Edges: {}".format(stats["node_count"], stats["edge_count"]))
|
||||
`ContextGraph` exposes a full Distance Intelligence API for exploring semantic neighborhoods and blending proximity into retrieval.
|
||||
|
||||
<Info>
|
||||
Full Distance Intelligence reference — distance matrices, API endpoints, embedding cache, Explorer UI — is covered in the dedicated [Distance Intelligence](distance) page. This section documents the context-layer API.
|
||||
Full Distance Intelligence reference — distance matrices, API endpoints, embedding cache, Explorer UI — is covered in the dedicated [Distance Intelligence](/reference/distance) page. This section documents the context-layer API.
|
||||
</Info>
|
||||
|
||||
### Neighbors with Distance Metadata
|
||||
@@ -1087,8 +1087,8 @@ class EntityLink:
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
- [Vector Store](vector_store) — Embedding storage backend for memory retrieval.
|
||||
- [Knowledge Graph](kg) — Graph algorithms and analytics used inside ContextGraph.
|
||||
- [Vector Store](/reference/vector_store) — Embedding storage backend for memory retrieval.
|
||||
- [Knowledge Graph](/reference/kg) — Graph algorithms and analytics used inside ContextGraph.
|
||||
- [Reasoning](reasoning) — Logical inference layered on top of context.
|
||||
- [Provenance](provenance) — W3C PROV-O lineage for every stored fact.
|
||||
|
||||
|
||||
@@ -227,6 +227,6 @@ result = build_knowledge_base(sources=["doc.pdf"], method="fast")
|
||||
</Tip>
|
||||
|
||||
- [Pipeline](pipeline) — Pipeline execution and step orchestration.
|
||||
- [Utils](utils) — Shared utilities used by Core internally.
|
||||
- [Utils](/reference/utils) — Shared utilities used by Core internally.
|
||||
- [Getting Started](../getting-started) — Learn the basics before using Core.
|
||||
- [LLMs](llms) — Configure LLM providers via ConfigManager.
|
||||
- [LLMs](/reference/llms) — Configure LLM providers via ConfigManager.
|
||||
|
||||
@@ -437,7 +437,7 @@ result = calculate_similarity(entity_a, entity_b, method="drug_name")
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
- [Conflicts](conflicts) — Detect value conflicts between non-duplicate entities.
|
||||
- [Knowledge Graph](kg) — GraphBuilder uses deduplication during construction.
|
||||
- [Normalize](normalize) — Normalize entity names before deduplication.
|
||||
- [Conflicts](/reference/conflicts) — Detect value conflicts between non-duplicate entities.
|
||||
- [Knowledge Graph](/reference/kg) — GraphBuilder uses deduplication during construction.
|
||||
- [Normalize](/reference/normalize) — Normalize entity names before deduplication.
|
||||
- [Provenance](provenance) — Track merged entity lineage.
|
||||
|
||||
@@ -607,9 +607,7 @@ The Knowledge Explorer embeds Distance Intelligence directly in the browser dash
|
||||
The 10× cache improvement applies when the graph is unchanged between requests. In write-heavy pipelines where nodes are added continuously, cache hit rates will be lower. Use `force_refresh=False` (default) for read-heavy Explorer usage and `force_refresh=True` for batch pipeline contexts.
|
||||
</Note>
|
||||
|
||||
- [Context Module](context) — `ContextGraph.get_neighbors()` and proximity-blended retrieval.
|
||||
- [Knowledge Graph Module](kg) — `NodeEmbedder`, `SimilarityCalculator`, and graph analytics.
|
||||
- [Context Module](/reference/context) — `ContextGraph.get_neighbors()` and proximity-blended retrieval.
|
||||
- [Knowledge Graph Module](/reference/kg) — `NodeEmbedder`, `SimilarityCalculator`, and graph analytics.
|
||||
- [Visualization](visualization) — Programmatic distance heatmaps and ego-mode graph renders.
|
||||
- [Explorer](explorer) — Knowledge Explorer with built-in Distance Intelligence dashboard.
|
||||
|
||||
- [Distance Intelligence](https://github.com/semantica-agi/semantica/blob/main/cookbook/advanced/12_Distance_Intelligence.ipynb) — Semantic neighborhoods and distance matrices · Advanced
|
||||
- [Explorer](/reference/explorer) — Knowledge Explorer with built-in Distance Intelligence dashboard.
|
||||
|
||||
@@ -619,7 +619,7 @@ providers = check_available_providers()
|
||||
# → {"sentence_transformers": True, "fastembed": True, "openai": False}
|
||||
```
|
||||
|
||||
- [Vector Store](vector_store) — Store and search the generated embeddings.
|
||||
- [Split](split) — Chunk text before embedding for better retrieval quality.
|
||||
- [KG Module](kg) — Distance Intelligence uses graph embeddings for semantic neighbourhoods.
|
||||
- [Vector Store](/reference/vector_store) — Store and search the generated embeddings.
|
||||
- [Split](/reference/split) — Chunk text before embedding for better retrieval quality.
|
||||
- [KG Module](/reference/kg) — Distance Intelligence uses graph embeddings for semantic neighbourhoods.
|
||||
- [Deduplication](deduplication) — Semantic deduplication uses embedding distance for entity resolution.
|
||||
|
||||
+209
-49
@@ -1,64 +1,224 @@
|
||||
---
|
||||
title: "Evals Module"
|
||||
description: "Evaluation framework for measuring Knowledge Graph quality, extraction accuracy, and pipeline performance: coming soon."
|
||||
description: "Score decision records, audit trails, and reasoning output with deterministic and model-backed evaluators plus a small run harness."
|
||||
icon: "chart-line"
|
||||
---
|
||||
|
||||
**`semantica.evals`** is planned as a comprehensive evaluation framework for measuring **extraction accuracy, graph quality, and pipeline performance**.
|
||||
`semantica.evals` measures the quality of decision intelligence outputs. It takes
|
||||
the decisions, audit trails, and reasoning text your pipeline produces and scores
|
||||
them against expectations you define, returning a structured summary you can log,
|
||||
assert on in tests, or track across runs.
|
||||
|
||||
<Warning>
|
||||
**`semantica.evals` is not yet implemented.** The module is a placeholder with `__all__ = []`. No classes or functions are available for import. This page describes the planned API only.
|
||||
</Warning>
|
||||
- A registry of named evaluators, from exact string matching to ROUGE overlap and
|
||||
LLM-as-judge
|
||||
- `decision_scores`, a composite evaluator for `Decision` objects that checks
|
||||
outcome, confidence bounds, required fields, provenance, and (optionally)
|
||||
policy compliance
|
||||
- A `evaluate()` runner that applies several evaluators to a list of cases and
|
||||
aggregates pass / fail / error counts
|
||||
- Per-evaluator **objectives** that let you override an evaluator's built-in
|
||||
verdict at the run level
|
||||
|
||||
## Planned Features
|
||||
<Note>
|
||||
The module is versioned separately from the package: `semantica.evals.__version__`
|
||||
is `"0.1.0"`. The public surface described here is stable, but expect additive
|
||||
changes (new evaluators, new objective options) before it reaches 1.0.
|
||||
</Note>
|
||||
|
||||
When released, `semantica.evals` will provide:
|
||||
## Public API
|
||||
|
||||
| Planned Class | Role |
|
||||
| :--- | :--- |
|
||||
| `KGEvaluator` | Completeness, consistency, schema compliance, coverage, and orphan node detection |
|
||||
| `ExtractionEvaluator` | NER precision / recall / F1 and relation extraction metrics against gold datasets |
|
||||
| `PipelineBenchmark` | Throughput (docs/sec), per-step latency, peak memory, and error rate |
|
||||
| `RegressionTracker` | Record runs and compare metrics across commits or config changes |
|
||||
| `EvalReport` | Structured report: `{scores, regressions, recommendations}` |
|
||||
| `DeduplicationEvaluator` | Merge precision, false positive / false negative rates |
|
||||
| `ReasoningEvaluator` | Inference accuracy, rule coverage, and derivation depth |
|
||||
|
||||
## Current Workaround
|
||||
|
||||
Until `semantica.evals` ships, use `semantica.ontology.OntologyEvaluator` for ontology quality metrics:
|
||||
| Name | Kind | Role |
|
||||
| :--- | :--- | :--- |
|
||||
| `evaluate(cases, evaluators, config=None, target_fn=None)` | function | Run named evaluators over each case, return an `EvalSummary` |
|
||||
| `list_evaluators()` | function | Sorted names of every registered evaluator |
|
||||
| `get_evaluator(name)` | function | Look up a single evaluator function by name |
|
||||
| `EvalMetric` | dataclass (frozen) | One evaluator's result: `score`, `passed`, `meta` |
|
||||
| `CaseResult` | namedtuple | One case's result: `case_id`, `status`, `metrics`, `details` |
|
||||
| `EvalSummary` | dataclass | Aggregate across cases: `total`, `passed`, `failed`, `errors`, `pass_rate`, `cases` |
|
||||
|
||||
```python
|
||||
from semantica.ontology import OntologyEvaluator
|
||||
|
||||
evaluator = OntologyEvaluator()
|
||||
|
||||
# evaluate_ontology takes the ontology dict only
|
||||
result = evaluator.evaluate_ontology(ontology)
|
||||
|
||||
print("Coverage: ", result.coverage_score)
|
||||
print("Completeness:", result.completeness_score)
|
||||
print("Gaps: ", result.gaps)
|
||||
print("Suggestions: ", result.suggestions)
|
||||
|
||||
# Full report with class granularity and relation completeness
|
||||
report = evaluator.generate_report(ontology)
|
||||
print("Coverage score: ", report["evaluation"]["coverage_score"])
|
||||
print("Completeness score:", report["evaluation"]["completeness_score"])
|
||||
print("Relation coverage: ", report["relation_completeness"]["relation_coverage"])
|
||||
import semantica.evals as evals
|
||||
from semantica.evals import evaluate, list_evaluators, get_evaluator
|
||||
```
|
||||
|
||||
`EvaluationResult` fields returned by `evaluate_ontology()`:
|
||||
## Built-in evaluators
|
||||
|
||||
| Field | Type | Description |
|
||||
| :----- | :---- | :----------- |
|
||||
| `coverage_score` | `float` | Fraction of competency questions answerable by the ontology |
|
||||
| `completeness_score` | `float` | Average of class and property completeness scores |
|
||||
| `gaps` | `List[str]` | Identified gaps in coverage |
|
||||
| `suggestions` | `List[str]` | Improvement suggestions |
|
||||
| `metrics` | `dict` | Detailed sub-metrics |
|
||||
Every evaluator is a plain function `fn(actual, expected, config=None) -> EvalMetric`
|
||||
registered under a stable name. `list_evaluators()` returns the current set:
|
||||
|
||||
- [Semantic Extract](semantic_extract) — Extraction module.
|
||||
- [Knowledge Graph](kg) — Graph quality assessment.
|
||||
- [Pipeline](pipeline) — Pipeline performance metrics.
|
||||
- [Ontology Evaluator](ontology) — Available now for ontology quality metrics.
|
||||
```python
|
||||
>>> list_evaluators()
|
||||
['decision_scores', 'exact_match', 'keyword_check', 'length_range',
|
||||
'levenshtein', 'llm_as_judge', 'numeric_range', 'regex_match', 'rouge',
|
||||
'temporal_range']
|
||||
```
|
||||
|
||||
| Name | Passes when | Relevant `config` keys |
|
||||
| :--- | :--- | :--- |
|
||||
| `exact_match` | `actual == expected` | none |
|
||||
| `regex_match` | `re.search(expected, actual)` matches | none |
|
||||
| `keyword_check` | every required term appears in `actual` (word-boundary) | `required` (falls back to `expected`) |
|
||||
| `numeric_range` | `min <= actual <= max` | `min`, `max` (both required) |
|
||||
| `temporal_range` | ISO datetime `actual` falls in `[min, max]` | `min`, `max` as ISO strings (both required) |
|
||||
| `length_range` | `min <= len(actual) <= max` | `min` (default 0), `max` (required) |
|
||||
| `levenshtein` | normalized similarity `>= threshold` | `threshold` (default 0.8) |
|
||||
| `rouge` | ROUGE-1 F1 `> 0` and `>= threshold` | `threshold` (default 0.0) |
|
||||
| `llm_as_judge` | caller-supplied `judge_fn(actual, expected)` returns truthy | `judge_fn` (required callable) |
|
||||
| `decision_scores` | all configured sub-checks on a `Decision` pass | see below |
|
||||
|
||||
An evaluator that cannot run (bad regex, unparseable datetime, no `judge_fn`) returns an
|
||||
`EvalMetric` with an `"error"` key in `meta` rather than raising. Evaluators that
|
||||
require numeric bounds (`numeric_range`, `length_range`) instead return a failing
|
||||
metric with a `"reason"` key when the bound is missing — they do not raise and do
|
||||
not set `"error"`.
|
||||
|
||||
### `decision_scores`
|
||||
|
||||
`decision_scores` accepts a `Decision` (from `semantica.context.decision_models`)
|
||||
or its dict form and runs a set of field-level and governance checks. The score is
|
||||
the fraction of checks that passed; `passed` is `True` only when all of them did.
|
||||
|
||||
| Sub-check | Controlled by |
|
||||
| :--- | :--- |
|
||||
| Outcome matches | `expected_outcome` in config, or the case's `expected`; **skipped** when neither is set |
|
||||
| Confidence in range | `min_confidence` (default 0.0), `max_confidence` (default 1.0); always run |
|
||||
| `decision_maker`, `reasoning`, `scenario` non-empty | always run |
|
||||
| Provenance present in metadata | `provenance_key` (default `"provenance"`); always run |
|
||||
| Policy compliance | `policy_engine` and `policy_id` both set; skipped otherwise |
|
||||
|
||||
Passing `causal_chain_exists` in config raises `NotImplementedError`. That key is a
|
||||
reserved slot for a future release.
|
||||
|
||||
## Running an evaluation
|
||||
|
||||
`evaluate()` takes a list of cases and a list of evaluator names. A case is either
|
||||
a `(expected, actual)` tuple or a dict:
|
||||
|
||||
```python
|
||||
{
|
||||
"id": "loan-001", # optional, generated if absent
|
||||
"expected": ..., # optional; some evaluators read it, some don't
|
||||
"actual": ..., # the value under test
|
||||
"config": {...}, # optional, per-evaluator settings for this case
|
||||
"target_fn": callable, # optional, called with the case to produce `actual`
|
||||
}
|
||||
```
|
||||
|
||||
If `actual` is missing, the runner calls the case's `target_fn` (or the
|
||||
`target_fn` passed to `evaluate()`) to produce it. Per-case `config` is deep-merged
|
||||
over the top-level `config`, so a case can override one evaluator's settings
|
||||
without discarding the rest.
|
||||
|
||||
```python
|
||||
from datetime import datetime
|
||||
|
||||
from semantica.context.decision_models import Decision
|
||||
from semantica.evals import evaluate
|
||||
|
||||
decision = Decision(
|
||||
decision_id="d-1",
|
||||
category="loan",
|
||||
scenario="loan-request",
|
||||
reasoning="vetted against lending policy v3",
|
||||
outcome="approve",
|
||||
confidence=0.87,
|
||||
timestamp=datetime.now(),
|
||||
decision_maker="approver-a",
|
||||
metadata={"provenance": "workflow:loan/v3"},
|
||||
)
|
||||
|
||||
cases = [
|
||||
{
|
||||
"id": "loan-001",
|
||||
"actual": decision,
|
||||
"config": {
|
||||
"decision_scores": {
|
||||
"expected_outcome": "approve",
|
||||
"min_confidence": 0.7,
|
||||
}
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
summary = evaluate(cases, ["decision_scores"])
|
||||
print(summary.pass_rate) # 1.0
|
||||
```
|
||||
|
||||
Evaluators run independently per case. If one raises, that case's `status` becomes
|
||||
`"error"` and the exception text is captured in the metric's `meta`; the rest of
|
||||
the run continues.
|
||||
|
||||
## Objectives
|
||||
|
||||
By default each evaluator decides its own pass / fail. An **objective** overrides
|
||||
that verdict at the run level, keyed by evaluator name under `config`:
|
||||
|
||||
```python
|
||||
# Raise levenshtein's bar from its default 0.8 to 0.9
|
||||
evaluate(
|
||||
[("apple", "aple")],
|
||||
evaluators=["levenshtein"],
|
||||
config={"levenshtein": {"objective": {"direction": "maximize", "threshold": 0.9}}},
|
||||
)
|
||||
|
||||
# Lower is better
|
||||
evaluate(
|
||||
[("night", "nacht")],
|
||||
evaluators=["levenshtein"],
|
||||
config={"levenshtein": {"objective": {"direction": "minimize", "threshold": 0.5}}},
|
||||
)
|
||||
|
||||
# Expect the metric NOT to match
|
||||
evaluate(
|
||||
[("ok", "ok")],
|
||||
evaluators=["exact_match"],
|
||||
config={"exact_match": {"objective": {"expect": False}}},
|
||||
)
|
||||
```
|
||||
|
||||
Rules:
|
||||
|
||||
- `maximize` with `threshold`: pass iff `score >= threshold`. `maximize` with no
|
||||
threshold is a no-op and the evaluator's own verdict stands.
|
||||
- `minimize` with `threshold`: pass iff `score <= threshold`. `minimize`
|
||||
**requires** a threshold; omitting it raises `ValueError`.
|
||||
- `expect` (`True` / `False`): pass iff `bool(score)` equals it. Cannot be combined
|
||||
with `direction` or `threshold`, and must be a real boolean.
|
||||
- A metric that already carries an `"error"` in its `meta` is unaffected by any
|
||||
objective.
|
||||
- Invalid objective config is validated for every case before any evaluator runs,
|
||||
so a bad objective fails the whole run up front rather than partway through.
|
||||
|
||||
## Reading the summary
|
||||
|
||||
```python
|
||||
summary = evaluate(cases, ["decision_scores"])
|
||||
|
||||
summary.total, summary.passed, summary.failed, summary.errors
|
||||
summary.pass_rate # passed / total, or 1.0 for an empty case list
|
||||
|
||||
for case in summary.cases:
|
||||
print(case.case_id, case.status) # status: "pass" | "fail" | "error"
|
||||
for name, metric in case.metrics.items():
|
||||
print(name, metric.score, metric.passed)
|
||||
print(metric.meta.get("reasons", {})) # per-sub-check failure reasons
|
||||
```
|
||||
|
||||
`EvalMetric` is frozen (`score: float`, `passed: bool`, `meta: dict`). `CaseResult`
|
||||
is a namedtuple, and `EvalSummary` is a plain dataclass, so all three are
|
||||
straightforward to serialize for logging or regression tracking.
|
||||
|
||||
## Notes
|
||||
|
||||
- `llm_as_judge` needs `config["judge_fn"]`, a callable
|
||||
`judge_fn(actual, expected) -> bool` you supply. No LLM backend is imported
|
||||
unless you pass one in.
|
||||
- `decision_scores` governance checks are opt-in: policy compliance is only
|
||||
evaluated when both `policy_engine` and `policy_id` are present.
|
||||
|
||||
## See also
|
||||
|
||||
- [Decision Intelligence](/guides/decision-intelligence) — producing the `Decision` records this module scores
|
||||
- [Reasoning](/reference/reasoning) — inference output that reasoning-text evaluators can measure
|
||||
- [Policy Engine](/guides/policy-engine) — the `policy_engine` used by `decision_scores`
|
||||
- [Ontology Evaluator](/reference/ontology) — separate tooling for ontology quality metrics
|
||||
|
||||
@@ -403,7 +403,7 @@ Semantic neighborhood requires node embeddings stored in node properties (keys `
|
||||
**Session state lost after restart**
|
||||
Session state is in-memory only. Use `POST /api/export` to save a JSON snapshot before shutting down.
|
||||
|
||||
- [Context](context) — Build and save the ContextGraph that Explorer loads.
|
||||
- [Context](/reference/context) — Build and save the ContextGraph that Explorer loads.
|
||||
- [Ontology](ontology) — Programmatic ontology management and SHACL generation.
|
||||
- [Visualization](visualization) — Programmatic graph rendering without the Explorer server.
|
||||
- [Export](export) — Export to RDF, Parquet, and other formats without launching a server.
|
||||
|
||||
@@ -394,7 +394,7 @@ The `export_csv` convenience function delegates to `CSVExporter.export()`. For p
|
||||
**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`.
|
||||
</Tip>
|
||||
|
||||
- [Triplet Store](triplet_store) — Store RDF exports in a SPARQL-queryable backend.
|
||||
- [Triplet Store](/reference/triplet_store) — Store RDF exports in a SPARQL-queryable backend.
|
||||
- [Ontology](ontology) — Export OWL ontologies.
|
||||
- [Provenance](provenance) — Include provenance metadata in RDF exports.
|
||||
- [Pipeline](pipeline) — Add export as a final pipeline step.
|
||||
|
||||
@@ -503,7 +503,7 @@ stats = store.get_stats()
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
- [KG Module](kg) — Build the graph before persisting it.
|
||||
- [Triplet Store](triplet_store) — RDF triple store for semantic web and SPARQL queries.
|
||||
- [KG Module](/reference/kg) — Build the graph before persisting it.
|
||||
- [Triplet Store](/reference/triplet_store) — RDF triple store for semantic web and SPARQL queries.
|
||||
- [Visualization](visualization) — Visualize graphs stored in any backend.
|
||||
- [Context](context) — AgentContext uses GraphStore for memory retrieval.
|
||||
- [Context](/reference/context) — AgentContext uses GraphStore for memory retrieval.
|
||||
|
||||
@@ -646,7 +646,7 @@ from semantica.ingest import ingest_file
|
||||
result = ingest_file("source_path", method="my_format")
|
||||
```
|
||||
|
||||
- [Parse](parse) — Parse raw sources into structured text and tables.
|
||||
- [Parse](/reference/parse) — Parse raw sources into structured text and tables.
|
||||
- [Pipeline](pipeline) — Orchestrate ingest as the first pipeline step.
|
||||
- [Snowflake Integration](../integrations/snowflake) — Snowflake-specific setup and authentication guide.
|
||||
- [Databricks Integration](../integrations/databricks) — Databricks Unity Catalog setup, authentication, and lineage guide.
|
||||
|
||||
@@ -75,10 +75,10 @@ kg = builder.build({"entities": entities, "relationships": relationships})
|
||||
## Temporal Knowledge Graphs (v0.4.0+)
|
||||
|
||||
<Info>
|
||||
Full temporal reference including `BiTemporalFact`, `TemporalReasoningEngine`, Allen interval algebra, and `TemporalNormalizer` is covered in the dedicated [Temporal Intelligence](temporal) page. This section documents the KG-layer temporal API.
|
||||
Full temporal reference including `BiTemporalFact`, `TemporalReasoningEngine`, Allen interval algebra, and `TemporalNormalizer` is covered in the dedicated [Temporal Intelligence](/reference/temporal) page. This section documents the KG-layer temporal API.
|
||||
</Info>
|
||||
|
||||
The temporal stack — see the [Temporal Intelligence](temporal) page for the full reference.
|
||||
The temporal stack — see the [Temporal Intelligence](/reference/temporal) page for the full reference.
|
||||
|
||||
### Building a Temporal Graph
|
||||
|
||||
@@ -264,7 +264,7 @@ versioner.verify_checksum(past_kg)
|
||||
```
|
||||
|
||||
<Tip>
|
||||
See the [Temporal Intelligence](temporal) reference for the full class API, domain examples (personnel changes, policy evolution, financial timelines), and configuration options.
|
||||
See the [Temporal Intelligence](/reference/temporal) reference for the full class API, domain examples (personnel changes, policy evolution, financial timelines), and configuration options.
|
||||
</Tip>
|
||||
|
||||
|
||||
@@ -475,10 +475,10 @@ kg:
|
||||
default_validity: infinite
|
||||
```
|
||||
|
||||
- [Graph Store](graph_store) — Persist graphs in Neo4j, FalkorDB, or Apache AGE.
|
||||
- [Semantic Extract](semantic_extract) — Source of entities and relationships fed to GraphBuilder.
|
||||
- [Graph Store](/reference/graph_store) — Persist graphs in Neo4j, FalkorDB, or Apache AGE.
|
||||
- [Semantic Extract](/reference/semantic_extract) — Source of entities and relationships fed to GraphBuilder.
|
||||
- [Visualization](visualization) — Visualize knowledge graphs interactively.
|
||||
- [Conflicts](conflicts) — Conflict detection and resolution.
|
||||
- [Conflicts](/reference/conflicts) — Conflict detection and resolution.
|
||||
|
||||
### Cookbooks
|
||||
|
||||
|
||||
@@ -439,7 +439,7 @@ extractor = NERExtractor(
|
||||
)
|
||||
```
|
||||
|
||||
- [Semantic Extract](semantic_extract) — Use LLMs for NER and relation extraction.
|
||||
- [Semantic Extract](/reference/semantic_extract) — Use LLMs for NER and relation extraction.
|
||||
- [Agno Integration](../integrations/agno) — LLM providers in Agno multi-agent teams.
|
||||
- [Reasoning](reasoning) — LLM-backed deductive and abductive reasoning.
|
||||
- [Context](context) — GraphRAG uses LLMs for reasoning over knowledge graphs.
|
||||
- [Context](/reference/context) — GraphRAG uses LLMs for reasoning over knowledge graphs.
|
||||
|
||||
@@ -6,7 +6,7 @@ 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**:
|
||||
|
||||
- 12 MCP tools exposed: extract entities, query graph, record decisions, run reasoning, export results
|
||||
- 15 MCP tools exposed: extract entities, query graph, record decisions, run reasoning, export results
|
||||
- No Python code required after launch: configure once, use from any MCP-aware client
|
||||
- Compatible with Claude Desktop, Windsurf, Cline, Continue, VS Code, Roo Code, Cursor
|
||||
|
||||
@@ -40,12 +40,12 @@ python -m semantica.mcp_server
|
||||
|
||||
## What You Get
|
||||
|
||||
- **12 MCP Tools** — Extract entities, extract relations, record decisions, query decisions, find precedents, trace causal chains, add entities, add relationships, run analytics, summarise graph, run reasoning, export graph.
|
||||
- **15 MCP Tools** — Extract entities, extract relations, record decisions, query decisions, find precedents, trace causal chains, add entities, add relationships, run analytics, summarise graph, run reasoning, export graph, query the live graph, update nodes, archive nodes.
|
||||
- **3 Readable Resources** — Live graph JSON (`semantica://graph/summary`), decision list, and schema/version info: readable by any MCP client.
|
||||
- **Zero Infrastructure** — Runs over stdio: no server, no port, no Docker required. One config block to activate in any MCP client.
|
||||
- **Persistent Graphs** — Point `SEMANTICA_KG_PATH` at a saved graph file to reload it automatically on every server startup.
|
||||
- **Decision Intelligence** — Record decisions, find precedents via hybrid similarity search, and trace causal chains across agent runs.
|
||||
- **REST Alternative** — The [Explorer](explorer) module offers a full HTTP API and browser dashboard if you prefer programmatic access.
|
||||
- **REST Alternative** — The [Explorer](/reference/explorer) module offers a full HTTP API and browser dashboard if you prefer programmatic access.
|
||||
|
||||
## Installation
|
||||
|
||||
@@ -159,7 +159,7 @@ The MCP server is included in the base install: no extras required.
|
||||
|
||||
## Tools
|
||||
|
||||
The MCP server exposes 12 tools that any connected AI assistant can call:
|
||||
The MCP server exposes 15 tools that any connected AI assistant can call:
|
||||
|
||||
| Tool | Category | Description |
|
||||
| :---- | :-------- | :----------- |
|
||||
@@ -173,6 +173,9 @@ The MCP server exposes 12 tools that any connected AI assistant can call:
|
||||
| `add_relationship` | Graph Operations | Add a directed edge between two nodes |
|
||||
| `get_graph_summary` | Graph Operations | Node count, decision count, graph status |
|
||||
| `get_graph_analytics` | Graph Operations | PageRank centrality and community detection |
|
||||
| `query_graph` | Graph Operations | Fetch a node, traverse its neighbours, or keyword-search nodes |
|
||||
| `update_node` | Graph Operations | Merge properties onto a node and persist to `SEMANTICA_KG_PATH` |
|
||||
| `delete_node` | Graph Operations | Soft-delete (archive) a node and persist to `SEMANTICA_KG_PATH` |
|
||||
| `run_reasoning` | Reasoning | Forward-chain IF/THEN rules over facts |
|
||||
| `export_graph` | Reasoning & Export | Serialise the graph (`turtle`/`ttl`: RDF Turtle aliases, `nt`, `xml`, `json-ld`, `json`) |
|
||||
|
||||
@@ -386,6 +389,51 @@ Takes no input parameters.
|
||||
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="query_graph" icon="magnifying-glass">
|
||||
|
||||
Read the live graph in one of three modes, set by `mode`:
|
||||
|
||||
- `node` — return a single node by `node_id`.
|
||||
- `neighbors` (default) — traverse outward and inward from `node_id` up to `depth` hops (clamped to 1-5, default 1). Optional `relationship_types` filters edge types; optional `limit` caps results.
|
||||
- `search` — keyword match `query` against each node's id and content. Optional `node_type` restricts the scan; `limit` defaults to 50.
|
||||
|
||||
**Input:**
|
||||
|
||||
```json
|
||||
{ "mode": "neighbors", "node_id": "apple_inc", "depth": 2 }
|
||||
```
|
||||
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="update_node" icon="pen">
|
||||
|
||||
Merge a set of properties onto an existing node. The change is applied in memory and, when `SEMANTICA_KG_PATH` is set, written back to that file so it survives a restart. Returns `persisted: false` when no path is configured.
|
||||
|
||||
**Input:**
|
||||
|
||||
```json
|
||||
{
|
||||
"node_id": "task_42",
|
||||
"properties": { "status": "done", "note": "shipped in v0.6.7" }
|
||||
}
|
||||
```
|
||||
|
||||
`node_id` and a non-empty `properties` object are required. Updating a missing node returns an error.
|
||||
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="delete_node" icon="box-archive">
|
||||
|
||||
Soft-delete a node: it stays in the graph for history but is marked `status: "archived"`. Persists to `SEMANTICA_KG_PATH` when configured.
|
||||
|
||||
**Input:**
|
||||
|
||||
```json
|
||||
{ "node_id": "task_42" }
|
||||
```
|
||||
|
||||
</Accordion>
|
||||
|
||||
</AccordionGroup>
|
||||
|
||||
### Reasoning
|
||||
@@ -445,7 +493,7 @@ The MCP server exposes three readable resources:
|
||||
| `semantica://decisions/list` | All recorded decisions (up to 50) |
|
||||
| `semantica://schema/info` | Server version and available tools |
|
||||
|
||||
- [Context](context) — The ContextGraph that the MCP server operates on.
|
||||
- [Semantic Extract](semantic_extract) — NER and relation extraction powering the MCP tools.
|
||||
- [Context](/reference/context) — The ContextGraph that the MCP server operates on.
|
||||
- [Semantic Extract](/reference/semantic_extract) — NER and relation extraction powering the MCP tools.
|
||||
- [Reasoning](reasoning) — Forward-chaining engine behind run_reasoning.
|
||||
- [Agno Integration](../integrations/agno) — Use Semantica inside Agno multi-agent teams.
|
||||
|
||||
@@ -584,7 +584,7 @@ normalized = normalize_text("Apple Inc.", method="expand_suffixes")
|
||||
# → "Apple Incorporated"
|
||||
```
|
||||
|
||||
- [Parse](parse) — Parse documents before normalization.
|
||||
- [Split](split) — Chunk normalized text for embedding.
|
||||
- [Parse](/reference/parse) — Parse documents before normalization.
|
||||
- [Split](/reference/split) — Chunk normalized text for embedding.
|
||||
- [Deduplication](deduplication) — Resolve duplicate entities after normalization.
|
||||
- [Pipeline](pipeline) — Include normalization as a named pipeline step.
|
||||
|
||||
@@ -287,6 +287,6 @@ ontology_data = ingest_ontology("schema.jsonld") # JSON-LD
|
||||
</Note>
|
||||
|
||||
- [Reasoning](reasoning) — Apply inference rules over ontology axioms.
|
||||
- [Knowledge Graph](kg) — The graph being modeled by the ontology.
|
||||
- [Knowledge Graph](/reference/kg) — The graph being modeled by the ontology.
|
||||
- [Export](export) — Export ontologies as RDF, OWL, or JSON-LD.
|
||||
- [Conflicts](conflicts) — Detect ontology constraint violations.
|
||||
- [Conflicts](/reference/conflicts) — Detect ontology constraint violations.
|
||||
|
||||
@@ -298,6 +298,6 @@ for source in sources:
|
||||
</Note>
|
||||
|
||||
- [Ingest](ingest) — Load files before parsing.
|
||||
- [Split](split) — Chunk parsed text for embedding and extraction.
|
||||
- [Split](/reference/split) — Chunk parsed text for embedding and extraction.
|
||||
- [Docling Integration](../integrations/docling) — Full Docling integration setup guide.
|
||||
- [Semantic Extract](semantic_extract) — Extract entities and relations from parsed text.
|
||||
- [Semantic Extract](/reference/semantic_extract) — Extract entities and relations from parsed text.
|
||||
|
||||
@@ -497,7 +497,7 @@ result = engine.execute_pipeline(
|
||||
|
||||
## SPARQL CONSTRUCT Template Steps
|
||||
|
||||
Use the `"construct_template"` step type to render and execute a [SPARQL CONSTRUCT template](triplet_store#sparql-construct-templates) as part of a pipeline. `store_backend` and `construct_template_registry` are execution-time resources, not step config — pass them to `execute_pipeline()`, the same way `delta_mode` steps receive `version_manager` and `triplet_store`:
|
||||
Use the `"construct_template"` step type to render and execute a [SPARQL CONSTRUCT template](/reference/triplet_store#sparql-construct-templates) as part of a pipeline. `store_backend` and `construct_template_registry` are execution-time resources, not step config — pass them to `execute_pipeline()`, the same way `delta_mode` steps receive `version_manager` and `triplet_store`:
|
||||
|
||||
```python
|
||||
from semantica.pipeline import PipelineBuilder, ExecutionEngine
|
||||
@@ -589,6 +589,6 @@ StepStatus.SKIPPED # Skipped due to FailureHandler "skip" strategy
|
||||
</AccordionGroup>
|
||||
|
||||
- [Ingest](ingest) — First step in most pipelines.
|
||||
- [Semantic Extract](semantic_extract) — Core extraction step.
|
||||
- [Knowledge Graph](kg) — Graph construction step.
|
||||
- [Semantic Extract](/reference/semantic_extract) — Core extraction step.
|
||||
- [Knowledge Graph](/reference/kg) — Graph construction step.
|
||||
- [Export](export) — Final output step.
|
||||
|
||||
@@ -522,7 +522,7 @@ Provenance tracking in Semantica produces the following audit artifacts:
|
||||
`ProvenanceManager` does not include built-in Turtle or JSON-LD serialization. Use `entry.to_dict()` and `get_lineage()` to retrieve provenance data, then serialize with your preferred RDF library if W3C PROV-O RDF output is required.
|
||||
</Note>
|
||||
|
||||
- [Change Management](change_management) — Version control and snapshot audit trails.
|
||||
- [Change Management](/reference/change_management) — Version control and snapshot audit trails.
|
||||
- [Ingest](ingest) — Provenance begins at the ingestion stage.
|
||||
- [Export](export) — Include provenance metadata in RDF exports.
|
||||
- [Context](context) — Decision provenance via AgentContext.
|
||||
- [Context](/reference/context) — Decision provenance via AgentContext.
|
||||
|
||||
@@ -482,7 +482,7 @@ step.confidence # float
|
||||
`GraphReasoner` requires a configured LLM provider. If the provider fails to initialize, `reason()` returns an error string instead of raising. Check `reasoner.provider is not None` before calling if you need to surface failures explicitly.
|
||||
</Warning>
|
||||
|
||||
- [Knowledge Graph](kg) — The knowledge graph being reasoned over.
|
||||
- [Knowledge Graph](/reference/kg) — The knowledge graph being reasoned over.
|
||||
- [Ontology](ontology) — Ontology axioms and SHACL constraints for logical reasoning.
|
||||
- [Triplet Store](triplet_store) — RDF backend for SPARQL-based reasoning.
|
||||
- [Context](context) — Reasoning integrated into agent decision intelligence.
|
||||
- [Triplet Store](/reference/triplet_store) — RDF backend for SPARQL-based reasoning.
|
||||
- [Context](/reference/context) — Reasoning integrated into agent decision intelligence.
|
||||
|
||||
@@ -322,6 +322,6 @@ export SEMANTICA_SEED_MERGE_STRATEGY=seed_first
|
||||
</Tip>
|
||||
|
||||
- [Ingest](ingest) — Load unstructured data alongside seed data.
|
||||
- [Knowledge Graph](kg) — The target graph that seed data populates.
|
||||
- [Knowledge Graph](/reference/kg) — The target graph that seed data populates.
|
||||
- [Deduplication](deduplication) — Handle duplicates during seed-extracted merge.
|
||||
- [Pipeline](pipeline) — Incorporate seed loading as a named pipeline step.
|
||||
|
||||
@@ -410,7 +410,7 @@ triplets = trip.extract(text)
|
||||
| `ml` | Fast | Free | High | Limited |
|
||||
| `llm` | Medium | API cost | Highest | Yes (schema) |
|
||||
|
||||
- [LLM Providers](llms) — Configure which LLM is used for extraction.
|
||||
- [Knowledge Graph](kg) — Build graphs from extracted entities and relationships.
|
||||
- [Parse Module](parse) — Parse documents before extraction.
|
||||
- [LLM Providers](/reference/llms) — Configure which LLM is used for extraction.
|
||||
- [Knowledge Graph](/reference/kg) — Build graphs from extracted entities and relationships.
|
||||
- [Parse Module](/reference/parse) — Parse documents before extraction.
|
||||
- [Deduplication](deduplication) — Resolve duplicate entities after extraction.
|
||||
|
||||
@@ -373,7 +373,7 @@ for chunk in chunks:
|
||||
|
||||
For the full pipeline orchestration API, see the [Pipeline reference](pipeline).
|
||||
|
||||
- [Parse](parse) — Parse documents before chunking: produces sections and metadata.
|
||||
- [Embeddings](embeddings) — Embed chunks for vector search and semantic chunking.
|
||||
- [Semantic Extract](semantic_extract) — Extract entities and relations from individual chunks.
|
||||
- [Parse](/reference/parse) — Parse documents before chunking: produces sections and metadata.
|
||||
- [Embeddings](/reference/embeddings) — Embed chunks for vector search and semantic chunking.
|
||||
- [Semantic Extract](/reference/semantic_extract) — Extract entities and relations from individual chunks.
|
||||
- [Pipeline](pipeline) — Integrate splitting as a named pipeline step.
|
||||
|
||||
@@ -874,8 +874,8 @@ kg:
|
||||
engine: allen # allen | point_in_time_only
|
||||
```
|
||||
|
||||
- [Knowledge Graph Module](kg) — Core graph construction, `GraphBuilder`, analytics.
|
||||
- [Context Module](context) — Decision temporal windows and `find_active_nodes()`.
|
||||
- [Knowledge Graph Module](/reference/kg) — Core graph construction, `GraphBuilder`, analytics.
|
||||
- [Context Module](/reference/context) — Decision temporal windows and `find_active_nodes()`.
|
||||
- [Provenance](provenance) — W3C PROV-O lineage stamped alongside temporal metadata.
|
||||
- [Export](export) — OWL, Turtle, JSON-LD, and Parquet export with temporal annotations.
|
||||
|
||||
|
||||
@@ -564,4 +564,4 @@ for row in result.bindings:
|
||||
- [Export](export) — Export knowledge graphs to RDF formats.
|
||||
- [Ontology](ontology) — Load OWL ontologies and store as RDF triples.
|
||||
- [Reasoning](reasoning) — SPARQL-based property chain inference.
|
||||
- [Graph Store](graph_store) — Property graph alternative for Cypher queries.
|
||||
- [Graph Store](/reference/graph_store) — Property graph alternative for Cypher queries.
|
||||
|
||||
@@ -222,5 +222,5 @@ from semantica.utils import read_json_file
|
||||
config = read_json_file("config.json")
|
||||
```
|
||||
|
||||
- [Core](core) — Framework orchestration that uses Utils internally.
|
||||
- [Core](/reference/core) — Framework orchestration that uses Utils internally.
|
||||
- [Pipeline](pipeline) — Uses ProgressTracker for per-step tracking.
|
||||
|
||||
@@ -588,7 +588,7 @@ store.create_index(index_type="pq", metric="L2", m=8)
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
- [Embeddings](embeddings) — Generate the vectors stored here.
|
||||
- [Context](context) — AgentContext uses VectorStore for memory retrieval.
|
||||
- [Split](split) — Chunk documents before embedding and storing.
|
||||
- [Embeddings](/reference/embeddings) — Generate the vectors stored here.
|
||||
- [Context](/reference/context) — AgentContext uses VectorStore for memory retrieval.
|
||||
- [Split](/reference/split) — Chunk documents before embedding and storing.
|
||||
- [Ingest](ingest) — Ingest documents before embedding and storing.
|
||||
|
||||
@@ -288,9 +288,9 @@ For a full browser-based UI with search, path finding, and the Ontology Hub, lau
|
||||
semantica-explorer --graph my_graph.json
|
||||
```
|
||||
|
||||
See the [Explorer reference](explorer) for the full feature set and REST API.
|
||||
See the [Explorer reference](/reference/explorer) for the full feature set and REST API.
|
||||
|
||||
- [Knowledge Graph](kg) — The graph being visualized.
|
||||
- [Knowledge Graph](/reference/kg) — The graph being visualized.
|
||||
- [Ontology](ontology) — Visualize ontology class structure.
|
||||
- [Embeddings](embeddings) — Generate the embeddings visualized here.
|
||||
- [Explorer](explorer) — Full interactive Knowledge Explorer UI.
|
||||
- [Embeddings](/reference/embeddings) — Generate the embeddings visualized here.
|
||||
- [Explorer](/reference/explorer) — Full interactive Knowledge Explorer UI.
|
||||
|
||||
+33
-7
@@ -8,8 +8,9 @@ Connects Claude Code, Cursor, Windsurf, Cline, Continue, VS Code (GitHub Copilot
|
||||
## Quick start
|
||||
|
||||
```bash
|
||||
# From the repo root
|
||||
pip install -e ".[mcp]"
|
||||
# From the repo root — no extra install flag needed; the root mcp/ package is
|
||||
# part of the repository and does not require an external MCP SDK.
|
||||
pip install -e .
|
||||
|
||||
# Test the server (type a JSON-RPC request, press Enter)
|
||||
python -m mcp
|
||||
@@ -89,7 +90,14 @@ python -m mcp [--debug]
|
||||
|
||||
## Per-tool configuration
|
||||
|
||||
### Claude Code (`~/.claude/settings.json`)
|
||||
### Claude Code (`~/.claude.json` or `.mcp.json`)
|
||||
|
||||
Claude Code supports two MCP configuration scopes:
|
||||
|
||||
- **User scope** — `~/.claude.json` applies across all projects for your user account.
|
||||
- **Project scope** — `.mcp.json` in your project root applies only to that project.
|
||||
|
||||
Both files use the same `mcpServers` structure:
|
||||
|
||||
```json
|
||||
{
|
||||
@@ -97,15 +105,33 @@ python -m mcp [--debug]
|
||||
"semantica": {
|
||||
"command": "python",
|
||||
"args": ["-m", "mcp"],
|
||||
"cwd": "/path/to/semantica"
|
||||
"env": {
|
||||
"PYTHONPATH": "/path/to/semantica"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Or use the plugin bundle:
|
||||
> **Why `PYTHONPATH`?** The root `mcp/` package is intentionally not included in
|
||||
> the installed wheel, so `python -m mcp` only works when the repository is on
|
||||
> Python's import path. Setting `PYTHONPATH` here ensures this works regardless
|
||||
> of the working directory Claude uses when it launches the server.
|
||||
|
||||
Or add it via the CLI (user scope):
|
||||
|
||||
```bash
|
||||
claude mcp add semantica python -m mcp --cwd /path/to/semantica
|
||||
claude mcp add --scope user semantica \
|
||||
-e PYTHONPATH=/path/to/semantica \
|
||||
-- python -m mcp
|
||||
```
|
||||
|
||||
Or for project scope (omit `--scope user`):
|
||||
|
||||
```bash
|
||||
claude mcp add semantica \
|
||||
-e PYTHONPATH=/path/to/semantica \
|
||||
-- python -m mcp
|
||||
```
|
||||
|
||||
---
|
||||
@@ -216,7 +242,7 @@ Add to your Q Developer MCP config:
|
||||
|
||||
| Variable | Default | Description |
|
||||
|---|---|---|
|
||||
| `SEMANTICA_KG_PATH` | *(in-memory)* | Path to persist/load the graph (JSON file) |
|
||||
| `SEMANTICA_KG_PATH` | *(in-memory only)* | Path to a JSON file used to **load** the graph on startup and **persist** mutations (record decisions, add entities/relationships) back to disk after each change. When unset the graph lives in memory only and is lost when the server exits. |
|
||||
|
||||
---
|
||||
|
||||
|
||||
+35
-7
@@ -16,6 +16,13 @@ log = logging.getLogger("semantica.mcp.session")
|
||||
|
||||
_graph: Optional[Any] = None
|
||||
|
||||
# Tracks whether the last graph initialisation successfully loaded the
|
||||
# configured SEMANTICA_KG_PATH file. When True (or no path was configured)
|
||||
# mutation handlers are allowed to save. When False an existing file failed
|
||||
# to load; saving would overwrite the original data with an empty graph, so
|
||||
# persistence is blocked until the process is restarted with a readable file.
|
||||
_load_ok: bool = True
|
||||
|
||||
|
||||
def get_graph() -> Any:
|
||||
"""
|
||||
@@ -24,24 +31,45 @@ def get_graph() -> Any:
|
||||
The graph is created with advanced_analytics=True so all centrality,
|
||||
community-detection, and embedding features are available.
|
||||
"""
|
||||
global _graph
|
||||
global _graph, _load_ok
|
||||
if _graph is None:
|
||||
from semantica.context import ContextGraph
|
||||
|
||||
_graph = ContextGraph(advanced_analytics=True)
|
||||
_load_ok = True # default: safe to persist
|
||||
|
||||
kg_path = os.environ.get("SEMANTICA_KG_PATH", "").strip()
|
||||
if kg_path and os.path.exists(kg_path):
|
||||
try:
|
||||
_graph.load(kg_path)
|
||||
log.info("Graph loaded from %s", kg_path)
|
||||
except Exception as exc:
|
||||
log.warning("Could not load graph from %s: %s", kg_path, exc)
|
||||
# Only attempt to load if the file has content. An empty file
|
||||
# means the path was just created (e.g. a fresh tempfile) and
|
||||
# should be treated as "start with empty graph" rather than a
|
||||
# corrupt-file failure.
|
||||
if os.path.getsize(kg_path) > 0:
|
||||
try:
|
||||
_graph.load_from_file(kg_path)
|
||||
log.info("Graph loaded from %s", kg_path)
|
||||
except Exception as exc:
|
||||
log.warning(
|
||||
"Could not load graph from %s: %s — persistence disabled "
|
||||
"to protect existing data; restart the server to retry.",
|
||||
kg_path, exc,
|
||||
)
|
||||
_load_ok = False # do not overwrite the original file
|
||||
|
||||
return _graph
|
||||
|
||||
|
||||
def is_persistence_safe() -> bool:
|
||||
"""Return True when it is safe to write mutations back to SEMANTICA_KG_PATH.
|
||||
|
||||
Returns False after a failed load so that mutation handlers do not
|
||||
overwrite the original (possibly intact) file with a fresh empty graph.
|
||||
"""
|
||||
return _load_ok
|
||||
|
||||
|
||||
def reset_graph() -> None:
|
||||
"""Reset the singleton (mainly useful in tests)."""
|
||||
global _graph
|
||||
global _graph, _load_ok
|
||||
_graph = None
|
||||
_load_ok = True
|
||||
|
||||
+35
-1
@@ -5,6 +5,7 @@ Decision intelligence tools — record, query, precedents, causal chain, impact.
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import os
|
||||
|
||||
from mcp.schemas import (
|
||||
ANALYZE_DECISION_IMPACT,
|
||||
@@ -13,7 +14,7 @@ from mcp.schemas import (
|
||||
QUERY_DECISIONS,
|
||||
RECORD_DECISION,
|
||||
)
|
||||
from mcp.session import get_graph
|
||||
from mcp.session import get_graph, is_persistence_safe
|
||||
|
||||
log = logging.getLogger("semantica.mcp.tools.decisions")
|
||||
|
||||
@@ -37,6 +38,39 @@ def handle_record_decision(args: dict) -> dict:
|
||||
valid_from=args.get("valid_from"),
|
||||
valid_until=args.get("valid_until"),
|
||||
)
|
||||
# Persist back to disk so the decision survives server restarts.
|
||||
# Skip when the initial load failed to avoid overwriting original data.
|
||||
kg_path = os.environ.get("SEMANTICA_KG_PATH", "").strip()
|
||||
if kg_path:
|
||||
if not is_persistence_safe():
|
||||
# Roll back the in-memory mutation so the client-visible state
|
||||
# matches the persisted state (neither is saved).
|
||||
if hasattr(graph, "_decisions") and decision_id in graph._decisions:
|
||||
del graph._decisions[decision_id]
|
||||
if hasattr(graph, "_decision_index"):
|
||||
cat = args.get("category", "")
|
||||
if cat in graph._decision_index:
|
||||
graph._decision_index[cat].discard(decision_id)
|
||||
return {
|
||||
"error": (
|
||||
"Persistence blocked: the configured SEMANTICA_KG_PATH "
|
||||
"could not be loaded at startup. Restart the server with "
|
||||
"a readable graph file to re-enable persistence."
|
||||
)
|
||||
}
|
||||
try:
|
||||
graph.save_to_file(kg_path)
|
||||
except Exception as save_exc:
|
||||
# Atomic write failed. Roll back the in-memory mutation so the
|
||||
# client-visible and persisted states remain consistent.
|
||||
if hasattr(graph, "_decisions") and decision_id in graph._decisions:
|
||||
del graph._decisions[decision_id]
|
||||
if hasattr(graph, "_decision_index"):
|
||||
cat = args.get("category", "")
|
||||
if cat in graph._decision_index:
|
||||
graph._decision_index[cat].discard(decision_id)
|
||||
log.exception("save_to_file failed after record_decision; mutation rolled back")
|
||||
return {"error": f"Mutation rolled back: could not persist graph: {save_exc}"}
|
||||
return {
|
||||
"decision_id": decision_id,
|
||||
"status": "recorded",
|
||||
|
||||
+70
-1
@@ -5,9 +5,10 @@ Graph tools — add entities/relationships, search, analytics, summary.
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import os
|
||||
|
||||
from mcp.schemas import ADD_ENTITY, ADD_RELATIONSHIP, EMPTY, GET_ANALYTICS, SEARCH_GRAPH
|
||||
from mcp.session import get_graph
|
||||
from mcp.session import get_graph, is_persistence_safe
|
||||
|
||||
log = logging.getLogger("semantica.mcp.tools.graph")
|
||||
|
||||
@@ -25,6 +26,35 @@ def handle_add_entity(args: dict) -> dict:
|
||||
node_type=args.get("type", "Entity"),
|
||||
metadata=args.get("metadata", {}),
|
||||
)
|
||||
# Persist back to disk so the entity survives server restarts.
|
||||
# Skip when the initial load failed to avoid overwriting original data.
|
||||
kg_path = os.environ.get("SEMANTICA_KG_PATH", "").strip()
|
||||
if kg_path:
|
||||
if not is_persistence_safe():
|
||||
# Roll back: remove the node we just added.
|
||||
try:
|
||||
with graph._lock:
|
||||
graph._drop_node_from_indexes(node_id)
|
||||
except Exception:
|
||||
pass
|
||||
return {
|
||||
"error": (
|
||||
"Persistence blocked: the configured SEMANTICA_KG_PATH "
|
||||
"could not be loaded at startup. Restart the server with "
|
||||
"a readable graph file to re-enable persistence."
|
||||
)
|
||||
}
|
||||
try:
|
||||
graph.save_to_file(kg_path)
|
||||
except Exception as save_exc:
|
||||
# Roll back: remove the node so in-memory and persisted state agree.
|
||||
try:
|
||||
with graph._lock:
|
||||
graph._drop_node_from_indexes(node_id)
|
||||
except Exception:
|
||||
pass
|
||||
log.exception("save_to_file failed after add_entity; mutation rolled back")
|
||||
return {"error": f"Mutation rolled back: could not persist graph: {save_exc}"}
|
||||
return {"status": "added", "id": node_id, "type": args.get("type", "Entity")}
|
||||
except Exception as exc:
|
||||
log.exception("add_entity failed")
|
||||
@@ -46,6 +76,45 @@ def handle_add_relationship(args: dict) -> dict:
|
||||
edge_type=rel_type,
|
||||
metadata=args.get("metadata", {}),
|
||||
)
|
||||
# Persist back to disk so the relationship survives server restarts.
|
||||
# Skip when the initial load failed to avoid overwriting original data.
|
||||
kg_path = os.environ.get("SEMANTICA_KG_PATH", "").strip()
|
||||
if kg_path:
|
||||
if not is_persistence_safe():
|
||||
# Roll back: remove the edge we just added (last matching edge).
|
||||
try:
|
||||
with graph._lock:
|
||||
for edge in reversed(list(graph.edges)):
|
||||
if (edge.source_id == source
|
||||
and edge.target_id == target
|
||||
and edge.edge_type == rel_type):
|
||||
graph._drop_edge_from_indexes(edge)
|
||||
break
|
||||
except Exception:
|
||||
pass
|
||||
return {
|
||||
"error": (
|
||||
"Persistence blocked: the configured SEMANTICA_KG_PATH "
|
||||
"could not be loaded at startup. Restart the server with "
|
||||
"a readable graph file to re-enable persistence."
|
||||
)
|
||||
}
|
||||
try:
|
||||
graph.save_to_file(kg_path)
|
||||
except Exception as save_exc:
|
||||
# Roll back: remove the edge so in-memory and persisted state agree.
|
||||
try:
|
||||
with graph._lock:
|
||||
for edge in reversed(list(graph.edges)):
|
||||
if (edge.source_id == source
|
||||
and edge.target_id == target
|
||||
and edge.edge_type == rel_type):
|
||||
graph._drop_edge_from_indexes(edge)
|
||||
break
|
||||
except Exception:
|
||||
pass
|
||||
log.exception("save_to_file failed after add_relationship; mutation rolled back")
|
||||
return {"error": f"Mutation rolled back: could not persist graph: {save_exc}"}
|
||||
return {"status": "added", "source": source, "target": target, "type": rel_type}
|
||||
except Exception as exc:
|
||||
log.exception("add_relationship failed")
|
||||
|
||||
@@ -588,16 +588,15 @@ class AgentMemory:
|
||||
return False
|
||||
|
||||
# Remove from vector store unless a caller is staging an atomic local update.
|
||||
if not skip_vector:
|
||||
if self.vector_store:
|
||||
try:
|
||||
vector_ids = list(self._vector_ids.get(memory_id, [])) or [
|
||||
memory_id
|
||||
]
|
||||
self._delete_vector_ids(vector_ids)
|
||||
except Exception as e:
|
||||
self.logger.warning(f"Failed to delete from vector store: {e}")
|
||||
self._vector_ids.pop(memory_id, None)
|
||||
if not skip_vector and self.vector_store:
|
||||
try:
|
||||
vector_ids = list(self._vector_ids.get(memory_id, [])) or [memory_id]
|
||||
self._delete_vector_ids(vector_ids)
|
||||
except Exception as e:
|
||||
self.logger.warning(f"Failed to delete from vector store: {e}")
|
||||
# Bookkeeping runs unconditionally: a skip_vector delete still removes the
|
||||
# item, so leaving its tracked ids behind would orphan them permanently.
|
||||
self._vector_ids.pop(memory_id, None)
|
||||
|
||||
memory_item = self.memory_items[memory_id]
|
||||
|
||||
@@ -1588,12 +1587,16 @@ class AgentMemory:
|
||||
memory_ids.append(memory_id)
|
||||
return memory_ids
|
||||
|
||||
def batch_delete(self, memory_ids: List[str]) -> int:
|
||||
def batch_delete(self, memory_ids: List[str], *, skip_vector: bool = False) -> int:
|
||||
"""
|
||||
Batch delete.
|
||||
|
||||
Args:
|
||||
memory_ids: List of memory IDs to delete
|
||||
skip_vector: If True, skip each item's own vector-store cascade
|
||||
(see ``delete_memory``). A caller that is already erasing these
|
||||
ids' vectors itself passes this to avoid a redundant,
|
||||
best-effort delete against the vector store.
|
||||
|
||||
Returns:
|
||||
Number of memories deleted
|
||||
@@ -1603,7 +1606,7 @@ class AgentMemory:
|
||||
"""
|
||||
deleted = 0
|
||||
for memory_id in memory_ids:
|
||||
if self.delete_memory(memory_id):
|
||||
if self.delete_memory(memory_id, skip_vector=skip_vector):
|
||||
deleted += 1
|
||||
return deleted
|
||||
|
||||
|
||||
@@ -1203,8 +1203,30 @@ class ContextGraph:
|
||||
"links": links_data,
|
||||
}
|
||||
|
||||
with open(path, "w", encoding="utf-8") as f:
|
||||
json.dump(data, f, indent=2, ensure_ascii=False)
|
||||
# Write atomically: serialize to a sibling temp file then replace the
|
||||
# destination in one OS-level rename. This guarantees the destination
|
||||
# is either the old contents or the new contents — never a partial write
|
||||
# — so a crash or disk-full error during json.dump cannot corrupt the
|
||||
# sole persisted copy of the graph.
|
||||
dest = Path(path)
|
||||
dest.parent.mkdir(parents=True, exist_ok=True)
|
||||
fd, tmp_path = tempfile.mkstemp(
|
||||
dir=dest.parent, prefix=".kg_tmp_", suffix=".json"
|
||||
)
|
||||
try:
|
||||
with os.fdopen(fd, "w", encoding="utf-8") as f:
|
||||
json.dump(data, f, indent=2, ensure_ascii=False)
|
||||
f.flush()
|
||||
os.fsync(f.fileno())
|
||||
os.replace(tmp_path, dest)
|
||||
except Exception:
|
||||
# Clean up the temp file on any failure so we don't litter the
|
||||
# directory with partial writes.
|
||||
try:
|
||||
os.unlink(tmp_path)
|
||||
except OSError:
|
||||
pass
|
||||
raise
|
||||
|
||||
self.logger.info(f"Saved context graph to {path}")
|
||||
|
||||
|
||||
@@ -34,6 +34,7 @@ Example:
|
||||
'unsupported'
|
||||
"""
|
||||
|
||||
import inspect
|
||||
from dataclasses import dataclass, field
|
||||
from datetime import datetime, timezone
|
||||
from typing import Any, Dict, Iterable, List, Optional, Sequence, Tuple, Union
|
||||
@@ -150,6 +151,24 @@ class ErasureCoordinator:
|
||||
more than actually occurred. Erasing the graph last means a partial
|
||||
failure leaves the node present and the receipt incomplete, which is
|
||||
recoverable and honest.
|
||||
|
||||
Note:
|
||||
An explicit ``vector_store=False`` also suppresses ``AgentMemory``'s
|
||||
own internal vector cascade, not just the coordinator's leg (#1378).
|
||||
``AgentMemory.delete_memory()`` deletes an item's vectors best-effort:
|
||||
it catches a vector-store failure, logs it, and still returns ``True``,
|
||||
so without this a caller who opted out of the vector leg could still
|
||||
have ``memory.vector_store`` mutated underneath them while the receipt
|
||||
read ``vectors: not_configured``. ``vector_store=False`` is taken to
|
||||
mean "no vector activity at all", so the coordinator passes
|
||||
``skip_vector=True`` through to ``memory.batch_delete()`` in that case,
|
||||
and ``receipt.stores["vectors"]["status"]`` stays ``"not_configured"``
|
||||
honestly -- the caller opted the vector store out entirely, rather than
|
||||
the coordinator having erased it. This only applies when
|
||||
``vector_store=False`` was passed explicitly; when no vector store
|
||||
exists anywhere (no ``memory`` was supplied, or ``memory`` has no
|
||||
``vector_store`` attribute), there is nothing to suppress and
|
||||
``memory.batch_delete()`` is called as before.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
@@ -170,6 +189,12 @@ class ErasureCoordinator:
|
||||
|
||||
self.graph = graph
|
||||
self.memory = memory
|
||||
# Distinct from `self.vector_store is None`: that's also true when no
|
||||
# vector store exists anywhere (no memory, or memory with no
|
||||
# vector_store attribute), where there is nothing to suppress and
|
||||
# forcing skip_vector onto a duck-typed memory would break callers
|
||||
# whose batch_delete() doesn't accept that kwarg.
|
||||
self._vector_leg_disabled = vector_store is False
|
||||
if vector_store is False:
|
||||
self.vector_store: Optional[Any] = None
|
||||
elif vector_store is not None:
|
||||
@@ -424,6 +449,20 @@ class ErasureCoordinator:
|
||||
return {"status": STATUS_NOT_CONFIGURED}
|
||||
|
||||
deleted = 0
|
||||
skip_vector = self._vector_leg_disabled and _accepts_skip_vector(
|
||||
self.memory.batch_delete
|
||||
)
|
||||
if self._vector_leg_disabled and not skip_vector:
|
||||
# The class docstring only requires find_by_entity/batch_delete; a
|
||||
# duck-typed adapter is not required to support skip_vector. Falling
|
||||
# back to the plain call keeps the memory leg working -- the
|
||||
# adapter's own cascade (if it has one) just can't be suppressed.
|
||||
self.logger.warning(
|
||||
"Memory adapter %r has no skip_vector support; its own vector "
|
||||
"cascade (if any) could not be suppressed for %r",
|
||||
type(self.memory).__name__,
|
||||
entity_id,
|
||||
)
|
||||
try:
|
||||
# Sweep in pages until dry rather than passing one large limit:
|
||||
# ``find_by_entity`` has historically defaulted to ``limit=10`` and
|
||||
@@ -454,7 +493,10 @@ class ErasureCoordinator:
|
||||
"detail": "memory items carry no 'memory_id'",
|
||||
}
|
||||
|
||||
removed = self.memory.batch_delete(memory_ids)
|
||||
if skip_vector:
|
||||
removed = self.memory.batch_delete(memory_ids, skip_vector=True)
|
||||
else:
|
||||
removed = self.memory.batch_delete(memory_ids)
|
||||
deleted += removed
|
||||
if removed == 0:
|
||||
# No progress: another page would return the same items.
|
||||
@@ -564,6 +606,25 @@ def _memory_item_id(item: Any) -> Optional[str]:
|
||||
return str(memory_id) if memory_id else None
|
||||
|
||||
|
||||
def _accepts_skip_vector(batch_delete: Any) -> bool:
|
||||
"""True when ``batch_delete`` takes a ``skip_vector`` keyword.
|
||||
|
||||
``skip_vector`` is an ``AgentMemory``-specific extension, not part of the
|
||||
duck-typed contract the class docstring promises (``find_by_entity`` and
|
||||
``batch_delete`` only). Passing it to an adapter that doesn't accept it
|
||||
would raise ``TypeError`` and fail the whole memory leg, so this is
|
||||
checked before ever passing the kwarg.
|
||||
"""
|
||||
try:
|
||||
signature = inspect.signature(batch_delete)
|
||||
except (TypeError, ValueError):
|
||||
return False
|
||||
for parameter in signature.parameters.values():
|
||||
if parameter.name == "skip_vector" or parameter.kind == inspect.Parameter.VAR_KEYWORD:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
#: Dict keys a backend uses to report whether a delete succeeded, and the
|
||||
#: values that mean it did not. Qdrant returns ``{"status": <UpdateStatus>}``
|
||||
#: and Pinecone ``{"deleted": True}``; neither is a bool, so a bare
|
||||
|
||||
@@ -253,7 +253,7 @@ class GraphAnalyzer:
|
||||
graph,
|
||||
start_time=None,
|
||||
end_time=None,
|
||||
metrics=["node_count", "edge_count", "density", "communities"],
|
||||
metrics=None,
|
||||
interval=None,
|
||||
**options,
|
||||
):
|
||||
@@ -271,6 +271,8 @@ class GraphAnalyzer:
|
||||
Returns:
|
||||
Evolution analysis results with time series data
|
||||
"""
|
||||
if metrics is None:
|
||||
metrics = ["node_count", "edge_count", "density", "communities"]
|
||||
self.logger.info("Analyzing temporal evolution")
|
||||
|
||||
from .temporal_query import TemporalGraphQuery
|
||||
|
||||
@@ -72,19 +72,34 @@ os.environ["SEMANTICA_DISABLE_PROGRESS"] = "1"
|
||||
# ── lazy graph session ──────────────────────────────────────────────────────
|
||||
_graph: Any = None
|
||||
|
||||
# Tracks whether the last _get_graph() call successfully loaded the configured
|
||||
# SEMANTICA_KG_PATH file. When False (load failed) mutation handlers skip
|
||||
# save_to_file to avoid overwriting the original file with an empty graph.
|
||||
_kg_load_ok: bool = True
|
||||
|
||||
|
||||
def _get_graph():
|
||||
global _graph
|
||||
global _graph, _kg_load_ok
|
||||
if _graph is None:
|
||||
from semantica.context import ContextGraph
|
||||
_graph = ContextGraph(advanced_analytics=True)
|
||||
_kg_load_ok = True # default: safe to persist
|
||||
kg_path = os.environ.get("SEMANTICA_KG_PATH")
|
||||
if kg_path and os.path.exists(kg_path):
|
||||
try:
|
||||
_graph.load_from_file(kg_path)
|
||||
log.info("Loaded graph from %s", kg_path)
|
||||
except Exception as exc:
|
||||
log.warning("Could not load graph from %s: %s", kg_path, exc)
|
||||
# Only attempt to load if the file has content. An empty file
|
||||
# means the path was just created (fresh destination) and should
|
||||
# be treated as "start with empty graph" not a corrupt-file failure.
|
||||
if os.path.getsize(kg_path) > 0:
|
||||
try:
|
||||
_graph.load_from_file(kg_path)
|
||||
log.info("Loaded graph from %s", kg_path)
|
||||
except Exception as exc:
|
||||
log.warning(
|
||||
"Could not load graph from %s: %s — persistence disabled "
|
||||
"to protect existing data; restart the server to retry.",
|
||||
kg_path, exc,
|
||||
)
|
||||
_kg_load_ok = False # do not overwrite the original file
|
||||
return _graph
|
||||
|
||||
|
||||
@@ -179,6 +194,35 @@ def _tool_record_decision(args: dict) -> dict:
|
||||
valid_from=args.get("valid_from"),
|
||||
valid_until=args.get("valid_until"),
|
||||
)
|
||||
# Persist back to disk so the decision survives server restarts.
|
||||
kg_path = os.environ.get("SEMANTICA_KG_PATH")
|
||||
if kg_path:
|
||||
if not _kg_load_ok:
|
||||
# Roll back to keep in-memory state consistent with persisted state.
|
||||
if hasattr(graph, "_decisions") and decision_id in graph._decisions:
|
||||
del graph._decisions[decision_id]
|
||||
if hasattr(graph, "_decision_index"):
|
||||
cat = args.get("category", "")
|
||||
if cat in graph._decision_index:
|
||||
graph._decision_index[cat].discard(decision_id)
|
||||
return {
|
||||
"error": (
|
||||
"Persistence blocked: the configured SEMANTICA_KG_PATH "
|
||||
"could not be loaded at startup. Restart the server to retry."
|
||||
)
|
||||
}
|
||||
try:
|
||||
graph.save_to_file(kg_path)
|
||||
except Exception as save_exc:
|
||||
# Atomic write failed. Roll back to keep states consistent.
|
||||
if hasattr(graph, "_decisions") and decision_id in graph._decisions:
|
||||
del graph._decisions[decision_id]
|
||||
if hasattr(graph, "_decision_index"):
|
||||
cat = args.get("category", "")
|
||||
if cat in graph._decision_index:
|
||||
graph._decision_index[cat].discard(decision_id)
|
||||
log.exception("save_to_file failed after record_decision; mutation rolled back")
|
||||
return {"error": f"Mutation rolled back: could not persist graph: {save_exc}"}
|
||||
return {"decision_id": decision_id, "status": "recorded"}
|
||||
|
||||
|
||||
@@ -246,6 +290,31 @@ def _tool_add_entity(args: dict) -> dict:
|
||||
graph = _get_graph()
|
||||
graph.add_node(node_id=node_id, label=label, node_type=node_type,
|
||||
metadata=args.get("metadata", {}))
|
||||
# Persist back to disk so the entity survives server restarts.
|
||||
kg_path = os.environ.get("SEMANTICA_KG_PATH")
|
||||
if kg_path:
|
||||
if not _kg_load_ok:
|
||||
try:
|
||||
with graph._lock:
|
||||
graph._drop_node_from_indexes(node_id)
|
||||
except Exception:
|
||||
pass
|
||||
return {
|
||||
"error": (
|
||||
"Persistence blocked: the configured SEMANTICA_KG_PATH "
|
||||
"could not be loaded at startup. Restart the server to retry."
|
||||
)
|
||||
}
|
||||
try:
|
||||
graph.save_to_file(kg_path)
|
||||
except Exception as save_exc:
|
||||
try:
|
||||
with graph._lock:
|
||||
graph._drop_node_from_indexes(node_id)
|
||||
except Exception:
|
||||
pass
|
||||
log.exception("save_to_file failed after add_entity; mutation rolled back")
|
||||
return {"error": f"Mutation rolled back: could not persist graph: {save_exc}"}
|
||||
return {"status": "added", "id": node_id}
|
||||
|
||||
|
||||
@@ -259,6 +328,41 @@ def _tool_add_relationship(args: dict) -> dict:
|
||||
graph = _get_graph()
|
||||
graph.add_edge(source_id=source, target_id=target, edge_type=rel_type,
|
||||
metadata=args.get("metadata", {}))
|
||||
# Persist back to disk so the relationship survives server restarts.
|
||||
kg_path = os.environ.get("SEMANTICA_KG_PATH")
|
||||
if kg_path:
|
||||
if not _kg_load_ok:
|
||||
try:
|
||||
with graph._lock:
|
||||
for edge in reversed(list(graph.edges)):
|
||||
if (edge.source_id == source
|
||||
and edge.target_id == target
|
||||
and edge.edge_type == rel_type):
|
||||
graph._drop_edge_from_indexes(edge)
|
||||
break
|
||||
except Exception:
|
||||
pass
|
||||
return {
|
||||
"error": (
|
||||
"Persistence blocked: the configured SEMANTICA_KG_PATH "
|
||||
"could not be loaded at startup. Restart the server to retry."
|
||||
)
|
||||
}
|
||||
try:
|
||||
graph.save_to_file(kg_path)
|
||||
except Exception as save_exc:
|
||||
try:
|
||||
with graph._lock:
|
||||
for edge in reversed(list(graph.edges)):
|
||||
if (edge.source_id == source
|
||||
and edge.target_id == target
|
||||
and edge.edge_type == rel_type):
|
||||
graph._drop_edge_from_indexes(edge)
|
||||
break
|
||||
except Exception:
|
||||
pass
|
||||
log.exception("save_to_file failed after add_relationship; mutation rolled back")
|
||||
return {"error": f"Mutation rolled back: could not persist graph: {save_exc}"}
|
||||
return {"status": "added", "source": source, "target": target, "type": rel_type}
|
||||
|
||||
|
||||
|
||||
@@ -375,7 +375,7 @@ class HierarchicalChunker:
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
levels: List[str] = ["section", "paragraph", "sentence"],
|
||||
levels: Optional[List[str]] = None,
|
||||
chunk_sizes: Optional[List[int]] = None,
|
||||
**kwargs,
|
||||
):
|
||||
@@ -387,6 +387,8 @@ class HierarchicalChunker:
|
||||
chunk_sizes: Chunk sizes for each level
|
||||
**kwargs: Additional options
|
||||
"""
|
||||
if levels is None:
|
||||
levels = ["section", "paragraph", "sentence"]
|
||||
self.levels = levels
|
||||
self.chunk_sizes = chunk_sizes or [2000, 1000, 500]
|
||||
self.options = kwargs
|
||||
|
||||
@@ -1402,7 +1402,7 @@ def split_embedding_semantic(
|
||||
|
||||
def split_hierarchical(
|
||||
text: str,
|
||||
levels: List[str] = ["section", "paragraph", "sentence"],
|
||||
levels: Optional[List[str]] = None,
|
||||
chunk_sizes: Optional[List[int]] = None,
|
||||
**kwargs,
|
||||
) -> List[Chunk]:
|
||||
@@ -1420,6 +1420,8 @@ def split_hierarchical(
|
||||
"""
|
||||
if chunk_sizes is None:
|
||||
chunk_sizes = [2000, 1000, 500]
|
||||
if levels is None:
|
||||
levels = ["section", "paragraph", "sentence"]
|
||||
|
||||
# Start with largest level
|
||||
if "section" in levels:
|
||||
|
||||
@@ -299,6 +299,44 @@ class PineconeSearch:
|
||||
)
|
||||
|
||||
|
||||
def _pinecone_listed_ids(response: Any) -> List[str]:
|
||||
"""Extract vector IDs from a list_paginated() response.
|
||||
|
||||
Accepts record objects, bare id strings and dicts, since what listing
|
||||
returns has changed across pinecone SDK major versions.
|
||||
"""
|
||||
records = getattr(response, "vectors", None)
|
||||
if records is None and isinstance(response, dict):
|
||||
records = response.get("vectors")
|
||||
|
||||
ids: List[str] = []
|
||||
for record in records or []:
|
||||
if isinstance(record, str):
|
||||
ids.append(record)
|
||||
elif isinstance(record, dict):
|
||||
if record.get("id") is not None:
|
||||
ids.append(record["id"])
|
||||
else:
|
||||
record_id = getattr(record, "id", None)
|
||||
if record_id is not None:
|
||||
ids.append(record_id)
|
||||
return ids
|
||||
|
||||
|
||||
def _pinecone_next_token(response: Any) -> Optional[str]:
|
||||
"""Return the continuation token, or None when the listing is exhausted."""
|
||||
pagination = getattr(response, "pagination", None)
|
||||
if pagination is None and isinstance(response, dict):
|
||||
pagination = response.get("pagination")
|
||||
if pagination is None:
|
||||
return None
|
||||
|
||||
token = getattr(pagination, "next", None)
|
||||
if token is None and isinstance(pagination, dict):
|
||||
token = pagination.get("next")
|
||||
return token or None
|
||||
|
||||
|
||||
class PineconeStore:
|
||||
"""
|
||||
Pinecone store for vector storage and similarity search.
|
||||
@@ -735,6 +773,91 @@ class PineconeStore:
|
||||
self.logger.warning(f"Failed to filter Pinecone vectors by metadata: {e}")
|
||||
return []
|
||||
|
||||
def iter_all(self, batch_size: int = 500, namespace: str = ""):
|
||||
"""
|
||||
Iterate over every stored vector by listing IDs then fetching them.
|
||||
|
||||
Paginates with an opaque continuation token, which is why this exists
|
||||
instead of scan_vectors(offset, limit): the token for page N cannot be
|
||||
constructed without walking there.
|
||||
|
||||
Needs two calls per page, unlike the other backends, because listing
|
||||
returns IDs only. Both calls are namespace scoped and must agree, and
|
||||
listing covers one namespace rather than the whole index.
|
||||
|
||||
Args:
|
||||
batch_size: IDs to request per list_paginated() call
|
||||
namespace: Namespace to enumerate (default: the default namespace)
|
||||
|
||||
Yields:
|
||||
Result dicts with 'id', 'metadata', and 'vector', in listing order
|
||||
|
||||
Raises:
|
||||
ProcessingError: If the index is not initialized, if the installed
|
||||
SDK does not expose list_paginated(), or if the listing stops
|
||||
advancing.
|
||||
"""
|
||||
if self.index is None or not PINECONE_AVAILABLE:
|
||||
raise ProcessingError(
|
||||
"Index not initialized. Call create_index() or get_index() first."
|
||||
)
|
||||
|
||||
# list_paginated() rather than list(): list() is an auto-paging
|
||||
# iterator in current SDKs but reads as plain id lists in older
|
||||
# examples. Threading the token explicitly is version-agnostic.
|
||||
list_paginated = getattr(self.index.index, "list_paginated", None)
|
||||
if not callable(list_paginated):
|
||||
raise ProcessingError(
|
||||
"This pinecone SDK version does not expose Index.list_paginated(), "
|
||||
"which full enumeration requires."
|
||||
)
|
||||
|
||||
token = None
|
||||
while True:
|
||||
kwargs: Dict[str, Any] = {"limit": batch_size, "namespace": namespace}
|
||||
if token is not None:
|
||||
kwargs["pagination_token"] = token
|
||||
|
||||
response = list_paginated(**kwargs)
|
||||
vector_ids = _pinecone_listed_ids(response)
|
||||
|
||||
# A page listing zero ids is not necessarily exhaustion: Pinecone's
|
||||
# contract is that a scan ends only when there's no pagination
|
||||
# token, and a page can legitimately come back empty while
|
||||
# pagination.next is still set (sparse/filtered namespaces,
|
||||
# eventual-consistency windows on serverless indexes). Skip the
|
||||
# fetch (nothing to hydrate) but still fall through to the token
|
||||
# check below instead of returning early, or a gap like that
|
||||
# silently truncates the scan with no error.
|
||||
if vector_ids:
|
||||
fetched = self.index.fetch_vectors(vector_ids, namespace=namespace)
|
||||
vectors = fetched.get("vectors") or {}
|
||||
|
||||
for vector_id in vector_ids:
|
||||
entry = vectors.get(vector_id)
|
||||
if entry is None:
|
||||
# fetch() omits ids it cannot find: deleted since listing.
|
||||
continue
|
||||
values = entry.get("values")
|
||||
yield {
|
||||
"id": vector_id,
|
||||
"metadata": entry.get("metadata") or {},
|
||||
"vector": np.array(values) if values is not None else None,
|
||||
}
|
||||
|
||||
next_token = _pinecone_next_token(response)
|
||||
if not next_token:
|
||||
return
|
||||
if next_token == token:
|
||||
# Distinct from exhaustion above: a partial scan here would be
|
||||
# indistinguishable from a complete one.
|
||||
raise ProcessingError(
|
||||
"Pinecone returned the same pagination token twice, so the "
|
||||
"listing is not advancing. Refusing to return a truncated "
|
||||
"scan."
|
||||
)
|
||||
token = next_token
|
||||
|
||||
def fetch_vectors(
|
||||
self, vector_ids: List[str], namespace: str = "", **options
|
||||
) -> Dict[str, Any]:
|
||||
|
||||
@@ -1037,5 +1037,195 @@ class TestClearResetsDecisionIndexes(unittest.TestCase):
|
||||
self.assertEqual(g._decisions[did]["category"], "new")
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Part 15: semantica.mcp_server mutation persistence (#1134)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
class TestMCPServerMutationPersistence(unittest.TestCase):
|
||||
"""_tool_record_decision, _tool_add_entity, and _tool_add_relationship must
|
||||
each call save_to_file when SEMANTICA_KG_PATH is configured so mutations
|
||||
survive server restarts.
|
||||
|
||||
Mirrors update_node / delete_node which already had this behaviour from
|
||||
PR #967. These tests extend coverage to the three previously missing tools.
|
||||
"""
|
||||
|
||||
# ---- helpers --------------------------------------------------------
|
||||
|
||||
def _isolated_mcp_graph(self):
|
||||
"""Return a fresh ContextGraph injected as the mcp_server singleton."""
|
||||
import semantica.mcp_server as mcp_mod
|
||||
g = ContextGraph(advanced_analytics=False)
|
||||
self._original_graph = mcp_mod._graph
|
||||
mcp_mod._graph = g
|
||||
return g
|
||||
|
||||
def _restore_mcp_graph(self):
|
||||
import semantica.mcp_server as mcp_mod
|
||||
mcp_mod._graph = self._original_graph
|
||||
|
||||
# ---- record_decision ------------------------------------------------
|
||||
|
||||
def test_record_decision_persists_to_kg_path(self):
|
||||
"""_tool_record_decision must write the graph to SEMANTICA_KG_PATH."""
|
||||
from semantica.mcp_server import _tool_record_decision
|
||||
|
||||
g = self._isolated_mcp_graph()
|
||||
try:
|
||||
with tempfile.NamedTemporaryFile(suffix=".json", delete=False) as f:
|
||||
path = f.name
|
||||
try:
|
||||
with patch.dict(os.environ, {"SEMANTICA_KG_PATH": path}):
|
||||
result = _tool_record_decision({
|
||||
"category": "mcp_server_persist",
|
||||
"scenario": "Testing packaged server persistence",
|
||||
"reasoning": "save_to_file must be called on mutation",
|
||||
"outcome": "verified",
|
||||
"confidence": 0.99,
|
||||
})
|
||||
|
||||
self.assertNotIn("error", result, result)
|
||||
self.assertIn("decision_id", result)
|
||||
|
||||
# File must have been written.
|
||||
self.assertGreater(os.path.getsize(path), 0,
|
||||
"save_to_file must have written to the KG_PATH file")
|
||||
|
||||
# Simulate restart: reload into a fresh graph.
|
||||
g2 = ContextGraph(advanced_analytics=False)
|
||||
g2.load_from_file(path)
|
||||
decisions = list(g2.find_nodes(node_type="decision"))
|
||||
self.assertGreater(len(decisions), 0,
|
||||
"Decision must be present after save → load")
|
||||
cats = [d.get("category") or (d.get("metadata") or {}).get("category")
|
||||
for d in decisions]
|
||||
self.assertIn("mcp_server_persist", cats)
|
||||
finally:
|
||||
os.unlink(path)
|
||||
finally:
|
||||
self._restore_mcp_graph()
|
||||
|
||||
def test_record_decision_works_without_kg_path(self):
|
||||
"""_tool_record_decision must succeed when SEMANTICA_KG_PATH is unset."""
|
||||
from semantica.mcp_server import _tool_record_decision
|
||||
|
||||
g = self._isolated_mcp_graph()
|
||||
try:
|
||||
env = {k: v for k, v in os.environ.items() if k != "SEMANTICA_KG_PATH"}
|
||||
with patch.dict(os.environ, env, clear=True):
|
||||
result = _tool_record_decision({
|
||||
"category": "no_path",
|
||||
"scenario": "no kg path",
|
||||
"reasoning": "in-memory only",
|
||||
"outcome": "ok",
|
||||
"confidence": 0.5,
|
||||
})
|
||||
self.assertNotIn("error", result, result)
|
||||
self.assertIn("decision_id", result)
|
||||
finally:
|
||||
self._restore_mcp_graph()
|
||||
|
||||
# ---- add_entity -----------------------------------------------------
|
||||
|
||||
def test_add_entity_persists_to_kg_path(self):
|
||||
"""_tool_add_entity must write the graph to SEMANTICA_KG_PATH."""
|
||||
from semantica.mcp_server import _tool_add_entity
|
||||
|
||||
g = self._isolated_mcp_graph()
|
||||
try:
|
||||
with tempfile.NamedTemporaryFile(suffix=".json", delete=False) as f:
|
||||
path = f.name
|
||||
try:
|
||||
with patch.dict(os.environ, {"SEMANTICA_KG_PATH": path}):
|
||||
result = _tool_add_entity({
|
||||
"id": "mcp_server_entity_test",
|
||||
"label": "Persistence Entity",
|
||||
"type": "TestEntity",
|
||||
})
|
||||
|
||||
self.assertNotIn("error", result, result)
|
||||
self.assertEqual(result.get("status"), "added")
|
||||
|
||||
self.assertGreater(os.path.getsize(path), 0)
|
||||
|
||||
g2 = ContextGraph(advanced_analytics=False)
|
||||
g2.load_from_file(path)
|
||||
self.assertTrue(g2.has_node("mcp_server_entity_test"),
|
||||
"Entity must be present after save → load")
|
||||
finally:
|
||||
os.unlink(path)
|
||||
finally:
|
||||
self._restore_mcp_graph()
|
||||
|
||||
def test_add_entity_works_without_kg_path(self):
|
||||
"""_tool_add_entity must succeed when SEMANTICA_KG_PATH is unset."""
|
||||
from semantica.mcp_server import _tool_add_entity
|
||||
|
||||
g = self._isolated_mcp_graph()
|
||||
try:
|
||||
env = {k: v for k, v in os.environ.items() if k != "SEMANTICA_KG_PATH"}
|
||||
with patch.dict(os.environ, env, clear=True):
|
||||
result = _tool_add_entity({"id": "ephemeral_ent", "label": "E"})
|
||||
self.assertNotIn("error", result, result)
|
||||
self.assertEqual(result.get("status"), "added")
|
||||
finally:
|
||||
self._restore_mcp_graph()
|
||||
|
||||
# ---- add_relationship -----------------------------------------------
|
||||
|
||||
def test_add_relationship_persists_to_kg_path(self):
|
||||
"""_tool_add_relationship must write the graph to SEMANTICA_KG_PATH."""
|
||||
from semantica.mcp_server import _tool_add_entity, _tool_add_relationship
|
||||
|
||||
g = self._isolated_mcp_graph()
|
||||
try:
|
||||
with tempfile.NamedTemporaryFile(suffix=".json", delete=False) as f:
|
||||
path = f.name
|
||||
try:
|
||||
with patch.dict(os.environ, {"SEMANTICA_KG_PATH": path}):
|
||||
_tool_add_entity({"id": "rel_src_mcp", "label": "Source"})
|
||||
_tool_add_entity({"id": "rel_tgt_mcp", "label": "Target"})
|
||||
result = _tool_add_relationship({
|
||||
"source": "rel_src_mcp",
|
||||
"target": "rel_tgt_mcp",
|
||||
"type": "PROVEN_BY",
|
||||
})
|
||||
|
||||
self.assertNotIn("error", result, result)
|
||||
self.assertEqual(result.get("status"), "added")
|
||||
|
||||
self.assertGreater(os.path.getsize(path), 0)
|
||||
|
||||
g2 = ContextGraph(advanced_analytics=False)
|
||||
g2.load_from_file(path)
|
||||
edges = list(g2.find_edges())
|
||||
self.assertTrue(any(e.get("type") == "PROVEN_BY" for e in edges),
|
||||
"PROVEN_BY edge must be present after save → load")
|
||||
finally:
|
||||
os.unlink(path)
|
||||
finally:
|
||||
self._restore_mcp_graph()
|
||||
|
||||
def test_add_relationship_works_without_kg_path(self):
|
||||
"""_tool_add_relationship must succeed when SEMANTICA_KG_PATH is unset."""
|
||||
from semantica.mcp_server import _tool_add_entity, _tool_add_relationship
|
||||
|
||||
g = self._isolated_mcp_graph()
|
||||
try:
|
||||
env = {k: v for k, v in os.environ.items() if k != "SEMANTICA_KG_PATH"}
|
||||
with patch.dict(os.environ, env, clear=True):
|
||||
_tool_add_entity({"id": "src_no_p", "label": "S"})
|
||||
_tool_add_entity({"id": "tgt_no_p", "label": "T"})
|
||||
result = _tool_add_relationship({
|
||||
"source": "src_no_p",
|
||||
"target": "tgt_no_p",
|
||||
"type": "RELATED_TO",
|
||||
})
|
||||
self.assertNotIn("error", result, result)
|
||||
self.assertEqual(result.get("status"), "added")
|
||||
finally:
|
||||
self._restore_mcp_graph()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
@@ -678,7 +678,7 @@ class TestSeparateVectorStoreHandling(unittest.TestCase):
|
||||
"""
|
||||
|
||||
def test_vector_store_false_disables_vector_leg_entirely(self):
|
||||
"""vector_store=False must disable the vector leg, not try memory.vector_store."""
|
||||
"""vector_store=False must disable the vector leg AND memory's own cascade (#1378)."""
|
||||
memory_store = _SelectiveDeleteStore()
|
||||
memory = _memory_with_embedding("customer-4471", memory_store)
|
||||
|
||||
@@ -689,8 +689,91 @@ class TestSeparateVectorStoreHandling(unittest.TestCase):
|
||||
|
||||
# Vector leg should report not_configured, not attempt deletion
|
||||
self.assertEqual(receipt.stores["vectors"]["status"], STATUS_NOT_CONFIGURED)
|
||||
# Memory's own cascade still runs, but coordinator doesn't track it
|
||||
self.assertTrue(receipt.complete)
|
||||
# Memory's own internal vector cascade must be suppressed too, not just
|
||||
# unreported: the embedding memory owns is left untouched, and the
|
||||
# backend's delete method is never even called.
|
||||
self.assertEqual(memory_store.attempts, [])
|
||||
self.assertTrue(memory_store.live)
|
||||
|
||||
def test_vector_store_false_regression_refusing_backend_never_called(self):
|
||||
"""Regression for #1378: a refusing backend must not be called at all.
|
||||
|
||||
Reproduces the exact bug report -- a vector store whose delete_vectors()
|
||||
always returns False (refuses) bound as memory.vector_store, with the
|
||||
coordinator's own vector leg disabled via vector_store=False. Before the
|
||||
fix, delete_memory()'s internal cascade would still call the refusing
|
||||
store, catch the failure, log a warning, and return True regardless --
|
||||
so receipt.complete read True while the embedding stayed live and the
|
||||
backend had in fact been asked to delete it. Pinned here so the delete
|
||||
method call count can't silently regress back to nonzero.
|
||||
"""
|
||||
refusing_store = _SelectiveDeleteStore(refuse={"vec-0"})
|
||||
memory = _memory_with_embedding("customer-4471", refusing_store)
|
||||
|
||||
receipt = ErasureCoordinator(
|
||||
memory=memory, vector_store=False
|
||||
).erase_entity("customer-4471")
|
||||
|
||||
self.assertTrue(receipt.complete)
|
||||
self.assertEqual(receipt.stores["vectors"]["status"], STATUS_NOT_CONFIGURED)
|
||||
self.assertEqual(len(refusing_store.attempts), 0) # delete_calls == 0
|
||||
|
||||
def test_skip_vector_deletion_does_not_orphan_local_vector_id_tracking(self):
|
||||
"""skip_vector=True must still pop the item's own _vector_ids entry.
|
||||
|
||||
Regression: delete_memory(skip_vector=True) used to leave the item's
|
||||
entry in AgentMemory._vector_ids behind since the pop() lived inside
|
||||
the `if not skip_vector` block alongside the actual vector-store
|
||||
delete. That orphaned entry never got cleaned up and leaked into
|
||||
to_dict()/from_dict() snapshots.
|
||||
"""
|
||||
memory = _memory_with_embedding("customer-4471", _SelectiveDeleteStore())
|
||||
memory_id = next(iter(memory.memory_items))
|
||||
self.assertIn(memory_id, memory._vector_ids)
|
||||
|
||||
ErasureCoordinator(memory=memory, vector_store=False).erase_entity(
|
||||
"customer-4471"
|
||||
)
|
||||
|
||||
self.assertNotIn(memory_id, memory.memory_items)
|
||||
self.assertNotIn(memory_id, memory._vector_ids)
|
||||
|
||||
def test_memory_adapter_without_skip_vector_support_is_not_broken(self):
|
||||
"""A duck-typed memory whose batch_delete() lacks skip_vector must still work.
|
||||
|
||||
The class docstring only requires find_by_entity and batch_delete; an
|
||||
adapter is not obligated to support skip_vector. The coordinator must
|
||||
detect that and fall back to the plain call rather than raising
|
||||
TypeError and failing the whole memory leg.
|
||||
"""
|
||||
|
||||
class _PlainAdapter:
|
||||
def __init__(self):
|
||||
self.items = {"m1": {"memory_id": "m1", "entities": [{"id": "customer-4471"}]}}
|
||||
|
||||
def find_by_entity(self, entity_id, limit=None):
|
||||
return [
|
||||
item
|
||||
for item in self.items.values()
|
||||
if any(e.get("id") == entity_id for e in item.get("entities", []))
|
||||
]
|
||||
|
||||
def batch_delete(self, memory_ids):
|
||||
removed = 0
|
||||
for memory_id in memory_ids:
|
||||
if self.items.pop(memory_id, None) is not None:
|
||||
removed += 1
|
||||
return removed
|
||||
|
||||
adapter = _PlainAdapter()
|
||||
|
||||
receipt = ErasureCoordinator(
|
||||
memory=adapter, vector_store=False
|
||||
).erase_entity("customer-4471")
|
||||
|
||||
self.assertEqual(receipt.stores["memory"]["status"], STATUS_ERASED)
|
||||
self.assertEqual(adapter.items, {})
|
||||
|
||||
def test_separate_vector_store_only_handles_coordinator_store(self):
|
||||
"""When coordinator has a different vector_store, it only handles that one.
|
||||
|
||||
@@ -242,6 +242,107 @@ class TestGraphAnalyzer(unittest.TestCase):
|
||||
self.mock_connectivity.analyze_connectivity.assert_called_once()
|
||||
mock_metrics.assert_called_once()
|
||||
|
||||
class TestAnalyzeTemporalEvolutionMutableDefault(unittest.TestCase):
|
||||
"""Regression tests for fix: replace mutable default argument in
|
||||
GraphAnalyzer.analyze_temporal_evolution (metrics=[...] -> None).
|
||||
|
||||
TemporalGraphQuery is imported lazily inside the method body
|
||||
(``from .temporal_query import TemporalGraphQuery``), so it is patched
|
||||
at its definition site: ``semantica.kg.temporal_query.TemporalGraphQuery``.
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
self.mock_tracker_patcher = patch("semantica.kg.graph_analyzer.get_progress_tracker")
|
||||
self.mock_get_tracker = self.mock_tracker_patcher.start()
|
||||
self.mock_get_tracker.return_value = MagicMock()
|
||||
|
||||
self.mock_centrality_patcher = patch("semantica.kg.graph_analyzer.CentralityCalculator")
|
||||
self.mock_centrality_patcher.start()
|
||||
|
||||
self.mock_community_patcher = patch("semantica.kg.graph_analyzer.CommunityDetector")
|
||||
self.mock_community_patcher.start()
|
||||
|
||||
self.mock_connectivity_patcher = patch("semantica.kg.graph_analyzer.ConnectivityAnalyzer")
|
||||
self.mock_connectivity_patcher.start()
|
||||
|
||||
# TemporalGraphQuery is imported *inside* the method body, so patch it
|
||||
# at the definition module rather than at the caller module.
|
||||
self.mock_tq_patcher = patch(
|
||||
"semantica.kg.temporal_query.TemporalGraphQuery", autospec=False
|
||||
)
|
||||
mock_tq_cls = self.mock_tq_patcher.start()
|
||||
self.mock_tq = MagicMock()
|
||||
self.mock_tq.analyze_evolution.return_value = {"snapshots": []}
|
||||
mock_tq_cls.return_value = self.mock_tq
|
||||
|
||||
def tearDown(self):
|
||||
patch.stopall()
|
||||
|
||||
def _make_analyzer(self):
|
||||
return GraphAnalyzer()
|
||||
|
||||
def test_default_metrics_value_is_canonical(self):
|
||||
"""When metrics=None, the four canonical metric names must be used."""
|
||||
analyzer = self._make_analyzer()
|
||||
graph = {"entities": [], "relationships": []}
|
||||
result = analyzer.analyze_temporal_evolution(graph)
|
||||
|
||||
self.assertEqual(
|
||||
sorted(result["metrics_tracked"]),
|
||||
sorted(["node_count", "edge_count", "density", "communities"]),
|
||||
)
|
||||
|
||||
def test_default_metrics_independent_across_calls(self):
|
||||
"""Mutating the returned metrics_tracked list must not affect the next call."""
|
||||
analyzer = self._make_analyzer()
|
||||
graph = {"entities": [], "relationships": []}
|
||||
|
||||
result1 = analyzer.analyze_temporal_evolution(graph)
|
||||
# Mutate the returned list in-place.
|
||||
result1["metrics_tracked"].append("MUTATED")
|
||||
|
||||
result2 = analyzer.analyze_temporal_evolution(graph)
|
||||
self.assertNotIn(
|
||||
"MUTATED",
|
||||
result2["metrics_tracked"],
|
||||
"Mutable default leaked: 'MUTATED' appeared in the second call's metrics list",
|
||||
)
|
||||
|
||||
def test_result_contains_metrics_tracked_key(self):
|
||||
"""Return value must include 'metrics_tracked' with the default list."""
|
||||
analyzer = self._make_analyzer()
|
||||
graph = {"entities": [], "relationships": []}
|
||||
result = analyzer.analyze_temporal_evolution(graph)
|
||||
|
||||
self.assertIn("metrics_tracked", result)
|
||||
self.assertEqual(
|
||||
sorted(result["metrics_tracked"]),
|
||||
sorted(["node_count", "edge_count", "density", "communities"]),
|
||||
)
|
||||
|
||||
def test_explicit_metrics_override_is_respected(self):
|
||||
"""Explicitly passed metrics must be forwarded and reflected in the return value."""
|
||||
analyzer = self._make_analyzer()
|
||||
graph = {"entities": [], "relationships": []}
|
||||
custom = ["node_count"]
|
||||
result = analyzer.analyze_temporal_evolution(graph, metrics=custom)
|
||||
|
||||
self.assertEqual(result["metrics_tracked"], custom)
|
||||
|
||||
def test_explicit_metrics_mutation_does_not_affect_default(self):
|
||||
"""Mutating the list passed as an explicit argument must not corrupt
|
||||
a subsequent default call."""
|
||||
analyzer = self._make_analyzer()
|
||||
graph = {"entities": [], "relationships": []}
|
||||
|
||||
explicit = ["node_count"]
|
||||
analyzer.analyze_temporal_evolution(graph, metrics=explicit)
|
||||
explicit.append("MUTATED")
|
||||
|
||||
result = analyzer.analyze_temporal_evolution(graph)
|
||||
self.assertNotIn("MUTATED", result["metrics_tracked"])
|
||||
|
||||
|
||||
class TestTemporalGraphQuery(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.mock_tracker_patcher = patch("semantica.utils.progress_tracker.get_progress_tracker")
|
||||
|
||||
@@ -638,3 +638,89 @@ Body paragraph under a distinct heading for separation checks.
|
||||
self.SAMPLE * 3, chunk_size=80, ner_method="pattern"
|
||||
)
|
||||
assert len(chunks) >= 1
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Mutable-default regression tests (fix: replace mutable default arguments)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
class TestMutableDefaultRegression:
|
||||
"""Regression tests proving that mutable default arguments do not leak
|
||||
between calls. Each test mutates the list returned / stored by one call
|
||||
and verifies that a subsequent call still receives the *original* default
|
||||
value, not the mutated one.
|
||||
"""
|
||||
|
||||
# --- split_hierarchical -------------------------------------------------
|
||||
|
||||
def test_split_hierarchical_default_levels_are_independent_across_calls(self):
|
||||
"""Mutating the levels list from one call must not affect the next."""
|
||||
text = "Para one.\n\nPara two.\n\nPara three."
|
||||
|
||||
# First call – capture and mutate the levels list indirectly by
|
||||
# passing explicit levels and then appending to a reference.
|
||||
call1_levels: list = ["paragraph"]
|
||||
chunks1 = split_hierarchical(text, levels=call1_levels, chunk_sizes=[1000])
|
||||
# Mutate the list that was passed in.
|
||||
call1_levels.append("MUTATED")
|
||||
|
||||
# Second call with default levels=None must still use the canonical default.
|
||||
chunks2 = split_hierarchical(text)
|
||||
# The function must succeed and produce chunks (not raise because
|
||||
# "MUTATED" is not a valid level name).
|
||||
assert len(chunks2) >= 1
|
||||
|
||||
def test_split_hierarchical_none_default_creates_fresh_list_each_call(self):
|
||||
"""Two calls with levels=None must receive independent list objects."""
|
||||
text = "A sentence.\n\nAnother sentence."
|
||||
|
||||
# Patch the body assignment so we can capture it.
|
||||
captured: list = []
|
||||
original_fn = split_hierarchical.__wrapped__ if hasattr(split_hierarchical, "__wrapped__") else None
|
||||
|
||||
# Use a simpler black-box approach: call twice and verify behaviour.
|
||||
chunks_a = split_hierarchical(text)
|
||||
chunks_b = split_hierarchical(text)
|
||||
|
||||
# Both calls should produce identical results (same default).
|
||||
assert len(chunks_a) == len(chunks_b)
|
||||
assert [c.text for c in chunks_a] == [c.text for c in chunks_b]
|
||||
|
||||
def test_split_hierarchical_default_chunk_sizes_are_independent_across_calls(self):
|
||||
"""Mutating chunk_sizes in one call must not affect the next."""
|
||||
text = "Para A.\n\nPara B."
|
||||
mutable_sizes = [5000, 2000, 1000]
|
||||
split_hierarchical(text, chunk_sizes=mutable_sizes)
|
||||
# Mutate after first call.
|
||||
mutable_sizes[0] = 1 # Would produce very different chunking if leaked.
|
||||
|
||||
# Second call with default chunk_sizes=None must still use canonical defaults.
|
||||
chunks = split_hierarchical(text)
|
||||
assert len(chunks) >= 1
|
||||
|
||||
# --- HierarchicalChunker ------------------------------------------------
|
||||
|
||||
def test_hierarchical_chunker_default_levels_independent_across_instances(self):
|
||||
"""Mutating levels on one instance must not affect a second instance
|
||||
created with the default."""
|
||||
chunker_a = HierarchicalChunker()
|
||||
# Mutate the instance attribute that was built from the default.
|
||||
chunker_a.levels.append("MUTATED")
|
||||
|
||||
chunker_b = HierarchicalChunker()
|
||||
assert "MUTATED" not in chunker_b.levels, (
|
||||
"Mutation of chunker_a.levels leaked into chunker_b — "
|
||||
"mutable default not fixed properly"
|
||||
)
|
||||
|
||||
def test_hierarchical_chunker_default_levels_value(self):
|
||||
"""Default levels must equal the canonical list."""
|
||||
chunker = HierarchicalChunker()
|
||||
assert chunker.levels == ["section", "paragraph", "sentence"]
|
||||
|
||||
def test_hierarchical_chunker_explicit_levels_preserved(self):
|
||||
"""Explicitly passed levels must be stored as given."""
|
||||
custom = ["document", "paragraph"]
|
||||
chunker = HierarchicalChunker(levels=custom)
|
||||
assert chunker.levels == custom
|
||||
|
||||
@@ -0,0 +1,294 @@
|
||||
"""Regression tests for root mcp/ graph persistence (issue #1134).
|
||||
|
||||
Covers:
|
||||
1. get_graph() loads an existing JSON file via load_from_file(), not the
|
||||
nonexistent .load() method (the original bug).
|
||||
2. get_graph() with a nonexistent / unset SEMANTICA_KG_PATH starts cleanly.
|
||||
3. handle_record_decision persists to SEMANTICA_KG_PATH and the mutation
|
||||
survives a fresh load_from_file() call.
|
||||
4. handle_add_entity persists to SEMANTICA_KG_PATH and survives reload.
|
||||
5. handle_add_relationship persists to SEMANTICA_KG_PATH and survives reload.
|
||||
6. All three mutation tools work correctly when SEMANTICA_KG_PATH is unset
|
||||
(no errors, no persistence attempt).
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import tempfile
|
||||
import unittest
|
||||
from unittest.mock import patch
|
||||
|
||||
from semantica.context.context_graph import ContextGraph
|
||||
|
||||
import mcp.session as _session
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Helpers
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def _fresh_graph() -> ContextGraph:
|
||||
"""Return a minimal ContextGraph ready for use in tests."""
|
||||
g = ContextGraph(advanced_analytics=False)
|
||||
g.add_node("seed_node", node_type="entity", label="Seed")
|
||||
return g
|
||||
|
||||
|
||||
class _IsolatedSession:
|
||||
"""Context manager that resets the mcp.session singleton before and after
|
||||
each test so tests are independent of process-level state."""
|
||||
|
||||
def __enter__(self):
|
||||
_session.reset_graph()
|
||||
return self
|
||||
|
||||
def __exit__(self, *_):
|
||||
_session.reset_graph()
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 1. get_graph() loading — regression against _graph.load()
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
class TestMCPSessionLoad(unittest.TestCase):
|
||||
"""get_graph() must load an existing file using load_from_file(), not .load()."""
|
||||
|
||||
def test_get_graph_loads_existing_kg_path(self):
|
||||
"""When SEMANTICA_KG_PATH points to a valid JSON file the graph must
|
||||
contain the persisted nodes after get_graph() returns."""
|
||||
g = _fresh_graph()
|
||||
g.add_node("persistent_node", node_type="entity", label="Should survive")
|
||||
|
||||
with tempfile.NamedTemporaryFile(suffix=".json", delete=False) as f:
|
||||
path = f.name
|
||||
try:
|
||||
g.save_to_file(path)
|
||||
|
||||
with _IsolatedSession():
|
||||
with patch.dict(os.environ, {"SEMANTICA_KG_PATH": path}):
|
||||
loaded = _session.get_graph()
|
||||
|
||||
self.assertTrue(
|
||||
loaded.has_node("persistent_node"),
|
||||
"Node saved before server start must be present after load",
|
||||
)
|
||||
self.assertTrue(
|
||||
loaded.has_node("seed_node"),
|
||||
"seed_node from the persisted graph must also be present",
|
||||
)
|
||||
finally:
|
||||
os.unlink(path)
|
||||
|
||||
def test_get_graph_with_nonexistent_kg_path_starts_empty(self):
|
||||
"""When SEMANTICA_KG_PATH does not exist the graph initialises empty
|
||||
(no error) — matching pre-existing behaviour."""
|
||||
with _IsolatedSession():
|
||||
with patch.dict(os.environ, {"SEMANTICA_KG_PATH": "/nonexistent/path.json"}):
|
||||
loaded = _session.get_graph()
|
||||
|
||||
# An empty graph has no nodes; at minimum it must be a ContextGraph.
|
||||
self.assertIsNotNone(loaded)
|
||||
nodes = list(loaded.find_nodes())
|
||||
self.assertEqual(nodes, [], "Graph must be empty when KG_PATH does not exist")
|
||||
|
||||
def test_get_graph_without_kg_path_starts_empty(self):
|
||||
"""When SEMANTICA_KG_PATH is absent the graph initialises empty."""
|
||||
with _IsolatedSession():
|
||||
env = {k: v for k, v in os.environ.items() if k != "SEMANTICA_KG_PATH"}
|
||||
with patch.dict(os.environ, env, clear=True):
|
||||
loaded = _session.get_graph()
|
||||
|
||||
self.assertIsNotNone(loaded)
|
||||
|
||||
def test_get_graph_uses_load_from_file_not_load(self):
|
||||
"""Regression: ContextGraph has no .load() method; get_graph() must
|
||||
call load_from_file() or the AttributeError is silently swallowed and
|
||||
the graph silently stays empty. This test verifies the fix directly."""
|
||||
g = _fresh_graph()
|
||||
with tempfile.NamedTemporaryFile(suffix=".json", delete=False) as f:
|
||||
path = f.name
|
||||
try:
|
||||
g.save_to_file(path)
|
||||
|
||||
with _IsolatedSession():
|
||||
with patch.dict(os.environ, {"SEMANTICA_KG_PATH": path}):
|
||||
# If the old _graph.load(path) bug were present the graph
|
||||
# would be empty (exception swallowed). With the fix the
|
||||
# node must be present.
|
||||
loaded = _session.get_graph()
|
||||
|
||||
self.assertTrue(
|
||||
loaded.has_node("seed_node"),
|
||||
"load_from_file must have been called; if .load() was used "
|
||||
"the AttributeError is swallowed and the graph stays empty",
|
||||
)
|
||||
finally:
|
||||
os.unlink(path)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 2–5. Mutation persistence
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
class TestMCPPackageMutationPersistence(unittest.TestCase):
|
||||
"""Mutations via the root mcp/ tool handlers must persist to SEMANTICA_KG_PATH
|
||||
so the data survives a server restart (simulated by a fresh load_from_file)."""
|
||||
|
||||
# ---- record_decision ------------------------------------------------
|
||||
|
||||
def test_record_decision_persists_when_kg_path_set(self):
|
||||
"""handle_record_decision must write to disk when SEMANTICA_KG_PATH is set."""
|
||||
from mcp.tools.decisions import handle_record_decision
|
||||
|
||||
with tempfile.NamedTemporaryFile(suffix=".json", delete=False) as f:
|
||||
path = f.name
|
||||
try:
|
||||
with _IsolatedSession():
|
||||
with patch.dict(os.environ, {"SEMANTICA_KG_PATH": path}):
|
||||
result = handle_record_decision({
|
||||
"category": "test_persistence",
|
||||
"scenario": "Verifying mcp/ decision persistence",
|
||||
"reasoning": "KG_PATH must be written on mutation",
|
||||
"outcome": "verified",
|
||||
"confidence": 0.99,
|
||||
})
|
||||
|
||||
self.assertNotIn("error", result, result)
|
||||
self.assertIn("decision_id", result)
|
||||
|
||||
# The file must have been written (or overwritten from empty).
|
||||
self.assertTrue(os.path.exists(path), "save_to_file must create the file")
|
||||
self.assertGreater(os.path.getsize(path), 0, "Persisted file must not be empty")
|
||||
|
||||
# Simulate server restart: load into a fresh graph.
|
||||
g2 = ContextGraph(advanced_analytics=False)
|
||||
g2.load_from_file(path)
|
||||
decisions = list(g2.find_nodes(node_type="decision"))
|
||||
self.assertGreater(len(decisions), 0, "Decision must survive reload")
|
||||
cats = [d.get("category") or (d.get("metadata") or {}).get("category")
|
||||
for d in decisions]
|
||||
self.assertIn("test_persistence", cats,
|
||||
"Decision category must be present after reload")
|
||||
finally:
|
||||
os.unlink(path)
|
||||
|
||||
def test_record_decision_works_without_kg_path(self):
|
||||
"""handle_record_decision must succeed even when SEMANTICA_KG_PATH is unset."""
|
||||
from mcp.tools.decisions import handle_record_decision
|
||||
|
||||
with _IsolatedSession():
|
||||
env = {k: v for k, v in os.environ.items() if k != "SEMANTICA_KG_PATH"}
|
||||
with patch.dict(os.environ, env, clear=True):
|
||||
result = handle_record_decision({
|
||||
"category": "no_path",
|
||||
"scenario": "No persistence path configured",
|
||||
"reasoning": "Should still work in-memory",
|
||||
"outcome": "ok",
|
||||
"confidence": 0.5,
|
||||
})
|
||||
|
||||
self.assertNotIn("error", result, result)
|
||||
self.assertIn("decision_id", result)
|
||||
|
||||
# ---- add_entity -----------------------------------------------------
|
||||
|
||||
def test_add_entity_persists_when_kg_path_set(self):
|
||||
"""handle_add_entity must write to disk when SEMANTICA_KG_PATH is set."""
|
||||
from mcp.tools.graph import handle_add_entity
|
||||
|
||||
with tempfile.NamedTemporaryFile(suffix=".json", delete=False) as f:
|
||||
path = f.name
|
||||
try:
|
||||
with _IsolatedSession():
|
||||
with patch.dict(os.environ, {"SEMANTICA_KG_PATH": path}):
|
||||
result = handle_add_entity({
|
||||
"id": "entity_persist_test",
|
||||
"label": "Persistence Test Entity",
|
||||
"type": "TestType",
|
||||
})
|
||||
|
||||
self.assertNotIn("error", result, result)
|
||||
self.assertEqual(result.get("status"), "added")
|
||||
|
||||
self.assertTrue(os.path.exists(path))
|
||||
self.assertGreater(os.path.getsize(path), 0)
|
||||
|
||||
g2 = ContextGraph(advanced_analytics=False)
|
||||
g2.load_from_file(path)
|
||||
self.assertTrue(
|
||||
g2.has_node("entity_persist_test"),
|
||||
"Entity must be present in the graph after reload",
|
||||
)
|
||||
finally:
|
||||
os.unlink(path)
|
||||
|
||||
def test_add_entity_works_without_kg_path(self):
|
||||
"""handle_add_entity must succeed when SEMANTICA_KG_PATH is unset."""
|
||||
from mcp.tools.graph import handle_add_entity
|
||||
|
||||
with _IsolatedSession():
|
||||
env = {k: v for k, v in os.environ.items() if k != "SEMANTICA_KG_PATH"}
|
||||
with patch.dict(os.environ, env, clear=True):
|
||||
result = handle_add_entity({"id": "no_path_entity", "label": "ephemeral"})
|
||||
|
||||
self.assertNotIn("error", result, result)
|
||||
self.assertEqual(result.get("status"), "added")
|
||||
|
||||
# ---- add_relationship -----------------------------------------------
|
||||
|
||||
def test_add_relationship_persists_when_kg_path_set(self):
|
||||
"""handle_add_relationship must write to disk when SEMANTICA_KG_PATH is set."""
|
||||
from mcp.tools.graph import handle_add_relationship
|
||||
|
||||
with tempfile.NamedTemporaryFile(suffix=".json", delete=False) as f:
|
||||
path = f.name
|
||||
try:
|
||||
with _IsolatedSession():
|
||||
with patch.dict(os.environ, {"SEMANTICA_KG_PATH": path}):
|
||||
# Nodes must exist before an edge can be added.
|
||||
from mcp.tools.graph import handle_add_entity
|
||||
handle_add_entity({"id": "rel_src", "label": "Source"})
|
||||
handle_add_entity({"id": "rel_tgt", "label": "Target"})
|
||||
result = handle_add_relationship({
|
||||
"source": "rel_src",
|
||||
"target": "rel_tgt",
|
||||
"type": "TESTED_BY",
|
||||
})
|
||||
|
||||
self.assertNotIn("error", result, result)
|
||||
self.assertEqual(result.get("status"), "added")
|
||||
|
||||
self.assertTrue(os.path.exists(path))
|
||||
self.assertGreater(os.path.getsize(path), 0)
|
||||
|
||||
g2 = ContextGraph(advanced_analytics=False)
|
||||
g2.load_from_file(path)
|
||||
edges = list(g2.find_edges())
|
||||
edge_types = [e.get("type") for e in edges]
|
||||
self.assertIn("TESTED_BY", edge_types,
|
||||
"Relationship must be present after reload")
|
||||
finally:
|
||||
os.unlink(path)
|
||||
|
||||
def test_add_relationship_works_without_kg_path(self):
|
||||
"""handle_add_relationship must succeed when SEMANTICA_KG_PATH is unset."""
|
||||
from mcp.tools.graph import handle_add_entity, handle_add_relationship
|
||||
|
||||
with _IsolatedSession():
|
||||
env = {k: v for k, v in os.environ.items() if k != "SEMANTICA_KG_PATH"}
|
||||
with patch.dict(os.environ, env, clear=True):
|
||||
handle_add_entity({"id": "src_no_path", "label": "S"})
|
||||
handle_add_entity({"id": "tgt_no_path", "label": "T"})
|
||||
result = handle_add_relationship({
|
||||
"source": "src_no_path",
|
||||
"target": "tgt_no_path",
|
||||
"type": "RELATED_TO",
|
||||
})
|
||||
|
||||
self.assertNotIn("error", result, result)
|
||||
self.assertEqual(result.get("status"), "added")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -0,0 +1,180 @@
|
||||
"""MCP stdio JSON-RPC framing regression test (#1134, point 1).
|
||||
|
||||
The original bug: progress output from the Semantica progress tracker was
|
||||
written to sys.stdout, which is also the MCP JSON-RPC transport channel.
|
||||
Interleaving progress text with JSON-RPC responses made every response
|
||||
unparseable and hung the client.
|
||||
|
||||
These tests exercise the *actual* root mcp/ server stdio framing loop
|
||||
(SemanticaMCPServer.run()) over a real subprocess pipe, not just the handler
|
||||
layer. They prove that:
|
||||
|
||||
1. Every non-empty stdout line produced by the running server is valid JSON.
|
||||
2. A valid JSON-RPC response is received for each request sent.
|
||||
3. No progress / non-JSON bytes appear on stdout even when a tool triggers
|
||||
the progress-producing code path (constructing a ContextGraph, which
|
||||
calls get_progress_tracker() and attempts to enable the tracker).
|
||||
|
||||
Tests that are already covered elsewhere are not duplicated here:
|
||||
- ConsoleProgressDisplay writing to stderr (test_progress_stream.py)
|
||||
- SEMANTICA_DISABLE_PROGRESS blocking re-enable (test_progress_tracker_regressions.py)
|
||||
- mcp import sets SEMANTICA_DISABLE_PROGRESS (test_mcp_package_export_graph.py)
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
import unittest
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Module-level helpers
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def _repo_root() -> str:
|
||||
return os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
|
||||
|
||||
|
||||
def _subprocess_env() -> dict[str, str]:
|
||||
"""Clean env with the repo on PYTHONPATH and no pre-set progress flag."""
|
||||
env = os.environ.copy()
|
||||
env["PYTHONPATH"] = _repo_root()
|
||||
env.pop("SEMANTICA_DISABLE_PROGRESS", None)
|
||||
return env
|
||||
|
||||
|
||||
def _jsonrpc(method: str, req_id: int | None, params: dict | None = None) -> bytes:
|
||||
msg: dict = {"jsonrpc": "2.0", "method": method}
|
||||
if req_id is not None:
|
||||
msg["id"] = req_id
|
||||
if params is not None:
|
||||
msg["params"] = params
|
||||
return (json.dumps(msg) + "\n").encode()
|
||||
|
||||
|
||||
def _assert_stdout_is_clean_json(test: unittest.TestCase,
|
||||
stdout: str,
|
||||
stderr: str = "") -> list[dict]:
|
||||
"""Assert every non-empty stdout line is valid JSON; return parsed objects.
|
||||
|
||||
Fails immediately with a useful diagnostic if any line is not JSON.
|
||||
"""
|
||||
lines = [ln for ln in stdout.splitlines() if ln.strip()]
|
||||
test.assertGreater(
|
||||
len(lines), 0,
|
||||
f"Expected at least one stdout line but got none.\nstderr={stderr!r}",
|
||||
)
|
||||
parsed = []
|
||||
for i, line in enumerate(lines):
|
||||
try:
|
||||
parsed.append(json.loads(line))
|
||||
except json.JSONDecodeError as exc:
|
||||
test.fail(
|
||||
f"stdout line {i} is not valid JSON (regression: progress leaked "
|
||||
f"to stdout?)\n line: {line!r}\n error: {exc}\n stderr={stderr!r}"
|
||||
)
|
||||
return parsed
|
||||
|
||||
|
||||
_INIT_REQUEST = _jsonrpc("initialize", 1, {
|
||||
"protocolVersion": "2024-11-05",
|
||||
"clientInfo": {"name": "test", "version": "0"},
|
||||
"capabilities": {},
|
||||
})
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Main regression suite
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
class TestMCPStdioFramingContract(unittest.TestCase):
|
||||
"""Run 'python -m mcp' exactly as an MCP client would, over a real pipe.
|
||||
|
||||
Each test sends a complete JSON-RPC session through stdin and asserts that
|
||||
every byte on stdout is valid JSON — catching the exact failure mode from
|
||||
#1134 where progress output corrupted the transport stream.
|
||||
"""
|
||||
|
||||
TIMEOUT = 30
|
||||
|
||||
def _run(self, *requests: bytes) -> subprocess.CompletedProcess:
|
||||
return subprocess.run(
|
||||
[sys.executable, "-m", "mcp"],
|
||||
input=b"".join(requests),
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
timeout=self.TIMEOUT,
|
||||
cwd=_repo_root(),
|
||||
env=_subprocess_env(),
|
||||
check=False,
|
||||
)
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
def test_initialize_stdout_is_valid_json_rpc(self):
|
||||
"""An initialize request must produce a single valid JSON-RPC response."""
|
||||
proc = self._run(_INIT_REQUEST)
|
||||
|
||||
self.assertEqual(proc.returncode, 0,
|
||||
f"server crashed:\n{proc.stderr.decode()}")
|
||||
responses = _assert_stdout_is_clean_json(
|
||||
self, proc.stdout.decode(), proc.stderr.decode()
|
||||
)
|
||||
init_resp = next((r for r in responses if r.get("id") == 1), None)
|
||||
self.assertIsNotNone(init_resp, f"No id=1 response in: {responses}")
|
||||
self.assertIn("serverInfo", init_resp.get("result", {}))
|
||||
|
||||
def test_tools_call_stdout_is_clean_json_rpc(self):
|
||||
"""A tools/call round-trip through the full stdio framing loop must keep
|
||||
stdout free of any non-JSON bytes.
|
||||
|
||||
run_reasoning is used because Reasoner.infer_with_results() explicitly
|
||||
calls self.progress_tracker.start_tracking(), making it the minimal
|
||||
deterministic tool path that exercises the progress-rendering code.
|
||||
Before the #1134 fix, that start_tracking call wrote a progress bar
|
||||
directly to stdout, corrupting the JSON-RPC framing. Every byte on
|
||||
stdout must still be valid JSON-RPC after the fix.
|
||||
"""
|
||||
proc = self._run(
|
||||
_INIT_REQUEST,
|
||||
_jsonrpc("notifications/initialized", None),
|
||||
_jsonrpc("tools/call", 2, {
|
||||
"name": "run_reasoning",
|
||||
"arguments": {
|
||||
"facts": ["Person(Alice)", "Employee(Alice)"],
|
||||
"rules": ["IF Employee(?x) THEN Worker(?x)"],
|
||||
},
|
||||
}),
|
||||
)
|
||||
self.assertEqual(proc.returncode, 0,
|
||||
f"server crashed:\n{proc.stderr.decode()}")
|
||||
|
||||
stdout = proc.stdout.decode()
|
||||
stderr = proc.stderr.decode()
|
||||
responses = _assert_stdout_is_clean_json(self, stdout, stderr)
|
||||
|
||||
tool_resp = next((r for r in responses if r.get("id") == 2), None)
|
||||
self.assertIsNotNone(
|
||||
tool_resp,
|
||||
f"No id=2 response in stdout.\nstdout={stdout!r}\nstderr={stderr!r}",
|
||||
)
|
||||
# The framing must be a valid JSON-RPC result object regardless of
|
||||
# whether the reasoner dependency is available in this environment.
|
||||
self.assertIn("jsonrpc", tool_resp)
|
||||
self.assertEqual(tool_resp["jsonrpc"], "2.0")
|
||||
self.assertIn("id", tool_resp)
|
||||
# If the tool succeeded the response must carry MCP content.
|
||||
if "result" in tool_resp:
|
||||
content = tool_resp["result"].get("content", [])
|
||||
self.assertGreater(len(content), 0,
|
||||
"Expected non-empty content list in result")
|
||||
# The embedded tool payload must itself be valid JSON.
|
||||
inner = json.loads(content[0]["text"])
|
||||
self.assertIn("derived_facts", inner)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -249,6 +249,191 @@ class TestPineconeIndex(unittest.TestCase):
|
||||
mock_index.query.assert_called_once()
|
||||
|
||||
|
||||
class TestPineconeIterAll(unittest.TestCase):
|
||||
"""PineconeStore.iter_all() list-then-fetch enumeration."""
|
||||
|
||||
def _page(self, ids, next_token):
|
||||
"""Stand-in for a list_paginated() response."""
|
||||
response = MagicMock()
|
||||
response.vectors = [MagicMock(id=vector_id) for vector_id in ids]
|
||||
response.pagination = MagicMock(next=next_token)
|
||||
return response
|
||||
|
||||
def _store(self, pages, fetch_results):
|
||||
store = PineconeStore()
|
||||
wrapper = MagicMock()
|
||||
raw_index = MagicMock()
|
||||
raw_index.list_paginated.side_effect = list(pages)
|
||||
wrapper.index = raw_index
|
||||
wrapper.fetch_vectors.side_effect = list(fetch_results)
|
||||
store.index = wrapper
|
||||
return store, wrapper, raw_index
|
||||
|
||||
@patch('semantica.vector_store.pinecone_store.PINECONE_AVAILABLE', True)
|
||||
def test_threads_pagination_token_across_pages(self):
|
||||
store, _, raw_index = self._store(
|
||||
[self._page(["a", "b"], "token-1"), self._page(["c"], None)],
|
||||
[
|
||||
{"vectors": {"a": {"values": [0.1], "metadata": {}},
|
||||
"b": {"values": [0.2], "metadata": {}}}},
|
||||
{"vectors": {"c": {"values": [0.3], "metadata": {}}}},
|
||||
],
|
||||
)
|
||||
|
||||
result = list(store.iter_all(batch_size=2))
|
||||
|
||||
self.assertEqual([item["id"] for item in result], ["a", "b", "c"])
|
||||
calls = raw_index.list_paginated.call_args_list
|
||||
self.assertNotIn("pagination_token", calls[0][1])
|
||||
self.assertEqual(calls[1][1]["pagination_token"], "token-1")
|
||||
|
||||
@patch('semantica.vector_store.pinecone_store.PINECONE_AVAILABLE', True)
|
||||
def test_hydrates_listed_ids_with_a_fetch(self):
|
||||
"""Listing returns ids only, so each page needs a fetch()."""
|
||||
store, wrapper, _ = self._store(
|
||||
[self._page(["a"], None)],
|
||||
[{"vectors": {"a": {"values": [0.1, 0.2], "metadata": {"tag": "x"}}}}],
|
||||
)
|
||||
|
||||
item = list(store.iter_all())[0]
|
||||
|
||||
self.assertEqual(item["id"], "a")
|
||||
self.assertEqual(item["metadata"], {"tag": "x"})
|
||||
np.testing.assert_allclose(item["vector"], np.array([0.1, 0.2]))
|
||||
wrapper.fetch_vectors.assert_called_once_with(["a"], namespace="")
|
||||
|
||||
@patch('semantica.vector_store.pinecone_store.PINECONE_AVAILABLE', True)
|
||||
def test_list_and_fetch_use_the_same_namespace(self):
|
||||
store, wrapper, raw_index = self._store(
|
||||
[self._page(["a"], None)],
|
||||
[{"vectors": {"a": {"values": [0.1], "metadata": {}}}}],
|
||||
)
|
||||
|
||||
list(store.iter_all(namespace="prod"))
|
||||
|
||||
self.assertEqual(raw_index.list_paginated.call_args[1]["namespace"], "prod")
|
||||
wrapper.fetch_vectors.assert_called_once_with(["a"], namespace="prod")
|
||||
|
||||
@patch('semantica.vector_store.pinecone_store.PINECONE_AVAILABLE', True)
|
||||
def test_skips_ids_deleted_between_list_and_fetch(self):
|
||||
"""fetch() omits ids it cannot find rather than returning blanks."""
|
||||
store, _, _ = self._store(
|
||||
[self._page(["a", "gone"], None)],
|
||||
[{"vectors": {"a": {"values": [0.1], "metadata": {}}}}],
|
||||
)
|
||||
|
||||
result = list(store.iter_all())
|
||||
|
||||
self.assertEqual([item["id"] for item in result], ["a"])
|
||||
|
||||
@patch('semantica.vector_store.pinecone_store.PINECONE_AVAILABLE', True)
|
||||
def test_raises_when_pagination_token_repeats(self):
|
||||
"""A stalled token must not loop forever, nor quietly return a partial
|
||||
scan that reads as a complete one."""
|
||||
store, _, raw_index = self._store(
|
||||
[self._page(["a"], "same"), self._page(["b"], "same")],
|
||||
[
|
||||
{"vectors": {"a": {"values": [0.1], "metadata": {}}}},
|
||||
{"vectors": {"b": {"values": [0.2], "metadata": {}}}},
|
||||
],
|
||||
)
|
||||
|
||||
with self.assertRaises(ProcessingError):
|
||||
list(store.iter_all())
|
||||
|
||||
self.assertEqual(raw_index.list_paginated.call_count, 2)
|
||||
|
||||
@patch('semantica.vector_store.pinecone_store.PINECONE_AVAILABLE', True)
|
||||
def test_empty_listing_yields_nothing_without_fetching(self):
|
||||
store, wrapper, _ = self._store([self._page([], None)], [])
|
||||
|
||||
self.assertEqual(list(store.iter_all()), [])
|
||||
wrapper.fetch_vectors.assert_not_called()
|
||||
|
||||
@patch('semantica.vector_store.pinecone_store.PINECONE_AVAILABLE', True)
|
||||
def test_continues_past_an_empty_page_with_a_live_token(self):
|
||||
"""An empty page is not necessarily the end: Pinecone can legitimately
|
||||
list zero ids for a page while pagination.next is still set (sparse
|
||||
or filtered namespaces, eventual-consistency windows on serverless
|
||||
indexes). Only the absence of a next token means exhaustion."""
|
||||
store, wrapper, raw_index = self._store(
|
||||
[
|
||||
self._page(["a"], "token-1"),
|
||||
self._page([], "token-2"), # empty page, but the token still advances
|
||||
self._page(["b"], None),
|
||||
],
|
||||
[
|
||||
{"vectors": {"a": {"values": [0.1], "metadata": {}}}},
|
||||
{"vectors": {"b": {"values": [0.2], "metadata": {}}}},
|
||||
],
|
||||
)
|
||||
|
||||
result = list(store.iter_all(batch_size=1))
|
||||
|
||||
self.assertEqual([item["id"] for item in result], ["a", "b"])
|
||||
self.assertEqual(raw_index.list_paginated.call_count, 3)
|
||||
# Nothing to hydrate on the empty page, so only two fetches happen.
|
||||
self.assertEqual(wrapper.fetch_vectors.call_count, 2)
|
||||
|
||||
@patch('semantica.vector_store.pinecone_store.PINECONE_AVAILABLE', True)
|
||||
def test_accepts_plain_string_ids_from_listing(self):
|
||||
"""SDK generations differ on what listing yields."""
|
||||
store, _, _ = self._store(
|
||||
[self._page([], None)],
|
||||
[{"vectors": {"a": {"values": [0.1], "metadata": {}}}}],
|
||||
)
|
||||
response = MagicMock()
|
||||
response.vectors = ["a"]
|
||||
response.pagination = MagicMock(next=None)
|
||||
store.index.index.list_paginated.side_effect = [response]
|
||||
|
||||
self.assertEqual([item["id"] for item in store.iter_all()], ["a"])
|
||||
|
||||
@patch('semantica.vector_store.pinecone_store.PINECONE_AVAILABLE', True)
|
||||
def test_handles_missing_values_and_metadata(self):
|
||||
store, _, _ = self._store(
|
||||
[self._page(["a"], None)],
|
||||
[{"vectors": {"a": {"values": None, "metadata": None}}}],
|
||||
)
|
||||
|
||||
item = list(store.iter_all())[0]
|
||||
|
||||
self.assertIsNone(item["vector"])
|
||||
self.assertEqual(item["metadata"], {})
|
||||
|
||||
@patch('semantica.vector_store.pinecone_store.PINECONE_AVAILABLE', True)
|
||||
def test_raises_when_list_paginated_unavailable(self):
|
||||
store = PineconeStore()
|
||||
wrapper = MagicMock()
|
||||
wrapper.index = MagicMock(spec=["query", "fetch"])
|
||||
store.index = wrapper
|
||||
|
||||
with self.assertRaises(ProcessingError):
|
||||
list(store.iter_all())
|
||||
|
||||
@patch('semantica.vector_store.pinecone_store.PINECONE_AVAILABLE', True)
|
||||
def test_raises_when_index_not_initialized(self):
|
||||
"""Must fail loudly: an empty scan reads the same as an empty source."""
|
||||
with self.assertRaises(ProcessingError):
|
||||
list(PineconeStore().iter_all())
|
||||
|
||||
@patch('semantica.vector_store.pinecone_store.PINECONE_AVAILABLE', False)
|
||||
def test_raises_when_pinecone_unavailable(self):
|
||||
store = PineconeStore()
|
||||
store.index = MagicMock()
|
||||
|
||||
with self.assertRaises(ProcessingError):
|
||||
list(store.iter_all())
|
||||
|
||||
@patch('semantica.vector_store.pinecone_store.PINECONE_AVAILABLE', True)
|
||||
def test_propagates_listing_errors(self):
|
||||
store, _, raw_index = self._store([], [])
|
||||
raw_index.list_paginated.side_effect = RuntimeError("connection reset")
|
||||
|
||||
with self.assertRaises(RuntimeError):
|
||||
list(store.iter_all())
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
print("DEBUG: Starting unittest.main()")
|
||||
unittest.main()
|
||||
|
||||
Reference in New Issue
Block a user