Compare commits

...
Author SHA1 Message Date
KaifAhmad1andClaude Sonnet 5 1003492e7c fix(release): sync CITATION.cff to v0.6.8
Qodo review on #1476 caught that CITATION.cff still declared v0.6.7 /
2026-08-28, which would have made GitHub's generated citation disagree
with the package metadata and CHANGELOG this same PR bumps to v0.6.8.
Not caught by the existing release-prep process since CITATION.cff was
only added in #1266, after that process was last documented.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-05 19:05:14 +05:30
KaifAhmad1andClaude Sonnet 5 4ae45c82f8 chore(release): prepare v0.6.8
Bump version, cut CHANGELOG's Unreleased section into 0.6.8, backfill
changelog entries for the 96 PRs merged since v0.6.7 that were missing
from it, and refresh version-dependent references in README/docs.

This release exists primarily to ship the release-signing hardening
that landed in #1266/#1329 (SLSA build-provenance attestation +
Sigstore signing, with .sigstore.json bundles attached to the GitHub
Release) — v0.6.7 was tagged two days before that fix merged, so every
release OpenSSF Scorecard's Signed-Releases check has seen so far
predates it. Cutting v0.6.8 is what actually exercises the fix.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-05 19:00:25 +05:30
ce0ae1cb88 refactor(context): expose public temporal normalizer (#1455)
* refactor(context): expose public temporal normalizer

* test(context): strengthen temporal normalizer coverage

---------

Co-authored-by: Sameer Kadam <sskadam6305@gmail.com>
Co-authored-by: Sameer Kadam <sameerkadam@Sameers-MacBook-Air.local>
2026-09-05 16:14:01 +05:30
Mahmoud Ahmed a59944971e fix: correct production env var to SEMANTICA_API_KEY (fixes #1429) (#1473) 2026-09-05 14:04:18 +05:00
Zohaib Hassnain 820f7257f2 docs(decision-intelligence): fix broken add_decision pattern and hybrid search description (#1466) 2026-09-05 00:38:55 +05:00
Mohd Kaif 230d91d794 Update README to refine project description
Removed 'open-source' from the description to streamline the message.
2026-09-04 23:30:53 +05:30
Mohd Kaif 652c50b45d Revise project description in README
Updated the description to emphasize developer-first approach and open-source nature.
2026-09-04 23:27:13 +05:30
Mohd Kaif 97ae95c352 Merge pull request #1437 from pkupt/fix/1430-shacl-limit-env
explorer: name SHACL limit env vars in error messages
2026-09-04 21:23:53 +05:30
Mohd Kaif 60c8bac866 Merge branch 'main' into fix/1430-shacl-limit-env 2026-09-04 20:58:12 +05:30
b82ae98417 docs(setup): tighten prose across quickstart, installation, and CLI s… (#1459)
* docs(setup): tighten prose across quickstart, installation, and CLI setup guides

* fix(cli-setup): correct semantica-server default binding address

The server.py main() binds to 127.0.0.1 by default and documents
SEMANTICA_HOST as the override to expose beyond localhost.
The previous documentation (from main and carried through this PR)
incorrectly stated 0.0.0.0:8000, which would lead users to believe
the server is network-accessible by default.

Fix all three occurrences in cli-setup.md:
- Installed Commands table
- When to Use Each Command prose bullet
- REST server usage example code comment

---------

Co-authored-by: AutoHarness Bot <bot@autoharness.local>
Co-authored-by: Mohd Kaif <98801504+KaifAhmad1@users.noreply.github.com>
Co-authored-by: Sameer Kadam <sskadam6305@gmail.com>
2026-09-04 20:35:05 +05:30
4568aeba2c docs: tighten prose in community and governance pages (#1442)
Co-authored-by: AutoHarness Bot <bot@autoharness.local>
Co-authored-by: Sameer Kadam <sskadam6305@gmail.com>
2026-09-04 20:13:20 +05:30
08d6ec62dc docs(cookbook): tighten prose across notebook catalogue (#1457)
* docs(cookbook): tighten prose across notebook catalogue

* Resolved comments

---------

Co-authored-by: AutoHarness Bot <bot@autoharness.local>
Co-authored-by: Sameer Kadam <sskadam6305@gmail.com>
2026-09-04 20:04:53 +05:30
6c40dea0c7 docs(architecture): tighten core principles and scalability prose (#1458)
Co-authored-by: AutoHarness Bot <bot@autoharness.local>
Co-authored-by: Sameer Kadam <sskadam6305@gmail.com>
2026-09-04 19:51:22 +05:30
478b00243b docs(resources): tighten prose across FAQ, learning paths, citation, … (#1456)
* docs(resources): tighten prose across FAQ, learning paths, citation, and license

* docs(faq): cut two leftover chained/dramatic-pause colons

"unstructured data: documents, APIs, databases: into structured..."
chained two colons in one sentence, and "...reached a conclusion:
not just what it said" is the exact "X: not Y" pattern #1426 flags
for removal. Neither was touched by this PR's original pass.

* docs_check: raise Mintlify export timeout from 300s to 600s

The "Validate Documentation" CI check has been timing out at exactly
300s on this branch, on main, and on an unrelated branch in the same
window (3 consecutive retries here, all with the same "timed out
after 300 s" error and no other diagnostic output). Local runs finish
well under the limit, so this isn't a content problem — the export
step just has no headroom left as the docs site has grown to 27
modules. Doubling the timeout gives it room without masking real
export failures, which still fail immediately with their own error.

---------

Co-authored-by: AutoHarness Bot <bot@autoharness.local>
Co-authored-by: KaifAhmad1 <kaifahmad087@gmail.com>
2026-09-04 17:19:47 +05:30
66cf952047 docs(glossary): tighten prose and remove colon pauses in glossary (#1454)
* docs(glossary): tighten prose and remove colon pauses

* Resolved Comments

---------

Co-authored-by: AutoHarness Bot <bot@autoharness.local>
Co-authored-by: Mohd Kaif <98801504+KaifAhmad1@users.noreply.github.com>
2026-09-04 16:37:01 +05:30
Mohd Kaif c2edf7e7ab Merge pull request #1453 from Deep070203/docs-modules-selection-guidance
docs(modules): tighten prose, remove chained colons, and use develope…
2026-09-04 16:31:31 +05:30
KaifAhmad1 24668f1d85 docs(modules): cut leftover chained colon in MCP Server integrations line
"Claude Desktop, ..., Cline: 15 MCP tools exposed" chained a second
colon onto the "Integrations:" label colon, the exact pattern #1426
asks this cleanup pass to remove. Split into two sentences.
2026-09-04 16:13:59 +05:30
Hitesh_GandZohaib Hassnain [109234410+ZohaibHassan16@users.noreply.github.com](mailto:109234410+ZohaibHassan16@users.noreply.github.com) 6b8122d757 This is resubmit of the pr for issue feat(integrations): add Google ADK support (#1312)
Adds Google ADK (Agent Development Kit) support to Semantica.

`semantica_kg_tools()` and `semantica_decision_tools()` expose entity/relation extraction, graph updates, and decision recording as ADK `FunctionTool`s. `SemanticaSessionService` implements ADK session storage on top of a Semantica `ContextGraph`, so session state, events, and knowledge graph data can live in the same graph instead of keeping sessions in memory.

There were also a number of dependency and CI fixes needed to get the integration working reliably. `google-adk` is pinned to a range that avoids the CI `websockets` conflict, the deprecated `pinecone-client` dependency was replaced with `pinecone`, Windows-only dependencies now have the appropriate platform markers, and `requirements-ci.txt` was regenerated to match. A `pip-audit` pass also required updates to `google-adk` and `starlette` for known CVEs.

Some unrelated `pyproject.toml` changes had slipped in during rebases, so the previous version, dependency bounds, `ingest-sap`/LangChain entries, and package-data settings were restored.

A few bugs in the initial ADK implementation were fixed during review:

* `extract_relations()` was calling `RelationExtractor.extract_entities()`, which doesn't exist on that extractor. The failure was being caught and returned in the tool's `error` field, leaving callers with an empty relation list. It now calls the correct extraction path.
* The repo's top-level `mcp/` package shadowed the third-party `mcp` package imported by `google.adk`, causing `google.adk` imports to fail from a normal repo checkout. The local package was moved to `semantica_mcp/mcp/`.

The MCP move needed a follow-up as well. `semantica/cli.py` and four existing tests were still importing from `mcp.*`, and the modules under `semantica_mcp/mcp/` still used the old absolute imports internally. `semantica_mcp` was also missing from the setuptools package include list and had no `__init__.py`, so it wouldn't have been included in an installed package. Those imports and packaging settings are fixed now.

The session service and ADK tools also had a few other problems:

* `list_sessions()` returned a plain list instead of ADK's `ListSessionsResponse`. The original import for that type doesn't work against the installed `google-adk` package, so it was silently falling back to a stub. `user_id` was also incorrectly required instead of being optional.
* Session node IDs were built by joining `app_name`, `user_id`, and `session_id` with unescaped colons, which allowed different identities to produce the same graph node ID. Each component is now encoded before joining.
* `kg_tools.py` and `decision_tools.py` each had their own lock registry and default graph instance. Sharing a graph between the two modules therefore didn't share the lock, and using both factories without an explicit graph produced two different defaults. The shared state now lives in one module used by both.
* `add_to_graph` had a `TypeError` compatibility fallback that couldn't succeed with the current `RelationExtractor` API and could hide the original extraction error. That fallback was removed.
* `append_event` persisted partial streaming events even though ADK's base session service skips them.
* `get_session()` ignored its `config` argument, so `num_recent_events` and `after_timestamp` had no effect.
* The async session-service methods performed synchronous graph scans while holding a `threading.RLock` on the event loop thread. That work now runs in worker threads with `asyncio.to_thread()` so a slow or contended graph operation doesn't block the loop.

---

Co-authored-by: Zohaib Hassnain [109234410+ZohaibHassan16@users.noreply.github.com](mailto:109234410+ZohaibHassan16@users.noreply.github.com)
2026-09-04 15:34:48 +05:00
Mohd Kaif be438c2201 Merge branch 'main' into docs-modules-selection-guidance 2026-09-04 16:03:33 +05:30
Mohd Kaif dc99b11d71 Merge pull request #1452 from Deep070203/docs-contributing-prose
docs(contributing): tighten prose and use developer-first guidance
2026-09-04 15:39:53 +05:30
Mohd Kaif 172b9ff718 Merge branch 'main' into docs-contributing-prose 2026-09-04 15:29:51 +05:30
KaifAhmad1 22fde95d7c docs(community-projects): fill in blank LLM provider table cells
Google Gemini, HuggingFace, DeepSeek, and Novita AI rows were left as
a stray colon with no notes. Fill them in to match the pattern of the
other rows, sourced from semantica/semantic_extract/providers.py.
2026-09-04 15:27:01 +05:30
Yuang PengandSameer Kadam b92fed2d0a feat(explorer): add Markdown editor write path (#1349)
* feat(explorer): add Markdown editing write path

* fix(explorer): address markdown editor review findings

* feat(explorer): add Markdown editor write path

---------

Co-authored-by: Sameer Kadam <sskadam6305@gmail.com>
2026-09-04 01:38:17 +05:30
AutoHarness Bot 8ede86f643 Resolved comments 2026-09-03 15:29:41 -04:00
AutoHarness Bot ace7adbbdd docs(contributing): tighten prose and use developer-first guidance 2026-09-03 14:59:10 -04:00
AutoHarness Bot 3e942cbea0 docs(modules): tighten prose, remove chained colons, and use developer-first guidance 2026-09-03 14:57:23 -04:00
Mohd Kaif 8910d2c949 docs(ontology): document quality gate threshold semantics (#1450)
* docs(ontology): document quality gate threshold semantics

The Ontology Quality Gate section (#1397) showed a thresholds={...}
example but never explained what min_coverage, max_errors,
max_warnings, or fail_on_warnings actually mean, or that
fail_on_warnings is a separate parameter rather than a thresholds
key. Add a concise defaults/semantics table, verified against
OntologyQualityGate.DEFAULT_THRESHOLDS and __init__ in quality_gate.py.

* docs(ontology): explain thresholds as prose instead of a table

A four-row table was heavier than this needed; each threshold's
meaning reads faster as two connected sentences.
2026-09-03 23:40:00 +05:30
Mohd Kaif 4c6eab632f Merge pull request #1397 from T1mn/codex/ontology-quality-gate
feat(ontology): add a CI-friendly quality gate
2026-09-03 23:20:39 +05:30
Mohd Kaif 103ab04970 Merge branch 'main' into codex/ontology-quality-gate 2026-09-03 23:05:32 +05:30
Zohaib Hassnain 88a57b39f9 docs: replace retired claude-sonnet-4-20250514 model id (#1449)
* docs: replace retired claude-sonnet-4-20250514 model id

* docs(llms): sweep retired model as qodo found
2026-09-03 22:29:50 +05:00
Zohaib HassnainandMohd Kaif f45499b5a7 fix(deps): unblock python 3.9 core install (#1445)
Co-authored-by: Mohd Kaif <98801504+KaifAhmad1@users.noreply.github.com>
2026-09-03 22:51:43 +05:30
Zohaib Hassnain c04adcd1a9 docs(semantic-extraction): fix summary output and model ID (#1448)
* docs(semantic-extraction): fix summary print and the model id

* chore: correct model id

* docs: make triplet valid split meaningful
2026-09-03 22:14:23 +05:00
Zohaib Hassnain a85cf913a5 docs(reasoning): clarify Datalog query result ordering (#1447)
* docs(reasoning): note DatalogReasoner.query() result is not guaranteed

* keep lists[dict] shape
2026-09-03 21:58:39 +05:00
Mohd Kaif 6ba433fea0 docs(index): rewrite landing page as a crisp developer welcome (#1446)
Replace the long feature-dump landing page with a lean "Welcome to
Semantica" page: a two-line problem/positioning statement (deterministic
semantic layer, no LLM required for graph construction, reasoning, or
provenance), five capability bullets, the multi-provider quickstart
snippet, and a 4-step onboarding path. Drops the redundant module
table, industry-use-case grid, and duplicate link lists in favor of
linking out to Core Concepts, guides, and the API reference. Keeps a
collapsed module-list accordion so the page still satisfies
docs_check.py's full-module-coverage check.
2026-09-03 22:26:38 +05:30
Guofang.Tang 75bcb64681 Merge branch 'main' into codex/ontology-quality-gate 2026-09-04 00:34:54 +08:00
Zohaib Hassnain f6a0e4a32e docs(pipeline): wire configured FailureHandler into the engine (#1444) 2026-09-03 21:17:02 +05:00
Mohd Kaif 111bcf997e Revamp badges and add community links in README
Updated badge styles and added community links.
2026-09-03 21:36:23 +05:30
Zohaib Hassnain 6a07ad29be docs(modules): fix code examples to match the current API (#1443)
* docs: rewrite every code example against the actual API

* docs: address Qodo
2026-09-03 21:03:54 +05:00
Mohd Kaif 064f0eccad Merge pull request #1432 from taljeon/codex/docs-langchain-prose
docs(langchain): tighten integration prose
2026-09-03 21:03:24 +05:30
pkupt af56bd865d test: assert SHACL limit messages name the env var 2026-09-03 21:16:10 +08:00
pkupt 3845fa7206 fix: name SHACL limit env vars in error messages
The three limit-exceeded messages in validate_shacl now name the env var
that controls each limit, and the SHACL validation guide documents all
four resource-limit variables with their defaults.
2026-09-03 21:16:10 +08:00
taljeon afec253451 docs(langchain): tighten integration prose 2026-09-03 21:58:36 +09:00
Wei Tao dd1e654047 fix(explorer): load registered schemas in Ontology Editor (#1278)
The Ontology Hub editor selected a registered ontology but left the canvas empty, and opening an ontology deep link landed on the Welcome workspace instead of the editor. Two independent causes: the application shell ignored `ontologyTab`/`ontologyEntity` URL state at startup, and the editor loaded registry metadata but never fetched the selected ontology's schema nodes and structural edges. The backend now exposes a bounded schema subgraph for one ontology at `GET /api/ontology/graph?uri=...`, and the editor maps that response into React Flow nodes and edges with loading, error, selection, and layout handling.

Five things came out of review on the new endpoint and the editor that consumes it.

The edge selection originally included an edge whenever either its source or its target was a core node. That let a property owned by a completely unrelated ontology leak into the requested one just because its `rdfs:domain` or `rdfs:range` happened to point at one of the requested ontology's classes. Edges are now selected only when their source is a core node, so the requested ontology can still reference outward to external vocabulary, but nothing from an unrelated ontology gets pulled in the other direction.

The backend accepts both compact and full-IRI forms for node types (`owl:Class` and `http://www.w3.org/2002/07/owl#Class` are equivalent), but the frontend classifier only recognized the compact strings, so a full-IRI class or ontology node fell through to `"external"`, wrong panel, wrongly read-only. Classification moved into `ontologyEditorModel.ts` as `classifyNodeType`, which compacts known full IRIs before matching.

An ontology imported through the fallback RDF parser, one with no `owl:Ontology` or `skos:ConceptScheme` declaration, minted a synthetic registry URI but never created a matching graph node or set `scheme_uri` on the classes and properties it imported. `_node_belongs_to_ontology` had nothing to associate those nodes with, so `core_node_ids` ended up empty and the endpoint 404'd for a registered ontology that genuinely had data. The fallback parser now records that ownership and emits a matching `owl:Ontology` node whenever it has to synthesize a URI.

Nested namespaces that were never registered as their own ontology got silently absorbed into whichever parent prefix matched, in both directions: a fragment-delimited nested name (`<stem>/child#Term`) and a path-delimited one (`<stem>/child/Term`). The first fix only handled the fragment form; prefix ownership now only extends to names minted directly in the ontology's own namespace (`<stem>#Term` or `<stem>/Term`), and any further delimiter of either kind marks a nested vocabulary that isn't absorbed until it's registered or carries an explicit owner. Once registered, the nested namespace owns its own nodes as before.

Selecting a node in the editor writes `ontologyEntity=<id>` into the URL. Switching ontologies via the dropdown cleared the in-memory selection but left that parameter pointing at the old ontology, so a reload after switching could resolve the stale ID and jump back. The dropdown now clears the parameter on change.

Regression tests cover each fix directly: inward-edge exclusion, the full-IRI classification matrix, an end-to-end fallback-import test that forces the parser path and opens the resulting ontology, and a nested-namespace ownership matrix covering both delimiter forms in both the registered and unregistered case.
2026-09-03 17:58:20 +05:00
KevinandSameer Kadam 6b8437781e fix(ontology): stop inferring framework entity fields as datatype properties (#1420)
* fix(ontology): stop inferring framework entity fields as datatype properties

* test(ontology): assert framework fields do not leak as datatype properties

* test(ontology): fix test file formatting

* test(ontology): cover unmerged graphbuilder entities

---------

Co-authored-by: Sameer Kadam <sskadam6305@gmail.com>
2026-09-03 18:24:52 +05:30
Zohaib Hassnain ba85215aea docs(graphrag): fix broken string literals and clarify max_hops (#1431)
The Banking domain example built basel_cre20_text / bcbs239_text with bare indented string continuations (no parens, no backslash), raising IndentationError. Wrapped both in parentheses like the Clinical example. Separately, the guide passed max_hops= to retrieve() and stated it overrides the constructor's expansion depth: it does not. AgentContext.retrieve(max_hops=) is only consumed by _apply_proximity_metadata (a proximity-radius filter that needs anchor_node), and expansion depth is fixed by max_expansion_hops passed into ContextRetriever. Removed max_hops from the non-anchored retrieve call, annotated the anchored ones, corrected the intro and tuning sections, and noted query_with_reasoning() does take a real per-call max_hops. Also replaced an invented node/edge count comment with the real store() return keys and qualified an ingest_file() reference.
2026-09-03 17:48:07 +05:00
T1mn 8778e6a837 fix(ontology): address follow-up quality gate findings 2026-09-03 19:57:04 +08:00
Mohd Kaif 5809418421 docs: tighten prose in concepts.md, guides/graphrag.md, reference/context.md (#1422)
Flagship pass establishing the crisp-prose style for the rest of
docs/: remove em dashes from explanatory prose (leave them in
simulated document/alert string literals, which are data, not our
voice), replace colon-as-dramatic-pause constructions, and fix two
broken relative links in reference/context.md ([Reasoning](reasoning)
and [Provenance](provenance) were missing their leading slash and
would 404 on the live site, the same class of bug fixed sitewide in
PR #1407). concepts.md's intro also picks up the new context/semantic
layer tagline. No code examples, tables, or technical content
changed.
2026-09-03 17:20:04 +05:30
Mohd Kaif 40efab6796 docs(index): cut marketing copy, remove em dashes, make crisp (#1421)
* docs(index): cut marketing copy, remove em dashes, make crisp

Replace the narrative hook and rhetorical-question opening with a
direct statement. Trim the persuasive framing on the problem list
and industry section to plain, factual bullets. Replace every em
dash with plain sentence structure or a colon, and drop the
repeated colon-as-dramatic-pause construction from the opening.
No content or links removed; only the framing and punctuation
changed.

* docs: update tagline to context/semantic layer for high-stakes domains

Replace "The Accountability and Context Layer for AI" with "The
Context and Semantic Layer for AI in High-Stakes Domains" across
docs.json (description, og:title) and index.md (frontmatter
description, opening sentence, and the Core Concepts step bullet).
Audit trail and accountability remain a downstream property, not
the headline framing.
2026-09-03 17:00:03 +05:30
Mohd Kaif 837654fc4f docs: restructure nav — drop FAQ/Changelog tabs, add API Reference tab (#1419)
Remove the standalone FAQ and Changelog top-level tabs. FAQ and
Community pages move into the Overview tab as their own groups
(still fully reachable, just relocated). Changelog was only an
external link to GitHub releases and had no pages of its own.

Split the API reference pages (reference/*) out of the Modules tab
into a new, dedicated API Reference tab, so Modules now holds only
the conceptual guides and API Reference holds every module's class
and function documentation.
2026-09-03 16:19:43 +05:30
Mohd Kaif 2daa937811 docs: simplify custom.css to a static, professional style (#1418)
Remove decorative hover animations (code block/card lift+glow, table
row highlighting, list item highlighting, animated nav underline,
button lift+glow) and the page-load fade-in transition. Keep the
color/typography branding, accessibility focus rings, and scrollbar
styling.
2026-09-03 15:59:36 +05:30
Mohd Kaif 9321b9d27e Merge pull request #1392 from pkupt/fix/1374-weaviate-delete
feat(weaviate): add delete_vectors to WeaviateStore
2026-09-03 15:53:00 +05:30
Mohd Kaif d5a7ea9f9a Merge branch 'main' into fix/1374-weaviate-delete 2026-09-03 15:47:37 +05:30
Zohaib Hassnain b872b29628 docs(concepts): rewrite code examples to match the actual API (#1417)
* docs(concepts): rewrite every code example against real API

* add Qodo review
2026-09-03 15:02:27 +05:00
Mohd Kaif 01352d5fd5 Merge branch 'main' into codex/ontology-quality-gate 2026-09-03 15:18:43 +05:30
Zohaib Hassnain bcc49f232d Merge branch 'main' into fix/1374-weaviate-delete 2026-09-03 14:41:05 +05:00
Zohaib Hassnain 9e8db764d1 docs(quickstart): read parsed full_text (#1415)
* docs(quickstart): read parsed full_text

* correct schema
2026-09-03 14:40:58 +05:00
Mohd Kaif d9ed017b8c Merge branch 'main' into fix/1374-weaviate-delete 2026-09-03 15:06:24 +05:30
Zohaib Hassnain 865aad54df docs(getting-started): fix broken APIs in the Knowledge Graph and GraphRAG tabs (#1414)
* docs(getting-started): fix broken APIs in KG and GraphRAG tabs

* docs: tighten GraphRAG example

* docs: use extract_text() so the PDF example doesn't keyError
2026-09-03 14:33:23 +05:00
Zohaib HassnainandSameer Kadam 2d776b7370 docs(evals): update docs for the current evals API (#1398)
* document evals API

* docs(evals): fix evaluator behavior details

---------

Co-authored-by: Sameer Kadam <sskadam6305@gmail.com>
2026-09-03 14:12:49 +05:00
b177fa7556 fix: replace mutable default arguments with None + in-body defaults (#1068)
* fix mutable default argument in graph_analyzer.py

* fix mutable default argument in kg_chunkers.py

* fix mutable default argument in methods.py

* Address review: move default-init code out of docstrings, default levels in split_hierarchical

Three findings from the Qodo review:

- analyze_temporal_evolution: the 'if metrics is None' block had landed
  inside the docstring, so it never executed and metrics_tracked came back
  None. Moved below the docstring where it runs.

- HierarchicalChunker.__init__: the same misplacement turned the docstring
  into a dead string constant and broke help()/introspection. Moved the
  default-init below it.

- split_hierarchical: the signature now defaults levels to None, but the
  body still ran 'in levels' membership tests — calling it without levels
  raised TypeError. Defaults to the documented hierarchy, matching the
  class-level default.

* test: add mutable-default regression tests for the three fixed sites

- tests/split/test_chunkers.py: TestMutableDefaultRegression (6 tests)
  - split_hierarchical() default levels and chunk_sizes stay independent across calls
  - HierarchicalChunker() default levels stay independent across instances

- tests/kg/test_kg.py: TestAnalyzeTemporalEvolutionMutableDefault (5 tests)
  - analyze_temporal_evolution() default metrics value is canonical
  - mutations to a returned metrics_tracked list do not affect the next call
  - explicit metrics override is forwarded and reflected in the return value
  - mutating an explicitly passed list does not corrupt a subsequent default call

All 96 tests in the two affected test files pass.

---------

Co-authored-by: Zohaib Hassnain <109234410+ZohaibHassan16@users.noreply.github.com>
Co-authored-by: Sameer Kadam <sskadam6305@gmail.com>
2026-09-03 13:18:03 +05:30
Mohd Kaif 4559ac6536 Merge pull request #1407 from Duansg/fix-1405
docs: convert relative page links to root paths to fix 404s on the live site
2026-09-03 13:02:30 +05:30
Mohd Kaif 39c35549d2 Merge branch 'main' into fix-1405 2026-09-03 12:53:38 +05:30
Mohd Kaif d54d74c810 Merge pull request #1410 from semantica-agi/fix-required-ci-checks-docs
ci: report required checks for docs-only PRs
2026-09-03 12:41:08 +05:30
Sameer6305 2086a21615 fix(ci): fail-closed detector, merge-base diff, any-depth markdown filter 2026-09-03 12:13:51 +05:30
Sameer6305 b4af22d724 ci: report required checks for docs-only PRs 2026-09-03 12:03:51 +05:30
Duansg a59688c6f9 additional fixes 2026-09-02 20:37:40 -07:00
Duansg 40466269b8 docs: convert relative page links to root paths to fix 404s on the live site 2026-09-02 20:20:55 -07:00
T1mn 6a55b8c3ee fix(ontology): address quality gate review findings 2026-09-03 10:04:46 +08:00
Zohaib Hassnain 38ae5b580b docs: fix two broken cookbook notebook links (#1403)
* docs: fix two dead notebook links

* docs(learning-more): describe the embeddings notebooks
2026-09-03 04:21:21 +05:00
Zohaib Hassnain 279fdbf15b docs(quickstart): qodo findings addressed (#1402) 2026-09-03 04:18:17 +05:00
Harsh Arora 45915e50a3 fix(context): vector_store=False must suppress AgentMemory's internal vector cascade in ErasureCoordinator (#1395)
* fix(erasure): ensure vector_store=False disables internal vector cascade in AgentMemory

* fix(erasure): ensure skip_vector=True does not orphan local vector ID tracking
2026-09-03 04:05:53 +05:00
Zohaib Hassnain b7b60d4a17 docs(quickstart): fix broken code against real APIs (#1401) 2026-09-03 04:05:19 +05:00
Zohaib Hassnain 25d2ea5fe9 docs: update stale latest version claims 2026-09-03 03:50:50 +05:00
Zohaib Hassnain 3c68cd12ad docs(mcp): correct tool count (#1399) 2026-09-03 03:40:52 +05:00
Sameer Kadam 798a7455e4 fix(mcp): complete persistence and setup fixes (#1394)
MCP's stdio transport uses stdout for JSON-RPC framing, so anything else written there corrupts every response after it. The original #1134 bug was progress-tracker output landing on stdout during tool calls that construct a `ContextGraph`, which is exactly what happens on any request that triggers reasoning or extraction. This PR closes out the remaining pieces of that fix: loading now goes through `load_from_file()` instead of the older `load()` path on the root graph, and mutations, `record_decision`, `add_entity`, `add_relationship`, now persist back to `SEMANTICA_KG_PATH` when it's configured, in both MCP server implementations (the root `mcp/` package and the packaged `semantica.mcp_server`), not just one.

Four things came out of review on top of that.

The stdio regression test originally exercised `get_graph_summary`, which doesn't touch the progress tracker at all, so it couldn't have caught the original bug. Swapped it for `run_reasoning`: `Reasoner.infer_with_results()` calls `progress_tracker.start_tracking()` directly, the exact call site that corrupted stdout before, so this is the minimal path that actually proves the fix. The test now spawns a real `python -m mcp` subprocess, sends it a `tools/call` for `run_reasoning`, and asserts every single line on stdout parses as JSON.

Loading a corrupt or unreadable `SEMANTICA_KG_PATH` used to fail silently and fall through to an empty graph, which meant the next mutation would happily save that empty graph over the original file. Both implementations now track whether the initial load actually succeeded. If it didn't, every mutation handler refuses to save and returns an error instead, so a broken file on disk stays broken rather than getting silently replaced with nothing. An empty file is treated differently: that's a fresh destination, not a corrupt one, and starts a normal empty graph without tripping the guard.

`save_to_file` used to `open(path, 'w')` and `json.dump` directly into the destination, so a crash or disk-full error mid-write could leave a truncated file as the only copy of the graph. It now writes to a temp file in the same directory, flushes, fsyncs, and only then `os.replace`s the destination, so the destination is always either the old contents or the new contents, never a partial write. The temp file gets cleaned up if anything fails before the replace.

And since a mutation is applied to the in-memory graph before the save happens, a save failure used to leave the in-memory graph ahead of what's on disk, an entity or decision the client thinks succeeded but that never made it to the file. `record_decision`, `add_entity`, and `add_relationship` all roll back the in-memory mutation now if `save_to_file` raises, so the client-visible state and the persisted state never diverge: either both hold the change or neither does.

104 tests passing across the MCP, persistence, and progress-tracking suites.
2026-09-03 03:35:20 +05:00
T1mn 471cbe7711 feat(ontology): add deterministic quality gate 2026-09-03 02:00:57 +08:00
Mohd Kaif bd584b7402 Update features list in README
Removed 'Self-Hostable' and 'Auditable' from the features list.
2026-09-02 22:21:41 +05:30
Mohd Kaif a4500f5b20 Merge pull request #1396 from semantica-agi/readme-enterprise-connectors-update
docs: tighten README audience list, add SAP connector mentions, log Salesforce ingestor
2026-09-02 21:50:51 +05:30
KaifAhmad1 bdd12e8ac6 fix: correct JWT auth requirements in changelog, add missing SAP install extra
- CHANGELOG: JWT Bearer requires username too, not just consumer_key + private key
- README: add pip install semantica[ingest-sap] to the install-extras list, which was missing despite SAP appearing in the supported-sources lists

Addresses Qodo review feedback on #1396.
2026-09-02 21:45:24 +05:30
KaifAhmad1 48204d4e02 docs: tighten README audience list, add SAP to connector mentions, log unreleased Salesforce ingestor
- Trim "Who it's for" bullets in README for concision
- Propagate SAP OData connector mentions across README's integration lists (was only in the What's New section)
- Add missing CHANGELOG entry for the unreleased Salesforce ingestor (#1240)
- Remove sample `semantica doctor` output lines from the quickstart snippet
2026-09-02 21:23:29 +05:30
Zohaib Hassnain 1bc873cbbd Merge pull request #1328 from semantica-agi/feat/pinecone-iter-all
feat(vector_store): add pinecone iter_all
2026-09-02 20:07:54 +05:30
KaifAhmad1 4dd88375e1 fix(vector_store): don't short-circuit pinecone iter_all() on an empty page
if not vector_ids: return fired before the continuation token was ever
checked. Pinecone's actual pagination contract is that a scan is only
exhausted when the response carries no pagination token -- a page can
legitimately list zero ids while pagination.next is still set (sparse
or filtered namespaces, eventual-consistency windows on serverless
indexes). This was flagged in review but the fix commit that followed
only addressed the separate repeated-token stall case, not this one.

Reproduced concretely against the unfixed code: a page with data,
followed by an empty page with a live token, followed by a page with
more data -- the last page was silently dropped with no error raised,
exactly the #1083 failure mode (store migrate reporting success after
copying only part of a collection).

Now the empty-page case skips the pointless fetch() call but still
falls through to the same next_token check every other path already
goes through, so a live token continues the scan and only a genuinely
absent token (or one that's stopped advancing) ends it.

Added test_continues_past_an_empty_page_with_a_live_token, the
"empty page + non-None next token" case the original review asked for
and that wasn't otherwise covered.
2026-09-02 19:54:24 +05:30
Mohd Kaif fc899c6966 Merge pull request #1326 from semantica-agi/feat/milvus-iter-all
feat(vector_store): add milvus iter_all
2026-09-02 19:37:54 +05:30
KaifAhmad1 98bd632585 Merge remote-tracking branch 'origin/main' into feat/milvus-iter-all 2026-09-02 19:20:55 +05:30
KevinandSameer Kadam 30a91a3a78 feat(evals): add per-metric objective support to runner (closes #1091) (#1092)
* chore: ignore .worktrees directory

* feat(evals): add eval metric and result models

* feat(evals): add evaluator registry

* feat(evals): add exact/regex/range/length evaluators

* feat(evals): add keyword/levenshtein/rouge/llm-as-judge evaluators

* feat(evals): add decision_scores composite evaluator

* feat(evals): add evaluation runner

* feat(evals): expose public API and module proxy

* fix(evals): resolve __all__ names and repair usage example

* docs(evals): add usage docs and changelog entry

* style(evals): tidy evaluator metadata and wiring comments

* fix(evals): honor expected arg and classify error metrics

* fix(evals): export get_evaluator and fix shared meta default

* fix(evals): guard provenance check against non-dict metadata

* docs: add objective layer design spec for semantica.evals

* docs: refine objective spec for consistency with AIP Evals semantics

* docs: add implementation plan for evals objective layer

* docs: fix plan tests to use module-level pytest import

* feat(evals): add per-metric objective support to runner

* docs(evals): document per-metric objectives

* docs(evals): fix minimize example threshold to demonstrate pass

* fix(evals): validate objective config shape strictly

* docs(evals): clarify objective examples and Boolean semantics

* fix(evals): honor direction-only minimize, fail fast on objectives, deep-merge case config

- minimize without threshold is now a no-op, matching maximize (issue #1091
  requires thresholds to be optional for both directions)
- objective config is parsed for every case before any target_fn/evaluator
  runs, so an invalid per-case objective rejects the run up front
- per-case evaluator config deep-merges over the global config so a case
  that overrides one setting keeps the run-level objective
- regression tests for all three, plus updated docs/CHANGELOG

Addresses 3 of 4 Qodo findings on #1092 (the 4th, 'result models defined
twice', is a false positive: types live in types.py)

* fix: finalize eval objectives review

---------

Co-authored-by: Sameer Kadam <sskadam6305@gmail.com>
2026-09-02 18:47:28 +05:30
Mohd Kaif 23126106a3 Merge pull request #1317 from semantica-agi/feat/weaviate-iter-all
feat(vector_store): add weaviate iter_all
2026-09-02 18:39:57 +05:30
Mohd Kaif 4b001b4c9d Merge branch 'main' into feat/weaviate-iter-all 2026-09-02 18:29:14 +05:30
pkupt df42a015b0 test(weaviate): cover delete_vectors and erasure integration 2026-09-02 20:45:22 +08:00
KaifAhmad1 6c9eb2296d fix(vector_store): don't treat an empty weaviate page as end of scan
iter_all() unconditionally returned on any empty fetch_objects() page,
regardless of pagination mode. That's safe for offset/single_page (an
empty page there is a direct, unambiguous statement about live rows),
but not for cursor mode: `after` has no server-issued continuation
value of its own, it's derived client-side from the last object's uuid,
so an empty page gives nothing to advance it with. If Weaviate's cursor
walks internal storage position rather than strict uuid order, a batch
can in principle land entirely on a gap (e.g. tombstoned objects) with
live data past it -- the same risk already confirmed and fixed for
Qdrant's scroll cursor in #1316. Reproduced concretely against the
pre-fix code: a full page followed by an empty page followed by a page
with real data silently dropped that last page with no error raised.

iter_all() now falls back to offset pagination once when a cursor-mode
page comes back empty, rather than assuming that's the end. Offset
addresses live rows directly by position and has no equivalent gap, so
an empty page there (or in single_page mode) is trustworthy and still
ends the scan immediately.

Also updates test_iter_all_empty_collection_yields_nothing and
test_iter_all_requests_vectors, which needed a second empty page now
that a genuinely empty collection takes two calls (cursor, then the
confirming offset check) to report as such.
2026-09-02 17:43:52 +05:30
KaifAhmad1 1ad17beaf6 fix(vector_store): sync qdrant iter_all() with #1316's stall-guard fix
This branch was forked from an earlier commit of feat/vector-store-iter-all
(#1316), before that PR fixed a false-positive/silent-truncation bug in
QdrantStore.iter_all(): an empty scroll page with a still-advancing cursor
(e.g. a window landing entirely on tombstoned points) was treated as the
end of the collection instead of continuing. Syncing qdrant_store.py,
vector_store.py, and their tests to #1316's current tip (fa967983) so this
branch doesn't reintroduce the already-fixed bug once merged. Content-only
sync of the 4 shared files (verified via diff against origin/feat/vector-store-iter-all)
rather than a full branch merge, to avoid pulling in unrelated main drift
that has landed on that branch since this one diverged.
2026-09-02 17:39:42 +05:30
Zohaib Hassnain 110f6deb1e Merge pull request #1316 from semantica-agi/feat/vector-store-iter-all
feat(vector_store): add iter_all enumeration for cursor-based backends
2026-09-02 17:37:11 +05:30
pkupt 9df54ffcd0 feat(weaviate): add delete_vectors to WeaviateStore 2026-09-02 20:02:37 +08:00
Zohaib Hassnain b8299b1427 chore: clean it 2026-09-02 17:19:19 +05:30
Zohaib Hassnain fa967983e6 fix(vector_store): dedupe qdrant record conversion, don't abort iter_all on a live cursor with an empty page 2026-09-02 17:19:19 +05:30
Zohaib Hassnain bbd423c50a fix(vector_store): raise on stalled pinecone pagination instead of truncating 2026-09-02 17:19:19 +05:30
Zohaib Hassnain fcdad56893 making it clean 2026-09-02 17:19:19 +05:30
Zohaib Hassnain 6b36379f15 feat(vector_store): add pinecone iter_all 2026-09-02 17:19:19 +05:30
Zohaib Hassnain f2e7b9ed75 fix(vector_store): raise instead of truncating when a qdrant scan cannot advance 2026-09-02 17:19:19 +05:30
Zohaib Hassnain 5e80ebd837 fix(vector_store): drop qdrant migrate wiring, keep iter_all only
VectorStore cannot actually migrate to or from qdrant yet. _init_backend_store constructs QdrantStore without connecting or selecting a collection, so reads raise a Collection not initialized error, and the facade store_vectors dispatches only to add/add_vectors while QdrantStore exposes insert_vectors, so writes raise NotImplementedError.

Both are pre-existing facade gaps that nothing had exposed, since migrate previously only allowed faiss/sqlite/pgvector. Adding qdrant to the allowlist claimed support that does not work end to end, so it is removed along with the dimension inference that only fires for backends missing a .dimension attribute. Tracked separately; this PR keeps just the iter_all primitive.
2026-09-02 17:19:19 +05:30
Zohaib Hassnain d175f894a4 feat(vector_store): add iter_all enumeration and wire up qdrant migration 2026-09-02 17:19:19 +05:30
Mohd Kaif 3d32254b07 Merge pull request #1390 from semantica-agi/fix/security-scan-pip-audit-migration
fix(ci): migrate security-scan from Safety to pip-audit
2026-09-02 16:39:01 +05:30
KaifAhmad1 b5199ae6e3 fix(ci): handle pip-audit skipped dependencies, restore manual trigger, fix stale docs
Addresses review feedback on this PR:

- Guard 2 and the PR-comment JS parser both required every dependency
  in pip-audit's report to carry an array-valued `vulns` field. A
  dependency pip-audit can't resolve/audit is reported instead as
  {"name": ..., "skip_reason": ...} with no `vulns` key at all (see
  pip_audit._format.json.JsonFormat._format_dep) - a normal, documented
  shape, not a malformed one. That made a single unauditable package
  hard-fail the whole job and show "Invalid report structure" in the PR
  comment, reintroducing the same class of scan-unrelated CI break this
  migration was meant to fix for Safety. Both now accept skipped
  entries, treat them as zero vulns, and surface them explicitly (job
  log + PR comment) instead of silently dropping or crashing on them.
  Verified the fixed jq queries and JS parse logic against synthetic
  pip-audit report fixtures covering the normal, skipped, and malformed
  shapes.

- Restored a `workflow_dispatch` trigger on security-scan.yml. Deleting
  security.yml (which had it) left no way to manually run a dependency
  audit on demand.

- Updated SECURITY.md, which still described security.yml as a live
  scanning workflow and Safety as an active scanner after this PR
  deletes both.
2026-09-02 16:22:16 +05:30
Mohd Kaif db48f73755 Merge branch 'main' into fix/security-scan-pip-audit-migration 2026-09-02 16:01:17 +05:30
Zohaib Hassnain 07113d2d2d fix(ci): migrate security scan from Safety to pip audit 2026-09-02 15:21:36 +05:00
Shubham SrivastavaandSameer Kadam 909ccf0ded test: install extractor dispatch mocks per test, not at module scope (#1337)
The module assigned MagicMocks into sys.modules at import time and never
removed them. pytest imports every test module during collection before
running anything, so those mocks were live while later modules were
imported and each bound them into its own globals.

132 tests passed alone and failed in a full-suite run as a result. Full
suite goes from 199 failed / 5506 passed to 67 failed / 5638 passed.

A tearDownModule cannot fix this: collection has already finished by the
time it runs. The extractors resolve 'from .methods import
get_entity_method' lazily inside their methods, so the stand-in only has
to be in sys.modules while a test executes - it is now installed per test
via patch.dict in setUp and removed by addCleanup.

Co-authored-by: Sameer Kadam <sskadam6305@gmail.com>
2026-09-02 15:31:38 +05:30
Guofang.Tang fb69b033be fix(ontology): resolve endpoints in direct property inference (#1229)
The relationship-endpoint fix merged in #1170 covers the main ontology generation pipeline, but the public property-inference path still had the same gap.

`OntologyGenerator.infer_properties()`, and the `PropertyGenerator` it delegates to, fell back to `owl:Thing` for both domain and range when a relationship used entity IDs or aliases instead of explicit `source_type` / `target_type` values. The pipeline resolved those endpoints correctly, but the public API path did not.

This moves the existing alias-building and endpoint-resolution logic out of `OntologyGenerator` and into a shared `relationship_utils` module:

* `build_entity_aliases`
* `get_relationship_endpoint`
* `resolve_relationship_endpoint_type`

`OntologyGenerator` now uses those shared helpers instead of keeping its own copies.

`PropertyGenerator._infer_object_properties()` now also receives the entity list, builds the same alias index, and uses the shared endpoint resolver. This replaces the old fallback:

```python
rel.get("source_type") or self._infer_class_from_entity(...)
```

which could only fall back to `owl:Thing` because `_infer_class_from_entity()` never actually resolved an entity.

There are two small behavior changes from centralizing the logic. `build_entity_aliases()` now converts `entity_type` to `str` before adding it to the alias set, avoiding mixed-type alias values. `resolve_relationship_endpoint_type()` returns `None` rather than `""` when there is no usable explicit type, since an empty string isn't a meaningful endpoint type.

The new `test_public_infer_properties_resolves_id_endpoints` covers the broken public API path directly. It creates entities and ID-based relationships through `infer_classes()` / `infer_properties()` and verifies that the inferred `worksFor` property resolves to `Person` for the domain and `Organization` for the range instead of falling back to `owl:Thing`.

That exercises the same endpoint-resolution behavior already covered by the pipeline tests, but through the public entry point that was still missing it.
2026-09-02 14:26:13 +05:00
Guofang.Tang c10090dc9b fix(ci): fail closed on malformed Safety reports (#1366)
The Security Scan workflow already scans `requirements-ci.txt` directly, but malformed Safety output could still be treated as a clean scan. If the report existed on disk but `vulnerabilities` was missing, `null`, or the wrong type, the workflow could end up counting it as zero findings.

This adds a structural check immediately after the report is written. `vulnerabilities` must be an array; otherwise the step fails closed with a clear error instead of treating a broken report as a successful scan.

There was a related problem in the PR reporting path. The comment step already knew how to render an `Invalid report structure` warning, but that branch was effectively unreachable. In GitHub Actions, a custom `if:` is implicitly gated by `success()` unless it includes a status function such as `always()` or `failure()`. Once the Safety step exited non-zero, the Upload and Comment steps were skipped, so the warning could never be posted.

Fixing that required changing how Safety failures flow through the job rather than just adding another guard. The Safety step now uses `continue-on-error: true`, which lets Bandit and Semgrep continue running and allows the Upload and Comment steps to process the failed or malformed Safety result.

Because `continue-on-error` means the Safety step no longer carries the job's final failure signal itself, the workflow now tracks that state explicitly with `SAFETY_SCAN_STATUS`. It is set to `failed` at the start of the Safety step, before any validation runs, and changes to `passed` only when the report is valid and contains zero vulnerabilities.

That default-failed behavior covers every other exit path: a missing report, malformed `vulnerabilities` field, invalid vulnerability count, Safety failure, or an actual vulnerability finding all leave the status as `failed`.

A final `Enforce Safety Gate` step checks `SAFETY_SCAN_STATUS` and fails the job unless it is exactly `passed`. This keeps the same merge-blocking behavior while still allowing the rest of the security checks and reporting steps to run after a Safety failure.

This is a follow-up to #1356. The overlapping Safety behavior changes and duplicate pip-audit path from that PR were dropped after `main` picked up the canonical fix for the underlying `cuda-toolkit` crash. This change keeps only the report-validation hardening that remains independent of that fix.
2026-09-02 14:12:33 +05:00
Zohaib Hassnain 28c96c2539 fix(ci): update actions/deploy-pages pin to current v5 (v5.0.1) (#1387) 2026-09-02 13:59:42 +05:00
Ahmad Bilal 170b4215a6 fix(vector_store): persist vector_ids and metadata across FAISS index save/load (#1272) (#1314)
`FAISSIndex.save()` previously wrote only the raw FAISS index. `load()` then rebuilt the wrapper with empty `vector_ids` and `metadata`, so that state was never restored.

That made a save/load round trip effectively unusable through the wrapper API: `scan_vectors()` returned no vectors, `count()` returned `0`, and `get_vector(id)` returned `None` for IDs that were present in the underlying FAISS index.

This also affected migration. `semantica store migrate --from faiss` could load a valid FAISS index, see zero vectors through `scan_vectors()`, migrate nothing, and still exit successfully. Since FAISS is a supported migration source, this was a silent data-loss path rather than just a persistence bug.

The fix adds a `.meta.json` sidecar next to the FAISS binary. It stores:

* `vector_ids`
* `metadata`
* `dimension`
* `index_type`

The sidecar is written atomically using a temporary file and rename. `save()` also serializes the metadata before writing the FAISS binary, so a serialization error fails before either persistence artifact is created. That avoids leaving a valid-looking index file behind without the metadata needed to use it correctly.

On load, `dimension` and `index_type` come from the sidecar rather than the caller's arguments. This makes the reconstructed wrapper reflect the index that was actually saved instead of relying on the caller to provide matching values.

There are also explicit checks for incomplete or inconsistent persisted state. If the sidecar is missing, which can happen with indexes written by older versions or when only the FAISS binary was copied, `load()` emits a `RuntimeWarning` instead of silently returning an apparently usable wrapper with no IDs or metadata. If the number of saved vector IDs doesn't match the FAISS index's `ntotal`, `load()` raises `ProcessingError` rather than returning a state where vectors exist in FAISS but can't be reached through `scan_vectors()`.

Metadata serialization changed during review as well. The first version used `json.dumps(..., default=str)`. That avoided failures for values such as `datetime`, `UUID`, and `set`, but it was lossy: those values came back as strings instead of their original Python types.

That was replaced with a tagged encoder/decoder that preserves the supported types across a round trip. It currently handles sets, datetimes, dates, UUIDs, NumPy scalars and arrays, and bytes, with bytes stored as base64.

The decoder also uses an exact-schema check for tagged values. A normal dictionary that happens to contain a reserved tag key alongside other fields is left alone instead of being interpreted as an encoded type.

The final implementation was spread across fourteen commits, mostly following review feedback. Those changes included cleaning up conflict markers from an unfinished stash pop, expanding round-trip and retry coverage, adding the missing-sidecar warning, adding an end-to-end `scan_vectors()` persistence test, replacing lossy metadata serialization with the tagged format, checking FAISS/sidecar count mismatches, adding `bytes` support, and reordering `save()` so metadata serialization happens before the FAISS index is written.
2026-09-02 13:44:42 +05:00
Zohaib Hassnain 5f600a3f36 fix(ci): ignore SFTY-20260723-60537 (CVE-2026-65918) in torchvision, unreachable transitive dep (#1385) 2026-09-02 13:37:03 +05:00
Mohd Kaif 1d18755a4e Delete cookbook/advanced/13_Manual_Ontology_Snowflake_Mapping.ipynb 2026-09-02 13:54:13 +05:30
Mohd Kaif 3acf801273 Merge pull request #1361 from taoche/fix/semantic-layer-basics-intro
docs(cookbook): rewrite Semantic Layer Basics as an introductory workflow
2026-09-02 13:22:42 +05:30
KaifAhmad1 796f181c75 docs(cookbook): fix stale RDFExporter claim, pin oxigraph install version
Step 5 explicitly avoids RDFExporter's compact projection and builds
the Turtle export from the TripletStore's own triples instead, but the
Summary cell still credited RDFExporter -- a leftover from before the
rdflib-based export replaced it. Correct the claim to match the code.

Also pin the install to >=0.6.7: earlier releases could return
ontology classes with an empty uri (#1103), which made entity_type
mappings silently resolve to None instead of raising, so the notebook
would appear to pass while never actually typing its instances.
2026-09-02 13:15:14 +05:30
Mohd Kaif 8e73ed8d4c Merge branch 'main' into fix/semantic-layer-basics-intro 2026-09-02 12:44:19 +05:30
Mohd Kaif 114641b39d Merge pull request #1359 from taoche/fix/cookbook-08-end-to-end
docs(cookbook): make notebook 08 a real rerunnable KG workflow
2026-09-02 12:26:00 +05:30
Mohd Kaif f71b711205 Merge branch 'main' into fix/cookbook-08-end-to-end 2026-09-02 12:11:43 +05:30
Mohd Kaif f9a661a4ed security(deps-dev): bump browserslist from 4.28.2 to 4.28.8 in /explorer (#1382)
Fixes GHSA-73wf-gq98-2v4g (prototype pollution / DoS via unguarded
browserslist-stats.json parsing) and GHSA-c83g-rgw3-j3cx (unbounded
cache growth leading to OOM), both patched upstream in 4.28.7.
2026-09-02 11:49:55 +05:30
Kevin 8e7aaee4f5 fix(vector-store): validate collection schema in MilvusStore.get_collection (#1344)
`get_collection()` attached any collection right after the existence check, with no look at its schema. A collection with an INT64 primary key, or one missing the `metadata` field entirely, would attach without complaint and only fail later, inside `get_vector()` or `get_metadata()`, with an error that gave no hint the real problem was upstream at attach time.

This adds a schema check between the attach and the assignment to `self.collection`, so a mismatch is caught at the point of failure instead of surfacing three calls later as an unrelated-looking error. The check validates against exactly the shape `create_collection()` builds: a `VARCHAR` primary key named `id` with `auto_id=False`, a `FLOAT_VECTOR` field named `vector`, and a `JSON` field named `metadata`. Anything else, wrong dtype, wrong name, a missing field, or an auto-generated id, is rejected before the store ever holds a reference to it.

The auto_id and metadata-dtype checks were added in a second pass after review. A collection with `auto_id=True` still attached cleanly and only broke once the store tried to insert with the explicit ids it always sends, and a `metadata` field that existed but wasn't `JSON`-typed only broke during a later write or metadata filter, for the same reason: schema drift that looked fine at attach time and failed downstream instead of at the source.

Nine tests cover this: the one matching-schema case that should succeed, and each rejection path independently, wrong pk dtype, missing pk, wrong pk name, auto_id pk, missing vector field, wrong vector dtype, missing metadata field, and non-JSON metadata.

Closes #1331.
2026-09-02 00:58:26 +05:00
Zohaib Hassnain af829f5f20 fix(vector_store): don't let iterator close() mask the real scan error, dedupe milvus result shaping, split unavailable/uninitialized messages 2026-09-01 23:09:53 +05:00
Zohaib Hassnain 930e7f9b71 docs(vector_store): note the milvus schema assumption 2026-09-01 23:09:53 +05:00
Zohaib Hassnain e335971dcd feat(vector_store): add milvus iter_all 2026-09-01 23:09:53 +05:00
Zohaib Hassnain 78682076d5 fix(vector_store): raise before yielding on a stalled weaviate cursor, extract v4 dict vectors, dedupe fallback ladder 2026-09-01 23:03:07 +05:00
Zohaib Hassnain 1227947be5 fix(vector_store): dedupe qdrant record conversion, don't abort iter_all on a live cursor with an empty page 2026-09-01 22:51:32 +05:00
Zohaib Hassnain b4a14d87f5 making it clean 2026-09-01 22:51:32 +05:00
Zohaib Hassnain 3bf89e523f fix(vector_store): raise instead of truncating when a qdrant scan cannot advance 2026-09-01 22:51:32 +05:00
Zohaib Hassnain 2b5b62bb8d fix(vector_store): drop qdrant migrate wiring, keep iter_all only
VectorStore cannot actually migrate to or from qdrant yet. _init_backend_store constructs QdrantStore without connecting or selecting a collection, so reads raise a Collection not initialized error, and the facade store_vectors dispatches only to add/add_vectors while QdrantStore exposes insert_vectors, so writes raise NotImplementedError.

Both are pre-existing facade gaps that nothing had exposed, since migrate previously only allowed faiss/sqlite/pgvector. Adding qdrant to the allowlist claimed support that does not work end to end, so it is removed along with the dimension inference that only fires for backends missing a .dimension attribute. Tracked separately; this PR keeps just the iter_all primitive.
2026-09-01 22:51:32 +05:00
Zohaib Hassnain 3a0f3f672a feat(vector_store): add iter_all enumeration and wire up qdrant migration 2026-09-01 22:51:32 +05:00
e040d84d59 feat(context): add ErasureCoordinator for cross-store entity erasure (#1027)
* feat(context): add ErasureCoordinator for cross-store entity erasure

purge_node() is graph-scope by design (#957), so an entity removed from the
graph can survive verbatim as an AgentMemory item and as an embedding. The
changelog names GDPR Article 17 as purge's motivation, and an Article 17
erasure the vector store can still answer queries from is not an erasure --
it is worse than none, because purge_node() returns True and writes a
tombstone attesting the content is gone.

ErasureCoordinator composes the existing public APIs to drive the cascade and
returns an ErasureReceipt recording what each store reported. Nothing in
context_graph.py or agent_memory.py changes behaviorally; ContextGraph keeps
its documented graph-scope contract instead of acquiring references that would
invert the dependency.

Honest partial reporting is the point. Stores report erased / not_found /
not_configured / unsupported / failed, and complete is False when any store
reports unsupported or failed. FAISS, Milvus and Weaviate expose no delete at
all, so erasure genuinely cannot be completed on them today -- the receipt
says so rather than reporting a success it did not achieve.

Erasure runs outward-in (vectors, memory, graph). The tombstone is the durable
attestation, so writing it first would let a crash mid-cascade leave a record
claiming more than happened; erasing the graph last leaves a partial failure
recoverable and honest.

The memory sweep pages until dry and re-queries afterwards rather than
trusting one find_by_entity() call, whose limit=10 default silently truncates
the very check a caller uses to decide the erasure is done. Unsupported vector
backends are detected by probing the wrapped backend, since the VectorStore
facade declares delete_vectors() for every backend and only raises
NotImplementedError once called.

27 tests against real ContextGraph/AgentMemory instances, including the
25-items-on-one-entity regression that fails against a naive single-call
sweep. Full tests/context/ suite: 596 passed.

Closes #1018

* docs(context): document ErasureCoordinator in the context API reference

* test(context): exercise ErasureCoordinator against a real VectorStore

The vector-leg tests asserted the three backend shapes the coordinator
expects -- delete_vectors / delete / neither -- against fakes, which is worth
exactly as much as the assumption that a real store looks like one of them.
VectorStore(backend="inmemory") runs without external services, so it can
hold that assumption to account.

Adds the end-to-end case the receipt actually attests to: a real ContextGraph,
AgentMemory and VectorStore, where the embedding is written by
AgentMemory.store() and has to be gone afterwards. That exercises the memory
leg's own delete_memory() vector cascade rather than the coordinator's model
of it.

The real backend also pins a limit worth knowing before trusting the receipt:
it pops the ids and returns True whether or not they were there, and no
backend offers a portable existence check, so `erased` on the vectors leg
means the store accepted the delete for the ids given -- not that embeddings
were really removed. The memory leg re-queries to confirm and so is the
stronger claim. Documented on STATUS_ERASED, _erase_vectors(), and both status
tables.

tests/context/: 599 passed.

* fix(context): address review findings on ErasureCoordinator

Timestamp drift (high). erase_entity() resolved erased_at up front but passed
the caller's original `at` down to purge_node(), so on the default at=None
path the coordinator and the graph each took their own now() and the receipt
attested to a different instant than the tombstone it points at -- breaking
the invariant this module states most loudly. The resolved value is now what
the graph receives. The existing test passed only because it supplied an
explicit `at`, which hides the drift; the regression test covers at=None,
which is what callers actually use.

Backend delete results. The vectors leg treated anything other than the
literal False as success, but no in-repo backend returns a bool -- Qdrant
returns {"status": <UpdateStatus>} and Pinecone {"deleted": True}, so every
dict read as success and the backend's own account of the delete was thrown
away. Results are now interpreted by shape and the payload is kept in the
receipt as backend_result, stringified so it stays JSON-serializable as an
audit record. Bool markers match by identity so a 0 count isn't read as
False; string markers match as substrings so an enum rendering as
"UpdateStatus.FAILED" isn't read as success.

Falsey vector store. The "at least one store" guard used `not vector_store`,
rejecting a valid store whose __bool__/__len__ makes an empty instance falsey
and then reporting vector_store=None when an object had been passed. It now
separates None (absent) from False (deliberately disabled) from provided, and
echoes what it received.

`at` annotations. Widened to int/float, matching the ContextGraph normalizer
they delegate to, so the coordinator stops advertising less than the API it
wraps.

tests/context/: 608 passed.

* fix(context): report memory-owned vectors that survive erasure (#1018)

A receipt could read complete while an embedding was still in the vector
store. The vector leg deleted `vector_ids` or `[entity_id]`, and the memory
leg relied on `AgentMemory.delete_memory()` to cascade to the vectors each
item owns. That cascade is best-effort: `_delete_vector_ids()` raises when a
backend returns False, `delete_memory()` catches it, logs a warning, and
still returns True. So `batch_delete` counted the item, the residual re-query
found no items, the memory leg reported `erased`, and nothing in the receipt
recorded that the embedding was refused.

Reproduced with a store that deletes the entity-keyed id and refuses the
memory-owned one: `receipt.complete` was True with the embedding still live.
That is the failure mode this module exists to prevent -- a receipt is a
compliance artifact, and one that overstates is worse than none.

Fix by deleting memory-owned vector ids through the coordinator's own vector
leg, which reports honestly, instead of trusting the memory leg's cascade.
The ids are collected before anything is deleted, while the items still exist
to be enumerated, and are unioned with any caller-supplied ids rather than
replacing them.

This needs one addition to AgentMemory: `vector_ids_for(memory_id)`, a
read-only accessor mirroring the fallback in `delete_memory` (an item stored
without tracked ids is keyed by its own memory id). Reaching into
`_vector_ids` from the coordinator would have been the internals-access
pattern this repo keeps getting bitten by. No existing AgentMemory behaviour
changes -- `delete_memory()` still cascades best-effort, so other callers are
unaffected; the coordinator simply no longer depends on that being reliable.
It does mean the vectors are attempted twice, which is a no-op on a working
store and only ever costs a log line.

Note this deviates from the PR's stated "nothing in agent_memory.py changes"
constraint. The constraint could not hold: with `_vector_ids` private and no
portable way to ask a vector store what it still holds, the coordinator had
no way to make the claim truthful without it.

Four tests: the refused-vector case (receipt must be incomplete), that
memory-owned ids reach the store, that explicit `vector_ids` do not displace
them, and the accessor's fallback. The first three were confirmed to fail
against the previous coordinator, on the `receipt.complete` assertion rather
than incidentally. 655 tests pass across tests/context and the agno
integration.

* fix(context): make erasure receipt vector failures honest

* fix(context): optimize erasure pagination handling

* fix(context): use one timestamp for batch erasure

* style: strip trailing whitespace from erasure.py and test file

* docs(changelog): correct test counts to 48 / 738 after review rounds

---------

Co-authored-by: Pravit Ampapathini <pravit.amp@gmail.com>
Co-authored-by: Sameer6305 <sskadam6305@gmail.com>
2026-09-01 23:07:02 +05:30
Mohd Kaif 18fb7c3ec0 Merge branch 'main' into fix/semantic-layer-basics-intro 2026-09-01 21:51:31 +05:30
Mohd Kaif 2eab7ab876 fix(ci): drop --ignore from Safety check, filter accepted CVEs in jq instead (#1371)
* fix(ci): drop --ignore from Safety check, filter accepted CVEs in jq instead

The follow-up to #1370: adding `--ignore SFTY-20260120-40557` to the
`safety check` invocation reintroduced the exact crash #1131/#1157 had
just fixed - "Unhandled exception happened: 'cuda-toolkit'" - but only
once Safety actually has a live vulnerability match to apply the ignore
against (the plain, un-ignored scan against the same requirements-ci.txt
had already succeeded and correctly reported that same match on main,
per the run right before this one).

I couldn't reproduce this locally: my local Safety installation doesn't
surface the live cuda-toolkit CVE match at all (its open-source
vulnerability DB appears to lag CI's), so --ignore never had a real
match to crash on in my testing. That's on me - I should have caught
that my "0 vulnerabilities" local result meant the DB hadn't even seen
the finding yet, not that the fix worked.

Since I can't safely iterate against Safety's own --ignore path without
live-DB access, this moves the "should we still fail on ID X" decision
out of Safety entirely: run the plain scan (the one path an actual CI
run has now proven doesn't crash), then filter the accepted vulnerability
ID out of the report ourselves in jq before counting/printing. Verified
the jq expression directly against a synthetic report shaped like a real
one (id present + one other unrelated id): filters exactly the intended
entry, and - as a bonus - iterating over a null/missing "vulnerabilities"
key with jq now raises inside jq the way the existing guard comment always
assumed it did, rather than silently coming back as 0.

* fix(ci): apply the accepted-CVE exclusion list to the PR comment too

Qodo caught a real gap on this PR: the jq-based exclusion I added only
covers the CI gate (the VULNS count and the failure-path detail print).
The "Comment PR with Security Results" step reads safety-report.json
independently in its own JS, with no filtering at all, so a PR touching
only the accepted cuda-toolkit CVE would still get a comment saying
"Found 1" even though the gate itself correctly treats it as
non-actionable and passes.

Export IGNORED_VULN_IDS via $GITHUB_ENV from the shell step so the JS
step can read the same list, and filter data.vulnerabilities there
before rendering - with a footnote naming what was excluded and why,
so the comment stays transparent about the accepted finding rather than
just silently hiding it.

Verified the JS logic standalone against two synthetic reports: one with
the accepted CVE plus an unrelated real one (shows only the real one,
plus the footnote), and one with only the accepted CVE (shows "No
findings" plus the footnote, rather than misleadingly looking identical
to a clean scan with no explanation).
2026-09-01 19:45:36 +05:30
Mohd Kaif d8822198cf fix(ci): ignore CVE-2025-33228 in cuda-toolkit - unfixable transitive pin, unreachable code path (#1370)
Merging #1357 surfaced a real (not crashed) Safety finding: cuda-toolkit
13.0.3.0 < 13.1.0 is affected by SFTY-20260120-40557 / CVE-2025-33228.

This can't be fixed with a version bump on our end: torch 2.13.0 (the
latest release on PyPI - there is no newer one) hard-pins
`cuda-toolkit[cublas,cudart,cufft,cufile,cupti,curand,cusolver,cusparse,
nvjitlink,nvrtc,nvtx]==13.0.3` on Linux via its own METADATA, not a loose
transitive requirement we control.

The underlying CVE is OS command injection in NVIDIA Nsight Systems'
gfx_hotspot recipe (process_nsys_rep_cli.py), which requires a human to
manually invoke that script with an attacker-supplied string. It isn't
reachable from any Semantica code path, and Nsight Systems isn't even
part of the extras torch requests here (cublas/cudart/cufft/cufile/
cupti/curand/cusolver/cusparse/nvjitlink/nvrtc/nvtx - no Nsight extra
among them).

Ignoring this one vulnerability ID only (not the whole package or a
blanket policy) so CI reflects actionable risk. Re-evaluate once torch
ships a release that pins a patched cuda-toolkit.
2026-09-01 19:14:21 +05:30
Mohd Kaif e6409217dd Merge pull request #1357 from taoche/fix/cookbook-07-graph-mapping
docs(cookbook): correct graph mapping and deduplication in notebook 07
2026-09-01 18:41:18 +05:30
taoche e6c05df33e docs(cookbook): index semantic layer capstone 2026-09-01 19:51:49 +08:00
taoche c20a46f026 docs(cookbook): preserve complete semantic layer RDF 2026-09-01 19:45:40 +08:00
taoche 0dbe9274eb docs(cookbook): install notebook 08 NER model 2026-09-01 19:45:39 +08:00
taoche 3ed31b9182 docs(cookbook): make notebook 07 setup deterministic 2026-09-01 19:45:39 +08:00
Mohd Kaif 7300fb41b1 Merge pull request #1363 from 7487/fix/plugin-manifest-agents-array
fix(plugins): declare agents as an array of file paths in plugin.json
2026-09-01 16:57:52 +05:30
Mohd Kaif 218e5a33f3 Merge branch 'main' into fix/plugin-manifest-agents-array 2026-09-01 16:38:36 +05:30
Mohd Kaif 635f6e52f4 Merge pull request #1332 from semantica-agi/test/backend-facade-contract
Pin facade contract gaps for cloud backends
2026-09-01 15:20:08 +05:30
Mohd Kaif 9240a1b1f7 Merge branch 'main' into test/backend-facade-contract 2026-09-01 15:08:35 +05:30
3254b9be80 Serve the RDF export formats the MCP tool already offers (#1131) (#1157)
* feat(explorer): serve the RDF export formats the MCP tool already offers (#1131)

`POST /api/export` accepted only `json` and `csv` and answered 422 for everything
else, while the MCP `export_graph` tool resolved Turtle, N-Triples, RDF/XML,
JSON-LD, GraphML and Parquet through `semantica.export`. Two surfaces of one
product disagreeing about what the product can do — and for an RDF-native project,
a graph that loads as JSON-LD and cannot be exported as RDF is a one-way door.

The route now reaches the same exporters the MCP tool uses. Nothing is
reimplemented: `RDFExporter.export_to_rdf` and `GraphMLExporter.export` receive the
dict `session.build_graph_dict()` already builds.

The alias table is a copy of `mcp/tools/export.py::_FORMAT_ALIASES` plus the
spellings the issue mentioned (`ntriples`, `rdf-xml`), and a test asserts the two
tables agree — if either drifts, the formats a caller can use would depend on which
door they came through.

Media types and extensions per serializer, so a Turtle export is `text/turtle` and
not `application/json` with a `.json` name.

The 422 message now names what IS supported. The old one said only that the format
was unsupported, which reads as "this format does not exist" rather than "this door
does not open it" — that is what sent me looking through the library.

Parquet is left out on purpose: `ParquetExporter.export` writes a file and returns a
path, so serving it over HTTP is a different shape of change and deserves its own
review.

Tests, in `TestImportExport`: the seven RDF spellings, each **parsed with rdflib**
rather than asserted on strings — a response that merely looks like Turtle is what
lets this class of gap survive a suite. Plus the alias-agreement canary and the
error message. Three mutations (rejecting RDF again, breaking one alias, emptying
the message) each turn the matching tests red.

110 tests in `tests/explorer/test_explorer_api.py` pass.

* fix(explorer): complete RDF export support

* fix(explorer): secure GraphML temporary file handling

* fix(ci): scan declared dependencies with Safety

---------

Co-authored-by: 13g4d0 <13g4d0@users.noreply.github.com>
Co-authored-by: Sameer Kadam <sskadam6305@gmail.com>
2026-09-01 15:01:13 +05:30
Guofang.Tang dc81acaefd fix(ontology): reject normalized class name collisions (#1230)
ClassInferrer.infer_classes() groups entities by their type string, then
normalizes each group name (PascalCase + singularize) when it builds the
ontology class. Two source types that only differ in casing or plurality,
like Person and person, both normalize to the same class name. Nothing
caught that, so the second type's entities silently got treated as
instances of the first type's class, and property inference downstream
picked up whichever properties happened to win.

Added a pass right after entities get grouped by type: normalize every
type name that meets min_occurrences, and if two different source types
land on the same normalized name, raise ValidationError before any class
gets built. The check reuses the exact same min_occurrences filter the
real class-emission loop uses, so it only fires on collisions that would
actually produce duplicate classes, not on types that get filtered out
anyway.

The error carries validation_context with the normalized name mapped to
every source type that collided into it, so whoever's calling this can
see exactly what to rename instead of just getting a generic message.

Added test_class_inference_rejects_normalized_type_collisions covering
Person/person landing on the same class.

Follow-up to #1171.
2026-09-01 14:25:54 +05:00
7487 f44020c742 fix(plugins): declare agents as an array of file paths in plugin.json
Claude Code's plugin schema rejects "agents": "./agents" (a bare
directory string) with:

    Validation errors: agents: Invalid input

so the bundled plugin has never been installable. Unlike "skills",
which accepts a directory string, "agents" must be an array of .md
file paths.

Replaced the string with the explicit list of the three agent files.
Verified with `claude plugin validate plugins` (2.1.231): fails on the
old manifest with the error above, passes after this change.

Added tests/test_plugin_manifest.py to guard the manifest shape: agents
is a non-empty array of existing .md paths that stays in sync with
plugins/agents/, and the skills directory exists.

Fixes #1350
2026-09-01 17:02:52 +08:00
taocheandClaude Fable 5 0e7cef4677 docs(cookbook): move Semantic Layer Basics from Advanced to Introduction
Advanced chapter 09 labeled an introductory composition of
already-taught APIs as an enterprise semantic layer: TripletStore was
imported but never used, property_mappings stayed empty, mappings were
derived by fragile name matching (works_for never matched worksFor),
and the exported RDF was the original graph rather than an
ontology-aligned one.

Replace it with introduction/26_Semantic_Layer_Basics.ipynb, which
demonstrates the minimal semantic-layer composition honestly:

- build a small graph, generate an ontology (min_occurrences=1 so all
  demo classes are inferred, base_uri in the user's namespace)
- explicit entity-type, relationship-type, and property mappings read
  from the ontology's inferred_from metadata instead of name matching
- apply the mappings to produce an ontology-aligned graph, export it
  as Turtle, and note the file exporter's property projection
- store the aligned graph in the embedded Oxigraph TripletStore and
  answer a business question with one SPARQL query
- distinguish teaching mappings from governed production mappings and
  point to Advanced 13 as the production continuation

Advanced 13 gains a positioning note naming the new lesson as its
prerequisite; introduction/14_Ontology links forward to the new
lesson. All cells execute top to bottom (verified with the embedded
Oxigraph backend).

Closes #1325

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-09-01 15:56:49 +08:00
taocheandClaude Fable 5 5c366c6b7e docs(cookbook): make notebook 08 a real rerunnable end-to-end workflow
The first-knowledge-graph lesson read the parser output from a key it
never returns (content vs text), then masked the failure with
hard-coded entities, a manually assembled NetworkX graph, and an
uninvoked KGVisualizer; the final cell deleted the sample file, so
rerunning intermediate cells failed. Rework the notebook so every
stage consumes the previous stage's output:

- parse via parsed_document["text"] with an assertion that content
  was actually extracted
- real NERExtractor/RelationExtractor output replaces the simulated
  entities and wrong hard-coded offsets
- GraphBuilder builds the graph from actual relation endpoints via a
  mention-span -> graph-ID map (consistent with notebook 07)
- KGVisualizer.visualize_network renders the graph and saves HTML
- deletion moved to an explicit optional cleanup cell, so parsing and
  downstream cells stay rerunnable

Closes #1289

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-09-01 15:45:28 +08:00
taocheandClaude Fable 5 abb65feff0 docs(cookbook): build notebook 07 graph edges from real relation endpoints
The knowledge-graph lesson fabricated relationship endpoints from loop
indices, hid the corruption behind count-only output, and displayed
only merged duplicate groups as the deduplicated result. Rework the
notebook so that:

- graph edges come from Relation.subject/Relation.object mapped
  through a mention-span -> graph-ID table
- the sample text keeps two separate "Apple Inc." mentions without the
  sentence-boundary merge edge case
- entity resolution shows which mentions merged (merged_from) and
  remaps relationship endpoints onto the canonical entity
- deduplication reports merge operations separately from the complete
  deduplicated set (merged + untouched entities)
- each stage prints its transformed records, and lightweight
  assertions pin the expected canonical entities and edges

Closes #1287

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-09-01 15:39:25 +08:00
Wei TaoandSameer Kadam 8b125d6476 fix(explorer): keep small Full Graph relationships readable (#1277)
* fix(explorer): keep small full graphs readable

* perf(explorer): avoid redundant realtime edge sync

---------

Co-authored-by: Sameer Kadam <sskadam6305@gmail.com>
2026-09-01 11:11:53 +05:30
Mohd Kaif f3c540cfd2 docs(readme): reposition Semantica as the semantic/context layer (#1348)
Lead the README with Semantica's identity as a semantic/context/knowledge
layer (Context Graph, KG, ontology and vocabulary governance via OWL/SHACL/SKOS),
with decision provenance and audit trails framed as a property of that
structure rather than the flagship pitch.
2026-08-31 22:11:13 +05:30
Sameer Kadam 46b18fbee3 fix(docs): use GraphStore facade in Neo4j quickstart (#1340)
The Neo4j example in the persistent graph store section was passing a raw
Neo4jStore straight into GraphBuilder. GraphBuilder calls add_nodes() and
add_edges() on whatever it's given, and those only exist on the GraphStore
facade, not on Neo4jStore itself. Anyone who copied the example got:

AttributeError: 'Neo4jStore' object has no attribute 'add_nodes'

Swapped the import and construction to GraphStore(backend="neo4j", uri=...,
user=..., password=...), which wraps Neo4jStore internally and actually has
the methods GraphBuilder needs.

Added a test in tests/kg/test_graph_builder_with_graph_store.py that builds
a small graph through GraphBuilder with a mocked GraphStore and checks
add_nodes/add_edges get called. Also kept a test for GraphBuilder without a
graph_store at all, so that path doesn't regress either.

Closes #1135
2026-08-31 20:34:04 +05:00
Mohd Kaif b0679d4f67 fix(ci): stop checkov's suppressed checks from reopening as new alerts (#1346)
* fix(ci): drop unpinnable benchmarks/requirements.txt install

Scorecard flagged this pip install as unpinned-by-hash (#6082). Can't
hash-pin it - benchmarks/requirements.txt doesn't exist in this repo, so
there's nothing to compile a lockfile from. Dropping it instead of
leaving it unpinned: the job already fails on the next real step
(benchmarks/benchmarks_runner.py, also missing), so this line wasn't
doing anything useful to begin with.

* fix(ci): hash-pin the spacy model download in benchmark.yml

Qodo review on this PR: dropping the benchmarks/requirements.txt install
(the previous failure point) let the job actually reach
`python -m spacy download en_core_web_sm`, which fetches an unpinned,
unhashed wheel from spacy-models' GitHub releases - undoing the point of
this PR by exposing a real unpinned-install path instead of a dead one.

Replaced with a hash-pinned direct-URL entry in benchmark-extra.in/.txt
for en_core_web_sm-3.8.0 (matches the spacy==3.8.15 already pinned in
base-deps.txt). uv independently computed the same sha256 I got via a
manual curl+sha256 of the release asset, and a --require-hashes dry-run
install verifies clean.

* fix(ci): stop checkov's suppressed checks from reopening as new alerts

Root cause found, not just worked around: checkov's SARIF exporter
includes every evaluated check as an ordinary result, including ones it
internally marked SKIPPED via the inline # checkov:skip= comments and
checkov.io/skipN annotations already on the Helm chart. It never uses
SARIF's own `suppressions` field and never drops them - so the exact same
already-suppressed finding reopens as a brand-new code scanning alert
number on every single run, forever (#6035/#6036, #6112-6115,
#6128-6131 are all the same 4 findings, manually dismissed 3 times now).

checkov's JSON output *does* correctly record which checks were skipped.
Added .github/scripts/filter_checkov_skipped.py, which cross-references
the JSON's skipped_checks against the SARIF's results (matched by check
ID + the last two path segments, since the two outputs use different path
roots) and drops anything checkov itself already decided to suppress,
before upload. Verified locally against a real checkov+helm run: removed
exactly the 4 known-suppressed helm chart results, left the 2 genuinely
real findings (deploy/gcp/cloudrun-service.yaml, deploy/kubernetes/
deployment.yaml) untouched.
2026-08-31 19:29:24 +05:30
Mohd Kaif d135ad185f fix(ci): drop unpinnable benchmarks/requirements.txt install (#1345)
* fix(ci): drop unpinnable benchmarks/requirements.txt install

Scorecard flagged this pip install as unpinned-by-hash (#6082). Can't
hash-pin it - benchmarks/requirements.txt doesn't exist in this repo, so
there's nothing to compile a lockfile from. Dropping it instead of
leaving it unpinned: the job already fails on the next real step
(benchmarks/benchmarks_runner.py, also missing), so this line wasn't
doing anything useful to begin with.

* fix(ci): hash-pin the spacy model download in benchmark.yml

Qodo review on this PR: dropping the benchmarks/requirements.txt install
(the previous failure point) let the job actually reach
`python -m spacy download en_core_web_sm`, which fetches an unpinned,
unhashed wheel from spacy-models' GitHub releases - undoing the point of
this PR by exposing a real unpinned-install path instead of a dead one.

Replaced with a hash-pinned direct-URL entry in benchmark-extra.in/.txt
for en_core_web_sm-3.8.0 (matches the spacy==3.8.15 already pinned in
base-deps.txt). uv independently computed the same sha256 I got via a
manual curl+sha256 of the release asset, and a --require-hashes dry-run
install verifies clean.
2026-08-31 18:57:28 +05:30
Mohd Kaif 96dbd3f0d4 fix(security): bump checkov to 3.3.16, fix aiohttp CVEs in its lockfile (#1342)
Dependabot flagged 12 aiohttp advisories (1 high, rest moderate/low - CVE
range covering request smuggling, websocket/parser bugs, cookie/redirect
issues) against aiohttp==3.13.5 pinned in checkov.txt. checkov==3.3.1
itself pinned `aiohttp<3.14.0`, which excludes every fixed release;
3.3.16 (latest) relaxes that to `<3.15.0`, so bumping checkov also lets
aiohttp resolve to 3.14.3 (fixes all of them).

Two alerts remain open, both genuinely blocked upstream rather than
something a version bump here can fix:
- asteval: checkov 3.3.16 (latest, still) hard-pins asteval==1.0.6 with
  no range; the fix (1.0.9) is unresolvable without violating checkov's
  own declared dependency - confirmed via `uv pip compile` refusing to
  solve it. Needs checkov itself to bump the pin upstream.
- ecdsa: 0.19.2 is already the latest release; the Minerva timing-attack
  advisory has no patched version, since python-ecdsa's maintainers have
  stated side-channel attacks are out of scope for the project.

Both are checkov's own transitive deps, used only for local static IaC
analysis in defender-for-devops.yml (no network signing/cloud-auth calls
that would actually exercise ecdsa's signing path) - dismissing on
GitHub with that reasoning as a separate step.
2026-08-31 18:21:36 +05:30
Mohd Kaif d4cd44e7f1 fix(docker): split explorer-extra.txt by Python version, fix broken build (#1341)
* fix(docker): split explorer-extra.txt by Python version, fix broken build

main's container-scan.yml has been failing since PR #1338 merged:

  ERROR: In --require-hashes mode, all requirements must have their
  versions pinned with ==. These do not:
      standard-aifc from .../standard_aifc-3.13.0-py3-none-any.whl
      (from audioread==3.1.0->-r explorer-extra.txt (line 30))

Root cause: explorer-extra.txt was compiled with `--python-version 3.11`
but is installed on the Dockerfile's actual python:3.13-slim interpreter.
librosa's audioread dependency needs standard-aifc/standard-sunau only
under `python_version >= "3.13"` (Python 3.13 dropped aifc/sunau from
stdlib) - a file resolved for 3.11 has no hash for those packages at all,
so --require-hashes fails outright once pip resolves against the real
3.13 environment instead of silently under-pinning.

Splits the file in two: explorer-extra-py311.txt (ci.yml, unchanged
resolution) and explorer-extra-py313.txt (Dockerfile, newly compiled for
--python-version 3.13). They aren't interchangeable and shouldn't be
recombined - documented in .github/requirements/README.md, including how
to catch this class of bug before it ships again.

* fix(ci): correct stale -o path in explorer-extra-py311.txt header

Qodo review on this PR: the autogenerated header comment still said
-o .github/requirements/explorer-extra.txt (the pre-rename path), which
would silently regenerate the wrong file if someone copy-pasted it.
2026-08-31 17:52:23 +05:30
Mohd Kaif 832412cc01 Merge pull request #1338 from semantica-agi/fix/scorecard-pinned-dependencies
fix(ci): hash-pin every pip install for Scorecard Pinned-Dependencies
2026-08-31 17:31:14 +05:30
Sameer6305 dfbe98cebd fix(ci): generate Checkov lockfile for Windows 2026-08-31 17:21:59 +05:30
Sameer6305 89c6c8a45d fix(ci): hash-pin Checkov installation 2026-08-31 17:01:16 +05:30
Sameer Kadam 4b24053851 Merge branch 'main' into fix/scorecard-pinned-dependencies 2026-08-31 16:15:42 +05:30
cxzg007andSameer Kadam c111277a1f fix(context): make auto_generate_id a real InitVar in decision models (#1153)
The six decision-model dataclasses (Decision, DecisionContext, Policy,
PolicyException, Precedent, ApprovalChain) accepted `auto_generate_id`
only as a plain `__post_init__` parameter. Since it was neither a field
nor an InitVar, the generated `__init__` never forwarded it, so the
parameter was always its `True` default and the `auto_generate_id=False`
validation branch was unreachable dead code.

Declare `auto_generate_id: InitVar[bool] = True` on each dataclass so the
generated `__init__` forwards it to `__post_init__`, restoring the
required-id contract. Serialization is unaffected because InitVar is not
a real field. Add regression tests covering the auto-generate path, the
required-id error path, and the explicit-id path.

Fixes #1152

Co-authored-by: Sameer Kadam <sskadam6305@gmail.com>
2026-08-31 15:50:40 +05:30
KaifAhmad1 5ed98cefbd fix(ci): hash-pin PEP 517 build isolation deps (setuptools, wheel)
Qodo review on this PR: `pip install --no-deps -e .` / `pip install
--no-deps .` still leaves PEP 517 build isolation on by default, which
fetches [build-system] requires (setuptools==84.0.0, wheel==0.48.0)
completely outside any hash checking - the --require-hashes installs
right next to it didn't cover this at all.

Adds .github/requirements/pep517-build.txt, hash-locked to the exact
pyproject.toml [build-system] requires, and installs it before every
local-source install (Dockerfile, ci.yml, benchmark.yml) with
--no-build-isolation so pip reuses those hash-verified copies instead
of fetching its own.
2026-08-31 15:43:59 +05:30
KaifAhmad1 48737629d9 fix(ci): hash-pin every pip install for Scorecard Pinned-Dependencies
Scorecard's Pinned-Dependencies check requires pip installs to be
hash-verified, not just version-pinned - our existing pkg==X.Y.Z pins
(and even pip install -r requirements-ci.txt, despite that file already
carrying hashes) still scored a 4 because the pin/hash isn't visible on
the command line itself.

Adds .github/requirements/*.txt: hash-locked files generated via
`uv pip compile --generate-hashes` for every pip target that isn't
already requirements-ci.txt, covering standalone CI tooling (build,
wheel, twine, uv, pip-audit, safety/bandit/semgrep/jq, pip/setuptools
bootstrap) and the project's own local-source installs. The latter
(`pip install -e ".[explorer]"`, `pip install -e .`) can't be hash-pinned
directly since there's nothing to hash for a local source tree; split
into `pip install --no-deps -e .` plus a separate hash-pinned install of
the actual fetched dependencies instead.

Also adds --require-hashes to every `-r requirements-ci.txt` install so
hash verification is enforced explicitly rather than only implied by the
file's own content.

Simplifies the Dockerfile in the process: it now installs from the same
pre-generated explorer-extra.txt (copied in at build time) instead of
extracting constraints from requirements-ci.txt at build time, which
also means setuptools gets its CVE-2025-47273 fix as a side effect of
the hash-pinned install rather than a separate upgrade step.

benchmark.yml: pip install -r benchmarks/requirements.txt is left
unpinned - that directory doesn't exist in this repo, so there's nothing
to generate hashes from. Pre-existing breakage, unrelated to this change.
2026-08-31 15:29:40 +05:30
Mohd Kaif 1d62217cc5 Merge pull request #1334 from semantica-agi/fix/container-scan-cves
fix(docker): resolve Trivy-flagged CVEs in the built image
2026-08-31 14:34:32 +05:30
KaifAhmad1 bf292ccbbc fix(docker): address terrascan findings, drop apt-get upgrade
Two terrascan/GHAS findings on the previous commit:
- AC_DOCKER_0052 (no apt-get upgrade in Dockerfiles): dropped it. It also
  wasn't fixing anything - Debian's openssl fix for CVE-2026-14456 is still
  in trixie-proposed-updates, not reachable via a normal upgrade. Pin both
  base images by digest instead (matches #1329's approach) so the docker
  Dependabot ecosystem bumps them once Debian ships a rebuilt image with
  the fix, and document why the QUIC DoS isn't reachable here regardless
  (HTTP-only via uvicorn).
- AC_DOCKER_0010 (pin pip package versions): setuptools was `>=78.1.1`;
  pinned to the exact 84.0.0 already used by pyproject.toml/requirements-ci.txt.

Also fixes two build breaks this introduces on its own: requirements-ci.txt
wasn't in .dockerignore's allowlist or container-scan.yml's path trigger,
so the COPY in the prior commit would have failed the image build outright.
2026-08-31 14:06:28 +05:30
KaifAhmad1 64d942503b fix(docker): extract requirements-ci.txt pins with Python instead of sed
The sed expression to strip requirements-ci.txt's line-continuation
backslash (`[\]$`) is valid POSIX/GNU sed - verified it exits 0 and
strips correctly - but it's easy to misread as broken (a bot reviewer
flagged it as an unterminated bracket expression), and the seemingly
more obvious `\$`/` \$` forms silently fail to match at all rather
than erroring. Swap to a small `re.findall` extraction so there's no
backslash-escaping judgment call left for a reader (bot or human) to
second-guess.
2026-08-31 14:06:02 +05:30
KaifAhmad1 471a420b10 fix(docker): resolve Trivy-flagged CVEs in the built image
Container Security Scan flagged five HIGH-severity findings against
semantica:scan:
- setuptools 70.3.0 (CVE-2025-47273, path traversal) - the base image's
  bundled copy, never touched by our own build. Upgraded explicitly.
- msgpack 1.1.2 (GHSA-6v7p-g79w-8964, OOB read/crash) - `pip install
  ".[explorer]"` re-resolved deps from scratch instead of reusing the
  audited, hash-pinned requirements-ci.txt (which already pins
  msgpack==1.2.1), so it landed on an unpatched transitive version. Now
  installs against a constraints file derived from requirements-ci.txt.
- openssl / libssl3t64 / openssl-provider-legacy (CVE-2026-14456, QUIC
  server DoS) - the Debian fix is still in trixie-proposed-updates, not
  yet promoted to trixie-security, so it can't be pulled via apt today.
  Added an apt upgrade step so the next image rebuild picks it up
  automatically once Debian ships it; documented why this image isn't
  actually exposed to it in the meantime (HTTP-only via uvicorn, no QUIC
  listener).
2026-08-31 14:06:02 +05:30
Mohd KaifandSameer6305 a4aa71ad87 fix(ci): unblock py3.9 install matrix and raise Scorecard pinning/signing (#1329)
* fix(ci): unblock py3.9 install matrix and raise Scorecard pinning/signing

pip install semantica failed on Python 3.9 across all three OSes because
spacy had no upper bound, so pip resolved spacy 3.8.16 whose thinc>=8.3.12
requirement has no cp39 wheels and no working sdist build path. Cap
spacy/thinc for python_version < '3.10' to the last wheel-compatible pair.

Also addresses the two OpenSSF Scorecard findings that were actually
fixable in code:
- Pinned-Dependencies: Dockerfile base images (node:26-alpine,
  python:3.13-slim) were unpinned by digest; pin both, and pin five
  previously-unversioned pip install calls in CI (build, safety, bandit,
  semgrep, jq, pip-audit).
- Signed-Releases: attest-build-provenance only publishes to the GH
  attestations API, which Scorecard doesn't inspect. Sign dist/* with
  Sigstore and attach the .sigstore.json bundles as release assets.

* fix(ci): correct Sigstore artifact inputs

---------

Co-authored-by: Sameer6305 <sskadam6305@gmail.com>
2026-08-31 14:04:28 +05:30
Zohaib Hassnain 73b14c00ba test(vector_store): make contract xfails reachable and cover the backend roster 2026-08-31 13:27:45 +05:00
Zohaib Hassnain bd1ba24b24 fix(vector_store): carry weaviate offset fallback across pages, raise on truncation 2026-08-31 13:22:23 +05:00
Zohaib Hassnain e9a756eac2 test(vector_store): pin facade contract gaps for cloud backends 2026-08-31 12:59:40 +05:00
Zohaib Hassnain e8ff36f088 feat(vector_store): add weaviate iter_all 2026-08-31 03:01:37 +05:00
Zohaib Hassnain ec9e63e16f fix(vector_store): drop qdrant migrate wiring, keep iter_all only
VectorStore cannot actually migrate to or from qdrant yet. _init_backend_store constructs QdrantStore without connecting or selecting a collection, so reads raise a Collection not initialized error, and the facade store_vectors dispatches only to add/add_vectors while QdrantStore exposes insert_vectors, so writes raise NotImplementedError.

Both are pre-existing facade gaps that nothing had exposed, since migrate previously only allowed faiss/sqlite/pgvector. Adding qdrant to the allowlist claimed support that does not work end to end, so it is removed along with the dimension inference that only fires for backends missing a .dimension attribute. Tracked separately; this PR keeps just the iter_all primitive.
2026-08-31 03:00:56 +05:00
Zohaib Hassnain 274d5d1195 feat(vector_store): add iter_all enumeration and wire up qdrant migration 2026-08-31 02:34:26 +05:00
Mohd Kaif fa87a1a9be ci: add npm Dependabot ecosystem and container image scanning (#1286)
* ci: add npm Dependabot ecosystem and container image scanning

- dependabot.yml had no npm ecosystem entry for explorer/, so its
  lockfile was never watched - exactly why the brace-expansion/nanoid
  CVEs fixed in #1280 went undetected. Add it, mirroring the existing
  pip entry's schedule/labels/reviewer conventions.
- New container-scan.yml builds the root Dockerfile's image and scans
  it with Trivy (CRITICAL/HIGH OS+lib CVEs, SARIF to the Security tab)
  and Syft (SPDX SBOM artifact), on push to main, weekly, and manual
  dispatch. Neither the base-image scan nor an SBOM existed before -
  Dependabot's docker entry only bumps the base image tag, it doesn't
  scan built layers.
- Trivy runs report-only for now (no exit-code gate): this is its
  first run against the image, so the CRITICAL/HIGH baseline hasn't
  been triaged yet. Once reviewed, add exit-code: '1' to make it a
  hard gate, same as Safety/Bandit-HIGH in security-scan.yml.

* fix: run Trivy via digest-pinned image, not the aquasecurity/trivy-action wrapper

verify-action-pins.sh failed in CI: the aquasecurity GitHub org has an IP
allow list on its API that 403s the live tag->SHA resolution from
Actions-runner IPs (confirmed reproducible, not transient - resolves fine
from a non-blocked host). Rather than carve a skip exception into the pin
verifier for an org this script already flags as a past tag-repointing
target (see its "LiteLLM/Trivy 2026 incident" comment), pull Trivy as a
sha256-digest-pinned Docker Hub image instead. A digest is immutable and
verifiable independently of GitHub's API entirely, so it sidesteps the
IP block without weakening verification of the one action this repo
already treats as higher-risk. Confirmed the pinned digest
(aquasec/trivy@sha256:62b1e65e...) resolves live against Docker Hub's
registry API.

* fix: match container-scan.yml's push paths to what actually reaches the image

The path filter only watched explorer/package.json and package-lock.json,
but Dockerfile COPYs the whole explorer/ tree plus README.md, LICENSE, and
MANIFEST.in, and .dockerignore controls all of it. A frontend source change
or a README/LICENSE edit would change the built image without triggering a
scan, silently drifting until the next weekly run. Replace the filter with
exactly .dockerignore's opt-in list.
2026-08-30 21:39:30 +05:30
Mohd Kaif 08c78bfb40 fix(security): resolve Scorecard vulnerability and token-permission alerts (#1280)
- Bump explorer's brace-expansion (minimatch dep) 5.0.8 -> 5.0.9 and
  nanoid (postcss dep) 3.3.16 -> 3.3.18, fixing GHSA-rgw5-rvv9-x895 and
  GHSA-2v37-7h3g-55p8 (both DoS via unbounded input, both within the
  existing caret ranges declared by their parents).
- Move codeql.yml and defender-for-devops.yml's security-events: write
  (and codeql.yml's actions: read) from workflow-level down to their
  single job, matching Scorecard's Token-Permissions ideal of a
  read-only top-level default with sensitive scopes granted only where
  used.
2026-08-30 20:58:39 +05:30
Mohd Kaif 56b174781f ci: reusable install action, install-matrix, and release hardening (#1266)
Distribution and trust-signal infrastructure to make pip install semantica
frictionless in downstream CI, and to bring the release pipeline in line
with mature OSS practice.

- .github/actions/setup-semantica: reusable composite action other repos
  can call to install + verify semantica in one step
- install-matrix.yml: verifies the published package installs and imports
  cleanly across Ubuntu/macOS/Windows x Python 3.9-3.12, weekly and on
  release; backs a new README badge
- scorecard.yml: OpenSSF Scorecard analysis, weekly and on push to main,
  backing a new README badge
- release.yml: twine check gate before publish, catching a broken PyPI
  long-description render before it ships
- CITATION.cff: enables GitHub's native "Cite this repository" button
- examples/ci/: copy-paste GitHub Actions, GitLab CI, and CircleCI
  templates for projects adopting semantica
- GROWTH.md: tracked checklist of distribution channels, what's done vs
  outstanding, with guardrails against inflating metrics artificially

Fixes folded in along the way:

- Re-pinned softprops/action-gh-release to the immutable v3.0.3 tag
  instead of the floating v3, after verify-action-pins.sh caught the
  mutable tag had drifted to a newer commit
- setup-semantica now passes extras/version through env vars instead of
  interpolating ${{ inputs.* }} directly into the bash script, closing
  a script-injection vector for callers deriving these from event data
- install-matrix now triggers on the Release workflow's completion
  (workflow_run) instead of release: published, since the GitHub release
  is created before the PyPI upload runs and the old trigger could race
  the publish
- The workflow_run path derives the expected version from the triggering
  tag and passes it into setup-semantica's version input, so pip
  installs and verifies the exact release instead of whatever's latest
  on PyPI at the time
- setup-semantica's pip caching is now opt-in (default disabled), since
  actions/setup-python errors out with cache: 'pip' enabled when the
  caller repo has no requirements.txt/pyproject.toml to key on
- examples/ci/github-actions.yml pins actions/checkout and
  actions/setup-python to verified commit SHAs instead of mutable tags
- examples/ci templates guard the requirements.txt install step with
  -f requirements.txt and call out pyproject.toml/Poetry/Pipenv as
  alternatives, since not every project has a requirements.txt
2026-08-30 17:30:21 +05:00
Zohaib HassnainandSameer Kadam dfda4c561a feat(vector_store): add scan_vectors enumeration and wire up store mi… (#1264)
* feat(vector_store): add scan_vectors enumeration and wire up store migrate

* fix(vector_store): address Qodo finds

* fix(vector_store): make FAISS add_vectors idempotent for retried migrations

---------

Co-authored-by: Sameer Kadam <sskadam6305@gmail.com>
2026-08-30 17:31:57 +05:30
Zohaib HassnainandSameer Kadam ea0dd17bff feat(llms): add Gemini, Ollama, DeepSeek, Novita provider wrappers (#1262)
* feat(llms): add Gemini, Ollama, DeepSeek, Novita provider wrappers

* fix(llms): address Qodo review findings on provider wrappers PR

* fix(llms): address review findings

---------

Co-authored-by: Sameer Kadam <sskadam6305@gmail.com>
2026-08-30 17:00:14 +05:30
Shubham Srivastava f6cd62411b test(integrations): make crewai and langchain test dirs packages (#1252)
Both directories contain a test_degradation.py. Neither had an __init__.py,
so under pytest's default prepend import mode both modules were imported as
plain 'test_degradation' and the second collided with the first:

    import file mismatch:
    imported module 'test_degradation' has this __file__ attribute:
      tests/integrations/crewai/test_degradation.py
    which is not the same as the test file we want to collect:
      tests/integrations/langchain/test_degradation.py

That aborted collection for tests/integrations/, so the langchain
graceful-degradation tests never ran. tests/integrations/__init__.py
already exists, and most directories under tests/ carry one; these two
subpackages were simply missed.

Collection goes from 335 collected, 1 error to 337 collected.

Closes #1251
2026-08-30 15:23:43 +05:00
王林 cac6dfbe45 fix(explorer): surface server error detail in graph loading failures (#1260)
The nodes/edges fetch loops were throwing away the response body whenever the request returned a non-OK status.

Because of that, errors like a `503` caused by a missing `SEMANTICA_API_KEY` only showed up as:

`Fetch failed: 503`

even though the backend was already returning a more useful message in the response `detail`.

This change reads the JSON error body and includes `detail` in the thrown error when it's a string, so `GraphLoadingOverlay` can show the actual backend error to the user.

Closes #1256
2026-08-30 15:16:15 +05:00
Mohd Kaif 80e9737542 Merge pull request #1254 from dex0shubham/fix/1134-progress-stream-stderr
fix(utils): write console progress to stderr instead of stdout
2026-08-30 14:22:17 +05:30
Mohd Kaif 14fb975fa1 Merge branch 'main' into fix/1134-progress-stream-stderr 2026-08-30 14:12:18 +05:30
Mohd Kaif 8b0ac61afd Merge pull request #1255 from semantica-agi/feat/claude-wrapper
feat(llms): add first class Anthropic provider wrapper
2026-08-30 13:43:58 +05:30
KaifAhmad1 9d98eedaa1 fix(llms): replace retired default model and tidy Anthropic wrapper
claude-3-sonnet-20240229 was retired 2025-07-21, so the wrapper's
default model and every copy-paste doc example would fail at
generate() time out of the box. Switch to claude-sonnet-4-6
everywhere (wrapper default, __init__ docstring, docs guide, tests).

Also cleans up leftover docstring typos/spacing from the previous
review pass and adds unavailable-path test coverage for
generate_structured()/generate_typed() to match generate(), clearing
ANTHROPIC_API_KEY in those tests so they don't flake on a runner that
has a real key set.
2026-08-30 13:33:18 +05:30
Zohaib Hassnain 5ffb212a8f Merge branch 'main' into feat/claude-wrapper 2026-08-29 22:15:41 +05:00
Zohaib Hassnain ca7f743dab fix(llms): address Qodo review findings on Anthropic wrapper 2026-08-29 22:15:19 +05:00
Zohaib Hassnain 5cf59fdd88 feat(llms): add first class Anthropic provider wrapper 2026-08-29 22:01:16 +05:00
Mohd Kaif 0384a8de30 Merge pull request #1077 from cxzg007/fix/rete-pattern-matching
fix(reasoning): implement RETE alpha/beta matching with Token model (#300)
2026-08-29 21:57:15 +05:30
KaifAhmad1 f3d5932c24 Merge remote-tracking branch 'origin/main' into fix/rete-pattern-matching
Reconciles this PR's Token-based alpha/beta matching (#300) with the
rule-actions/provenance layer merged separately in #1096. That PR built
bind_reasoner()/execute_matches() action-firing/_executed_activations/
reset_action_history() on top of the still-broken always-True stubs
(via an interim _bindings_for_rule() regex re-extraction), so main and
this branch touched the same propagation code with incompatible shapes.

Kept this branch's Token(facts, bindings) model for alpha/beta
propagation (the actual fix for #300) and layered main's action/
provenance plumbing on top of it, sourcing Match.bindings directly from
Token.bindings instead of re-deriving them with _bindings_for_rule(),
which is now redundant and removed. Also fixes a 2-tuple/3-tuple
unpacking break in test_matches_reasoner_match_rule caused by
Reasoner._match_rule()'s return shape changing upstream, and drops an
unrelated encoding-only .gitignore diff.

Verified: tests/reasoning/ (106 tests) and flake8 --max-line-length=88
both clean on the merged tree.
2026-08-29 21:50:04 +05:30
Mohd Kaif a5aac7e22a Merge pull request #1232 from dex0shubham/test/1167-fastapi-collection-guards
test: guard fastapi-dependent modules so collection succeeds without the explorer extra
2026-08-29 21:20:20 +05:30
dex0shubham 3448ac0689 test(utils): restore both module bindings in the progress fixture
Importing a submodule rebinds it as an attribute of its parent package,
so restoring only the sys.modules entry left
semantica.utils.progress_tracker and
sys.modules['semantica.utils.progress_tracker'] pointing at different
objects for every test that ran afterwards.

Addresses review feedback on #1254.
2026-08-29 15:00:04 +01:00
dex0shubham 70dfbf151c fix(utils): write console progress to stderr instead of stdout
ConsoleProgressDisplay wrote every progress frame to sys.stdout. Progress
is diagnostic output, so stderr is the correct stream for it — tqdm and
most progress renderers default there for the same reason — and stdout
must stay clean for programs that carry a machine-readable protocol on
it. The stdio MCP servers put newline-delimited JSON-RPC on stdout, where
an interleaved progress bar makes a response body unparseable (#1134).

ConsoleProgressDisplay now takes an optional stream, defaulting to
stderr. The stream is resolved per write rather than captured at
construction, so a later rebinding of sys.stderr (pytest capture, for
instance) is honoured. All writes and the four bare flushes route through
it, and the emoji-capability probe now inspects that stream rather than
stdout, so a cp1252 stderr still degrades correctly.

The existing cp1252 tests in tests/deduplication/test_deduplication.py
patched sys.stdout to assert emoji auto-disabling; they now patch the
stream progress is actually written to. Their intent is unchanged.

Closes #1134 (point 1 only; the SEMANTICA_KG_PATH persistence and README
items remain with @akaszubski)
2026-08-29 13:53:48 +01:00
dex0shubham 47446ebdde test(explorer): guard the deterministic-rendering e2e module on fastapi
This module landed after the branch was opened and imports
semantica.explorer.app at module scope, so it reproduced the same
collection error on a clean [dev] install.
2026-08-29 13:31:00 +01:00
dex0shubham c8a591e89e test: scope the security-regression guard to the SPARQL class, guard the new decision-route test
Addresses review feedback on #1232.
2026-08-29 13:27:42 +01:00
dex0shubham ab86127e4e test: guard fastapi-dependent modules so collection succeeds without the explorer extra
Closes #1167
2026-08-29 13:27:42 +01:00
Mohd Kaif 30592b1285 Merge pull request #1245 from HsienW/fix/sliding-window-chunker-non-termination
fix(split): validate sliding window progress
2026-08-29 15:56:29 +05:30
Mohd Kaif aceb69a5bc Merge branch 'main' into fix/sliding-window-chunker-non-termination 2026-08-29 15:44:18 +05:30
Mohd Kaif e13c953bd8 Merge pull request #1249 from semantica-agi/fix/codeql-action-pin
ci: resync github/codeql-action pin to current v4
2026-08-29 14:12:47 +05:30
yzxcj797andSameer Kadam 85d6ccd0a5 fix(memory): find_by_entity returns all matches by default (#1024)
* fix(memory): find_by_entity returns all matches by default (limit=None, not 10)

* Address review: move find_by_entity tests to the AgentMemory area

The regression tests lived in tests/test_seed_manager.py, mixing unrelated
domains. Moved to tests/context/test_agent_memory_find_by_entity.py with a
shared fixture; the unbounded default itself is unchanged and deliberate —
it IS the fix (#1018): an erasure workflow computing what references an
entity cannot paginate, so silently truncating at 10 left live references
behind. Callers that want a page pass an explicit limit.

---------

Co-authored-by: Sameer Kadam <sskadam6305@gmail.com>
2026-08-29 14:09:40 +05:30
hsien wei dfd668c206 fix(split): validate sliding window progress
- Reject non-positive stride values before chunking and validate temporary overlap overrides before mutating chunker state.

- Restore the original overlap and custom stride with `try/finally` so state remains unchanged after both successful and failed `chunk_with_overlap()` calls.

- Add regression coverage for invalid stride and overlap values, valid boundary cases, and state restoration.
2026-08-28 05:03:45 +08:00
江俊杰 e74d0a274d Merge remote-tracking branch 'upstream/main' into fix/rete-pattern-matching
# Conflicts:
#	CHANGELOG.md
2026-08-25 10:04:54 +08:00
江俊杰 b570794515 perf(reasoning): precompile alpha node condition regex
unify_condition() rebuilt a regex (re.split + concat + re.match) for
every fact tested against every alpha node. Since RETE evaluates many
facts across many alpha nodes, this repeated construction added
significant overhead.

- Extract regex construction into _build_condition_regex() (reused by
  unify_condition and AlphaNode).
- AlphaNode.__init__ now compiles its condition once (no initial
  bindings at alpha time) into self._compiled and reuses it per fact.
- On compile failure, log a WARNING and treat the node as non-matching,
  consistent with the earlier observability fix.
- Add tests for the compiled path and the compile-failure fallback.

Refs #300
2026-08-19 10:24:24 +08:00
江俊杰 a94cec3b36 fix(reasoning): log unify_condition regex errors for observability
Previously unify_condition() silently caught re.error and returned None
with no log context, unlike Reasoner._match_pattern() which logs the
pattern/regex/fact on failure. This made malformed conditions hard to
diagnose in the RETE engine.

- Add a module-level logger ("semantica.rete_engine") for the standalone
  unify_condition() helper.
- On re.error, log a WARNING including the condition pattern, compiled
  regex, and fact string before returning None.
- Also catch unexpected exceptions (noqa BLE001) with the same context,
  mirroring Reasoner._match_pattern behaviour.
- Add tests asserting both error paths log a warning and return None.

Refs #300
2026-08-19 10:24:24 +08:00
江俊杰 f9b1295d14 fix(reasoning): implement RETE alpha/beta matching with Token model (#300)
AlphaNode._matches and BetaNode._can_join were placeholder stubs that
always returned True, so the Rete network fired every rule for every
fact. Add a regex-based unify_condition (reusing Reasoner._match_pattern's
approach) that binds ?vars via named groups and enforces repeated-variable
and cross-condition binding consistency.

Rework propagation around a Token model (facts + bindings) instead of bare
facts: AlphaNode emits single-fact tokens, and BetaNode.join merges left/
right tokens, concatenating facts in condition order and returning a merged
token only when shared variables agree. This fixes a P1 chained-join defect
where rules with three or more conditions lost bindings and accumulated
wrong facts at the third join, and a conflicting third condition could
spuriously fire. Beta nodes now keep both left/right token memories and
join each new token against every token on the opposite side.

Also fix an adjacent bug where beta nodes were never wired into their
inputs' children, blocking propagation. Adds tests/reasoning/test_rete_engine.py
including a TestThreeConditionChain suite (valid match, third-level conflict
suppression, insertion-order independence, complete in-order Match.facts,
multiple left tokens joining one right fact, parity against
Reasoner._match_rule, and reset clearing all token memory).
2026-08-19 10:24:24 +08:00
326 changed files with 44663 additions and 5217 deletions
+3
View File
@@ -18,6 +18,9 @@
.git/**
.github
.github/**
!.github/requirements/
!.github/requirements/explorer-extra-py313.txt
!.github/requirements/pep517-build.txt
.claude
.claude/**
.codex
@@ -0,0 +1,56 @@
name: 'Setup Semantica'
description: 'Install Python, cache pip, and install the semantica package into a workflow'
author: 'Semantica'
inputs:
python-version:
description: 'Python version to set up'
required: false
default: '3.11'
version:
description: 'Version constraint to append to the pip spec, e.g. "==0.6.7" or ">=0.6,<0.7". Leave empty for the latest release.'
required: false
default: ''
extras:
description: 'Comma-separated extras to install, e.g. "explorer,all"'
required: false
default: ''
cache:
description: 'Pip cache mode passed straight to actions/setup-python ("pip" to enable). Left empty (disabled) by default because this action is meant to run standalone in any caller repo, and actions/setup-python errors out if it cannot find a requirements.txt/pyproject.toml/setup.py/poetry.lock to key the cache on. Opt in only when the caller repo has one of those files.'
required: false
default: ''
outputs:
version:
description: 'The installed semantica version'
value: ${{ steps.verify.outputs.version }}
runs:
using: 'composite'
steps:
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7
with:
python-version: ${{ inputs.python-version }}
cache: ${{ inputs.cache }}
- name: Install semantica
shell: bash
env:
SEMANTICA_EXTRAS: ${{ inputs.extras }}
SEMANTICA_VERSION: ${{ inputs.version }}
run: |
python -m pip install --upgrade pip
if [ -n "$SEMANTICA_EXTRAS" ]; then
spec="semantica[$SEMANTICA_EXTRAS]$SEMANTICA_VERSION"
else
spec="semantica$SEMANTICA_VERSION"
fi
python -m pip install -- "$spec"
- name: Verify install
id: verify
shell: bash
run: |
VERSION=$(python -c "import semantica; print(semantica.__version__)")
echo "Installed semantica $VERSION"
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
+23
View File
@@ -101,6 +101,29 @@ updates:
allow:
- dependency-type: "production"
# Explorer frontend (npm)
- package-ecosystem: "npm"
directory: "/explorer"
schedule:
interval: "weekly"
day: "monday"
time: "03:30" # 3:30 AM UTC (9:00 AM IST)
open-pull-requests-limit: 10
reviewers:
- "KaifAhmad1"
assignees:
- "KaifAhmad1"
commit-message:
prefix: "security"
include: "scope"
labels:
- "dependencies"
- "javascript"
- "security"
allow:
- dependency-type: "production"
- dependency-type: "development"
# Docker dependencies (if you use Docker)
- package-ecosystem: "docker"
directory: "/"
+58
View File
@@ -0,0 +1,58 @@
# CI tool requirements
Hash-pinned `pip install` targets for CI/release/Dockerfile steps that install
something other than the project's own audited `requirements-ci.txt` set.
These exist because OpenSSF Scorecard's Pinned-Dependencies check flags any
`pip install` in a workflow or Dockerfile that isn't hash-verified, and
`requirements-ci.txt` alone doesn't cover build/release/security tooling or
the project's own local-source install.
Each `.txt` was generated from the adjacent `.in` (or, for `explorer-extra-py311.txt`,
`explorer-extra-py313.txt`, and `base-deps.txt`, from `pyproject.toml` directly) with:
```
uv pip compile <input> --python-version 3.11 --python-platform linux \
--constraint requirements-ci.txt --generate-hashes -o <output>.txt
```
(`--constraint requirements-ci.txt` is omitted for `bootstrap.txt`,
`build-tools.txt`, `uv-tool.txt`, `twine.txt`, `pip-audit.txt`, and
`security-scan-tools.txt`, since those install standalone tooling with no
version relationship to the project's own dependency tree.)
Regenerate a file the same way after bumping a pinned version, and re-run it
whenever `requirements-ci.txt` changes if the file used `--constraint` (see
each file's own autogenerated header comment for its exact command).
| File | Used by | Installs |
| --- | --- | --- |
| `bootstrap.txt` | security-scan.yml, benchmark.yml | pip, setuptools (upgrade before anything else) |
| `pep517-build.txt` | ci.yml, benchmark.yml, Dockerfile | exact `[build-system] requires` from `pyproject.toml` (setuptools, wheel) - installed with `--no-build-isolation` before any `pip install -e .` / `pip install .`, since `--no-deps` alone doesn't stop pip's PEP 517 build isolation from fetching those two *unhashed* |
| `explorer-extra-py311.txt` | ci.yml | semantica's base deps + the `explorer` extra, resolved for python 3.11 |
| `explorer-extra-py313.txt` | Dockerfile | the same, resolved for python 3.13 (the image's actual interpreter) |
| `pytest-tool.txt` | ci.yml | pytest, for the pre-all-extras deterministic test |
| `uv-tool.txt` | ci.yml | uv, to verify requirements-ci.txt is current |
| `build-tools.txt` | ci.yml, release.yml | build, wheel |
| `twine.txt` | release.yml | twine |
| `pip-audit.txt` | security-scan.yml | pip-audit |
| `security-scan-tools.txt` | security-scan.yml | bandit, semgrep, jq |
| `base-deps.txt` | benchmark.yml | semantica's base deps (no extras) |
| `benchmark-extra.txt` | benchmark.yml | the benchmark-only libs (neo4j, pdfplumber, etc.) |
`explorer-extra-py31{1,3}.txt` and `base-deps.txt` are large (they mirror
most of `requirements-ci.txt`) because semantica's `dependencies` list in
`pyproject.toml` isn't extras-gated - installing the package at all pulls
the full base set. That's expected, not a mistake.
`explorer-extra-py311.txt` and `explorer-extra-py313.txt` are **not**
interchangeable, and can't be collapsed into one file compiled for either
version: `librosa`'s `audioread` dependency needs `standard-aifc` /
`standard-sunau` only under `python_version >= "3.13"` (Python 3.13 dropped
`aifc`/`sunau` from stdlib). A file resolved for 3.11 simply omits those
packages' hashes, so installing it with `--require-hashes` on a real 3.13
interpreter (the Dockerfile's base image) fails outright rather than
silently under-pinning. Any other file shared across a 3.11 and 3.13
consumer would need the same split if it hits a similar stdlib-removal
edge case - check for `ERROR: In --require-hashes mode, all requirements
must have their versions pinned` on the *other* Python version before
assuming one `--python-version` covers every consumer.
File diff suppressed because it is too large Load Diff
+14
View File
@@ -0,0 +1,14 @@
rdflib
neo4j
faiss-cpu
torch
pyarrow
pdfplumber
python-pptx
openpyxl
lxml
python-docx
beautifulsoup4
chardet
langdetect
en-core-web-sm @ https://github.com/explosion/spacy-models/releases/download/en_core_web_sm-3.8.0/en_core_web_sm-3.8.0-py3-none-any.whl
File diff suppressed because it is too large Load Diff
+2
View File
@@ -0,0 +1,2 @@
pip
setuptools
+10
View File
@@ -0,0 +1,10 @@
# This file was autogenerated by uv via the following command:
# uv pip compile .github/requirements/bootstrap.in --generate-hashes --python-version 3.11 --python-platform linux -o .github/requirements/bootstrap.txt
pip==26.2.1 \
--hash=sha256:71138adf1f4ca900cdb7d289c21b7494329f2332b6d85f0e1c42108c0384ed3e \
--hash=sha256:f6ad667e89a1fe78046c8f13232b247200f5258d7828f3f7883d660878e0813f
# via -r .github/requirements/bootstrap.in
setuptools==84.0.0 \
--hash=sha256:51a52592b3b99e102b609654876bd65f19f999935166d1352678931132b0c670 \
--hash=sha256:f4695c21257f0d9b537ec2692c941d02ee143b7cc1276941349a546573b2ef73
# via -r .github/requirements/bootstrap.in
+2
View File
@@ -0,0 +1,2 @@
build==1.6.0
wheel==0.48.0
+20
View File
@@ -0,0 +1,20 @@
# This file was autogenerated by uv via the following command:
# uv pip compile .github/requirements/build-tools.in --generate-hashes --python-version 3.11 --python-platform linux -o .github/requirements/build-tools.txt
build==1.6.0 \
--hash=sha256:bd2c8afc603e7a2e0ce70e2ea85f0a6d02043bafbd307f5bada0f98669eca5af \
--hash=sha256:f7aaf1ebbb79178a02ba248bb524f2176b256017e17e8e4bd4289c7b38cc2bad
# via -r .github/requirements/build-tools.in
packaging==26.3 \
--hash=sha256:94edc256424af38762eb31306eed28beb9f0efc50a8837492c9d6fd6004aed79 \
--hash=sha256:d7193f7c8e4e93f444fde0262bf90af30e16fa0ad0ad44cb553c87339b23cd1c
# via
# build
# wheel
pyproject-hooks==1.2.0 \
--hash=sha256:1e859bd5c40fae9448642dd871adf459e5e2084186e8d2c2a79a824c970da1f8 \
--hash=sha256:9e5c6bfa8dcc30091c74b0cf803c81fdd29d94f01992a7707bc97babb1141913
# via build
wheel==0.48.0 \
--hash=sha256:3217dcc807155e45db462d7ef2431f5ddda0d7273b700d05a67b271ceb1287ab \
--hash=sha256:94800765601e9171bf5d58d066e640662842bcedcbab982b2c90787a2c987322
# via -r .github/requirements/build-tools.in
+1
View File
@@ -0,0 +1 @@
checkov==3.3.16
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+2
View File
@@ -0,0 +1,2 @@
setuptools==84.0.0
wheel==0.48.0
+14
View File
@@ -0,0 +1,14 @@
# This file was autogenerated by uv via the following command:
# uv pip compile .github/requirements/pep517-build.in --generate-hashes --python-version 3.11 --python-platform linux -o .github/requirements/pep517-build.txt
packaging==26.3 \
--hash=sha256:94edc256424af38762eb31306eed28beb9f0efc50a8837492c9d6fd6004aed79 \
--hash=sha256:d7193f7c8e4e93f444fde0262bf90af30e16fa0ad0ad44cb553c87339b23cd1c
# via wheel
setuptools==84.0.0 \
--hash=sha256:51a52592b3b99e102b609654876bd65f19f999935166d1352678931132b0c670 \
--hash=sha256:f4695c21257f0d9b537ec2692c941d02ee143b7cc1276941349a546573b2ef73
# via -r .github/requirements/pep517-build.in
wheel==0.48.0 \
--hash=sha256:3217dcc807155e45db462d7ef2431f5ddda0d7273b700d05a67b271ceb1287ab \
--hash=sha256:94800765601e9171bf5d58d066e640662842bcedcbab982b2c90787a2c987322
# via -r .github/requirements/pep517-build.in
+1
View File
@@ -0,0 +1 @@
pip-audit==2.10.1
+423
View File
@@ -0,0 +1,423 @@
# This file was autogenerated by uv via the following command:
# uv pip compile .github/requirements/pip-audit.in --generate-hashes --python-version 3.11 --python-platform linux -o .github/requirements/pip-audit.txt
boolean-py==5.0 \
--hash=sha256:60cbc4bad079753721d32649545505362c754e121570ada4658b852a3a318d95 \
--hash=sha256:ef28a70bd43115208441b53a045d1549e2f0ec6e3d08a9d142cbc41c1938e8d9
# via license-expression
cachecontrol==0.14.4 \
--hash=sha256:b7ac014ff72ee199b5f8af1de29d60239954f223e948196fa3d84adaffc71d2b \
--hash=sha256:e6220afafa4c22a47dd0badb319f84475d79108100d04e26e8542ef7d3ab05a1
# via pip-audit
certifi==2026.7.22 \
--hash=sha256:62f22742b58a1a33014a2b6b706588a8d7e2a88ae7bd1a6ebe8c992928483775 \
--hash=sha256:741e2c3b351ddf169a738da9f2c048608ff7f2c5cc02f1ebc6b118bb090d5d55
# via requests
charset-normalizer==3.5.1 \
--hash=sha256:00668ebb0609751758682eb0b5857e7c35b9f00e84dfdef062e103244ec94d45 \
--hash=sha256:012a22b88a77ca2e59b98ac5889b0deb604147666032f45e6d6e217634d2550d \
--hash=sha256:01e93745f7f219b703b60ba7afead36cfc4242782be5af484673fc500df12da5 \
--hash=sha256:04368edf83514385ffc3e1cfd4546e595f4f1272dd23ba437a93a9cc3741d47b \
--hash=sha256:0722590aabf9dc6a6c0343d523c05458fa2b5047dbe6302fd526bb570600753f \
--hash=sha256:07ffd07412fc5d5e84cd8952acf9ff7e4ed7a708e69d1bada19d8ba91711353f \
--hash=sha256:09a7bba9f739468c8e78c36a75c33768e53cb1959fc638f510454c14683f00d5 \
--hash=sha256:0b2b1b3fa5670c127b246df1d0c059defd41f689a868a3b9d79df9b1cac42d22 \
--hash=sha256:0c6dfb5ca6723eeed15aa8e564a014d69fcb8812f94eef11fe3631e0508199f5 \
--hash=sha256:0d929fc574b4d6fd9e7c0f5c2ede8716a41911923aa7fa5fce38e0818aa4a1ac \
--hash=sha256:13e3afe97712e8887cd516e960c63f0b93122971e5b5e4b2622fe7701771e838 \
--hash=sha256:15f024313246a4ed976c60f440bb8d257815513a681d212ff74fd46f7d715a90 \
--hash=sha256:195ce897c6153c0700078142cf8efe3e6454ca4cf4357499e4078dfd83396626 \
--hash=sha256:19a3dd5aa73cef1c99687c4fc57db016a9c17104ae1185da88ba566a5d3bebe4 \
--hash=sha256:1d1c7a53a6c2103925cdd6d7229f8c567379f211c869793df679f2e9f738c369 \
--hash=sha256:1f5883d77fd409a261abb5dc8ccbe335720d798b1de4abb3b1d47ccbbc76b53b \
--hash=sha256:21b82d8082f6f5e7f456ef0bd16323d08de1266efbfeb476e64b2a91d1471a4e \
--hash=sha256:252d099029bcbea642f2a06c4ed5046bdf8b5a8150b64afa5e027e88b106e5ee \
--hash=sha256:256dd4d85d9e4dc595e2bc983c980e73f62ddeb3165c58b4c3dfe78c5c8548c1 \
--hash=sha256:26422d45fd13551cf564c58932f7d72b4f58b93b0fcf18c35ba6be12b46bb102 \
--hash=sha256:2679de311c7946dde5d3b6f44941844133ff5c7cb86099c0061ab1e8901c20a8 \
--hash=sha256:29880d17a8eb0b5cfdfd8944b468322928059aa35f1f5fa8ff22b149ec0b42f8 \
--hash=sha256:2bced4061f000f7187254a02ad3433ae17eaf991747ceea2f478422590a5bba9 \
--hash=sha256:2e9cf9253119d8e5d111f05d71626786fd3d6193817316eab1ca088cdb8593cf \
--hash=sha256:2f06b7eae9dbe77fe1d644ca244dad508de8d302870a43f3c559b521270938a0 \
--hash=sha256:2f293479cce755c75f1697e87c409b7ae4c555c7dfecb6e988ad13abba943031 \
--hash=sha256:329fc3ccb63ad22d867d84c2adea759a64079a37ba4a343433b02c7a2816871e \
--hash=sha256:343fb4f2821043bd87095f7b08a1a181febc8e36ac64212143bbfd0a0e1bc235 \
--hash=sha256:3588e376b3ea2eea84976f67273d679f229e24c66dce7b82ae45aef04ff6e072 \
--hash=sha256:35aea775dc2bd5f54cd84a1cd2696cc3207c479cb9cf0bd346f0d343e4300ddb \
--hash=sha256:35fe081843b35aad20ffeccec3eeffbe637b15d14f3fb22cc1b59cd8ec17e93c \
--hash=sha256:36047af20e17097c3bb9476c2b7655f2f7aa51322c0ba58c07695bedf755a950 \
--hash=sha256:3617ac3cfd8b9888f145ad89dd6e692285834b0201c6074a5eeaad3fd4d668c2 \
--hash=sha256:366ec70f5547c640d3ce1985722490f23faf4eb5216a7eeba78277490e78dacb \
--hash=sha256:394fea06235c8543390050ed5f529187074b029fb027213f6c46ac11ab5d950e \
--hash=sha256:3d27167433c0d5f18dc850f07d0b3816221984fecdc405d6c157a6f0b8f8e9e6 \
--hash=sha256:3e5e1224c0a6a90e05843e07adfec669edebec17801c67072f51e59561d63c0b \
--hash=sha256:41876ee62a3dddf48ff1121ad8f0798032aa03f2fd35f21f34a4cab14f18d8d2 \
--hash=sha256:433c5a81eade63b47e522303bad236f59dba55ea6951746f5558355eeed8c75d \
--hash=sha256:4582c27e8c889d64811987b5967fbd3ae0c823fe1fd933b543d55ac20bb475fa \
--hash=sha256:485a0d363cafefcd2538a73c7c838daa2035f09b2c9f9b5e3133f80c6aeb84c2 \
--hash=sha256:494b70049a4d69aec6e8137c13af4cf8db8c9f9820a1392ac293b0dd2987a818 \
--hash=sha256:496846868fea80e479324862fa877f02411f2fd0f83b79ccee2607aa68b2a032 \
--hash=sha256:4abdc5f9ad448c1ecbfae2974b820535d6bc6e7eef63babbab3d81cf46968c71 \
--hash=sha256:4b599739b93b2cbeded49645ae3c8d1405c29ddfbceac1545c87a3f9580a9e96 \
--hash=sha256:4bea7f8ebe90bbd7f0e4a2de42ca6924ba23e3e76418c408ff82f1d46fabd687 \
--hash=sha256:4c4fb141a727957c93edfe5c32a26ceb6b5f6461d67146e2d39f51e16170bea8 \
--hash=sha256:4c9548dc78002099910abaebc0a72ac58b7d30931869e0351c09b507dff4ece3 \
--hash=sha256:4d26f14f041e83dd8edfd61f4cd4fa7285d31798b5bf1f28e70c367ba6c41d61 \
--hash=sha256:4f298bdadb8f0b9e5672877f647d1be9373ef5320c9e2f049795e26cad28b6a9 \
--hash=sha256:52ec005752a56ae79547a05c0139ca2501a0c866390b6115008456b9f0e7cde1 \
--hash=sha256:55261ac0d2941c42f196dd576f543d87a8ee03cd6f5e30dfb4d807b2e3b9121a \
--hash=sha256:56490c595a28b1bb27dfc583e816152a9767721ef58b2c03b13f954d2f707420 \
--hash=sha256:58d3e12c88e0950bca850ae1f7c256055c097639c2edb9eb123af9807d8b15e4 \
--hash=sha256:58d4aa13a59c969dbfdf9e6a9560e242cbfd9e8a8f50c2747714df1a423adf65 \
--hash=sha256:59171c6e45bf07d0d5cab3b0bf81d945035530f6873398b3b531c31184d46663 \
--hash=sha256:5b6d1386bf0096d26d3a863dc0a487a5b4eb9aa93cf5ba69683d29dde6b9d60f \
--hash=sha256:5c0ea61a470e070686aa30892fed79e297d2c8d0ab46b8bcdf027d38c51da591 \
--hash=sha256:5c84bec0ab5ae0c64bfe73a7d2adcb5ce73b467523fc27fd6a28ab2aa6cbe35a \
--hash=sha256:5ca0555312ae2fe82715cada7fac375530c2f3349e1eaa1bcb33d0283ac79a18 \
--hash=sha256:5d8531a6569d025f68e2321e7638fb7978f23db58e5f69f56913837aae03816e \
--hash=sha256:5e2d0e146dcb57034f8b97dc58d2d512cb90aba253960ce449f695fec6a82c6f \
--hash=sha256:5fc45d653ea8c9a20479167e11d4a0f8cb2fa3470737ab6f9c827532313187b7 \
--hash=sha256:6117b84ea48435e5356dc737f5121485c30920ba43375fa7b434fd753df0eac3 \
--hash=sha256:6199d5606e2bbf2b096cf64d03f8b6790c91081d5ac866b8e7bb6422738cc60c \
--hash=sha256:62b55f6722735a6c472f88361cde6640608773d9443cebdbb51abf436a1fcdd3 \
--hash=sha256:687c9ca3035544b113bea2055e180af96fb63c0c476e22a9180f51925186e7b7 \
--hash=sha256:6b7430cf5728e68f6c462254009a6ef4086e1bea43cf2f57aa9c55fb4f50ff96 \
--hash=sha256:6ba32c4d2abf1d2fe7cf27d280f4cca5664233b0f885549c7761719eb977f486 \
--hash=sha256:6c9cdde8becb25a7fde49924511aa2644d6f8081cc8df8e9452724303348d8e3 \
--hash=sha256:6df0ec430f9a831772c23ca5a224cba36517a58a84bb32c32bb59a9fa67c47f6 \
--hash=sha256:6e2912d4babbc65196ac13c2f53468dc57fb8b9c25ef913e8c59ddf7c6dc0e1b \
--hash=sha256:6e5e4d73d588ca5ed09df1b7dcd1b203d1df3c542e3f50d126c947d432b10731 \
--hash=sha256:70055ff39b97c99e7ae40ea3e393fb62aa2e44dbd9b29f8d14f42fb0025c3959 \
--hash=sha256:706bfd38730a5ac7a365793269a00f4e988178cec121391f4248d84ad8c972e9 \
--hash=sha256:7235dc28fc6dd9d832ac7c7bce95367dedb85929f17368a0c2bee1e080b9acbf \
--hash=sha256:774d157f112367ff4abd29019f38f023c24e00e56edc7829c20e358a5a913ad8 \
--hash=sha256:77efcff2b23071c349402ac1066667a3d011f62398d81408c9b88ad991747c9e \
--hash=sha256:789b8982559ae28dad2356519f841655756cdcd96616410590ae0b17454ee64f \
--hash=sha256:7ac76cf9afd34929d76eb7fcb63be476a4853d8a96f0dcf2d0db68a0cbdf9885 \
--hash=sha256:7c0c10730342b0c9b35dd1d619beb8214e520bd96a1f870f452680b238aab3e0 \
--hash=sha256:823f82903d189af463d7df250ef1f7f696f3cee08cc8d91deb565e8d425f6506 \
--hash=sha256:838648accb3a7fd9803fd45c87bce8509648eb0c11bc34e216141300977244f2 \
--hash=sha256:854066be00447fa8de2ccbbe893e2ffc4b123ef16d897af794c1e18bd4a714b0 \
--hash=sha256:85d5855daafc240cc045c026d7a15fd198a09b0fc8ff6f5ecbb5297b509cb11e \
--hash=sha256:85de3134b5379856e323ba37c19c9256d39425f7b76a63af52b09fb4664c2e8f \
--hash=sha256:87e4f41d375c0b9be2fb5251aee4b8a689169e134535aed81bf085c3b647451e \
--hash=sha256:88ca277405c2d3b71c4e1c2ee0e7966e807bcba86a69d11e19ba199d18ae4491 \
--hash=sha256:88e85ab89cb822c1e635f51d6d32e488f94e002e70e2f492bdb8b945543f345a \
--hash=sha256:8ac8c94b6539074e0f40899301273ac8402b9b3e01c7b7ba269ff30340aaaf20 \
--hash=sha256:8fe532b3c966d1fb794e0698e4589d0444017ae77fc0b31edea13c0e35bcc449 \
--hash=sha256:9085f87b0e38a2b92b8923059b4e8789fe40d9279712d15dcc670048d77079af \
--hash=sha256:90b7481fb62fbe172c558bc6fd1c4c98d82004a54a7551f20e11ac9bf0b8708c \
--hash=sha256:92caef967d287a407085d61176fce4012b1dd62daed4eb6d5ceb26d3d2538712 \
--hash=sha256:9362dd90aa7dab48c0054a21187791ccf05473f7dba5d92b8033ae62164675e7 \
--hash=sha256:94d78ecec2605a8d0398b0f365d5f12a63248438516f5dac536a5eff7337df4a \
--hash=sha256:94fbf1c0c6cc0d3d5e50f9a9313a8cdca90dd696d34b381cd1704f8c9e939f20 \
--hash=sha256:950f23cb393f85543777b0433f082cddd25b51ab398eac7971146495679efe5f \
--hash=sha256:96eefc178f8636b9c760c5829345307fd81cfae9ab1e80997dbddeb0f54ee9a3 \
--hash=sha256:96fef3e886d6a9874b14f27fc193fbdc69d5d8035783d86aa4e1cea594e695f9 \
--hash=sha256:977cdbd483a9cff38179bea4fd754289a6f2195c7abd414aba85410b3e66cc5e \
--hash=sha256:978eab16f55b4ab2c2a745be9a0a840bf8f09a7f227d9c76eb30214d078865a5 \
--hash=sha256:994e883d17c559cdfd38c84003c8b27d25424a1077272a17e7cd27bfe0bf57b2 \
--hash=sha256:9ac4444d8d4fd4c4bd08bf451ed3167aa9e7ec6cdb41b648794f1d1103652e36 \
--hash=sha256:9b5db6052055d34d41230fb78d7c439c23dc536a9896f6cb039e8dd92cfc1263 \
--hash=sha256:9d9a0dc7cbe9bec24c3f767c9122c41fe5a1bc43f47cd099d00d393e09769de4 \
--hash=sha256:9dbdd9205662134957cf0c324f639bdc5031c0ca056e2369e238db75187c0f11 \
--hash=sha256:9eea3ab2597a5e65fe65296e2d6a84570845a6b55532d90333d740d48bbc850a \
--hash=sha256:a2028475ba855475b8b4d3cfeb4994269c967aea8b9892dfba907f4263a863a3 \
--hash=sha256:a3a370082ce34d0612f421e15fe011c53bb1feff21a26d06ad4fb244dab5a375 \
--hash=sha256:a545775cfe815855ea32d7c27731d79da358ef2055b4a25830231b1622dd18aa \
--hash=sha256:a5cbd90ecf0fc62e64726917ad083b73001f0563657a87ec3c0b504e277dc90d \
--hash=sha256:a6d095662e73e74f0a49988e0593373e243e3a52e27bfeea0a859e88acf4a0f5 \
--hash=sha256:a6dac12ff6b846103483683f60c5f8fee205121adc58ffd87e90a90a3af69e99 \
--hash=sha256:a951ad59cad9145664a730d3036b40b844e74d2d3683da40111463cd3a83845d \
--hash=sha256:aa1099b956fb795e686d073568f6dc002a0bb89765ea6d5b055dd7d9bf1b116c \
--hash=sha256:aa2bb0b37202dca27175591f761108b5d34096ade1191ffe4808bdf6b1571488 \
--hash=sha256:aae2ee51122d3ae968a3837d97dc24a0aeebb0dea23694422cd172bd30017cd6 \
--hash=sha256:ab743e9bc90c1f73552ec33e10e3331315acd2c397b36065b591b0181de533cc \
--hash=sha256:ac00177c4831ffa650f8609e4bdddd5fe09c03b1c0c47acece7e6ea20421598b \
--hash=sha256:ac13b004224fb341e1e25a1ed5e19d32f57cdb2a403e01f003b46f051a550f6f \
--hash=sha256:acaf604462bf330b0d07e7a07c1d6e4adac79e5fb13e9c5140590542cafacc00 \
--hash=sha256:ae31a1a1db2ee6cc2942fccaf695c934bc7f3db9f2133a3fef1f367cf1a4ab10 \
--hash=sha256:ae4a097991662cd4fff0ddc74e0fe7874f82e00042fa0ea00855645ed0c79598 \
--hash=sha256:aea996a6aba25260827c9ea511d1addfde2da9eb686ac961838509086188b7e6 \
--hash=sha256:b39b69b347e5e47a3b5b8cfc005c68c1ba347474e3960236c4944a8ecd174962 \
--hash=sha256:b54e7e13267d49ffbfe68e25b3cbd774dab38fa37238f71265e91b36146eb21c \
--hash=sha256:b9af956078716df40d985fb0dfeb2c2120c5ca92ba4ff4b388acfd01cdc14d08 \
--hash=sha256:ba2f37ee79e6338845261a3c5b1784e5d1acdff2c0785b284f1b633033d136ab \
--hash=sha256:ba501e667c17d8411f98e67a022d9604ef179aff0e459b7e292c796837c13573 \
--hash=sha256:baf3775a2635e5a11fbd5e4e64ee69c7e86875d224a5c72aca4c141064589a90 \
--hash=sha256:bb57753e36e4855b8ca375069482250a6246372331a3e4f3407eaebb007443f5 \
--hash=sha256:bd6c173f04743d483881bffa1478d5a4624475b8cd1d2194956a75548e191c18 \
--hash=sha256:be47f99644b208bff7766314013f9acf57b056b04191d570d68ad14022cf5b1d \
--hash=sha256:c010f5581d9c612804cc59fcf7b524b707fbcb72828551237ab545bb5c7034af \
--hash=sha256:c1dcc36dcb96abc02236e182d17e0f71430152a6c2c7447421da2d2dc144edea \
--hash=sha256:c428c6c31eb5f4277d7f8eccaf767fbd548ddd5ce3c8b4f4cbbfab3d96b5904c \
--hash=sha256:c658c50ac0c98cd755a2dd50b7977d3bca7df401dcc47fbdfa87db53ef7d4e8b \
--hash=sha256:c71fb0d56c920c269cd3e2e3fe7c610e3f1fdb21a6ce60efa6430ff63676cea6 \
--hash=sha256:c7b742bf31c88566b4bb6335a7f393bb322e580b6bb98df7bd0c25e6e3519ce8 \
--hash=sha256:cc0329df4caaceb950d2f580b5ac716a377f7059624a0bafaeaf8a218c6ed774 \
--hash=sha256:cc5d36d96478aa9c60654bd932525bf32964c62a7281eafdf16d85003a8d6004 \
--hash=sha256:ce854f5f478050ade5a238731c4ca985a7d3b3cb53ff600a9b5c3b689b5f0a7a \
--hash=sha256:ced3fdd71aaa83ce593746c2edb42b7a59cb4c19c8b5c407781c72e493aae55a \
--hash=sha256:cee5dd7c6fb5dd52a0fe2a740f9bc6e3593f5f8b1788bde49de02086f30182b2 \
--hash=sha256:cfa1c0cc3a8f9f53f1243a5a99ac36fd003880199383b37672e86ddda9cb07e2 \
--hash=sha256:d1ee1e296209fdce05b81b663250eefa02213a2da7b41bf26f7829b8ba3545aa \
--hash=sha256:d59b75732e9b6f27388e10c14b0259cc5f2e48c78627d185e6a177b58ad3cffe \
--hash=sha256:d63600d620ad0064c3a748b950ac5ea38a80190e5498532efefa4b7b3f1da1f3 \
--hash=sha256:dd732602a7009217f658d5863d12d79d373a4de0eebc111094bcdd3bb8e0a6cc \
--hash=sha256:e06efa066f7dbadbc84ebc126a97c452a6451dfcf589d89d788484949e1cf795 \
--hash=sha256:e199fb99720074809a7720f1c0b4d919eea8b87e88713e0f8f602f7bef543d9d \
--hash=sha256:e4b018dc5a0eee4676e38fe84a47a427816c590b93b55d9025274ec4d6ffc2dc \
--hash=sha256:e6621fb2a4988d6e53eedc455e5903e2679f3967b8acb3d639f1b63c14a2e893 \
--hash=sha256:e71c909f353863b2b89c83de2ebed71ea6d0df8a6ef65a128193c5e650766bef \
--hash=sha256:e90251c0c7bdd54a100a0dce3c07b7e637278c93af29dbf78ebb89a58c4bac7d \
--hash=sha256:e9fbdce1e47394b09bc9f26ab117dfc8d6491977a11d86f592bb42c779db2fda \
--hash=sha256:eb12fb2ba69ffa05f8695f61c69e591dc4b4a12ac3757ac8af8adb259bf56d17 \
--hash=sha256:eda059b6bc8bc0812d626fd91a7ce01bf583df0a61296eff390fd94141a34e30 \
--hash=sha256:f03ac127268b43ef4fe9e6ab6794a6794b49485a0cc0c1db79876d2f33f75bc7 \
--hash=sha256:f298e218441525d3794428b4c8b8fb8662c6d3ea79925d4807ee6b9a96a3bca5 \
--hash=sha256:f5542f9b941279d82d41eb0aa9f98eba36fe4df5c7086c651df7944935b37182 \
--hash=sha256:f6f7deae3feb4edfa2efaf7c574fe88cbf055038a6abdb40188e4fff66d5699f \
--hash=sha256:f9b1e28d0e8dbfa858abdba91d6b547beaf2df1a59bec6da6faae7b96a4991a9 \
--hash=sha256:f9f8405c2c758532c74fed975dbee57be1f31a6e865c031870c79a6ed3212ada \
--hash=sha256:fa48b1b63d639f9483e0633e092f5851e2348c352f1f9bb6c8182f87884ef876 \
--hash=sha256:fb78f6e7fcd8ad785d28cd577168bc1aaee827b25bb8755638f694794ea98f0a \
--hash=sha256:fbc597639158fd7c14d55e808718848319540f51b0e6746e3eefa59723a4a348 \
--hash=sha256:fce8cbd4997efeb450bd298b54f755dcdff18d496f7a5ddbb4867c6d7c88fdc3 \
--hash=sha256:fd0350afdc3aabd5576f60ea109228bd5538139713c7b094c5cd27c73a98bc6f \
--hash=sha256:fd0a274c0e5f9a21565cd9d3dd749b61f96b7aa1e20a93aa1ba4029518f2e5c0 \
--hash=sha256:fdb8a068947befafba9952162645dc2fecaeb400e64584829ed5e9b2fbe21a7f
# via requests
cyclonedx-python-lib==11.12.0 \
--hash=sha256:0e807521a921a5c3cb8ce1153f8a61d29eedfe76a46aac2796b7c6b573391a54 \
--hash=sha256:16767c4039de90c04e9f03348f8f0ed4b8ff842eaa7eefcad3a95685f970dacf
# via pip-audit
defusedxml==0.7.1 \
--hash=sha256:1bb3032db185915b62d7c6209c5a8792be6a32ab2fedacc84e01b52c51aa3e69 \
--hash=sha256:a352e7e428770286cc899e2542b6cdaedb2b4953ff269a210103ec58f6198a61
# via py-serializable
filelock==3.32.4 \
--hash=sha256:22e58ca3b1ae3b98993b762d7338367ae64fe50252bf78d59da3bfebcdf1cedd \
--hash=sha256:2bde2e4cf732e0153406d8a7bc80620ecf5e621fe0d25e41143c4e3b4733ff30
# via cachecontrol
idna==3.19 \
--hash=sha256:5e0811a4383b21dc5838069f801c4fb62113b7447663d2530d2bd6e77b49bf15 \
--hash=sha256:815e7be7a7806d54abb586dc943addc79e8b2ee16915059658cbeff4b1b43bf4
# via requests
license-expression==30.4.4 \
--hash=sha256:421788fdcadb41f049d2dc934ce666626265aeccefddd25e162a26f23bcbf8a4 \
--hash=sha256:73448f0aacd8d0808895bdc4b2c8e01a8d67646e4188f887375398c761f340fd
# via cyclonedx-python-lib
markdown-it-py==4.2.0 \
--hash=sha256:04a21681d6fbb623de53f6f364d352309d4094dd4194040a10fd51833e418d49 \
--hash=sha256:9f7ebbcd14fe59494226453aed97c1070d83f8d24b6fc3a3bcf9a38092641c4a
# via rich
mdurl==0.1.2 \
--hash=sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8 \
--hash=sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba
# via markdown-it-py
msgpack==1.2.2 \
--hash=sha256:06d95f61de7afe4f4ff908a6feebfcb070d0582ac87c9cf3cedf8551cf634516 \
--hash=sha256:0708afbf6a9587f0bfe479a9825c141d14d91e2f6a5c8103cf28bc96f4edb5d9 \
--hash=sha256:0883a1578168929fd1640fbbc4614773f1a130e419a8a817dc2918d9af1b651c \
--hash=sha256:0a652ceeededf71d3fa40c303a02a149d42338d310162367b91c539d4bd6e0a3 \
--hash=sha256:0dd9173c5ebaf5ecc5ca86e7ae1db92934e1d57b856f3dd90698941431f4fd77 \
--hash=sha256:0e3315de5a4b2920ccef48d96b4448025e064a10d0f5a250f6584477d839c8d4 \
--hash=sha256:0e91332144f69bc3018c91232fac26da580ef748fb8eaddd7914d4458001cc4f \
--hash=sha256:0fbc1bed8a535389b41882cfae66376e248cd1680eaa94fd83193c73e1d24986 \
--hash=sha256:11e8c421e117d1c36728b423d0402555cccbf0c6f53e288f0e75b6b12100d70f \
--hash=sha256:1510f24612d4b983dff6935d9273e02c320cfd525727fbcb58836a75f589fdbc \
--hash=sha256:1814f92306ae7862908e9ece7cfd90e0dc87ded3e89b6ae7ffdd1175d6376fdc \
--hash=sha256:1e8cdd1f3e7cc52c751092a9bf740e81e6919ab109cd376ae2d965dad0bbae34 \
--hash=sha256:1f3af0baafd184436501004828bb3df64eeb2fc49dfe9d89abcf604956094563 \
--hash=sha256:1f6b6f8deb07d49090e1808c6ef9cb7d23ca17bef3aa6ed3e5e03df16606e60c \
--hash=sha256:226a62ffe99fe54c5c61d910ec64c3449b7766c3280bd286bf6c94838dde239a \
--hash=sha256:29cc2d5291711a52956a79a51f41c732329df39ad727c886bd8f0b5b9237a808 \
--hash=sha256:336525cc2688e43ea77dfb1a4ce012c8cde561835913801dbfcfdcf4111d8abb \
--hash=sha256:34e83e345194a2a51d8bd447dea9de2104f91e75b247f4735f14f04529f0746b \
--hash=sha256:352ed831042549cca8be23780e1fe7c9177e65ff02bf183509c4b4d33f671782 \
--hash=sha256:3e915d390d7068b257ca8b62f3fc59fad135c8631d1017ab03b0b924b07c5367 \
--hash=sha256:419a45c67a5c04213172a14b1864657e014665b77d7081b107a51707923dd39e \
--hash=sha256:42fd9260416885b4815caca5bdd14dfd5dda6cdade732d6c09104ef8f6228761 \
--hash=sha256:46ec851571d8f1b6e29794ebb9dd36f785008da6d14f57c702e60781d6caf648 \
--hash=sha256:4710d881d8fb047deed2485707409116722af2b992d3fefd73c7667c4e350839 \
--hash=sha256:4955accbd87f27beebef5f3ecc27503aa74cb016fb4f640868e749fd93194a35 \
--hash=sha256:4a4348705be86e029d04e741cf9ed0dfe03e942d7d3b92e838fa80d3aa2c3ebc \
--hash=sha256:4b554d8164ebb526892194f71dcd96ef1fefe0c250087498785d3ffc04a80be3 \
--hash=sha256:4d9a562aec0a92fe536da2e533d313b3d2a6b929157b1dec7ff623446dc0a8ab \
--hash=sha256:51dd39d23cfdea0400ed3ff2d29d1e83bd951d3aea79dc89be5b701a09edfe23 \
--hash=sha256:53679573c75cce5f82359e0bd4e6a97809a6b9a9b7a48fd1ba592f4a82cddc84 \
--hash=sha256:55faa6f8395e23b848c535ad5dcb96b3462f37f5e7f4ac500d500434f7345da7 \
--hash=sha256:58ce37a4a54577115922385d37201d9a44d66d0167dfbbf4770a2e9bf8ea7ba3 \
--hash=sha256:59d5b93efa45fd09f620d0c9ba81cde339a2c9937af3eea42ee9653094ce6640 \
--hash=sha256:6195257a107bf25872ef84aab7295078271eea3ac6413f0506b631f6c9586ed5 \
--hash=sha256:652d1bf13d01bac8fd569def0fe76745e55bcda01e30aa6332d5947ea3788839 \
--hash=sha256:682804bf31e43d46e51a9a33bd575b51e839d715ce6bd5612c055f7b28ad637b \
--hash=sha256:68df2947921d449f6dcfeafd86cb2cdde13327a8b447534bbe4ee5aaf32a5695 \
--hash=sha256:6f53285f20d592ed309ee19e509cc4c77a3bda1db02ad67e8a0949bb227a5a6d \
--hash=sha256:73b0e05c32c3cfc3cd84994908e57430c0ebc6813abf905d3f18ff115d54df3f \
--hash=sha256:77c2e018417dc1d66f235e383877ee885b60ade9d29e494dd581e08af2cb1923 \
--hash=sha256:7826f16edc763e768404f55605ef85dfcf5857e729c1ed29e0d7c180be4fe6d8 \
--hash=sha256:7afa5431f6f3487c584187ca6c8e2a34e9b106529893b3e720eabb068f6ac970 \
--hash=sha256:7d095df2627e5dd59ac7b0c5ad627a671c76e6020171e03cbe4621a61f0562c3 \
--hash=sha256:7fe374ba76eb0ecca13a1703daa8fa85825a6ddddbb52d4c1a732fa524194683 \
--hash=sha256:82b1bdf293267afaadcc608b125e7fc6576bb0785a60c4fa7d07c7ab76ed76ec \
--hash=sha256:86f173a584f72f6164801f31866d22a581f60c991572cf922aed9ab8eb422b77 \
--hash=sha256:8b1415d02e9bf722672af8a90f90813265a0cd0b14163187261e54a5592bc949 \
--hash=sha256:8b2a281b556f120a43e591ea39915741b7ad54d4727b9c4350a0a11692252533 \
--hash=sha256:8c6321a414f8b4a8dc43976b2fa8349156434ca9adedd9a187b796f7e1d3d3fc \
--hash=sha256:8dc4487097571f7311188c3eca2a3e86cd1f1db4c37c7a017bcc3fd38486cbfe \
--hash=sha256:90986cc9aab9d7d1d8f38bcbf65d3f7ac83bdd90c35765db7d691b4829698cba \
--hash=sha256:9352e6cdb510a7b1a5d3ccaccec730e82e50cf3484a3af7bdaab19e23b9589ff \
--hash=sha256:935b1cfad9b908b0fa845010f4271df4c2f04e1cd26e3f18acd61a45f93c9e36 \
--hash=sha256:9b659d77f8726fa5e7038967dda6b68d53cf34472c094cfa5b845454713b90d5 \
--hash=sha256:9bd3d1557c3fe1a095068210708a03e3e4795973392af6f4047060e70abd9a6c \
--hash=sha256:9bf452ff4d4981f25a18e9476e002bcc9263e7928024aa4d7148e25f7be3f929 \
--hash=sha256:9d7fb25b4442fae0cb2590272d06ab4f6caa526ee36a994edb81e946b874813e \
--hash=sha256:9db1ba1c1e6a84245a9dd866265b56b8a1e9461549cc72ed296d8cbfbd32961b \
--hash=sha256:9eb0b0e602064527a045ea28c4f174ed69383587e29cebe28947e3b84106eb2a \
--hash=sha256:9fd7f32e2f0fb334e7ecc5adb5cf0458785bd3a9d9d86f950e1715f101cebce5 \
--hash=sha256:a378e12ccc06d76efde115caf4073b7e5ff3cc18291d1341f9e65fb882e3f754 \
--hash=sha256:a4161eee7799863aee237c35c90427861f7b994416dd81ae829f560b0a81bdcd \
--hash=sha256:a9b4cf3685a135666d27d0d7a73fece74e2fad01d9b508fded89e843512f0e90 \
--hash=sha256:aa1120c653b76d8eafa50423b5eba06b5c9737f8692c74fa3afe03e84b8978ea \
--hash=sha256:b07c03f0da7e5279170df7745ddc732d526c8a198208936ec1a95c11ed2b2d5f \
--hash=sha256:b13b59e66f107cca1ba708dd5307179870ca1b15b19fcee7ccf722e5308d9212 \
--hash=sha256:b542ffc0a5c531eedc40419f291f1bd659aa8d4223408a5b51c88a2796083fd3 \
--hash=sha256:b5c696ae7cd7166b3657261adb855b461ff31f07823fdbae9de8bf80adfccc21 \
--hash=sha256:b68614fba0570349833b7dd999ff0aed4e5cc8d9eb6e3a7d4527be33c65e33d3 \
--hash=sha256:b8dd6c71d20c28d2d0eb0c51e7cccf3584afde3b1364f6629596186c9025bd54 \
--hash=sha256:b9b0c1f2aa7b0026b4bd50718100e8b04175e4f36e160aa852502377b5e572e7 \
--hash=sha256:c522420d78db2431887d45b518e304d86e27b9ad0b30f24e3806a6ad5d8bdbfc \
--hash=sha256:ccfd880988f8438d1c91c77d7edc58e70f4d2012e999167bc154c64c6f06ea6b \
--hash=sha256:cdb6cc6e1127d15879c47a8b3270716243da82d3e7feab1f5946872c75b3d60f \
--hash=sha256:cf66fb38703e61a486b01b56d43bb1f50698fbe99b6bd90feba10f24fab60b3b \
--hash=sha256:d13d07efbf655f9ae7a2352b630c52727b359005b21ba08a507585c9ac8c0896 \
--hash=sha256:d242f3c4ccf55b056e6cf901720dccde58f1df117898f2bbf3bcd6e38ec7c248 \
--hash=sha256:d24b38a825bcca41bb956de50eb98451ef291304a8607fad99e619043d3e79b9 \
--hash=sha256:d3c247d457ae9079974c7ce3c665396754a6d2baff7eaa51332212a8a5a3f13b \
--hash=sha256:d886baa46b2532135e7320067e6a44edb09ba5883a6096b0f9c044533984b8a8 \
--hash=sha256:e05a94a0442de86818a30281c6cc2cb9cc7aa148386fd3541c4d4774b73cb3a9 \
--hash=sha256:e1b99ad34613d5f8477fa5cf99bc4eaeaf27965588007c102370cd9a78fe9de5 \
--hash=sha256:e2eb7ea0ac3911a7aac9d8aaa36d40f216d99455b3274cd3fac38181bcd910cf \
--hash=sha256:e497ee34e8a3342bbde51b27c22d8db05a651df3361dd3daef5b3ab0d66f3e04 \
--hash=sha256:f11e09f10210a91c169e39c7a5a1f9090eaa73ad75555fafad5023c3053c47ba \
--hash=sha256:f466049b8e1ec0854287bbe9a074316826fe0e08dcf707245f98b1ae49e92650 \
--hash=sha256:f80361592c13d7226b4379c8941529b63fe1a9d0e05d2de8f3306b70e522b53f \
--hash=sha256:ffdd2f4950daf7815490f23087963e3420175b9609520b7ff5df64d351159c22
# via cachecontrol
packageurl-python==0.17.6 \
--hash=sha256:1252ce3a102372ca6f86eb968e16f9014c4ba511c5c37d95a7f023e2ca6e5c25 \
--hash=sha256:31a85c2717bc41dd818f3c62908685ff9eebcb68588213745b14a6ee9e7df7c9
# via cyclonedx-python-lib
packaging==26.3 \
--hash=sha256:94edc256424af38762eb31306eed28beb9f0efc50a8837492c9d6fd6004aed79 \
--hash=sha256:d7193f7c8e4e93f444fde0262bf90af30e16fa0ad0ad44cb553c87339b23cd1c
# via
# pip-audit
# pip-requirements-parser
pip==26.2.1 \
--hash=sha256:71138adf1f4ca900cdb7d289c21b7494329f2332b6d85f0e1c42108c0384ed3e \
--hash=sha256:f6ad667e89a1fe78046c8f13232b247200f5258d7828f3f7883d660878e0813f
# via pip-api
pip-api==0.0.34 \
--hash=sha256:8b2d7d7c37f2447373aa2cf8b1f60a2f2b27a84e1e9e0294a3f6ef10eb3ba6bb \
--hash=sha256:9b75e958f14c5a2614bae415f2adf7eeb54d50a2cfbe7e24fd4826471bac3625
# via pip-audit
pip-audit==2.10.1 \
--hash=sha256:1eb4565d19ebe5d48996f4b770b4d2b32887e12cb12cfa637f1a064011b55ffc \
--hash=sha256:99ef3f600a317c1945f1e89e227ef26e1c2d618429b8bd3fa6f4f7c440c4611a
# via -r .github/requirements/pip-audit.in
pip-requirements-parser==32.0.1 \
--hash=sha256:4659bc2a667783e7a15d190f6fccf8b2486685b6dba4c19c3876314769c57526 \
--hash=sha256:b4fa3a7a0be38243123cf9d1f3518da10c51bdb165a2b2985566247f9155a7d3
# via pip-audit
platformdirs==4.11.5 \
--hash=sha256:89f8d42695853b89c7170bd49bc3dc593f98a71e695ede88e06a3b247bc4563b \
--hash=sha256:e8b31f4f8bcbbedef91a6b57a706255e4f148d2a4e01648382a0a47342539173
# via pip-audit
py-serializable==2.1.0 \
--hash=sha256:9d5db56154a867a9b897c0163b33a793c804c80cee984116d02d49e4578fc103 \
--hash=sha256:b56d5d686b5a03ba4f4db5e769dc32336e142fc3bd4d68a8c25579ebb0a67304
# via cyclonedx-python-lib
pygments==2.21.0 \
--hash=sha256:2363c69b61c4a97c838da3b130dcd6468f4848992b21a82f2a63ec34377137d9 \
--hash=sha256:610ca751c9bc2492b38eb9a38a7fbc93edbbb2d7182edaf34e66ae493dee5c8c
# via rich
pyparsing==3.3.2 \
--hash=sha256:850ba148bd908d7e2411587e247a1e4f0327839c40e2e5e6d05a007ecc69911d \
--hash=sha256:c777f4d763f140633dcb6d8a3eda953bf7a214dc4eff598413c070bcdc117cbc
# via pip-requirements-parser
requests==2.34.2 \
--hash=sha256:2a0d60c172f83ac6ab31e4554906c0f3b3588d37b5cb939b1c061f4907e278e0 \
--hash=sha256:f288924cae4e29463698d6d60bc6a4da69c89185ad1e0bcc4104f584e960b9ed
# via
# cachecontrol
# pip-audit
rich==15.0.0 \
--hash=sha256:33bd4ef74232fb73fe9279a257718407f169c09b78a87ad3d296f548e27de0bb \
--hash=sha256:edd07a4824c6b40189fb7ac9bc4c52536e9780fbbfbddf6f1e2502c31b068c36
# via pip-audit
sortedcontainers==2.4.0 \
--hash=sha256:25caa5a06cc30b6b83d11423433f65d1f9d76c4c6a0c90e3379eaa43b9bfdb88 \
--hash=sha256:a163dcaede0f1c021485e957a39245190e74249897e2ae4b2aa38595db237ee0
# via cyclonedx-python-lib
tomli==2.4.1 \
--hash=sha256:01f520d4f53ef97964a240a035ec2a869fe1a37dde002b57ebc4417a27ccd853 \
--hash=sha256:0d85819802132122da43cb86656f8d1f8c6587d54ae7dcaf30e90533028b49fe \
--hash=sha256:136443dbd7e1dee43c68ac2694fde36b2849865fa258d39bf822c10e8068eac5 \
--hash=sha256:1d8591993e228b0c930c4bb0db464bdad97b3289fb981255d6c9a41aedc84b2d \
--hash=sha256:2190f2e9dd7508d2a90ded5ed369255980a1bcdd58e52f7fe24b8162bf9fedbd \
--hash=sha256:2c1c351919aca02858f740c6d33adea0c5deea37f9ecca1cc1ef9e884a619d26 \
--hash=sha256:36d2bd2ad5fb9eaddba5226aa02c8ec3fa4f192631e347b3ed28186d43be6b54 \
--hash=sha256:3d48a93ee1c9b79c04bb38772ee1b64dcf18ff43085896ea460ca8dec96f35f6 \
--hash=sha256:47149d5bd38761ac8be13a84864bf0b7b70bc051806bc3669ab1cbc56216b23c \
--hash=sha256:4ab97e64ccda8756376892c53a72bd1f964e519c77236368527f758fbc36a53a \
--hash=sha256:4b605484e43cdc43f0954ddae319fb75f04cc10dd80d830540060ee7cd0243cd \
--hash=sha256:504aa796fe0569bb43171066009ead363de03675276d2d121ac1a4572397870f \
--hash=sha256:51529d40e3ca50046d7606fa99ce3956a617f9b36380da3b7f0dd3dd28e68cb5 \
--hash=sha256:52c8ef851d9a240f11a88c003eacb03c31fc1c9c4ec64a99a0f922b93874fda9 \
--hash=sha256:559db847dc486944896521f68d8190be1c9e719fced785720d2216fe7022b662 \
--hash=sha256:5a881ab208c0baf688221f8cecc5401bd291d67e38a1ac884d6736cbcd8247e9 \
--hash=sha256:5cb41aa38891e073ee49d55fbc7839cfdb2bc0e600add13874d048c94aadddd1 \
--hash=sha256:5e262d41726bc187e69af7825504c933b6794dc3fbd5945e41a79bb14c31f585 \
--hash=sha256:5ee18d9ebdb417e384b58fe414e8d6af9f4e7a0ae761519fb50f721de398dd4e \
--hash=sha256:7008df2e7655c495dd12d2a4ad038ff878d4ca4b81fccaf82b714e07eae4402c \
--hash=sha256:734e20b57ba95624ecf1841e72b53f6e186355e216e5412de414e3c51e5e3c41 \
--hash=sha256:7c7e1a961a0b2f2472c1ac5b69affa0ae1132c39adcb67aba98568702b9cc23f \
--hash=sha256:7f86fd587c4ed9dd76f318225e7d9b29cfc5a9d43de44e5754db8d1128487085 \
--hash=sha256:7f94b27a62cfad8496c8d2513e1a222dd446f095fca8987fceef261225538a15 \
--hash=sha256:88dceee75c2c63af144e456745e10101eb67361050196b0b6af5d717254dddf7 \
--hash=sha256:8a650c2dbafa08d42e51ba0b62740dae4ecb9338eefa093aa5c78ceb546fcd5c \
--hash=sha256:8d65a2fbf9d2f8352685bc1364177ee3923d6baf5e7f43ea4959d7d8bc326a36 \
--hash=sha256:96481a5786729fd470164b47cdb3e0e58062a496f455ee41b4403be77cb5a076 \
--hash=sha256:a120733b01c45e9a0c34aeef92bf0cf1d56cfe81ed9d47d562f9ed591a9828ac \
--hash=sha256:b1d22e6e9387bf4739fbe23bfa80e93f6b0373a7f1b96c6227c32bef95a4d7a8 \
--hash=sha256:b8c198f8c1805dc42708689ed6864951fd2494f924149d3e4bce7710f8eb5232 \
--hash=sha256:c2541745709bad0264b7d4705ad453b76ccd191e64aa6f0fc66b69a293a45ece \
--hash=sha256:c742f741d58a28940ce01d58f0ab2ea3ced8b12402f162f4d534dfe18ba1cd6a \
--hash=sha256:c7f2c7f2b9ca6bdeef8f0fa897f8e05085923eb091721675170254cbc5b02897 \
--hash=sha256:d312ef37c91508b0ab2cee7da26ec0b3ed2f03ce12bd87a588d771ae15dcf82d \
--hash=sha256:d4d8fe59808a54658fcc0160ecfb1b30f9089906c50b23bcb4c69eddc19ec2b4 \
--hash=sha256:da25dc3563bff5965356133435b757a795a17b17d01dbc0f42fb32447ddfd917 \
--hash=sha256:eab21f45c7f66c13f2a9e0e1535309cee140182a9cdae1e041d02e47291e8396 \
--hash=sha256:eb0dc4e38e6a1fd579e5d50369aa2e10acfc9cace504579b2faabb478e76941a \
--hash=sha256:ec9bfaf3ad2df51ace80688143a6a4ebc09a248f6ff781a9945e51937008fcbc \
--hash=sha256:ede3e6487c5ef5d28634ba3f31f989030ad6af71edfb0055cbbd14189ff240ba \
--hash=sha256:f3c6818a1a86dd6dca7ddcaaf76947d5ba31aecc28cb1b67009a5877c9a64f3f \
--hash=sha256:f758f1b9299d059cc3f6546ae2af89670cb1c4d48ea29c3cacc4fe7de3058257 \
--hash=sha256:f8f0fc26ec2cc2b965b7a3b87cd19c5c6b8c5e5f436b984e85f486d652285c30 \
--hash=sha256:fd0409a3653af6c147209d267a0e4243f0ae46b011aa978b1080359fddc9b6cf \
--hash=sha256:ff18e6a727ee0ab0388507b89d1bc6a22b138d1e2fa56d1ad494586d61d2eae9 \
--hash=sha256:ff2983983d34813c1aeb0fa89091e76c3a22889ee83ab27c5eeb45100560c049
# via pip-audit
tomli-w==1.2.0 \
--hash=sha256:188306098d013b691fcadc011abd66727d3c414c571bb01b1a174ba8c983cf90 \
--hash=sha256:2dd14fac5a47c27be9cd4c976af5a12d87fb1f0b4512f81d69cce3b35ae25021
# via pip-audit
typing-extensions==4.16.0 \
--hash=sha256:481caa481374e813c1b176ada14e97f1f67a4539ce9cfeb3f350d78d6370c2e8 \
--hash=sha256:dc983d19a509c94dba722ee6abd33940f7c05a89e243c47e907eb4db6f1a43e5
# via cyclonedx-python-lib
urllib3==2.7.0 \
--hash=sha256:231e0ec3b63ceb14667c67be60f2f2c40a518cb38b03af60abc813da26505f4c \
--hash=sha256:9fb4c81ebbb1ce9531cce37674bbc6f1360472bc18ca9a553ede278ef7276897
# via requests
+1
View File
@@ -0,0 +1 @@
pytest==9.1.1
+32
View File
@@ -0,0 +1,32 @@
# This file was autogenerated by uv via the following command:
# uv pip compile .github/requirements/pytest-tool.in --generate-hashes --python-version 3.11 --python-platform linux --constraint requirements-ci.txt -o .github/requirements/pytest-tool.txt
iniconfig==2.3.0 \
--hash=sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730 \
--hash=sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12
# via
# -c requirements-ci.txt
# pytest
packaging==26.3 \
--hash=sha256:94edc256424af38762eb31306eed28beb9f0efc50a8837492c9d6fd6004aed79 \
--hash=sha256:d7193f7c8e4e93f444fde0262bf90af30e16fa0ad0ad44cb553c87339b23cd1c
# via
# -c requirements-ci.txt
# pytest
pluggy==1.6.0 \
--hash=sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3 \
--hash=sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746
# via
# -c requirements-ci.txt
# pytest
pygments==2.20.0 \
--hash=sha256:6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f \
--hash=sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176
# via
# -c requirements-ci.txt
# pytest
pytest==9.1.1 \
--hash=sha256:1088fbde8f2b49d95a549a195707afa7a76a3ce9bcadc26b6d71f0ffda5fe313 \
--hash=sha256:37a86b45efb9a47a61a36449063e8e18d0cab3161329fc099eb21783169c4f0c
# via
# -c requirements-ci.txt
# -r .github/requirements/pytest-tool.in
@@ -0,0 +1,3 @@
bandit==1.9.4
semgrep==1.175.0
jq==1.12.0
File diff suppressed because it is too large Load Diff
+1
View File
@@ -0,0 +1 @@
twine==7.0.0
+470
View File
@@ -0,0 +1,470 @@
# This file was autogenerated by uv via the following command:
# uv pip compile .github/requirements/twine.in --generate-hashes --python-version 3.11 --python-platform linux -o .github/requirements/twine.txt
backports-tarfile==1.2.0 \
--hash=sha256:77e284d754527b01fb1e6fa8a1afe577858ebe4e9dad8919e34c862cb399bc34 \
--hash=sha256:d75e02c268746e1b8144c278978b6e98e85de6ad16f8e4b0844a154557eca991
# via jaraco-context
certifi==2026.7.22 \
--hash=sha256:62f22742b58a1a33014a2b6b706588a8d7e2a88ae7bd1a6ebe8c992928483775 \
--hash=sha256:741e2c3b351ddf169a738da9f2c048608ff7f2c5cc02f1ebc6b118bb090d5d55
# via requests
cffi==2.1.1 \
--hash=sha256:046bfc24911b37851ee1b51aab8bffe713d89c68c6a057b09484ce9fd5f69b4e \
--hash=sha256:06c72bb76605a4b0cd0aad6930b69d4baf7dd5d806cfc409b824191099700e66 \
--hash=sha256:0beceaabe56af686895136a2de78db54ecd8e4046b236b8fd6d6cb61389e9bf2 \
--hash=sha256:154852545011f779917b11c78db2358d095da62a9a172b78ad0a583ee5adc0d0 \
--hash=sha256:194cffa889098ced9976c3fc6340305e43f6303657d298da55366907c05c22d6 \
--hash=sha256:19ee6127ee34de7d83ce3d371ebc5ed91addbdcc39f9ab15ce4eb35a4e534971 \
--hash=sha256:1a18a57b58cfb21fc28d72e876acf10eaed67a1ed96226f92af4df681d571c4c \
--hash=sha256:1aa5645c30469b09530c4ebca77ebf8f17618293c58f8549cb1a543a50236e7d \
--hash=sha256:1dea0e4d7d4f11f619fe8c1d76caf49e24405b4b5743c0e3be16a500ecd930c9 \
--hash=sha256:208f941bb9d18e768138677f0a6d2ce01f590df56043dda1df1535ac57c88517 \
--hash=sha256:210019b6c7cf07f081b4c54635c8cf744377001350e29cc0f81c4377b4797735 \
--hash=sha256:246fa40ce8645a614ff682e0b70f37134e460eaf93a775e0cbe3cca585a67a80 \
--hash=sha256:25792eac27877609e7bb06d42ff88278a6624fff2ba9bbb523c09616b117e80f \
--hash=sha256:27350daa11d4f10c540e6e89dada4c54feb7256ad03e9a4dc075ebad7ba360d1 \
--hash=sha256:28907ab9bfb6aa13184cfc17c6b8e1023c5ab6fd7076d8c20a35e59fe04f8f29 \
--hash=sha256:2ae64be792b8966f2c69538199728b290e34726562896df1e5dc8ffd8d8188e8 \
--hash=sha256:31348097ff5bbe827ccc41795d4dd099d9f0625e7def00ee653c137a490c2a6c \
--hash=sha256:3143d81e29e1e20a9ce10901ec369012947876596f75a222235965f2b7ae832e \
--hash=sha256:3222ba5d678f80a030e6afbcc33dc1ae5cb45facabb61cee2c7016b8432fde48 \
--hash=sha256:3311ed60d36f83378794e1009ac6258bafbf81f7888b4caa7b35a521e3f95813 \
--hash=sha256:334644fbac4eff73d985a17a91226df55d0f394160c4cfb880e084c8f7161cac \
--hash=sha256:34e261f78cb6ceaaa36f42f2613f4380d94d9c759a9c73c769ee6e0247364632 \
--hash=sha256:363e05fa78e15116c3c32c210ee36884fd6b9afa6d440e47112c3bd511d64cb6 \
--hash=sha256:398aff33cee2767e3e781d2554c54bd0dff386bb437581e0d8011fde1a942ec1 \
--hash=sha256:3d22a20b1fb1632cc72c22f95f7b0d2961c3e1c235f245ba4c606c4771035659 \
--hash=sha256:42a494cee34437f05546455144f2b5d9ac09b1face62bcfce597d2e521066688 \
--hash=sha256:42e2f76b9455f5a9a844f770bf3e200ed3da0e15f5df3db9c31fe80b04b3d004 \
--hash=sha256:42f6930c31dc7f50732c9ae793c2786c7b6b044195967bbdde40bb9be81c4cc0 \
--hash=sha256:456a61fa52d579ebf9df2e9552ead5129855dbaff6c1e5a9b1bc408809bdc062 \
--hash=sha256:471cee653ae88de62096552e6d24ccb4a5adb8c8c9f10b5054d0122c15bf2779 \
--hash=sha256:49cbc70e6542d4ccccb936558d1064a8012541e78f821f955cff24e357776c94 \
--hash=sha256:4a7c934f7360e8cd64fe9efadcbd10c7c6364f531e432b9a4bf5ccbc9e0e8b50 \
--hash=sha256:4be96343e422f2dfcd12ab5c9f5aebe03f82f737c6bffeca6830b3875cb44aab \
--hash=sha256:4f42141fc14250de6dde5ee7ea4432be017252d91f19c5ad043c084cea629cac \
--hash=sha256:507a24c282e0f42f8ed737cf048572cbf580468da5555764a8331735e9c736b6 \
--hash=sha256:51b31d1c98274844cfd7838ce00bfc27c7423a4dc00fc0772fc3331c2cc90676 \
--hash=sha256:58acb8ab8e295e6c5ea12f888cbb13cf21511ef2a3303a23f4325c29d17fe5c1 \
--hash=sha256:5a59cc1c4442bc3d5c703bf720b51138d0bfc173618807c9ee2490a7541dd3d9 \
--hash=sha256:5bb4e7ea95dcd6a014a6fef62e62467d67d8e582326443f3d68e71d6320a9fcf \
--hash=sha256:5c58fe613dc5e5336357eff555824a314d8e43282600435c8d1cb6a7a2fedd13 \
--hash=sha256:5e7cecbaadb83884793e05828cee59b210b24583b9c7425d0ba6a754fe22eb4e \
--hash=sha256:616f097f2fe415bc92a247f02e11f634e1f9e9a83d327e3c915c15089c87869e \
--hash=sha256:63bbfd5ded17c4840ac07cd8f1c21ba9d9708141f840b324f422f41b207e3973 \
--hash=sha256:64faea20f4e2613363a1a9b9c7dd73058f3ecd00133a511e72ad7c511658f527 \
--hash=sha256:661c298b4821edebead0c91edd2b00374d67ad7c5a1f7a91d4442633b79d6a72 \
--hash=sha256:68e62fe11f30d5ca8289242866f0a5291402d8529ca2178ab8afc5c9694ae890 \
--hash=sha256:6a8dddef476fab96d066d578fc88526767b836ab5ab21754e1d5bf3879c31c7c \
--hash=sha256:6e192623c49c94421616a5778fba35cf0d5a8d000650c1967ef4448ee5cdd990 \
--hash=sha256:7225e4514edb64eb6740324353e0da0711954fd8d7da4576755b1c6e09b697cd \
--hash=sha256:75f80557d1389eddbd0de2681f6a390a0c5338c31ddaa821381c203fc3fd50d9 \
--hash=sha256:770de9db11e84213beec501cfcaa013b019820ca881e03344dea5844f7876d94 \
--hash=sha256:7750c6449dff7864bb9bb27ddfb0267756189201a3afc911d82b3caacd70dfc3 \
--hash=sha256:7bde5e4cc5c10140859842b9d383af292b22639a4dffb725314baf45968cef80 \
--hash=sha256:7ce713ace7c0e4520535b42b77eaa742c16dab813978064913e5a3cf82973b41 \
--hash=sha256:7da0c5eff80f0197f3b3d1232ec5a682a9325f4ae9016a78f5f5ca35f9ced1f5 \
--hash=sha256:7dbb61fe3a7699468030f71bbe5f8a0e326a151daa91beb11a6fc1f980c55e1c \
--hash=sha256:811bd1e21d32de12efca32393a0ab3f5133b54fce9bd44b8bd77ab07da14bf6a \
--hash=sha256:8ef53b2de9bcb9197d31854256575d59dbac0cba72ac627bb291ef5eceb74be4 \
--hash=sha256:937c0052c05a31ca1daf18de3158eed4dbfcb9cc107adbea227728d647be701e \
--hash=sha256:9d2055050ea716bd38b7f7f1579c275386646b4894c155a3e2f3cd62ed41b7c6 \
--hash=sha256:9f8d177621de5cb38ee3e731eda45d421db093ec0739f46a5594babda7987a98 \
--hash=sha256:a2d7755bef5a12ed488f4ef1f1b69ee9191d7396083b755a5d2295f6edb4768b \
--hash=sha256:a48d62ab9d6f4f98c983223a547af44be6ca3691074c31cecced6facd3ba2dc1 \
--hash=sha256:a4f00aa42f75d6e4595e8866e748cc1705adc0cddfeb2ca86d0d03993d63ba03 \
--hash=sha256:a6e721d4b0e45d5b65e87534470e67b18dcd092c83f68fba09f152b9cbc061af \
--hash=sha256:a730a083190634c65cca36ba5f489531576ebd79bcd5c8e172130f6453127231 \
--hash=sha256:a931079504ecc49efed7744c476a5c343a92fabf66dec2db95edb1b2fdc770e2 \
--hash=sha256:aa9511c62d14da7aacc9b4bf51f3f697a621e83b2d6919008243c3aad168eea3 \
--hash=sha256:ab36d55f9ed2d067327667c2fea18dda018eb628dd6347aa01dda6cf1f5d3836 \
--hash=sha256:ad2c86c495b899d862ea0f4b42891b8713a3bd45dd4105c7fd51c2a72f39f3a5 \
--hash=sha256:aeae0e330c9f6acd681f647d46cefd30c29f93e3392882e792e82080c9691399 \
--hash=sha256:b0431303acaea1089ad4b3e9ce4e6518193def1118d4073ca848635ee4ea2e96 \
--hash=sha256:b5bdfd1c873d4e093aabc0ca84c4ca6dbc4f752afb5c86f146d9742580c9da2e \
--hash=sha256:baed1e86cc735622097354b9d1281406caf42ff42a886d29faa8e8d1630333be \
--hash=sha256:c1453022f490d2459a11819d83ad1d586e9ff65a12ac3e705ffebd46d3685dcf \
--hash=sha256:c26608d2222fb1e94487e4a387d85f13eb55d5ed725cb25a0c589ac4ee60e7bc \
--hash=sha256:c7659f22557c5a0bc4855cd635f55edec690cc008a40768527762cb9fb263455 \
--hash=sha256:c8c69575568085ba0b1b10c0249d779a214aea6f6522e949a0fc9fb0fcb449d0 \
--hash=sha256:c8d2c9fd1f2d16f780d15127abb050d13d1a76c03a4bd87d7e4980e45e511e12 \
--hash=sha256:ca82be1a1d406ecfe1d25dc16cb33488e5a16bf4438c9fb590484ea29d92478b \
--hash=sha256:cc572dace3f60ef98d7b12ff411d20f5362feb31a0439eab0085bbfd349982d7 \
--hash=sha256:d18e5ac0f2f03f4f518d3e23db0f0cad7faa1da8620e9c09461d443bbf6e6692 \
--hash=sha256:d28630f5854ab07ab1fd4aba756de52326c82e6be15d414b12793f1975048b54 \
--hash=sha256:d9c275eaacd24aa73f94ffd6de08fc3f932424d8b6c376f4bed7cde376fe7bc3 \
--hash=sha256:da0e573f9f97159390c89d9f1a9e41908b66d408cc5b58d08cf3847d844c531b \
--hash=sha256:dd31f52ea1086513bb9df30f8fcee9b8918323ae067a3d5b78bc826a000712be \
--hash=sha256:dddad92b554513a31f272570678ba307fb9f618f05e3d4a5eacafff9eae03e1d \
--hash=sha256:df423d40ee8654634421812bc3b196da3f9bd7d32929da813f8394c4348a5358 \
--hash=sha256:df913725b79db7bcf03448f36b7bf8815363417d5b58deecf9305e3e30f0f21a \
--hash=sha256:e0bcb7e0f677f543555d2adff3bf19c05f66cdb4796e5ff602442ab2fe3c4ef7 \
--hash=sha256:e2d65b31f36619cda3999b78b2aa9632e76b78448e7a56fc4240824200e7c4fc \
--hash=sha256:e6e8cff14d6fb0be70a09c0bdc58096f501952d04624ebf867e0e56da2df8960 \
--hash=sha256:f16c709686a78c727bbbf059f92b0bf41c6fc60deec706d2dc19f529175a6125 \
--hash=sha256:f24fb43132a4c6b4cb4eb029492919b2db645be6808d738f244fd146c03c32cb \
--hash=sha256:f53e442b08449d42821fa4a4fba000095af9f62742a500f978a9f557ec44339a \
--hash=sha256:f5cfbc5fe74540d335175b656c725d74d90e3730c626d92575eea35029d9afaa \
--hash=sha256:f81b3b8f3d4e343550fa4baa0e479bba9f2d29ce9c2e9b51d1ce1718d7442fcf \
--hash=sha256:f8ec5e643a9a937f64e1999eb9f75d072263751912dc5cd06d3c85f8f44be7c3 \
--hash=sha256:fb92203a88b3d3053034db775110081c49d28be6551923805e039924093761e4 \
--hash=sha256:fcd22650c908d7b7da162bbfaab594a1227a15d1643a98c68b122ac642fa2264
# via cryptography
charset-normalizer==3.5.1 \
--hash=sha256:00668ebb0609751758682eb0b5857e7c35b9f00e84dfdef062e103244ec94d45 \
--hash=sha256:012a22b88a77ca2e59b98ac5889b0deb604147666032f45e6d6e217634d2550d \
--hash=sha256:01e93745f7f219b703b60ba7afead36cfc4242782be5af484673fc500df12da5 \
--hash=sha256:04368edf83514385ffc3e1cfd4546e595f4f1272dd23ba437a93a9cc3741d47b \
--hash=sha256:0722590aabf9dc6a6c0343d523c05458fa2b5047dbe6302fd526bb570600753f \
--hash=sha256:07ffd07412fc5d5e84cd8952acf9ff7e4ed7a708e69d1bada19d8ba91711353f \
--hash=sha256:09a7bba9f739468c8e78c36a75c33768e53cb1959fc638f510454c14683f00d5 \
--hash=sha256:0b2b1b3fa5670c127b246df1d0c059defd41f689a868a3b9d79df9b1cac42d22 \
--hash=sha256:0c6dfb5ca6723eeed15aa8e564a014d69fcb8812f94eef11fe3631e0508199f5 \
--hash=sha256:0d929fc574b4d6fd9e7c0f5c2ede8716a41911923aa7fa5fce38e0818aa4a1ac \
--hash=sha256:13e3afe97712e8887cd516e960c63f0b93122971e5b5e4b2622fe7701771e838 \
--hash=sha256:15f024313246a4ed976c60f440bb8d257815513a681d212ff74fd46f7d715a90 \
--hash=sha256:195ce897c6153c0700078142cf8efe3e6454ca4cf4357499e4078dfd83396626 \
--hash=sha256:19a3dd5aa73cef1c99687c4fc57db016a9c17104ae1185da88ba566a5d3bebe4 \
--hash=sha256:1d1c7a53a6c2103925cdd6d7229f8c567379f211c869793df679f2e9f738c369 \
--hash=sha256:1f5883d77fd409a261abb5dc8ccbe335720d798b1de4abb3b1d47ccbbc76b53b \
--hash=sha256:21b82d8082f6f5e7f456ef0bd16323d08de1266efbfeb476e64b2a91d1471a4e \
--hash=sha256:252d099029bcbea642f2a06c4ed5046bdf8b5a8150b64afa5e027e88b106e5ee \
--hash=sha256:256dd4d85d9e4dc595e2bc983c980e73f62ddeb3165c58b4c3dfe78c5c8548c1 \
--hash=sha256:26422d45fd13551cf564c58932f7d72b4f58b93b0fcf18c35ba6be12b46bb102 \
--hash=sha256:2679de311c7946dde5d3b6f44941844133ff5c7cb86099c0061ab1e8901c20a8 \
--hash=sha256:29880d17a8eb0b5cfdfd8944b468322928059aa35f1f5fa8ff22b149ec0b42f8 \
--hash=sha256:2bced4061f000f7187254a02ad3433ae17eaf991747ceea2f478422590a5bba9 \
--hash=sha256:2e9cf9253119d8e5d111f05d71626786fd3d6193817316eab1ca088cdb8593cf \
--hash=sha256:2f06b7eae9dbe77fe1d644ca244dad508de8d302870a43f3c559b521270938a0 \
--hash=sha256:2f293479cce755c75f1697e87c409b7ae4c555c7dfecb6e988ad13abba943031 \
--hash=sha256:329fc3ccb63ad22d867d84c2adea759a64079a37ba4a343433b02c7a2816871e \
--hash=sha256:343fb4f2821043bd87095f7b08a1a181febc8e36ac64212143bbfd0a0e1bc235 \
--hash=sha256:3588e376b3ea2eea84976f67273d679f229e24c66dce7b82ae45aef04ff6e072 \
--hash=sha256:35aea775dc2bd5f54cd84a1cd2696cc3207c479cb9cf0bd346f0d343e4300ddb \
--hash=sha256:35fe081843b35aad20ffeccec3eeffbe637b15d14f3fb22cc1b59cd8ec17e93c \
--hash=sha256:36047af20e17097c3bb9476c2b7655f2f7aa51322c0ba58c07695bedf755a950 \
--hash=sha256:3617ac3cfd8b9888f145ad89dd6e692285834b0201c6074a5eeaad3fd4d668c2 \
--hash=sha256:366ec70f5547c640d3ce1985722490f23faf4eb5216a7eeba78277490e78dacb \
--hash=sha256:394fea06235c8543390050ed5f529187074b029fb027213f6c46ac11ab5d950e \
--hash=sha256:3d27167433c0d5f18dc850f07d0b3816221984fecdc405d6c157a6f0b8f8e9e6 \
--hash=sha256:3e5e1224c0a6a90e05843e07adfec669edebec17801c67072f51e59561d63c0b \
--hash=sha256:41876ee62a3dddf48ff1121ad8f0798032aa03f2fd35f21f34a4cab14f18d8d2 \
--hash=sha256:433c5a81eade63b47e522303bad236f59dba55ea6951746f5558355eeed8c75d \
--hash=sha256:4582c27e8c889d64811987b5967fbd3ae0c823fe1fd933b543d55ac20bb475fa \
--hash=sha256:485a0d363cafefcd2538a73c7c838daa2035f09b2c9f9b5e3133f80c6aeb84c2 \
--hash=sha256:494b70049a4d69aec6e8137c13af4cf8db8c9f9820a1392ac293b0dd2987a818 \
--hash=sha256:496846868fea80e479324862fa877f02411f2fd0f83b79ccee2607aa68b2a032 \
--hash=sha256:4abdc5f9ad448c1ecbfae2974b820535d6bc6e7eef63babbab3d81cf46968c71 \
--hash=sha256:4b599739b93b2cbeded49645ae3c8d1405c29ddfbceac1545c87a3f9580a9e96 \
--hash=sha256:4bea7f8ebe90bbd7f0e4a2de42ca6924ba23e3e76418c408ff82f1d46fabd687 \
--hash=sha256:4c4fb141a727957c93edfe5c32a26ceb6b5f6461d67146e2d39f51e16170bea8 \
--hash=sha256:4c9548dc78002099910abaebc0a72ac58b7d30931869e0351c09b507dff4ece3 \
--hash=sha256:4d26f14f041e83dd8edfd61f4cd4fa7285d31798b5bf1f28e70c367ba6c41d61 \
--hash=sha256:4f298bdadb8f0b9e5672877f647d1be9373ef5320c9e2f049795e26cad28b6a9 \
--hash=sha256:52ec005752a56ae79547a05c0139ca2501a0c866390b6115008456b9f0e7cde1 \
--hash=sha256:55261ac0d2941c42f196dd576f543d87a8ee03cd6f5e30dfb4d807b2e3b9121a \
--hash=sha256:56490c595a28b1bb27dfc583e816152a9767721ef58b2c03b13f954d2f707420 \
--hash=sha256:58d3e12c88e0950bca850ae1f7c256055c097639c2edb9eb123af9807d8b15e4 \
--hash=sha256:58d4aa13a59c969dbfdf9e6a9560e242cbfd9e8a8f50c2747714df1a423adf65 \
--hash=sha256:59171c6e45bf07d0d5cab3b0bf81d945035530f6873398b3b531c31184d46663 \
--hash=sha256:5b6d1386bf0096d26d3a863dc0a487a5b4eb9aa93cf5ba69683d29dde6b9d60f \
--hash=sha256:5c0ea61a470e070686aa30892fed79e297d2c8d0ab46b8bcdf027d38c51da591 \
--hash=sha256:5c84bec0ab5ae0c64bfe73a7d2adcb5ce73b467523fc27fd6a28ab2aa6cbe35a \
--hash=sha256:5ca0555312ae2fe82715cada7fac375530c2f3349e1eaa1bcb33d0283ac79a18 \
--hash=sha256:5d8531a6569d025f68e2321e7638fb7978f23db58e5f69f56913837aae03816e \
--hash=sha256:5e2d0e146dcb57034f8b97dc58d2d512cb90aba253960ce449f695fec6a82c6f \
--hash=sha256:5fc45d653ea8c9a20479167e11d4a0f8cb2fa3470737ab6f9c827532313187b7 \
--hash=sha256:6117b84ea48435e5356dc737f5121485c30920ba43375fa7b434fd753df0eac3 \
--hash=sha256:6199d5606e2bbf2b096cf64d03f8b6790c91081d5ac866b8e7bb6422738cc60c \
--hash=sha256:62b55f6722735a6c472f88361cde6640608773d9443cebdbb51abf436a1fcdd3 \
--hash=sha256:687c9ca3035544b113bea2055e180af96fb63c0c476e22a9180f51925186e7b7 \
--hash=sha256:6b7430cf5728e68f6c462254009a6ef4086e1bea43cf2f57aa9c55fb4f50ff96 \
--hash=sha256:6ba32c4d2abf1d2fe7cf27d280f4cca5664233b0f885549c7761719eb977f486 \
--hash=sha256:6c9cdde8becb25a7fde49924511aa2644d6f8081cc8df8e9452724303348d8e3 \
--hash=sha256:6df0ec430f9a831772c23ca5a224cba36517a58a84bb32c32bb59a9fa67c47f6 \
--hash=sha256:6e2912d4babbc65196ac13c2f53468dc57fb8b9c25ef913e8c59ddf7c6dc0e1b \
--hash=sha256:6e5e4d73d588ca5ed09df1b7dcd1b203d1df3c542e3f50d126c947d432b10731 \
--hash=sha256:70055ff39b97c99e7ae40ea3e393fb62aa2e44dbd9b29f8d14f42fb0025c3959 \
--hash=sha256:706bfd38730a5ac7a365793269a00f4e988178cec121391f4248d84ad8c972e9 \
--hash=sha256:7235dc28fc6dd9d832ac7c7bce95367dedb85929f17368a0c2bee1e080b9acbf \
--hash=sha256:774d157f112367ff4abd29019f38f023c24e00e56edc7829c20e358a5a913ad8 \
--hash=sha256:77efcff2b23071c349402ac1066667a3d011f62398d81408c9b88ad991747c9e \
--hash=sha256:789b8982559ae28dad2356519f841655756cdcd96616410590ae0b17454ee64f \
--hash=sha256:7ac76cf9afd34929d76eb7fcb63be476a4853d8a96f0dcf2d0db68a0cbdf9885 \
--hash=sha256:7c0c10730342b0c9b35dd1d619beb8214e520bd96a1f870f452680b238aab3e0 \
--hash=sha256:823f82903d189af463d7df250ef1f7f696f3cee08cc8d91deb565e8d425f6506 \
--hash=sha256:838648accb3a7fd9803fd45c87bce8509648eb0c11bc34e216141300977244f2 \
--hash=sha256:854066be00447fa8de2ccbbe893e2ffc4b123ef16d897af794c1e18bd4a714b0 \
--hash=sha256:85d5855daafc240cc045c026d7a15fd198a09b0fc8ff6f5ecbb5297b509cb11e \
--hash=sha256:85de3134b5379856e323ba37c19c9256d39425f7b76a63af52b09fb4664c2e8f \
--hash=sha256:87e4f41d375c0b9be2fb5251aee4b8a689169e134535aed81bf085c3b647451e \
--hash=sha256:88ca277405c2d3b71c4e1c2ee0e7966e807bcba86a69d11e19ba199d18ae4491 \
--hash=sha256:88e85ab89cb822c1e635f51d6d32e488f94e002e70e2f492bdb8b945543f345a \
--hash=sha256:8ac8c94b6539074e0f40899301273ac8402b9b3e01c7b7ba269ff30340aaaf20 \
--hash=sha256:8fe532b3c966d1fb794e0698e4589d0444017ae77fc0b31edea13c0e35bcc449 \
--hash=sha256:9085f87b0e38a2b92b8923059b4e8789fe40d9279712d15dcc670048d77079af \
--hash=sha256:90b7481fb62fbe172c558bc6fd1c4c98d82004a54a7551f20e11ac9bf0b8708c \
--hash=sha256:92caef967d287a407085d61176fce4012b1dd62daed4eb6d5ceb26d3d2538712 \
--hash=sha256:9362dd90aa7dab48c0054a21187791ccf05473f7dba5d92b8033ae62164675e7 \
--hash=sha256:94d78ecec2605a8d0398b0f365d5f12a63248438516f5dac536a5eff7337df4a \
--hash=sha256:94fbf1c0c6cc0d3d5e50f9a9313a8cdca90dd696d34b381cd1704f8c9e939f20 \
--hash=sha256:950f23cb393f85543777b0433f082cddd25b51ab398eac7971146495679efe5f \
--hash=sha256:96eefc178f8636b9c760c5829345307fd81cfae9ab1e80997dbddeb0f54ee9a3 \
--hash=sha256:96fef3e886d6a9874b14f27fc193fbdc69d5d8035783d86aa4e1cea594e695f9 \
--hash=sha256:977cdbd483a9cff38179bea4fd754289a6f2195c7abd414aba85410b3e66cc5e \
--hash=sha256:978eab16f55b4ab2c2a745be9a0a840bf8f09a7f227d9c76eb30214d078865a5 \
--hash=sha256:994e883d17c559cdfd38c84003c8b27d25424a1077272a17e7cd27bfe0bf57b2 \
--hash=sha256:9ac4444d8d4fd4c4bd08bf451ed3167aa9e7ec6cdb41b648794f1d1103652e36 \
--hash=sha256:9b5db6052055d34d41230fb78d7c439c23dc536a9896f6cb039e8dd92cfc1263 \
--hash=sha256:9d9a0dc7cbe9bec24c3f767c9122c41fe5a1bc43f47cd099d00d393e09769de4 \
--hash=sha256:9dbdd9205662134957cf0c324f639bdc5031c0ca056e2369e238db75187c0f11 \
--hash=sha256:9eea3ab2597a5e65fe65296e2d6a84570845a6b55532d90333d740d48bbc850a \
--hash=sha256:a2028475ba855475b8b4d3cfeb4994269c967aea8b9892dfba907f4263a863a3 \
--hash=sha256:a3a370082ce34d0612f421e15fe011c53bb1feff21a26d06ad4fb244dab5a375 \
--hash=sha256:a545775cfe815855ea32d7c27731d79da358ef2055b4a25830231b1622dd18aa \
--hash=sha256:a5cbd90ecf0fc62e64726917ad083b73001f0563657a87ec3c0b504e277dc90d \
--hash=sha256:a6d095662e73e74f0a49988e0593373e243e3a52e27bfeea0a859e88acf4a0f5 \
--hash=sha256:a6dac12ff6b846103483683f60c5f8fee205121adc58ffd87e90a90a3af69e99 \
--hash=sha256:a951ad59cad9145664a730d3036b40b844e74d2d3683da40111463cd3a83845d \
--hash=sha256:aa1099b956fb795e686d073568f6dc002a0bb89765ea6d5b055dd7d9bf1b116c \
--hash=sha256:aa2bb0b37202dca27175591f761108b5d34096ade1191ffe4808bdf6b1571488 \
--hash=sha256:aae2ee51122d3ae968a3837d97dc24a0aeebb0dea23694422cd172bd30017cd6 \
--hash=sha256:ab743e9bc90c1f73552ec33e10e3331315acd2c397b36065b591b0181de533cc \
--hash=sha256:ac00177c4831ffa650f8609e4bdddd5fe09c03b1c0c47acece7e6ea20421598b \
--hash=sha256:ac13b004224fb341e1e25a1ed5e19d32f57cdb2a403e01f003b46f051a550f6f \
--hash=sha256:acaf604462bf330b0d07e7a07c1d6e4adac79e5fb13e9c5140590542cafacc00 \
--hash=sha256:ae31a1a1db2ee6cc2942fccaf695c934bc7f3db9f2133a3fef1f367cf1a4ab10 \
--hash=sha256:ae4a097991662cd4fff0ddc74e0fe7874f82e00042fa0ea00855645ed0c79598 \
--hash=sha256:aea996a6aba25260827c9ea511d1addfde2da9eb686ac961838509086188b7e6 \
--hash=sha256:b39b69b347e5e47a3b5b8cfc005c68c1ba347474e3960236c4944a8ecd174962 \
--hash=sha256:b54e7e13267d49ffbfe68e25b3cbd774dab38fa37238f71265e91b36146eb21c \
--hash=sha256:b9af956078716df40d985fb0dfeb2c2120c5ca92ba4ff4b388acfd01cdc14d08 \
--hash=sha256:ba2f37ee79e6338845261a3c5b1784e5d1acdff2c0785b284f1b633033d136ab \
--hash=sha256:ba501e667c17d8411f98e67a022d9604ef179aff0e459b7e292c796837c13573 \
--hash=sha256:baf3775a2635e5a11fbd5e4e64ee69c7e86875d224a5c72aca4c141064589a90 \
--hash=sha256:bb57753e36e4855b8ca375069482250a6246372331a3e4f3407eaebb007443f5 \
--hash=sha256:bd6c173f04743d483881bffa1478d5a4624475b8cd1d2194956a75548e191c18 \
--hash=sha256:be47f99644b208bff7766314013f9acf57b056b04191d570d68ad14022cf5b1d \
--hash=sha256:c010f5581d9c612804cc59fcf7b524b707fbcb72828551237ab545bb5c7034af \
--hash=sha256:c1dcc36dcb96abc02236e182d17e0f71430152a6c2c7447421da2d2dc144edea \
--hash=sha256:c428c6c31eb5f4277d7f8eccaf767fbd548ddd5ce3c8b4f4cbbfab3d96b5904c \
--hash=sha256:c658c50ac0c98cd755a2dd50b7977d3bca7df401dcc47fbdfa87db53ef7d4e8b \
--hash=sha256:c71fb0d56c920c269cd3e2e3fe7c610e3f1fdb21a6ce60efa6430ff63676cea6 \
--hash=sha256:c7b742bf31c88566b4bb6335a7f393bb322e580b6bb98df7bd0c25e6e3519ce8 \
--hash=sha256:cc0329df4caaceb950d2f580b5ac716a377f7059624a0bafaeaf8a218c6ed774 \
--hash=sha256:cc5d36d96478aa9c60654bd932525bf32964c62a7281eafdf16d85003a8d6004 \
--hash=sha256:ce854f5f478050ade5a238731c4ca985a7d3b3cb53ff600a9b5c3b689b5f0a7a \
--hash=sha256:ced3fdd71aaa83ce593746c2edb42b7a59cb4c19c8b5c407781c72e493aae55a \
--hash=sha256:cee5dd7c6fb5dd52a0fe2a740f9bc6e3593f5f8b1788bde49de02086f30182b2 \
--hash=sha256:cfa1c0cc3a8f9f53f1243a5a99ac36fd003880199383b37672e86ddda9cb07e2 \
--hash=sha256:d1ee1e296209fdce05b81b663250eefa02213a2da7b41bf26f7829b8ba3545aa \
--hash=sha256:d59b75732e9b6f27388e10c14b0259cc5f2e48c78627d185e6a177b58ad3cffe \
--hash=sha256:d63600d620ad0064c3a748b950ac5ea38a80190e5498532efefa4b7b3f1da1f3 \
--hash=sha256:dd732602a7009217f658d5863d12d79d373a4de0eebc111094bcdd3bb8e0a6cc \
--hash=sha256:e06efa066f7dbadbc84ebc126a97c452a6451dfcf589d89d788484949e1cf795 \
--hash=sha256:e199fb99720074809a7720f1c0b4d919eea8b87e88713e0f8f602f7bef543d9d \
--hash=sha256:e4b018dc5a0eee4676e38fe84a47a427816c590b93b55d9025274ec4d6ffc2dc \
--hash=sha256:e6621fb2a4988d6e53eedc455e5903e2679f3967b8acb3d639f1b63c14a2e893 \
--hash=sha256:e71c909f353863b2b89c83de2ebed71ea6d0df8a6ef65a128193c5e650766bef \
--hash=sha256:e90251c0c7bdd54a100a0dce3c07b7e637278c93af29dbf78ebb89a58c4bac7d \
--hash=sha256:e9fbdce1e47394b09bc9f26ab117dfc8d6491977a11d86f592bb42c779db2fda \
--hash=sha256:eb12fb2ba69ffa05f8695f61c69e591dc4b4a12ac3757ac8af8adb259bf56d17 \
--hash=sha256:eda059b6bc8bc0812d626fd91a7ce01bf583df0a61296eff390fd94141a34e30 \
--hash=sha256:f03ac127268b43ef4fe9e6ab6794a6794b49485a0cc0c1db79876d2f33f75bc7 \
--hash=sha256:f298e218441525d3794428b4c8b8fb8662c6d3ea79925d4807ee6b9a96a3bca5 \
--hash=sha256:f5542f9b941279d82d41eb0aa9f98eba36fe4df5c7086c651df7944935b37182 \
--hash=sha256:f6f7deae3feb4edfa2efaf7c574fe88cbf055038a6abdb40188e4fff66d5699f \
--hash=sha256:f9b1e28d0e8dbfa858abdba91d6b547beaf2df1a59bec6da6faae7b96a4991a9 \
--hash=sha256:f9f8405c2c758532c74fed975dbee57be1f31a6e865c031870c79a6ed3212ada \
--hash=sha256:fa48b1b63d639f9483e0633e092f5851e2348c352f1f9bb6c8182f87884ef876 \
--hash=sha256:fb78f6e7fcd8ad785d28cd577168bc1aaee827b25bb8755638f694794ea98f0a \
--hash=sha256:fbc597639158fd7c14d55e808718848319540f51b0e6746e3eefa59723a4a348 \
--hash=sha256:fce8cbd4997efeb450bd298b54f755dcdff18d496f7a5ddbb4867c6d7c88fdc3 \
--hash=sha256:fd0350afdc3aabd5576f60ea109228bd5538139713c7b094c5cd27c73a98bc6f \
--hash=sha256:fd0a274c0e5f9a21565cd9d3dd749b61f96b7aa1e20a93aa1ba4029518f2e5c0 \
--hash=sha256:fdb8a068947befafba9952162645dc2fecaeb400e64584829ed5e9b2fbe21a7f
# via requests
cryptography==50.0.1 \
--hash=sha256:01f41478cf33fc605a6a089cd56d28b45c6c0b45a1928b61797f2621a04bac71 \
--hash=sha256:05ba322c4da95b262a212c345af888ef2c37c88c0509756ea00a0e6d68850f23 \
--hash=sha256:16c5ecd954b3330ebfb6605eca4fd952da8bef376551d5cc264534e3770a9ee6 \
--hash=sha256:2a93d05e34d5f67fba6f891fe85d929999baa7195e853923ea6d7576c9e68c5e \
--hash=sha256:2b34d76a652ea2b6faf777c35df230c5637842cd904e04f16230c3f9f03e4361 \
--hash=sha256:2ebbfb0f1fed745e91796e3e1080a1440423fdae8ece1b995a1d80883a409054 \
--hash=sha256:30a125032e5642a21ff816e021152bd4e7e94f03eff3f4b7fca41cd22bc3110f \
--hash=sha256:330fbb252391c596f1ae42c5754449dc924e6ad012dca8efe0d703f9f2d12ec6 \
--hash=sha256:359e62deae718bce96170e223fdcb6357e4fbd3bb7a3a75f4430763532560e49 \
--hash=sha256:407fe2b6db00939c05c0e945e9914238f2f0a430974839429dafc82b1ee6bee5 \
--hash=sha256:42be3bb70596b3abe4ac097b75be223e8b3ab614a0e5de068e3dcc54d71d6149 \
--hash=sha256:4c4188f7c0cf655be5c06342b817ed0f9595b69ffa2b12026e5353eed29dea88 \
--hash=sha256:51593d180cf6d179bde5c5d065bed81386b1f381656ae7d042b7ffc87a9895ad \
--hash=sha256:51afcfceb15597cf2635068e4ac9a56b2abde622edde17f37d85fd7b5306497a \
--hash=sha256:53e279950892dc102c6b4e52af03ae5ea92fac572a1ddab78ca73a997f62b69f \
--hash=sha256:55d16b1ef3ee0958d893a977b19777887e546c9954ea81b200c3301a864013f2 \
--hash=sha256:5dd9bda1c12b4162f6ff568eeb5e0ff956c28d14406e875cfe8a63a2d414ff20 \
--hash=sha256:5fe002589592ed749ce77fe0695fcbd3500dd61d7d6db5858a7544c612fa8e45 \
--hash=sha256:5fe939deeb161024a6be98229c953b6591fef1f41214497a78fe793a244c017f \
--hash=sha256:693c99b49bd37d0d096e4334c10232c77248c415b98d35236094cdf96d57258b \
--hash=sha256:76de83fbd91ac49c0feaaa983d0748fd7a53176afac5fb3bf7478d244f0eb527 \
--hash=sha256:79bf008d1f9af6071c797ad133e39915dfee7614f18f18f4db9072eb715064a3 \
--hash=sha256:804728ce710890870f3aaa344b2e161172d258d768ac139d02cfd9092d0d94e6 \
--hash=sha256:8921d58f426793c5f1b47f0b59575780de9a095214958d0eb37d909593db8367 \
--hash=sha256:8df2de9102026855887e4587084f6eabd80ed0f345b8ad8a7ac27ab9bf4723e0 \
--hash=sha256:9cb3cb952cf5a8abd50c782a98a89d71699715e802fe349704b47f2425b42a94 \
--hash=sha256:9dde0a357190eb3b1da1bb9ab750e9c85cba82ca5977aa0836cbb94e92611239 \
--hash=sha256:9ebcdd5519be9b652a46f507817a74591774fc3d6923ac364e4dfa64e36b291b \
--hash=sha256:a0b1a59e3a089064a0ec309e9428c8e3ae4e161419d20ac33600767e83fc658a \
--hash=sha256:a255449073358275b64b67d3f595f268bbef70e72b6edb65e0c70c735bf739c9 \
--hash=sha256:a8f40ea47330e71b594a7e246898f93177c259490c63183dbaf9e571d71ed9a5 \
--hash=sha256:ac02b07824d4d1001bd4367599f839c19cb171924c796e52c23508ac14c2c0cc \
--hash=sha256:aed8db4f6d71c51efb89530e12d9464e7bf2923d46c3205dc794a2a93f8c0648 \
--hash=sha256:b8f852c65863251b9e3a1b8c150ce21e59b522dbb6a7d4bc80e680d38388e986 \
--hash=sha256:be224a65493ec5b74a158ff22a5522ce4a5ca1e543c647a3a4730d4a09e5f959 \
--hash=sha256:ca83d00d9e69cd5eb63f2e69c3a5a59e0cecae5ae14c6ae0b35830fe3b37bad0 \
--hash=sha256:cbf74a81765ee67413503ca6e26dcc4f6f5a519822436cc0a1b97aab6c1b8a17 \
--hash=sha256:d63ae8f6481fec907ac0f588eee8a90aefde112c633131fe540e5711ddbb5a4e \
--hash=sha256:e22dfed744bd4002e909464cb23d2f0b05c6f3113a79ef2e9864a53db737c733 \
--hash=sha256:e2ca8fd1b6b4b82a1c4cb02841d0837e3c12336c2e24b520ab8ab3b969733d8f \
--hash=sha256:e74591e283fe6eb956416c929eb58262a719fe0311fd9054c62c3350ed8760d8 \
--hash=sha256:f74455bb086a85d5e81246412602aaa97ed095e504cd40dd261ef50be42205bf \
--hash=sha256:fb4b9672d389c738b175c4166e78310f8a70358886aacd9173ee03a85ffdc671 \
--hash=sha256:fc3ed7ebd2a8c96f5b166de0ab9b624996bef3b07bbeb19364dfb78222c22c80 \
--hash=sha256:fd3718b960d0b5dd213cdf03f3bcb7000e69dda0de8b956061947ff6bcff5558 \
--hash=sha256:ff838d62ec1bfce4f9ba7fa16f4a7b554cd8d0c299e6be37502161a660c84eef
# via secretstorage
docutils==0.23 \
--hash=sha256:25d013af9bf23bc1c7b2b093dff4208166c53a94786c9e447808335ef1185fea \
--hash=sha256:746f5060322511280a1e50eb76846ed6bf2342984b2ac04dc42caa1a8d78799e
# via readme-renderer
id==1.6.1 \
--hash=sha256:d0732d624fb46fd4e7bc4e5152f00214450953b9e772c182c1c22964def1a069 \
--hash=sha256:f5ec41ed2629a508f5d0988eda142e190c9c6da971100612c4de9ad9f9b237ca
# via twine
idna==3.19 \
--hash=sha256:5e0811a4383b21dc5838069f801c4fb62113b7447663d2530d2bd6e77b49bf15 \
--hash=sha256:815e7be7a7806d54abb586dc943addc79e8b2ee16915059658cbeff4b1b43bf4
# via requests
importlib-metadata==9.0.1 \
--hash=sha256:ab830580bc0ef3db61ce8fae716389e5462b67e033018bab6d8f80ef17172f99 \
--hash=sha256:bba5600596a7e21f3eef53281cf28d6a5195634d2f2b78ff9501a3272c6eaab0
# via keyring
jaraco-classes==3.4.0 \
--hash=sha256:47a024b51d0239c0dd8c8540c6c7f484be3b8fcf0b2d85c13825780d3b3f3acd \
--hash=sha256:f662826b6bed8cace05e7ff873ce0f9283b5c924470fe664fff1c2f00f581790
# via keyring
jaraco-context==6.1.2 \
--hash=sha256:bf8150b79a2d5d91ae48629d8b427a8f7ba0e1097dd6202a9059f29a36379535 \
--hash=sha256:f1a6c9d391e661cc5b8d39861ff077a7dc24dc23833ccee564b234b81c82dfe3
# via keyring
jaraco-functools==4.6.0 \
--hash=sha256:880c577ec9720b3a052d5bc611fb9f2269b3d87902ef42440df443b88e443280 \
--hash=sha256:99e3dc0060c5cbe8fcd1cdb36258e2a65ca40f1566b2033b12abb1bb44dd3c30
# via keyring
jeepney==0.9.0 \
--hash=sha256:97e5714520c16fc0a45695e5365a2e11b81ea79bba796e26f9f1d178cb182683 \
--hash=sha256:cf0e9e845622b81e4a28df94c40345400256ec608d0e55bb8a3feaa9163f5732
# via
# keyring
# secretstorage
keyring==25.7.0 \
--hash=sha256:be4a0b195f149690c166e850609a477c532ddbfbaed96a404d4e43f8d5e2689f \
--hash=sha256:fe01bd85eb3f8fb3dd0405defdeac9a5b4f6f0439edbb3149577f244a2e8245b
# via twine
markdown-it-py==4.2.0 \
--hash=sha256:04a21681d6fbb623de53f6f364d352309d4094dd4194040a10fd51833e418d49 \
--hash=sha256:9f7ebbcd14fe59494226453aed97c1070d83f8d24b6fc3a3bcf9a38092641c4a
# via rich
mdurl==0.1.2 \
--hash=sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8 \
--hash=sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba
# via markdown-it-py
more-itertools==11.1.0 \
--hash=sha256:48e8f4d9e7e5878571ecf6f2b4e57634f93cd474cc8cfbd2376f2d11b396e30d \
--hash=sha256:4b65538ae22f6fed0ce4874efd317463a7489796a0939fa66824dd542125a192
# via
# jaraco-classes
# jaraco-functools
nh3==0.3.7 \
--hash=sha256:157ec1eb7a62f3d9a7badb8d82d89aa810e3e24e097eedfa481a25d0c8a99877 \
--hash=sha256:15f5fbf090f5c88d61c820e1fc1fceecb6520cca9fe85649c06b57ef9dc9ff62 \
--hash=sha256:18f4278ecd157d43cb35acd5aae9f35cfa79f546b4922bd86536adc0f6312102 \
--hash=sha256:19f288c938ec6eef1f5d2c6cab47838e71fef8097e1c1233802be5a6230ba086 \
--hash=sha256:4968fe8d2db97c6f047659bf46a449fd8ec377f44ebf3e0a1b96c0d3a333ae32 \
--hash=sha256:5ffdfcb9a686ffb12765376bcfb6b5b55728516d3c0ee317d29982381ded3df8 \
--hash=sha256:614dac4a4c36ad084e78447d16fe898dedd762e354a7ab9cda2984e82f67883d \
--hash=sha256:618e3059caf41ccdf5dcccb3fa9df4cf6e4efe23d1382a8bbfca272a8a4f8bfc \
--hash=sha256:6698a822132beedab80f131c08d8d0ac5a178ddeb488d02ca4b67716ecfac7af \
--hash=sha256:6c3aa50eb26e9228238271db9f983cbc3b006dfbfeca2d4dc34c33ddc6ac5ea5 \
--hash=sha256:6e4280115d44c3b278eef712a86748c1a723105cd79feec46952383117ab4e59 \
--hash=sha256:70f5ac8626e899a4bab0ef74ca2f5bd602f49c7b739e6e5026b4afc6d63dac42 \
--hash=sha256:71860d01c16f4d8c72e334e0674beb2b0899dbd0bf760de18932ef4390303848 \
--hash=sha256:808def0c8c07843e6e50dc84f532457bfa2cfd17417b219a5d9e7c773709331a \
--hash=sha256:874b7d67a067bd29a59223f6270fc30da4edd8e6d87fd219fc93bcbaa662c946 \
--hash=sha256:91a4dab4e94d9fc54b9f67b1adfb23e81fab7ab43f33c3b8c97be9aa38f789ba \
--hash=sha256:94fd6e59553fbb9ffd8ba71bbd5a54e3126ba01799a097ae30d5341d750bc6ac \
--hash=sha256:9b7279d43323a25225df23576af6594a16693f61431170848b8b2ac21ad4f174 \
--hash=sha256:bc42bb1193c1e28a1e74c2cabaca178e118a7103e8832699fef8a2b3e2496493 \
--hash=sha256:be53a4825585f701955cb9baf49f478f56eb81e20294329fe4bc689dd5dd81fa \
--hash=sha256:d56e76bd3cadb09b6b0cef364850811663734b348a25f5f587a2819c495367bd \
--hash=sha256:de2b2aab32ea303405debefdcfc58043d3e635fa3f67b9eb140d2b0e0c0d2563 \
--hash=sha256:e8fd1ab205258b29254f72db377d99e2c96aa7653ef3b015ccab0420b094b506 \
--hash=sha256:eae64328e46a25785535afcb6885b6f182ecaf5ee8c88f8c075422db8aacc65b \
--hash=sha256:f04b7d333b27f13ca439da3cf1c75c2fba34f104969f6ce4ac8e7079699c2f4a \
--hash=sha256:f266d3f1b3647449923a8e406524632220dd5d8b647078dfe45b885d33d10479 \
--hash=sha256:fd4a70efb45d5372174f718878eb7a35c12677626a63b2f103b23b833457dcac
# via readme-renderer
packaging==26.3 \
--hash=sha256:94edc256424af38762eb31306eed28beb9f0efc50a8837492c9d6fd6004aed79 \
--hash=sha256:d7193f7c8e4e93f444fde0262bf90af30e16fa0ad0ad44cb553c87339b23cd1c
# via twine
pycparser==3.0 \
--hash=sha256:600f49d217304a5902ac3c37e1281c9fe94e4d0489de643a9504c5cdfdfc6b29 \
--hash=sha256:b727414169a36b7d524c1c3e31839a521725078d7b2ff038656844266160a992
# via cffi
pygments==2.21.0 \
--hash=sha256:2363c69b61c4a97c838da3b130dcd6468f4848992b21a82f2a63ec34377137d9 \
--hash=sha256:610ca751c9bc2492b38eb9a38a7fbc93edbbb2d7182edaf34e66ae493dee5c8c
# via
# readme-renderer
# rich
readme-renderer==46.0 \
--hash=sha256:af3e964914f6310a33ff67b72a4bdd940bed8d7c3bdecd2d14f40edf284bfe90 \
--hash=sha256:d0dae1f74bb273b534770cb4cccb6bb78735540afdb03c2146f4e19dcd412560
# via twine
requests==2.34.2 \
--hash=sha256:2a0d60c172f83ac6ab31e4554906c0f3b3588d37b5cb939b1c061f4907e278e0 \
--hash=sha256:f288924cae4e29463698d6d60bc6a4da69c89185ad1e0bcc4104f584e960b9ed
# via
# requests-toolbelt
# twine
requests-toolbelt==1.0.0 \
--hash=sha256:7681a0a3d047012b5bdc0ee37d7f8f07ebe76ab08caeccfc3921ce23c88d5bc6 \
--hash=sha256:cccfdd665f0a24fcf4726e690f65639d272bb0637b9b92dfd91a5568ccf6bd06
# via twine
rfc3986==2.0.0 \
--hash=sha256:50b1502b60e289cb37883f3dfd34532b8873c7de9f49bb546641ce9cbd256ebd \
--hash=sha256:97aacf9dbd4bfd829baad6e6309fa6573aaf1be3f6fa735c8ab05e46cecb261c
# via twine
rich==15.0.0 \
--hash=sha256:33bd4ef74232fb73fe9279a257718407f169c09b78a87ad3d296f548e27de0bb \
--hash=sha256:edd07a4824c6b40189fb7ac9bc4c52536e9780fbbfbddf6f1e2502c31b068c36
# via twine
secretstorage==3.5.0 \
--hash=sha256:0ce65888c0725fcb2c5bc0fdb8e5438eece02c523557ea40ce0703c266248137 \
--hash=sha256:f04b8e4689cbce351744d5537bf6b1329c6fc68f91fa666f60a380edddcd11be
# via keyring
twine==7.0.0 \
--hash=sha256:85cdb29c518efef867360ae4acd4b0dfd61c8654a22fca08e6f8539f05022177 \
--hash=sha256:b854164df26db268af05f49aa5c0344b10e27a494343ff05b1e0bad3b135f5a7
# via -r .github/requirements/twine.in
urllib3==2.7.0 \
--hash=sha256:231e0ec3b63ceb14667c67be60f2f2c40a518cb38b03af60abc813da26505f4c \
--hash=sha256:9fb4c81ebbb1ce9531cce37674bbc6f1360472bc18ca9a553ede278ef7276897
# via
# id
# requests
# twine
zipp==4.1.0 \
--hash=sha256:25ad4e16390cd314347dd8f1de67a2ac538ae658ed4ab9db16029c07c188e97f \
--hash=sha256:4cb57381f544315db7688e976e922a2b18cdb513d21cc194eb42232ba2a3e602
# via importlib-metadata
+1
View File
@@ -0,0 +1 @@
uv==0.12.1
+23
View File
@@ -0,0 +1,23 @@
# This file was autogenerated by uv via the following command:
# uv pip compile .github/requirements/uv-tool.in --generate-hashes --python-version 3.11 --python-platform linux -o .github/requirements/uv-tool.txt
uv==0.12.1 \
--hash=sha256:04290ea4001dca31ac8a8324113a4930dccad69ce35dbf6eaae307d54880890d \
--hash=sha256:153ec0959a15397514438aefc1d7cd04235f335dd6bb53ea0f9e6e82c5a49f03 \
--hash=sha256:173ee216f17d89fc39f65339d311a53584fc7de4918d27c0f3c7edafabc6b54d \
--hash=sha256:1de49d9b04438f1ad2f41a1441dbbe19e230b94fca56d632818cfaed69e03bfc \
--hash=sha256:1e8fd95fe98768e29436ad57f9ef7b68dc294b7b9862ef63396af8b15ab85e6c \
--hash=sha256:27211df9b277f440dea438a4e525ba40250fb721ad39b8927eefc2d91f9aea15 \
--hash=sha256:29399e1e73b67ed24abe82bc971aa4eb8419c4de804784290f39cf681f0b51ce \
--hash=sha256:2e9b0b86e180abc5968b979c6e25203b32e85969abb5083ee1e8b88a5aa98a76 \
--hash=sha256:3bd5db002adc763aa8d277f5b44f8d6e3fd82d20f2e51225b0bbdae1badc7259 \
--hash=sha256:41b8fc2335f682312a1ca39a7b4abfd6af800992065c663582ca3e4d51cf9258 \
--hash=sha256:5bd04849dd5346517cc4e57b4b3aa0b01c67c423878260c04f5893a038fe25b6 \
--hash=sha256:6f7e72543264d2420ebb2ddc84696a751af2d6c5910046b7666589118f47292b \
--hash=sha256:71f86410264c69a3e8acd18171897dd8ab1a13350cf40f718e4def5db2b724be \
--hash=sha256:76d87de420213ca92fa403e87023c4c7c6956c6726c6b96d91c42cfe620173a3 \
--hash=sha256:9331dda0dc4990512c232f86e1d3a7b83c13f459777fcc2bd46030911b40eaaa \
--hash=sha256:b255ac23958e45f39f9c7a4cd65890df5ef46f539a3b14de03bd296bbba9cb60 \
--hash=sha256:bd02f2da212e6a983115dc64a6fc94e9256c2d60e056d6b669de0a6025aaec05 \
--hash=sha256:e35e0030480a8c3bf8ecd87ae4a6f6a224009e15e96a6fbb3634ac11ab75d582 \
--hash=sha256:ead7ad064f291a5df358c3ffa8ffab347a32bd5a75a6a068ca22254c2539a829
# via -r .github/requirements/uv-tool.in
+76
View File
@@ -0,0 +1,76 @@
"""Drop checkov-suppressed results from its SARIF output before upload.
checkov's SARIF exporter includes every evaluated check as an ordinary
result, including ones it internally marked SKIPPED via an inline
`# checkov:skip=` comment or a `checkov.io/skipN` resource annotation - it
never uses SARIF's `suppressions` field, and never drops them. checkov's
JSON output *does* correctly record which checks were skipped, so this
cross-references the two: any SARIF result whose (check_id, file) pair
appears in the JSON's skipped_checks is removed before GitHub ever sees it.
Without this, every already-suppressed finding reopens as a brand new code
scanning alert on every run, forever (see #6035/#6036, #6112-6115,
#6128-6131 for the pattern this was chasing before this script existed).
Usage: filter_checkov_skipped.py <json_path> <sarif_in_path> <sarif_out_path>
"""
import json
import sys
def path_suffix(path: str, segments: int = 2) -> str:
"""Last N path segments, normalized to forward slashes, lowercased.
checkov's JSON file_path and SARIF artifactLocation.uri are relative to
different roots (the scanned directory vs. a temp helm-render dir), so
they can't be compared directly - but the last couple of segments
(e.g. "templates/service.yaml") are stable across both and specific
enough in practice to avoid cross-file collisions.
"""
normalized = path.replace("\\", "/").strip("/")
return "/".join(normalized.split("/")[-segments:]).lower()
def main() -> None:
json_path, sarif_in_path, sarif_out_path = sys.argv[1:4]
with open(json_path, encoding="utf-8") as f:
checkov_json = json.load(f)
if isinstance(checkov_json, dict):
checkov_json = [checkov_json]
skipped = set()
for block in checkov_json:
for check in block.get("results", {}).get("skipped_checks", []):
skipped.add((check["check_id"], path_suffix(check["file_path"])))
with open(sarif_in_path, encoding="utf-8") as f:
sarif = json.load(f)
removed = 0
for run in sarif.get("runs", []):
kept = []
for result in run.get("results", []):
rule_id = result.get("ruleId")
locations = result.get("locations") or [{}]
uri = (
locations[0]
.get("physicalLocation", {})
.get("artifactLocation", {})
.get("uri", "")
)
if (rule_id, path_suffix(uri)) in skipped:
removed += 1
continue
kept.append(result)
run["results"] = kept
with open(sarif_out_path, "w", encoding="utf-8") as f:
json.dump(sarif, f)
print(f"Removed {removed} checkov-suppressed result(s) from the SARIF before upload.")
if __name__ == "__main__":
main()
+31 -5
View File
@@ -28,11 +28,37 @@ jobs:
BENCHMARK_REAL_LIBS: "1"
run: |
python -m pip install --upgrade pip
pip install -e .
pip install -r benchmarks/requirements.txt
python -m spacy download en_core_web_sm
pip install rdflib neo4j faiss-cpu torch pyarrow pdfplumber python-pptx openpyxl lxml python-docx beautifulsoup4 chardet langdetect
pip install -r .github/requirements/bootstrap.txt --require-hashes
# --no-deps + a hash-pinned install of the same base dependency set
# (rather than a bare `pip install -e .`) so every fetched package
# is hash-verified (Scorecard Pinned-Dependencies); the local
# editable install itself has nothing to hash.
#
# --no-deps only skips *runtime* dependency resolution - `-e .`
# still does a PEP 517 build, which by default creates an isolated
# build env and fetches [build-system] requires (setuptools,
# wheel) completely outside any hash checking. Install
# pep517-build.txt (pins that exact build-system.requires) first
# and pass --no-build-isolation so pip reuses those hash-verified
# copies instead of fetching its own.
pip install -r .github/requirements/pep517-build.txt --require-hashes
pip install --no-deps --no-build-isolation -e .
pip install -r .github/requirements/base-deps.txt --require-hashes
# NOTE: benchmarks/ does not currently exist in this repo (neither
# requirements.txt nor benchmarks_runner.py below), so this job
# already fails on any real invocation - pre-existing, unrelated to
# this pinning change. The `pip install -r benchmarks/requirements.txt`
# step that used to be here is dropped rather than fixed: there's
# nothing to hash-pin without knowing what that file should
# contain, and an unpinned install here would just re-trip
# Scorecard's Pinned-Dependencies check for no real benefit, since
# the job can't run to completion regardless.
#
# `python -m spacy download en_core_web_sm` fetches an unpinned,
# unhashed wheel from spacy-models' GitHub releases - replaced with
# a hash-pinned direct-URL install of the same 3.8.0 model (matches
# the spacy==3.8.15 pinned in base-deps.txt) via benchmark-extra.txt.
pip install -r .github/requirements/benchmark-extra.txt --require-hashes
- name: Execute Benchmarks (Real Mode)
env:
+86 -11
View File
@@ -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
@@ -52,16 +102,39 @@ jobs:
# environment is installed. The Explorer extra supplies the
# production API dependencies without importing optional vector
# providers such as Pinecone during test collection.
pip install -e ".[explorer]" pytest==9.1.1
#
# --no-deps + a separate hash-pinned install (rather than the old
# `pip install -e ".[explorer]" pytest==9.1.1`) so every fetched
# package is hash-verified (Scorecard Pinned-Dependencies); the
# local editable install itself has nothing to hash.
# .github/requirements/explorer-extra-py311.txt is
# `uv pip compile pyproject.toml --extra explorer --python-version 3.11 --constraint requirements-ci.txt --generate-hashes`
# - regenerate it the same way if pyproject.toml's base/explorer
# deps change. Resolved specifically for this job's python 3.11
# (see the Dockerfile's explorer-extra-py313.txt for why this
# can't be shared with python 3.13: audioread needs extra
# standard-aifc/standard-sunau hashes only on 3.13+).
#
# --no-deps only skips *runtime* dependency resolution - `-e .`
# still does a PEP 517 build, which by default creates an isolated
# build env and fetches [build-system] requires (setuptools,
# wheel) completely outside any hash checking. Install
# pep517-build.txt (pins that exact build-system.requires) first
# and pass --no-build-isolation so pip reuses those hash-verified
# copies instead of fetching its own.
pip install -r .github/requirements/pep517-build.txt --require-hashes
pip install --no-deps --no-build-isolation -e .
pip install -r .github/requirements/explorer-extra-py311.txt --require-hashes
pip install -r .github/requirements/pytest-tool.txt --require-hashes
- name: Test deterministic Explorer backend path
run: |
pytest -q tests/explorer/test_explorer_deterministic_rendering_e2e.py
- name: Install pinned Python dependencies
run: |
pip install -r requirements-ci.txt
pip install -r requirements-ci.txt --require-hashes
- name: Verify requirements-ci.txt is up to date
run: |
pip install uv==0.12.1
pip install -r .github/requirements/uv-tool.txt --require-hashes
# Re-resolve with the committed file as a constraint: upstream package
# releases must NOT fail CI (deps only change when pyproject.toml
# changes intentionally). Compare only version lines (pkg==ver),
@@ -72,10 +145,10 @@ jobs:
diff \
<(grep -E '^[a-zA-Z0-9._-]+==' requirements-ci.txt | sed 's/ \\$//') \
<(grep -E '^[a-zA-Z0-9._-]+==' /tmp/requirements-ci-check.txt)
- run: pip install build
# wheel is build-time only (not in requirements-ci.txt) — install the
# same pinned version [build-system] declares so --no-isolation works.
- run: pip install wheel==0.48.0
# build is a dev-time dependency; wheel is build-time only (neither is
# in requirements-ci.txt) — install the same pinned versions
# [build-system] declares so --no-isolation works below.
- run: pip install -r .github/requirements/build-tools.txt --require-hashes
- name: Build package (no isolation — pinned deps)
run: python -m build --no-isolation
- name: Verify Explorer frontend is packaged
@@ -95,3 +168,5 @@ jobs:
print("Explorer frontend is packaged")
PY
- name: Run Google ADK Integration Tests
run: pytest tests/integrations/google_adk/
+4 -2
View File
@@ -10,13 +10,15 @@ on:
permissions:
contents: read
security-events: write
actions: read
jobs:
analyze:
name: Analyze Python
runs-on: ubuntu-latest
permissions:
contents: read
security-events: write # for github/codeql-action/upload-sarif below
actions: read # for github/codeql-action/init's CodeQL bundle cache lookup
steps:
- name: Checkout repository
+75
View File
@@ -0,0 +1,75 @@
name: Container Security Scan
on:
push:
branches: [main]
# Mirrors .dockerignore's opt-in list exactly - anything not listed there
# can't reach the build context, so it can't change the built image.
paths:
- 'Dockerfile'
- '.dockerignore'
- 'pyproject.toml'
- 'README.md'
- 'LICENSE'
- 'MANIFEST.in'
- '.github/requirements/explorer-extra-py313.txt'
- '.github/requirements/pep517-build.txt'
- 'semantica/**'
- 'integrations/**'
- 'explorer/**'
- '.github/workflows/container-scan.yml'
schedule:
- cron: '30 2 * * 1' # weekly, catches new CVEs published against the base image between pushes
workflow_dispatch:
permissions:
contents: read
jobs:
scan:
runs-on: ubuntu-latest
permissions:
contents: read
security-events: write # for github/codeql-action/upload-sarif below
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
- name: Build image
run: docker build -t semantica:scan .
# Run Trivy as a digest-pinned image rather than the aquasecurity/trivy-action
# marketplace wrapper: the aquasecurity GitHub org has an IP allow list on its
# API that 403s verify-action-pins.sh's live tag->SHA check from Actions-runner
# IPs, and this repo already treats Trivy's action pin as a known past target
# for tag-repointing (see the LiteLLM/Trivy 2026 incident note above). Pulling
# by sha256 digest from Docker Hub is immutable and verifiable independently of
# GitHub's API, so it sidesteps both problems at once instead of carving a skip
# exception into the pin verifier for an org already flagged as higher-risk.
#
# Report-only for now: this is Trivy's first run against this image, so we
# don't yet know the CRITICAL/HIGH baseline. Findings still land in the
# Security tab either way. Once triaged, add `--exit-code 1` (like
# Safety/Bandit-HIGH in security-scan.yml) to make it a hard gate.
- name: Scan image for vulnerabilities (Trivy)
run: |
docker run --rm \
-v /var/run/docker.sock:/var/run/docker.sock \
-v "$PWD:/output" \
aquasec/trivy@sha256:62b1e65e8869bc4b4c6aa4fa2b21595256c7c2f6018a9d9ad61caf87187c1969 \
image --format sarif --output /output/trivy-results.sarif \
--severity CRITICAL,HIGH --ignore-unfixed semantica:scan
- name: Upload Trivy SARIF
if: always()
uses: github/codeql-action/upload-sarif@cdf488f595d80d6e07e03d4674febd5ab45fa938 # v4
with:
sarif_file: trivy-results.sarif
category: trivy-container
- name: Generate SBOM (Syft)
if: always()
uses: anchore/sbom-action@3ad7283483fc7af8ff2b4ea19663c2d5ca935e26 # v0.24.2
with:
image: semantica:scan
format: spdx-json
output-file: semantica-sbom.spdx.json
+23 -5
View File
@@ -28,12 +28,14 @@ on:
permissions:
contents: read
security-events: write
jobs:
MSDO:
# currently only windows-latest is supported
runs-on: windows-latest
permissions:
contents: read
security-events: write # for github/codeql-action/upload-sarif below
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
@@ -66,7 +68,7 @@ jobs:
python-version: "3.12"
- name: Install Checkov
run: python -m pip install checkov==3.3.1
run: pip install -r .github/requirements/checkov.txt --require-hashes
- name: Run Checkov
shell: pwsh
@@ -74,12 +76,28 @@ jobs:
PYTHONUTF8: "1"
run: |
New-Item -ItemType Directory -Force reports | Out-Null
checkov --directory . --framework kubernetes helm dockerfile github_actions secrets bicep arm --soft-fail --output sarif --output-file-path reports/checkov.sarif
if (-not (Test-Path reports/checkov.sarif)) {
checkov --directory . --framework kubernetes helm dockerfile github_actions secrets bicep arm --soft-fail --output sarif --output json --output-file-path reports
if (-not (Test-Path reports/results_sarif.sarif)) {
$sarif = Get-ChildItem -Path reports -Recurse -Filter *.sarif | Select-Object -First 1
if ($null -eq $sarif) { throw "Checkov did not produce a SARIF file" }
Copy-Item $sarif.FullName reports/checkov.sarif
Copy-Item $sarif.FullName reports/results_sarif.sarif
}
if (-not (Test-Path reports/results_json.json)) {
$json = Get-ChildItem -Path reports -Recurse -Filter *.json | Select-Object -First 1
if ($null -eq $json) { throw "Checkov did not produce a JSON file" }
Copy-Item $json.FullName reports/results_json.json
}
# checkov's SARIF exporter includes checks it internally marked SKIPPED
# (via the inline `# checkov:skip=` comments / `checkov.io/skipN`
# annotations already on the Helm chart) as ordinary un-suppressed
# results - it never uses SARIF's own `suppressions` field, so GitHub
# opens a fresh alert for the same already-suppressed finding on every
# single run (see #6035/#6036, #6112-6115, #6128-6131). checkov's JSON
# output does correctly record the skip, so cross-reference it here
# instead of re-dismissing the same alerts by hand forever.
- name: Filter checkov's own suppressed checks out of the SARIF
run: python .github/scripts/filter_checkov_skipped.py reports/results_json.json reports/results_sarif.sarif reports/checkov.sarif
- name: Upload Checkov results to Security tab
uses: github/codeql-action/upload-sarif@cdf488f595d80d6e07e03d4674febd5ab45fa938 # v4
+1 -1
View File
@@ -65,4 +65,4 @@ jobs:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@cd2ce8fcbc39b97be8ca5fce6e763baed58fa128 # v5
uses: actions/deploy-pages@368f82528645a54fb793d4d04e342629a3f51346 # v5
+59
View File
@@ -0,0 +1,59 @@
name: Install Matrix
permissions:
contents: read
on:
schedule:
- cron: '0 6 * * 1' # weekly, catches upstream dependency breakage between releases
workflow_run:
# The Release workflow publishes the GitHub release *before* it uploads to
# PyPI (see release.yml), so triggering on `release: published` would race
# the PyPI upload and could pass by silently installing the prior version.
# workflow_run fires only after the whole Release workflow - including the
# PyPI publish step - has finished.
workflows: ['Release']
types: [completed]
workflow_dispatch:
jobs:
verify-install:
if: github.event_name != 'workflow_run' || github.event.workflow_run.conclusion == 'success'
name: pip install semantica (${{ matrix.os }}, py${{ matrix.python-version }})
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
python-version: ['3.9', '3.10', '3.11', '3.12']
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
- name: Pin expected version for release-triggered runs
id: expected-version
if: github.event_name == 'workflow_run'
shell: bash
env:
EXPECTED_TAG: ${{ github.event.workflow_run.head_branch }}
run: |
expected="${EXPECTED_TAG#v}"
if [ -z "$expected" ]; then
echo "::error::Could not determine a release tag from the triggering workflow run (head_branch was empty)."
exit 1
fi
echo "constraint===$expected" >> "$GITHUB_OUTPUT"
- id: setup-semantica
uses: ./.github/actions/setup-semantica
with:
python-version: ${{ matrix.python-version }}
cache: 'pip'
version: ${{ steps.expected-version.outputs.constraint }}
- name: Smoke test import
shell: bash
run: |
python -c "
import semantica
print('semantica', semantica.__version__, 'installed and importable')
"
+26 -8
View File
@@ -16,7 +16,7 @@ jobs:
cancel-in-progress: false
permissions:
contents: write # for the GitHub Release
id-token: write # for PyPI Trusted Publishing (OIDC) and attestation signing
id-token: write # for PyPI Trusted Publishing (OIDC), attestation signing, and Sigstore
attestations: write # for SLSA build provenance
# If you add another job to this workflow, give it its own explicit
# `permissions:` block rather than relying on the workflow-level default
@@ -39,11 +39,11 @@ jobs:
# Install the pinned dependency set (with hashes) so the sdist/wheel
# build runs against the same versions CI tests against.
- name: Install pinned build dependencies
run: pip install -r requirements-ci.txt
- run: pip install build
# wheel is build-time only (not in requirements-ci.txt) — install the
# same pinned version [build-system] declares so --no-isolation works.
- run: pip install wheel==0.48.0
run: pip install -r requirements-ci.txt --require-hashes
# build is a dev-time dependency; wheel is build-time only (neither is
# in requirements-ci.txt) — install the same pinned versions
# [build-system] declares so --no-isolation works below.
- run: pip install -r .github/requirements/build-tools.txt --require-hashes
- name: Build package (no isolation — pinned deps)
run: python -m build --no-isolation
- name: Verify Explorer frontend is packaged
@@ -63,11 +63,29 @@ jobs:
print("Explorer frontend is packaged")
PY
- name: Verify PyPI long-description will render
run: |
pip install -r .github/requirements/twine.txt --require-hashes
twine check dist/*
- name: Attest build provenance
uses: actions/attest-build-provenance@4d101475d8b20a2381f78447822ac1eab6504dd8 # v4
with:
subject-path: 'dist/*'
- uses: softprops/action-gh-release@3d0d9888cb7fd7b750713d6e236d1fcb99157228 # v3
# attest-build-provenance publishes to the GH attestations API only, which
# OpenSSF Scorecard's Signed-Releases check does not inspect - it looks for
# signature files attached as release assets. Sign here too so
# `dist/*.sigstore.json` bundles ship alongside the wheel/sdist on the
# GitHub Release itself.
- name: Sign artifacts with Sigstore
uses: sigstore/gh-action-sigstore-python@790bc6befb9d733738f18d8f895854b453640ec9 # v3.5.0
with:
files: dist/*
inputs: |
dist/*.whl
dist/*.tar.gz
- uses: softprops/action-gh-release@efb35369e0ad2afab669f228072c1b0d510eae64 # v3.0.3
with:
files: |
dist/*.whl
dist/*.tar.gz
dist/*.sigstore.json
- uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # release/v1
+45
View File
@@ -0,0 +1,45 @@
name: Scorecard supply-chain security
permissions: read-all
on:
branch_protection_rule:
schedule:
- cron: '30 1 * * 6' # weekly
push:
branches: [main]
jobs:
analysis:
name: Scorecard analysis
runs-on: ubuntu-latest
permissions:
security-events: write # to upload SARIF results
id-token: write # to publish results and get a badge
contents: read
actions: read # to detect GitHub Actions workflows
steps:
- name: Checkout code
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
with:
persist-credentials: false
- name: Run analysis
uses: ossf/scorecard-action@2d1146689b8cda280b9bc96326124645441f03bc # v2.4.4
with:
results_file: results.sarif
results_format: sarif
publish_results: true
- name: Upload artifact
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: SARIF file
path: results.sarif
retention-days: 5
- name: Upload to code-scanning
uses: github/codeql-action/upload-sarif@cdf488f595d80d6e07e03d4674febd5ab45fa938 # v4
with:
sarif_file: results.sarif
+221 -46
View File
@@ -3,6 +3,7 @@ name: Security Scan
on:
schedule:
- cron: '30 1 * * 1,4' # Mon/Thu 7 AM IST
workflow_dispatch:
push:
branches: [main]
paths-ignore:
@@ -12,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
@@ -44,46 +93,101 @@ jobs:
- name: Install dependencies
run: |
python -m pip install --upgrade pip
# Install the pinned dependency set FIRST so Safety scans Semantica's
# exact CI/release dependency tree (requirements-ci.txt is generated
# from pyproject.toml extras, so this covers the project's real deps).
pip install -r requirements-ci.txt
# Tooling AFTER the pinned set: installing safety/bandit/semgrep/jq
# first lets the pinned requirements overwrite their transitive deps
# (e.g. rich), which breaks the safety CLI at runtime.
pip install safety bandit semgrep jq
pip install -r .github/requirements/bootstrap.txt --require-hashes
# Install the pinned dependency set FIRST so pip-audit scans
# Semantica's exact CI/release dependency tree (requirements-ci.txt
# is generated from pyproject.toml extras, so this covers the
# project's real deps).
pip install -r requirements-ci.txt --require-hashes
# Tooling AFTER the pinned set: installing it first would let the
# pinned requirements overwrite the tooling's own transitive deps.
pip install -r .github/requirements/pip-audit.txt --require-hashes
pip install -r .github/requirements/security-scan-tools.txt --require-hashes
- name: Run Safety Check (Package Vulnerabilities)
- name: Run pip-audit (Package Vulnerabilities)
continue-on-error: true
run: |
# NOTE: Safety 3.x repurposed --output to select a console format
# (json/text/screen/...), not a file path. Writing JSON to a file
# now requires --save-json; the previous `--output safety-report.json`
# usage was silently invalid and never produced a report.
safety check --save-json safety-report.json || true
# Keep publishing reports and the PR comment even when the audit
# gate fails. The final gate below preserves the failure status.
echo 'AUDIT_SCAN_STATUS=failed' >> "$GITHUB_ENV"
# Guard 1: fail loudly if Safety exited before writing a report at all
# (network error, API auth failure, tool crash). Without this check a
# missing or empty file causes jq to fall back to "0", making a broken
# Same dependency tree Safety used to scan, and the same tool and
# invocation already proven reliable in security.yml.
pip-audit -r requirements-ci.txt --format=json --output=pip-audit-report.json || true
# Guard 1: fail loudly if pip-audit exited before writing a report
# at all (network error, tool crash). Without this check a missing
# or empty file causes jq to fall back to "0", making a broken
# scanner indistinguishable from a clean scan.
if [ ! -s safety-report.json ]; then
echo "::error::Safety scan produced no report (safety-report.json is missing or empty). Treating as failure — check for network errors, API auth failures, or Safety crashes in the logs above."
if [ ! -s pip-audit-report.json ]; then
echo "::error::pip-audit produced no report (pip-audit-report.json is missing or empty). Treating as failure — check for network errors or pip-audit crashes in the logs above."
exit 1
fi
# Guard 2: fail closed when the report doesn't have the shape the
# checks below assume: a non-empty dependencies array, each entry
# either carrying an array-valued vulns field or being a dependency
# pip-audit couldn't resolve/audit, which it reports as
# {"name": ..., "skip_reason": ...} with no vulns field at all
# (see pip_audit._format.json.JsonFormat._format_dep). That's a
# normal, documented report shape, not a malformed one — treating
# it as invalid would fail the whole job over a single unauditable
# package, the same kind of scan-unrelated CI break this migration
# away from Safety was meant to fix.
if ! jq -e '
(.dependencies | type == "array" and length > 0)
and all(.dependencies[]; type == "object" and ((.vulns | type == "array") or (.skip_reason | type == "string")))
' pip-audit-report.json >/dev/null 2>&1; then
echo "::error::pip-audit report has an invalid dependency structure. Expected a non-empty dependencies array where every entry has either a vulns array or a skip_reason. Treating as failure."
exit 1
fi
echo "Checking for package vulnerabilities..."
# No || echo "0" fallback: if jq fails (malformed JSON, missing key,
# vulnerabilities:null) VULNS will be empty or "null" so guard 2 below
# catches it rather than silently treating the broken report as zero.
VULNS=$(jq '.vulnerabilities | length' safety-report.json 2>/dev/null)
# Guard 2 above already confirmed pip-audit-report.json is valid
# JSON with a well-shaped dependencies array, so this count is
# always a plain non-negative integer.
SKIPPED=$(jq '[.dependencies[] | select(has("skip_reason"))] | length' pip-audit-report.json)
if [ "$SKIPPED" -gt 0 ]; then
echo "⚠️ pip-audit could not audit $SKIPPED dependencies (see pip-audit-report.json for skip_reason):"
jq -r '.dependencies[] | select(has("skip_reason")) | " - \(.name): \(.skip_reason)"' pip-audit-report.json
fi
# Guard 2: ensure VULNS is a non-negative integer before the -gt
# Vulnerability IDs reviewed and accepted as non-actionable for this
# project. Empty for now: pip-audit's OSV-backed database doesn't
# currently carry either of the findings Safety used to flag here
# (cuda-toolkit CVE-2025-33228, torchvision CVE-2026-65918), so
# there's nothing to exclude. Left in place so a future finding can
# be added the same way without restructuring this step - see git
# history on this file for the reasoning behind past entries.
IGNORED_VULN_IDS=""
# Exported so the "Comment PR with Security Results" step below can
# apply the same exclusion list to the raw report - it reads
# pip-audit-report.json independently in JS, so without this the PR
# comment would show an accepted finding as live even though this
# gate correctly treats it as non-actionable.
echo "IGNORED_VULN_IDS=$IGNORED_VULN_IDS" >> "$GITHUB_ENV"
# No []? / || echo "0" fallback: if jq fails (malformed JSON) VULNS
# will be empty or "null" so Guard 3 below catches it rather than
# silently treating the broken report as zero.
# `.vulns // []` guards against skipped dependencies, which carry
# no vulns field at all (see the skip_reason handling above) -
# without the fallback, iterating `null[]` raises inside jq and
# this whole computation silently evaluates to empty.
VULNS=$(jq --arg ignored "$IGNORED_VULN_IDS" '
($ignored | split(",") | map(select(length > 0))) as $ignore_list
| [.dependencies[] | (.vulns // [])[] | select(.id as $id | ($ignore_list | index($id)) | not)]
| length
' pip-audit-report.json 2>/dev/null)
# Guard 3: ensure VULNS is a non-negative integer before the -gt
# comparison. "null" (missing/null key) or "" (jq parse failure) would
# cause bash's -gt to throw an arithmetic error and fall through to the
# success branch — the same silent-pass bug as a missing file.
if ! [[ "$VULNS" =~ ^[0-9]+$ ]]; then
echo "::error::Safety report exists but 'vulnerabilities' is missing or non-numeric (got: '${VULNS}'). The report may be malformed or Safety may have written an error-only JSON. Treating as failure."
echo "::error::pip-audit report exists but dependency vulnerabilities are missing or non-numeric (got: '${VULNS}'). The report may be malformed or contain an error-only JSON response. Treating as failure."
exit 1
fi
@@ -92,12 +196,18 @@ jobs:
echo "CI will fail to prevent merging of vulnerable dependencies"
echo ""
echo "Vulnerability details:"
jq -r '.vulnerabilities[] | "- \(.package_name)==\(.analyzed_version): \(.vulnerability_id) (\(.CVE // "no CVE assigned"))"' safety-report.json || true
jq --arg ignored "$IGNORED_VULN_IDS" -r '
($ignored | split(",") | map(select(length > 0))) as $ignore_list
| .dependencies[] as $dependency
| ($dependency.vulns // [])[] | select(.id as $id | ($ignore_list | index($id)) | not)
| "- \($dependency.name)==\($dependency.version): \(.id)"
' pip-audit-report.json || true
exit 1
else
echo "✅ No security vulnerabilities found"
echo "✅ No actionable security vulnerabilities found${IGNORED_VULN_IDS:+ (ignored: $IGNORED_VULN_IDS)}"
echo 'AUDIT_SCAN_STATUS=passed' >> "$GITHUB_ENV"
fi
- name: Run Bandit (Code Security Linter)
run: |
bandit -r semantica/ -f json -o bandit-report.json || true
@@ -135,17 +245,18 @@ jobs:
fi
- name: Upload Security Reports
if: always()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: security-reports
retention-days: 14
path: |
safety-report.json
pip-audit-report.json
bandit-report.json
semgrep-report.json
- name: Comment PR with Security Results
if: github.event_name == 'pull_request'
if: always() && github.event_name == 'pull_request'
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9
with:
script: |
@@ -168,6 +279,12 @@ jobs:
}
const items = parse(data);
if (items === null) {
return [
'### ' + title,
'⚠️ Invalid report structure in ' + reportPath + ' — check the job logs.',
].join('\n');
}
if (items.length === 0) {
return [`### ${title}`, `✅ No findings.`].join('\n');
}
@@ -184,14 +301,64 @@ jobs:
return lines.join('\n');
}
const safetySection = renderSection(
'Safety — dependency vulnerabilities',
'safety-report.json',
(data) => (data.vulnerabilities || []).map(
(v) => `- \`${v.package_name}==${v.analyzed_version}\`: ${v.vulnerability_id}` +
(v.CVE ? ` (${v.CVE})` : '') + ` — ${v.advisory || 'no advisory text'}`
)
);
// Mirrors the shell step's own IGNORED_VULN_IDS (passed through
// $GITHUB_ENV) so an accepted, non-actionable CVE that the CI
// gate already excluded doesn't reappear here as a live finding -
// this reads the same raw, unfiltered pip-audit-report.json.
const ignoredVulnIds = (process.env.IGNORED_VULN_IDS || '')
.split(',')
.map((id) => id.trim())
.filter(Boolean);
// A dependency pip-audit couldn't resolve/audit is reported as
// {"name": ..., "skip_reason": ...} with no vulns field at all
// (see pip_audit._format.json.JsonFormat._format_dep) - that's a
// normal report shape, not a malformed one, so it must not be
// treated as an invalid dependency below.
const isSkipped = (dependency) => typeof dependency.skip_reason === 'string';
let skippedDeps = [];
try {
const auditData = JSON.parse(fs.readFileSync('pip-audit-report.json', 'utf8'));
skippedDeps = (auditData.dependencies || []).filter(
(dependency) => dependency && typeof dependency === 'object' && isSkipped(dependency)
);
} catch (e) {
// Unreadable/unparseable report - renderSection's own
// report-missing branch below surfaces this.
}
const pipAuditSection = renderSection(
'pip-audit — dependency vulnerabilities',
'pip-audit-report.json',
(data) => {
if (
!Array.isArray(data.dependencies) ||
data.dependencies.length === 0 ||
data.dependencies.some(
(dependency) =>
!dependency ||
typeof dependency !== 'object' ||
(!Array.isArray(dependency.vulns) && !isSkipped(dependency))
)
) {
return null;
}
return data.dependencies.flatMap((dependency) =>
(dependency.vulns || [])
.filter((vulnerability) => !ignoredVulnIds.includes(vulnerability.id))
.map(
(vulnerability) => `- \`${dependency.name}==${dependency.version}\`: ${vulnerability.id}` +
(vulnerability.fix_versions?.length ? ` (fixed by ${vulnerability.fix_versions.join(', ')})` : '')
)
);
}
) + (ignoredVulnIds.length
? `\n\n_Excluded as accepted, non-actionable findings: ${ignoredVulnIds.join(', ')} — see the workflow file's inline comments for why._`
: '') + (skippedDeps.length
? `\n\n_Could not be audited: ${skippedDeps.map((d) => `\`${d.name}\` (${d.skip_reason})`).join(', ')}_`
: '');
const banditSection = renderSection(
'Bandit — HIGH-severity code issues',
@@ -212,7 +379,7 @@ jobs:
const comment = [
'# 🔒 Security Scan Results',
'',
safetySection,
pipAuditSection,
'',
banditSection,
'',
@@ -222,7 +389,7 @@ jobs:
'',
'*This security scan runs automatically on source-code PRs and bi-weekly (skipped for doc/markdown-only changes).*',
'',
'📊 **Security Policy**: CI fails on Safety vulnerabilities and Bandit HIGH-severity findings. Semgrep findings above are informational and do not block merge.',
'📊 **Security Policy**: CI fails on pip-audit vulnerabilities and Bandit HIGH-severity findings. Semgrep findings above are informational and do not block merge.',
].join('\n');
try {
@@ -237,3 +404,11 @@ jobs:
console.log('⚠️ Could not post security comment:', error.message);
console.log('📋 Security scan results saved to artifacts');
}
- name: Enforce Audit Gate
if: always()
run: |
if [ "${AUDIT_SCAN_STATUS:-failed}" != "passed" ]; then
echo "::error::pip-audit scan failed. See the pip-audit output and uploaded reports above."
exit 1
fi
-42
View File
@@ -1,42 +0,0 @@
name: Security
on:
schedule:
- cron: '0 0 * * 1'
workflow_dispatch:
pull_request:
branches: [main]
paths:
- 'pyproject.toml'
- 'requirements-ci.txt'
- '.github/workflows/security.yml'
permissions:
contents: read
jobs:
audit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7
with:
python-version: '3.11'
# Upgrade first: actions/setup-python's baked-in setuptools has been
# behind known-vulnerable floors before (e.g. PYSEC-2026-3447 /
# setuptools 75.1.0), so don't trust the preinstalled one.
- run: python -m pip install --upgrade pip setuptools
# Audit the pinned dependency set (requirements-ci.txt is compiled from
# pyproject.toml with --extra all — the same coverage as the [all]
# extra, minus the Linux-only gpu set — so this keeps scan parity with
# CI/release builds without a time-dependent resolution). This is the
# fix for PYSEC-2024-38 (#869): the bare-env job never had fastapi or
# python-multipart installed to look at.
- run: pip install -r requirements-ci.txt
# PR runs gate on findings, since they're scoped to actual
# pyproject.toml changes under review. The schedule/workflow_dispatch
# runs stay non-blocking until a full pass over pre-existing findings
# across the whole [all] tree has been done.
- run: pip install pip-audit
- run: pip-audit -r requirements-ci.txt
continue-on-error: ${{ github.event_name != 'pull_request' }}
BIN
View File
Binary file not shown.
+165
View File
@@ -9,6 +9,149 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
## [0.6.8] - 2026-09-05
### 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
- `erase_entity(entity_id, reason=..., at=..., vector_ids=...)` returns an `ErasureReceipt`; `erase_entities([...])` returns one receipt per entity, in order, so one entity's failure does not stop the rest
- **Honest partial reporting is the point.** Each store reports one of five statuses — `erased`, `not_found`, `not_configured` (store never bound; normal), `unsupported` (store cannot delete at all; retrying will not help), `failed` — and `receipt.complete` is `False` when any store reports `unsupported`/`failed`, with `receipt.incomplete_stores` naming them. A receipt reading `graph: erased, memory: 14 erased, vectors: unsupported on faiss` is actionable; a bare `True` is a compliance liability
- **Erasure runs outward-in: vectors → memory → graph.** The graph tombstone is the durable attestation that an erasure happened, so writing it first would let a crash mid-cascade leave a record claiming more than occurred. Erasing the graph last means a partial failure leaves the node present and the receipt incomplete — recoverable and honest; the reverse is neither
- **Partial failure is a result, not an exception**: a store that raises is recorded as `failed` (with the exception type) and the remaining legs still run, rather than aborting into a half-erased state with no record of which half
- **The memory sweep cannot be silently truncated.** `find_by_entity(entity_id, limit=10)` returned `results[:limit]`, so the obvious hand-rolled cascade erases the first ten items and reports success — an erasure check computed from a page already truncated by the very `limit` it was called with. The coordinator sweeps in pages until dry (deleting as it goes, so the next page is the remainder) rather than passing one large number that is only correct until someone exceeds it, then **re-queries once after the sweep** and reports `failed` with the residual count if anything survived. It also stops rather than spinning if `batch_delete` reports no progress on a non-empty page. Note `find_by_entity` returns items keyed `memory_id`, not `id`
- **`unsupported` vector backends are detected by probing, not by calling and catching.** `faiss_store.py`, `milvus_store.py` and `weaviate_store.py` expose no delete at all (FAISS cannot remove from a flat index without a rebuild), while the `VectorStore` facade declares `delete_vectors()` for *every* backend and only raises `NotImplementedError` once called — so probing the facade alone cannot tell a deletable backend from a delete-less one, and the coordinator looks at the backend it wraps. Probing also keeps a missing method distinguishable from an `AttributeError` raised *inside* a working one, which is exactly where guessing wrong produces a false clean bill of health. `NotImplementedError` at call time is still caught and reported as `unsupported`; a store returning `False` is reported as `failed`
- Backends are reached under either supported name — `delete_vectors(ids)` (pinecone/qdrant) or `delete(ids)` (pgvector/sqlite-vec) — and the receipt records which was used
- `vector_store` defaults to `memory.vector_store` when a memory is supplied, stays overridable for deployments binding a store the memory does not own, and accepts `False` to disable the vector leg. Vectors owned by memory items are removed by the memory leg's own `delete_memory()` cascade; the explicit vector leg covers entity-keyed embeddings written by something other than `AgentMemory`
- The receipt's `erased_at` is normalized through `ContextGraph`'s own temporal normalizer, so the receipt and the tombstone written by the same erasure cannot disagree about when it happened; an unparseable `at` is rejected before any store is touched rather than half way through the cascade
- `purge_node()`'s docstring now points at the coordinator, so callers reading the graph-scope caveat find the thing that completes the workflow
- New `tests/context/test_erasure_coordinator.py`: 48 tests against **real** `ContextGraph`/`AgentMemory` instances rather than mocks — the bug lives in the interaction between them, so mocking it away would test nothing. Covers the 25-items-on-one-entity regression that fails against a naive single `find_by_entity()` call, all three vector-backend shapes (`delete_vectors`/`delete`/neither) plus the facade-over-delete-less-backend shape, residual/no-progress/no-identifier memory failures, partial failure continuing the cascade, idempotency, receipt serialization, and `at` normalization
- Full `tests/context/` suite: 738 passed
- **Fixed during review** (Qodo): `erase_entity()` resolved `erased_at` up front but passed the caller's original `at` down to `purge_node()`, so on the default `at=None` path the coordinator and the graph each took their own `now()` and the receipt attested to a different instant than the tombstone it points at — breaking the one invariant this module states most loudly. The resolved timestamp is now passed to the graph. The existing test passed only because it supplied an explicit `at`, which hides the drift; a regression test now covers the `at=None` path that callers actually use
- **Fixed during review** (Qodo): the vectors leg treated any return value other than the literal `False` as success, but no in-repo backend returns a bool — Qdrant returns `{"status": <UpdateStatus>}` and Pinecone `{"deleted": True}`, so every dict was read as a success and the backend's own account of the delete was discarded. Delete results are now interpreted by shape (bool, dict with explicit failure markers, `None` for a void method, anything else at face value) and the backend payload is recorded in the receipt as `backend_result`, stringified so the receipt stays JSON-serializable as the audit record it is meant to be. Bool markers are matched by identity so a `0` count is not read as `False`, and string markers match as substrings so an enum rendering as `"UpdateStatus.FAILED"` is not read as a success
- **Fixed during review** (Qodo): the constructor's "at least one store" guard used `not vector_store`, rejecting a valid store whose `__bool__`/`__len__` makes an empty instance falsey, and reporting `vector_store=None` in the error when an object had been passed; it now distinguishes `None` (absent) from `False` (deliberately disabled) from any other value (provided), and echoes what it actually received
- **Fixed during review** (Qodo): `at` annotations accepted only `str`/`datetime` while the shared `ContextGraph` normalizer they delegate to also takes epoch seconds; widened to `int`/`float` with the docstrings updated, so the coordinator no longer advertises less than the graph API it wraps
- **Known limitation, unchanged by this PR**: erasure still cannot be *completed* on FAISS/Milvus/Weaviate — `delete_vectors()` is declared on the `VectorStore` facade (`vector_store.py:786`) but not implemented across the backend set, under at least three different names. That is worth its own issue; the coordinator ships reporting `unsupported` and starts reporting `erased` for those backends once it is fixed, with no API change here
- **Ontology package gains a deterministic, CI-friendly quality gate for ontologies and knowledge graphs** (#1397, closes #1393) by @T1mn — machine-readable quality findings with severities, metrics, statistics, and configurable thresholds; deterministic checks cover ontology structure, class/property coverage, domain/range references, and KG relationship endpoints. Reuses the existing `OntologyValidator`, `OntologyEvaluator`, and `GraphValidator` with no new runtime dependencies. Exposed through `semantica.ontology` and `OntologyEngine`. New `semantica/ontology/quality_gate.py`; new `tests/ontology/test_ontology_quality_gate.py`, and the full targeted ontology/graph-validator suite the author ran alongside it: 63 passed, 4 skipped. This first version reports findings only — no auto-fix, dashboard, or benchmark integration yet.
- **`VectorStore` gains `scan_vectors()`/`iter_vectors()` enumeration, and `store migrate` becomes functional** (#1264, part of #1265) by @ZohaibHassan16 — previously vector stores exposed only `get_vector(id)`/`count()`, so there was no way to loop over all vectors, and `semantica store migrate` always told users to export/reindex manually. Adds `scan_vectors(offset, limit)` to `FAISSStore`, `SQLiteVecStore`, and `PgVectorStore` — backends that can support normal positional pagination; in-memory is handled directly by the facade, other backends delegate when they support it, and unsupported backends raise `NotImplementedError` rather than silently returning nothing. `store migrate` now actually migrates between faiss/sqlite/pgvector, copying vectors and metadata in batches and stamping `--namespace` onto metadata that doesn't already have one. Pinecone/Qdrant/Milvus/Weaviate are deferred to follow-up PRs since each backend paginates differently. 22 new tests covering backend scanning, facade behavior, and the migrate CLI.
- **`VectorStore.iter_vectors()` dispatches to a new `iter_all()` cursor primitive, with Qdrant as the first cursor-based backend** (#1316, part of #1265) by @ZohaibHassan16 — none of Qdrant/Pinecone/Milvus/Weaviate's native pagination APIs can properly implement positional `scan_vectors(offset, limit)` (Qdrant's cursor is a point ID, Pinecone's is an opaque continuation token, Milvus's `offset` is capped at a 16,384-result window, Weaviate's cursor is the previous object's UUID), so rather than faking positional offsets, backends can now implement `iter_all(batch_size)` as a generator over their native paging API; the facade uses it via `callable()` when present (consistent with existing `count()` dispatch) and falls back to the `scan_vectors()` loop otherwise. `QdrantStore.iter_all()` threads `scroll()`'s `next_page_offset` between requests — correctly yielding a final non-empty page even when the cursor is already exhausted — and raises on an uninitialized store rather than returning empty, so `store migrate` can't report success after copying zero vectors (the failure mode from #1083). Also fixes `store migrate` inferring vector dimension from a nonexistent `._backend_store.dimension` attribute on Qdrant/Milvus/Weaviate (silently falling back to a wrong default of 768) by reading dimension off the first scanned record and chaining it back into the iterator. Qdrant is added to `store migrate`'s supported backends. 6 facade dispatch tests, new `tests/vector_store/test_qdrant_store.py` (10 tests), and 5 CLI dimension-inference tests.
- **`WeaviateStore.iter_all()` adds cursor-based full-collection iteration for Weaviate** (#1317, part of #1265, stacked on #1316) by @ZohaibHassan16 — Weaviate's `fetch_objects(after=<uuid>)` pagination has no way to map a numeric offset to a cursor, so this reuses/extracts the cursor-loop and version-fallback logic already in `filter_by_metadata`. Unlike that method's `seen_ids` set (unbounded memory over a full scan), `iter_all()` detects a stalled scan by checking whether the next cursor advanced, keeping memory use O(1). An empty page under cursor pagination is not treated as end-of-scan on its own — `after` has no server-issued continuation value of its own, so a batch could in principle land entirely on a gap (tombstoned objects) with live data past it, the same risk previously confirmed for Qdrant's scroll cursor — so the iterator falls back to an offset-based check once before ending the scan. Also fixes `_extract_vector()` silently producing a corrupted 0-d array against a real (non-mocked) Weaviate collection by unwrapping weaviate-client v4's `{'default': [...]}` vector shape. New `tests/vector_store/test_weaviate_store.py` covering cursor threading, short-page termination, the empty-page/gap fallback, stalled-cursor termination, and the offset fallback when a client rejects `after`. Wiring Weaviate into `store migrate` itself is deferred to #1335 — the facade's write dispatch (`store_vectors()` only recognizes `add`/`add_vectors`, not Weaviate's `add_objects`) and initialization (the facade never calls `connect()`/collection-selection) aren't ready for a backend shaped like this one.
- **`MilvusStore.iter_all()` adds Milvus to the `iter_vectors()` cursor family via Milvus's query iterator** (#1326, part of #1265, stacked on #1316) by @ZohaibHassan16 — Milvus's `query(offset=...)` caps `offset + limit` at a documented 16,384-result window, so an offset-based scan would silently truncate any collection larger than that; `query_iterator()` is the primitive actually meant for scans beyond it. The iterator is closed in a `finally` block since it holds server-side state, covered by tests for both normal exhaustion and early/exception-path abandonment. Matches the missing-iterator-raises-rather-than-returns-empty behavior established for Qdrant (#1316) and Weaviate (#1317), so an unsupported `pymilvus` version can't make `store migrate` look like it copied an empty collection successfully; `store migrate` wiring for Milvus is left for a separate PR. New `tests/vector_store/test_milvus_store.py`: 13 tests (Milvus had no dedicated test file before).
- **`WeaviateStore` gains `delete_vectors()`, completing Weaviate support for `ErasureCoordinator`** (#1392) by @pkupt — Weaviate half of #1374 (Milvus landed in #1391; FAISS stays unsupported since flat indices can't delete in place). IDs are the object UUIDs `store_vectors()` returns, deleted one at a time via `collection.data.delete_by_id`, which returns `False` rather than raising for a missing UUID, so the erasure receipt's `backend_result` count stays honest. 10 tests cover single/multi-id deletes, not-found-uuid counting, empty ids, and the missing-collection path, plus two integration tests binding `WeaviateStore` as a backend; author notes this is logic-level coverage since Weaviate wasn't available locally to verify live wire behavior.
- **`semantica.llms` gains a first-class `Anthropic` provider wrapper** (#1255, closes #1253) by @ZohaibHassan16 — matches the existing `Groq`/`OpenAI` wrapper pattern (`generate`, `generate_structured`, `generate_typed`, `is_available`) over the `AnthropicProvider` already used internally by semantic extraction; previously reachable only through the generic LiteLLM passthrough. New docs section in `docs/guides/llm-integrations.md`; 6 new tests in `tests/test_llm_anthropic.py`.
- **`semantica.llms` gains `Gemini`, `Ollama`, `DeepSeek`, and `Novita` provider wrappers** (#1262, closes #1261) by @ZohaibHassan16 — these four providers already existed in `semantic_extract/providers.py` but weren't exposed from the public `semantica.llms` API. Each follows the same `generate`/`generate_structured`/`generate_typed`/`is_available` pattern as `Groq`/`OpenAI`/`Anthropic`. Adds the missing `llm-novita` extra to `pyproject.toml` (uses the `openai` dependency, like DeepSeek), included in `llm-all`; docs added for Gemini/Ollama/DeepSeek, and the existing Novita docs updated to use the new wrapper instead of calling `create_provider()` directly. 32 new tests (8 per provider), following the `test_llm_anthropic.py` pattern.
- **Explorer's read-only Markdown viewer becomes a full editor for live `ContextGraph` nodes and host-supplied `AgentMemory` items** (#1349, closes #1327) by @genni613
- New canonical single-resource Markdown export/apply methods on `ContextGraph` and `AgentMemory`; resource IDs are validated against frontmatter before mutation, stale writes are rejected via `expected_revision` with HTTP 409, and writes validate fully before commit so failures can't leave a partial mutation. Edits apply to the live in-memory runtime object only — this PR does not introduce disk or restart persistence.
- New Explorer endpoints: `GET`/`PUT /api/markdown/{kind}/{resource_id:path}` and paginated `GET /api/memories`, returning structured 404/409/422/500 responses behind existing Explorer auth; `/api/info` now exposes `capabilities.agent_memory` so the UI can detect whether a host app supplied a memory store.
- Explorer UI gains Edit/Apply/Cancel alongside the existing Preview/Source/Copy; edits validate against the full canonical document (including supported frontmatter), no-op Applies are disabled, drafts persist across validation/conflict/network/server errors, navigation is guarded when a draft has unapplied changes, and Apply refreshes canonical source, revision, graph content, and labels. A new Memories workspace appears only when the host app supplies `create_app(agent_memory=...)`.
- Test coverage: domain round-trip/identity/validation/rollback tests, API success/conflict/authorization/failure-path tests, editor interaction tests (Apply/Cancel/dirty-navigation/retry), and capability/Memories-workspace tests. Author-reported: targeted Python acceptance suite 133 passed, 2 skipped; `npm run test:graph-workspace` 106 passed; `test:graph-store`, `test:deterministic-e2e`, and `test:plugin-registry` (7 passed) all green; `npm run build` passed. Full Python test collection was blocked locally by unrelated NumPy/h5py/spaCy binary incompatibilities.
- **New deterministic Explorer rendering example and end-to-end test covering build -> persist -> API -> frontend hydration -> canvas rendering** (#1041, closes #1037) by @alexsmolya — new `examples/explorer_deterministic_rendering_example.py` builds a canonical 4-node/3-edge graph (`Alice --WORKS_AT--> Acme`, `Bob --KNOWS--> Alice`, `Acme --LOCATED_IN--> New York`), persists it with `ContextGraph.save_to_file()`, and reloads with `GraphSession.from_file()`, printing setup/auth/launch guidance. New backend test `tests/explorer/test_explorer_deterministic_rendering_e2e.py` covers graph construction/serialization, `GraphSession`, and exact `/api/graph/*` node/edge/label responses across auth modes. New frontend tests (`deterministicExplorerRendering.test.ts`, `.e2e.ts`) mount the real Explorer app in Chromium, hydrate the real graph store through `useLoadGraph`, render the real Sigma canvas, and assert `WORKS_AT`/`KNOWS`/`LOCATED_IN` are actually drawn and stay labeled after zoom; redundant extra `label` plumbing is removed now that edge labels render from the already-hydrated `edgeType`. Author-reported: backend e2e 5 passed; frontend deterministic suites 49 graph-workspace + 1 graph-store + 7 plugin + 1 Chromium canvas E2E test passed; broader `tests/explorer` run 261 passed, 2 skipped, 2 pre-existing unrelated SHACL failures.
- **`integrations/google_adk`: first-class Google ADK support** (#1312, resubmit) by @Hitesh-XS — new `integrations/google_adk/` package (`kg_tools.py`, `decision_tools.py`, `session_service.py`) exposing Semantica's context-graph and decision-intelligence APIs as Google ADK tools and a session service, with its own README. Bundles `google-adk` into the Agentic Framework Integrations section of `pyproject.toml` and into the `all` extra. Also restores packaging state that had regressed on `main` (pinned `anthropic`/`pyarrow` bounds, `ingest-sap`, `langchain`, and package-data fixes) and replaces the deprecated `pinecone-client` dependency with the official `pinecone` package, which had been crashing context-graph initialization — and with it every integration test touching Pinecone. `mcp/` is renamed to `semantica_mcp/mcp/`, with import paths updated across MCP tests and tools. Author reports all 36 tests in `tests/integrations/google_adk/` passing against the corrected Pinecone dependency.
### Changed
- **`docs/guides/decision-intelligence.md`: fixed a broken `add_decision` pattern and a wrong hybrid-search description** (#1466) by @ZohaibHassan16 — the alternative "build a `Decision` object, pass to `add_decision`" pattern silently produced nodes invisible to `find_precedents`/`get_causal_chain`/`get_decision_insights` and raised `ValueError` on trace; replaced with the working keyword-argument form. Corrected the hybrid search description (was described as semantic similarity + Node2Vec embeddings at 0.7/0.3; actually word-level Jaccard overlap + connection-count structural similarity) and fixed a wrong decision id in the banking loan example that silently attached to a phantom node
- **Tightened prose for clarity and conciseness across the setup, architecture, cookbook, resources, glossary, modules, contributing, and community-facing docs** (#1459, #1458, #1457, #1456, #1454, #1453, #1452, #1442) by @Deep070203`cli-setup.md`, `explorer-setup.md`, `installation.md`, `quickstart.md`, `architecture.md`, `cookbook.md`, `citation.md`, `faq.md`, `learning-more.md`, `project-license.md`, `glossary.md`, `choose-your-module.md`, `modules.md`, `contributing-guide.md`, `community-projects.md`, `community.md`, and `governance.md`; no technical content changed
- **`docs/reference/ontology.md`: documented Quality Gate threshold semantics** (#1450) by @KaifAhmad1 — added a `### Thresholds` table covering `min_coverage`, `max_errors`, `max_warnings`, and `fail_on_warnings` (noting the latter is a separate constructor/call parameter, not a `thresholds` key), verified against `OntologyQualityGate.DEFAULT_THRESHOLDS`
- **Replaced the retired `claude-sonnet-4-20250514` model id in docs and LLM wrappers** (#1449) by @ZohaibHassan16 — updated roughly 15 examples across `graphrag.md`, `llm-integrations.md`, `multi-agent.md`, `ontology.md`, and `reference/llms.md` (plus the LiteLLM/Anthropic wrapper defaults) to `claude-sonnet-5`, `claude-opus-4-7`, and a current Bedrock model id
- **`docs/guides/semantic-extraction.md`: fixed a wrong triplet count and a retired model id** (#1448) by @ZohaibHassan16 — the pipeline example printed `{}/{} triplets valid` using the Turtle output's string length instead of the triplet count (producing output like `7/4231`); now uses a real `triplets_total` value. Also replaced `claude-sonnet-4-6` with the dated model id used elsewhere, and clarified the sample NER output is illustrative
- **`docs/reference/reasoning.md`: clarified Datalog query result ordering** (#1447) by @ZohaibHassan16 — the `datalog.query(...)` example implied a fixed result order; results are set-backed and unordered, so the comment no longer implies otherwise
- **`docs/index.md`: rewrote the landing page as a lean developer welcome** (#1446) by @KaifAhmad1 — replaced the long feature-dump page with a shorter one built around Semantica's deterministic semantic/context-infrastructure positioning, trimming the module table, use-case grid, and duplicate link lists (kept as a collapsed accordion so the module-coverage check still passes)
- **`docs/guides/pipeline.md`: fixed the retry-policy example** (#1444) by @ZohaibHassan16 — the example configured a `FailureHandler` with custom retry policies but never assigned it to the `ExecutionEngine`, which builds its own handler, so the configured policies were silently ignored; added `engine.failure_handler = handler`. Also replaced a hardcoded node/edge-count output comment with a shape-only example
- **`docs/modules.md`: fixed code examples across the module catalogue to match the current API** (#1443) by @ZohaibHassan16 — corrected snippets using nonexistent or outdated APIs (e.g. `NERExtractor`'s `method="llm"`, `SimilarityCalculator.calculate_similarity()`, `Reasoner.apply_transitivity()`/`infer()`, `EntityResolver`, `ConflictDetector.resolve()`, treating `Pipeline` as a builder/runtime API) across extraction, graph building, reasoning, deduplication, conflicts, embeddings, vector store, export, pipeline, seed data, and evals sections; all 31 code blocks now parse and were run against current source
- **`docs/integrations/langchain.md`: tightened integration prose** (#1432) by @taljeon — replaced a remaining em dash with direct sentences and reformatted the component list as name/type pairs; no technical content changed
- **`docs/guides/graphrag.md`: fixed broken example strings and clarified `max_hops`** (#1431) by @ZohaibHassan16 — the banking example's multi-line string literals raised `IndentationError`; wrapped in parentheses to match the working Clinical example. Clarified that `AgentContext.retrieve(max_hops=)` only bounds anchored proximity scoring rather than graph-expansion depth (`max_expansion_hops` controls that); also fixed a made-up node/edge count comment
- **Tightened prose and fixed two broken relative links in `concepts.md`, `guides/graphrag.md`, and `reference/context.md`** (#1422) by @KaifAhmad1 — removed em dashes from explanatory prose (left intact in simulated document/alert examples); fixed `reference/context.md` links to `reasoning`/`provenance` that were missing a leading slash and would 404; updated `concepts.md`'s intro tagline to match #1421
- **`docs/index.md`: rewrote landing-page prose to be crisp and direct** (#1421) by @KaifAhmad1 — cut the marketing/storytelling framing and all em dashes; updated the tagline to "The Context and Semantic Layer for AI in High-Stakes Domains" across `docs.json` and `index.md`, keeping audit trail/accountability as a property rather than the headline
- **Restructured the docs nav** (#1419) by @KaifAhmad1 — dropped the standalone FAQ and Changelog tabs (their pages moved under Overview) and added a dedicated API Reference tab holding the `reference/*` pages split out of Modules
- **`docs/assets/custom.css`: replaced decorative hover/fade animations with static styling** (#1418) by @KaifAhmad1 — removed the page-load fade-in and hover lift/glow effects on code blocks, cards, buttons, and nav links site-wide, keeping the existing color palette and accessibility focus rings
- **`docs/concepts.md`: fixed 9 of 13 code examples that no longer matched the current API** (#1417) by @ZohaibHassan16 — corrected the `GraphBuilder`, GraphRAG, forward-chaining/Rete/Datalog reasoning, `GraphReasoner`, `SimilarityCalculator`, provenance, and `MethodRegistry` snippets, plus the distance-band terminology and engine comparison table
- **`docs/quickstart.md`: fixed the parsed-document example to read `full_text`** (#1415) by @ZohaibHassan16
- **`docs/getting-started.md`: fixed broken Knowledge Graph and GraphRAG "Choose Your Path" examples** (#1414) by @ZohaibHassan16 — the extractor calls now pass parsed text instead of a `FileObject`, and the GraphRAG example uses `context.store()` + `retrieve(use_graph=True, ...)` instead of the nonexistent `load_graph()`/`query(mode=...)` APIs
- **Fixed ~300 relative body links across 74 docs pages that 404'd on the live site** (#1407, closes #1405) by @Duansg — GitHub Pages' trailing-slash redirect resolved hand-written relative Markdown links against the wrong base path; links are now rewritten as root paths
- **Fixed two broken cookbook notebook links** (#1403) by @ZohaibHassan16`docs/learning-more.md` pointed to a nonexistent `09_Embeddings.ipynb` (now the correct `12_Embedding_Generation.ipynb`), and `docs/reference/distance.md`'s dead link to a nonexistent Distance Intelligence notebook was removed
- **`docs/quickstart.md`/`docs/faq.md`: addressed Qodo review findings** (#1402, follow-up to #1401) by @ZohaibHassan16
- **`docs/quickstart.md`: fixed the Full Pipeline walkthrough against current APIs** (#1401) by @ZohaibHassan16 — corrected the parse, extract, ingest (`WebIngestor`/`XMLIngestor`), export (`ArangoAQLExporter`, Parquet), OCR, and `PipelineBuilder` examples, and fixed a stale `Pipeline(workers=N)` example also present in `faq.md`
- **Updated stale latest-version references to v0.6.7** across `docs/faq.md`, `docs/index.md`, and `docs/quickstart.md` (#1400) by @ZohaibHassan16
- **`docs/reference/mcp_server.md` and related pages: documented all 15 MCP tools** (#1399) by @ZohaibHassan16 — added the three previously-undocumented tools (`query_graph`, `update_node`, `delete_node`) and corrected the tool count everywhere it appeared
- **`docs/reference/evals.md`: rewritten to match the shipped `semantica.evals` API** (#1398) by @ZohaibHassan16 — replaced the stale "not yet implemented" placeholder with `evaluate()`, `list_evaluators()`, `EvalMetric`/`CaseResult`/`EvalSummary`, all 10 built-in evaluators, and the `decision_scores` sub-checks
- **README: propagated SAP OData connector mentions consistently and trimmed the audience list** (#1396) by @KaifAhmad1 — added SAP mentions to the Enterprise Data Platforms bullet, ingest summary, module reference table, and supported-sources line (previously only in "What's New"); tightened the "Who it's for" bullets; removed sample `semantica doctor` output from the quickstart snippet
- **Rewrote the Semantic Layer Basics cookbook lesson as a runnable introductory workflow** (#1361, closes #1325) by @taoche — replaced the removed `advanced/09_Semantic_Layer_Construction.ipynb`, which never used `TripletStore`, left mappings empty, and never executed a query, with `introduction/26_Semantic_Layer_Basics.ipynb`, whose ontology, mappings, RDF, and SPARQL query now agree end to end
- **Rewrote cookbook notebook 08 into a real, rerunnable knowledge-graph workflow** (#1359, closes #1289) by @taoche — it previously read the wrong parser key, substituted hard-coded extraction fixtures, bypassed `GraphBuilder`, and never called `KGVisualizer`; it now runs parse → NER/relation extraction → `GraphBuilder``KGVisualizer` end to end
- **Fixed cookbook notebook 07's graph mapping and deduplication output** (#1357, closes #1287) by @taoche — edges were built from loop indices instead of extracted relation endpoints, and the dedup output showed only merge operations, making 5 mentions falsely appear to collapse to 1 entity instead of the correct 4
- **README: repositioned Semantica's opening pitch around the semantic/context/knowledge layer** (#1348) by @KaifAhmad1 — leads with Context Graph, KG, and ontology governance (OWL/SHACL/SKOS) rather than framing audit trails as the flagship pattern; reordered the hero pillar list to lead with Context Management/Knowledge Modeling ahead of Decision Intelligence
- **Hash-pin every pip install across the Dockerfile and CI workflows for Scorecard Pinned-Dependencies** (#1338) by @KaifAhmad1 — CI/build hardening, no runtime behavior change. Closes 21 OpenSSF Scorecard alerts: existing `pkg==X.Y.Z` version pins (even installs already reading a hashed `requirements-ci.txt`) still scored low because no hash is visible on the install command itself. Adds hash-locked `.github/requirements/*.txt` files (via `uv pip compile --generate-hashes`) for every pip target not already covered, adds `--require-hashes` to all `-r requirements-ci.txt` installs, and splits local-source installs into `pip install --no-deps -e .` plus a separately hash-pinned dependency install (a local source tree has nothing to hash directly). The Dockerfile now installs from a pre-generated `explorer-extra.txt` rather than extracting constraints at build time
- **Test-only contributions**: fixed `sys.modules` mock leakage in `test_extractors_dispatch.py` that made 132 tests pass in isolation but fail in a full-suite run, by installing the mocks per-test via `patch.dict`/`addCleanup` instead of at module scope (#1337, closes #1336, by @dex0shubham); added missing `__init__.py` package markers to `tests/integrations/crewai/` and `tests/integrations/langchain/`, fixing a pytest collection abort from two same-named `test_degradation.py` files colliding under prepend import mode (#1252, closes #1251, by @dex0shubham); guarded fastapi-dependent Explorer test modules so `tests/explorer/` and `tests/test_security_regression.py` collect successfully without the `explorer` extra installed (#1232, closes #1167, by @dex0shubham)
- **`ContextGraph`'s temporal-input normalizer is now a public API** (#1455, closes #1377) by @Saket7002`normalize_temporal_input` is exposed publicly so `context/erasure.py`'s `ErasureCoordinator` can call it directly instead of reaching across modules for a private helper. No behavior change. Regression coverage added for the public normalizer; full targeted run (`test_context_graph_retraction.py` + `test_erasure_coordinator.py`): 101 passed.
- **New acceptance tests pin known contract gaps between `VectorStore`'s facade and the Qdrant/Pinecone/Milvus/Weaviate backends, as strict `xfail`** (#1332) by @ZohaibHassan16 — existing vector-store tests all bypass `_init_backend_store` (the code path that actually constructs cloud backend adapters), either mocking backend internals directly or injecting a fake backend, which is how #1316 could be fully green while broken end to end: a Qdrant-backed `VectorStore` can't read (no connection/collection ever established) and can't write (`store_vectors()` doesn't dispatch to `QdrantStore.insert_vectors`). New `tests/vector_store/test_backend_facade_contract.py` constructs each backend through the real facade path and marks the two capability gaps `xfail(strict=True)` for Qdrant/Pinecone/Weaviate (Milvus already passes, pinned separately as a control) — a fix will flip these to unexpected passes and fail the suite until the marker is removed, making them acceptance criteria rather than assertions of the broken behavior itself. 13 new tests (6 pass, 7 xfail); no application code changed.
- **CI now reports required status checks correctly on docs-only PRs** (#1410) by @Sameer6305`ci.yml`/`security-scan.yml` still trigger on every PR including docs-only changes, but skip their expensive jobs for docs-only diffs while still reporting a check status, so required checks don't block on jobs that never ran; full build/security scans are preserved for source or mixed changes, and non-PR triggers are unaffected.
- **CI gains npm Dependabot coverage for `explorer/` and container image scanning** (#1286) by @KaifAhmad1`dependabot.yml` previously had no `npm` ecosystem entry for `explorer/`, which is why the `brace-expansion`/`nanoid` CVEs fixed in #1280 went undetected until a manual check; added, mirroring the existing `pip` entry's schedule/labels/reviewers. New `container-scan.yml` builds the Dockerfile image, scans it with Trivy (CRITICAL/HIGH to the Security tab as SARIF, `ignore-unfixed: true`), and generates an SPDX SBOM with Syft, running on push to main, weekly, and on manual dispatch. Trivy runs report-only for now (no `exit-code` gate) until the first CRITICAL/HIGH baseline is triaged.
- **Distribution and trust-signal infrastructure: reusable install action, a PyPI install matrix, and release-pipeline hardening** (#1266) by @KaifAhmad1
- New `.github/actions/setup-semantica` composite action other repos can call to install and verify `semantica` in one step
- New `install-matrix.yml` verifies the *published* PyPI package installs and imports cleanly across Ubuntu/macOS/Windows and Python 3.9-3.12, on a weekly schedule and on every release, backing a new "pip install" README badge
- New `scorecard.yml` runs OpenSSF Scorecard analysis weekly and on push to main, backing a new README trust-signal badge
- `release.yml` gains a `twine check` gate before publish, catching a broken PyPI long-description render before it ships; the existing Trusted Publishing/OIDC + SLSA attestation signing flow is otherwise unchanged
- New `CITATION.cff` (enables GitHub's native "Cite this repository" button alongside the existing `docs/citation.md`) and `examples/ci/` copy-paste GitHub Actions/GitLab CI/CircleCI templates for downstream adopters
- New `GROWTH.md` tracks distribution-channel status with explicit guardrails against artificially inflating download/install metrics
- No application code changed; new workflow YAML validated with `yaml.safe_load` and new action pins verified against the GitHub API
- **Resynced `github/codeql-action` pin to current v4 SHA** (#1249) by @ZohaibHassan16 — the v4 tag's underlying SHA had changed, failing "Verify Action Pins" on every PR; all 8 refs across `codeql.yml` and `defender-for-devops.yml` updated and reverified (40/40 clean).
### Fixed
- **README's production deploy instructions pointed at an environment variable that exists nowhere in the codebase** (#1473, fixes #1429) by @v01dst`README.md:1546` told deployers to set `SEMANTICA_SECRET_KEY`, but the Explorer auth code (`semantica/explorer/dependencies.py:30`) reads `SEMANTICA_API_KEY` (with `SEMANTICA_ALLOW_ANONYMOUS=true` as the opt-out), so a deploy following the README set a silently-ignored variable and then hit 503s or unintended anonymous mode. One-line docs fix; `grep SEMANTICA_SECRET_KEY README.md` shows 0 hits afterward
- **The Python 3.9 install matrix was still broken after the spaCy/thinc fix in #1329** (#1445, closes #1347) by @ZohaibHassan16`scikit-learn`, `requests`, `chardet`, `grpcio`, `pillow`, `click`, and `onnxruntime` all now ship minimum versions requiring Python 3.10+, so a plain no-extras install on 3.9 failed to resolve. Adds Python-version markers for each, following the existing spaCy/thinc pattern: 3.9 is capped at the latest compatible release per package, 3.10+ stays unconstrained. Verified with `uv pip compile --python-version 3.9` for Linux/Windows/macOS, plus 3.10 and 3.12
- **Ontology property generation inferred framework bookkeeping fields as business datatype properties** (#1420, closes #1416) by @pkupt`_extract_data_properties` only skipped `id`/`type`/`entity_type`/`text`/`label`/`confidence`, so structural fields `GraphBuilder` and `EntityMerger` attach to entity dicts (`properties`, `relationships`, `metadata`, `provenance`, `merged_from`, `merge_strategy`) were emitted as bogus datatype properties alongside real attributes. The skip set is now a single `_CONTROL_FIELDS` constant covering all of them; flat top-level business attributes are unaffected. New `tests/ontology/test_ontology_framework_fields.py`
- **`ErasureCoordinator(vector_store=False)` didn't actually stop all vector deletion — it only stopped the coordinator's own leg** (#1395, closes #1378) by @Harsh4r0ra — disabling the vector leg made the coordinator itself report `status="not_configured"`, but `AgentMemory.batch_delete()``delete_memory()` still ran its own best-effort vector-delete cascade internally, catching any failure and returning `True` regardless, so `receipt.complete` could read `True` while an embedding was still live. A `skip_vector` flag is now threaded from `ErasureCoordinator` into a new keyword-only `AgentMemory.batch_delete(skip_vector=...)` parameter whenever the vector leg is explicitly disabled. The existing test that had asserted the buggy behavior is rewritten, plus a new regression test pinning `delete_calls == 0`
- **MCP graph persistence and setup were broken across multiple surfaces** (#1394, closes #1134) by @Sameer6305 — the root MCP server loaded graphs with a non-existent method instead of `load_from_file()`, and mutations made through MCP tools weren't persisted back to `SEMANTICA_KG_PATH` on either server implementation. Fixed graph loading, wired persistence through for both MCP server implementations, corrected the MCP installation and Claude Code setup docs (including the `claude mcp add` invocation and documenting the required `PYTHONPATH`), and added end-to-end MCP stdio JSON-RPC regression coverage
- **CI's Safety-based security scan crashed intermittently instead of reporting real findings** (#1390, closes #1389) by @ZohaibHassan16 — the same crash pattern previously seen with `cuda-toolkit` recurred with `torchvision`, and identical runs against `requirements-ci.txt` could either succeed or crash, so `IGNORED_VULN_IDS` couldn't help — Safety crashed before it ever wrote a report. Replaces the Safety step in `security-scan.yml` with `pip-audit` (already used successfully in `security.yml` against the same dependencies) and removes `security.yml` entirely now that `security-scan.yml` covers everything it did, plus Bandit, Semgrep, and PR reporting on a broader trigger set. `IGNORED_VULN_IDS` is now empty since `pip-audit`'s OSV source doesn't carry either CVE Safety was flagging. Verified via YAML/embedded-JS syntax checks, report-handling tests against six report shapes, and `verify-action-pins.sh` passing with 47 action references (down from 49 after removing `security.yml`)
- **`verify-action-pins.sh` failed after `actions/deploy-pages`'s v5 tag moved** (#1387) by @ZohaibHassan16 — the tag advanced from v5.0.0 to v5.0.1 (backoff/jitter added to deployment polling, confirmed via the GitHub API); the pinned SHA in `docs.yml` is updated to match. Verified all 49 action references pass
- **CI's security scan failed on an unreachable, transitive `torchvision` CVE** (#1385, closes #1384) by @ZohaibHassan16`SFTY-20260723-60537` (CVE-2026-65918) is a GIF-decoder finding in `torchvision`, pulled in transitively via `safetensors`/`sentence-transformers` and never used directly (confirmed by grep across `semantica/`, `mcp/`, `integrations/`); fixed upstream in commit `4e05dc2` but not yet in any released `torchvision`. Added to `IGNORED_VULN_IDS`, matching the existing `cuda-toolkit` precedent
- **`ErasureReceipt.to_dict()` returned nested dicts shared by reference with the live receipt** (#1381, fixes #1376) by @BinarySpecter`backend_result`'s nested dicts weren't copied, so a caller mutating the returned dict could corrupt the receipt's own internal state; the audit record it's meant to be is no longer safe to hand out. Fixed with a proper deep copy in `semantica/context/erasure.py`. `tests/context/test_erasure_coordinator.py`: 49 passed, 3 subtests
- **The `--ignore`-based Safety CVE suppression added in #1370 crashed CI on the very next run** (#1371) by @KaifAhmad1 — a correction to #1370: `--ignore` only crashes once Safety has to apply itself against a real match, and the push-triggered run on `main` immediately after #1370 merged hit the exact `'cuda-toolkit'` crash #1131/#1157 had already fixed, even though a plain scan (no `--ignore`) had run clean moments earlier on the same dependencies. The author notes their own pre-merge local testing was misleading — their local Safety database didn't surface the CVE at all, so `--ignore` never had a real match to crash against locally. Fix: drop `--ignore` entirely, run the plain scan proven not to crash, and filter the accepted vulnerability ID out of the JSON report in `jq` before both the count check and detail-printing. Also fixes a latent bug where `.vulnerabilities | length` silently returned `0` for a null/missing `vulnerabilities` key instead of erroring, which the existing Guard 2 comment had assumed already happened. Validated the jq filter against six synthetic report shapes rather than relying on a local Safety run
- **CI's security scan failed on a real, unfixable-upstream `cuda-toolkit` CVE with no released fix available** (#1370) by @KaifAhmad1`SFTY-20260120-40557` (CVE-2025-33228) is a hard `==13.0.3` pin from `torch==2.13.0`'s own wheel metadata (the latest available torch release), so no version bump can resolve it; the CVE itself is OS command injection in NVIDIA Nsight Systems' `gfx_hotspot` recipe, which Semantica never invokes and which isn't among the CUDA extras torch actually requests here. Added `--ignore SFTY-20260120-40557` to the `safety check` invocation, scoped to this one vulnerability ID with an inline comment explaining why and when to revisit. Verified locally against Safety 3.8.1 that the ignore only suppresses this ID and no others. (Superseded the following day by #1371, which found this `--ignore` itself reintroduced a Safety crash in live CI)
- **A malformed Safety report could be silently read as a clean scan** (#1366) by @T1mn — the Security Scan workflow had no check that `safety-report.json` actually contained a well-formed, array-valued `vulnerabilities` field before counting findings, so a present-but-malformed report risked passing as zero findings. Adds an independent fail-closed check that validates the field's shape and renders an explicit invalid-report warning instead of treating malformed data as clean; the existing `--file requirements-ci.txt` Safety scan and the separate `security.yml` pip-audit workflow are unchanged
- **The bundled Claude Code plugin failed to install entirely** (#1363, fixes #1350) by @7487`plugins/.claude-plugin/plugin.json` declared `"agents": "./agents"`, but unlike `skills`, Claude Code's plugin schema rejects a bare directory string for `agents` (`Validation errors: agents: Invalid input`) and requires an explicit array of `.md` file paths. Replaced with `["./agents/decision-advisor.md", "./agents/explainability.md", "./agents/kg-assistant.md"]`. New `tests/test_plugin_manifest.py` guards that `agents` stays a non-empty array of existing `.md` paths in sync with `plugins/agents/`. Verified with the official validator (Claude Code 2.1.231): validation now passes
- **Checkov's own suppressed findings kept reopening as brand-new GitHub code-scanning alerts on every rescan** (#1346) by @KaifAhmad1 — the same 4 Checkov k8s findings on `deploy/helm/knowledge-explorer` (namespace/seccomp) were already suppressed via working `checkov.io/skipN` annotations and correctly marked `SKIPPED` in Checkov's JSON output, but Checkov's SARIF exporter emits every evaluated check as an ordinary `level: warning` result regardless of skip status and never populates SARIF's own `suppressions` field — so GitHub had no way to know these were suppressed and opened new alert numbers across three separate scans. New `.github/scripts/filter_checkov_skipped.py` cross-references Checkov's JSON `skipped_checks` against the SARIF `results` (matched on check ID plus the last two path segments, since JSON and SARIF use different path roots) and drops already-suppressed results before the SARIF reaches GitHub. Verified locally against a real checkov 3.3.1 + helm 3.16.4 run: removed exactly the 4 known-suppressed results, left 2 genuinely real findings elsewhere in the repo untouched
- **A Scorecard Pinned-Dependencies alert flagged an install step for a directory that doesn't exist in the repo** (#1345) by @KaifAhmad1`benchmark.yml:51` ran `pip install -r benchmarks/requirements.txt`, but `benchmarks/` doesn't exist anywhere in the repository, so the step couldn't be hash-pinned and the job already failed on the very next real step (`benchmarks/benchmarks_runner.py`, also missing) — the line did nothing useful. Dropped it rather than leave it unpinned. Also closed directly via the API without a PR: #6099 (Dockerfile Pinned-Dependencies, dismissed won't-fix — installing our own git-tracked source with `--no-deps --no-build-isolation` has no third-party fetch to pin, and pip rejects `--hash`/`--require-hashes` on local directory targets) and #6112#6115 (same suppressed-Checkov-alert root cause as #1346, dismissed as false positive)
- **`MilvusStore.get_collection()` attached to a mismatched collection and only failed later, far from the root cause** (#1344, closes #1331) by @pkupt — the method wrapped `Collection(name)` right after the `has_collection` guard with no schema check, so an INT64-pk or metadata-less collection attached successfully and only surfaced an error deep inside `get_vector`/`get_metadata`. A schema check now runs immediately after attach, before the store assigns `self.collection`, so a mismatch is caught early with an error naming the actual problem. 9 new focused tests in `tests/vector_store/test_milvus_get_collection.py` cover the matching case and each rejection case
- **The Docker build broke outright after #1338, failing Container Security Scan on the build step itself rather than just SBOM/Trivy** (#1341) by @KaifAhmad1`explorer-extra.txt` was compiled with `--python-version 3.11` but installed on the Dockerfile's actual `python:3.13-slim` interpreter; `librosa`'s `audioread` dependency needs `standard-aifc`/`standard-sunau` only under `python_version >= "3.13"` (Python 3.13 dropped `aifc`/`sunau` from stdlib), and a lockfile resolved for 3.11 carries no hashes for those packages at all, so `--require-hashes` failed outright once pip resolved against the real 3.13 environment. Split into `explorer-extra-py311.txt` (used by `ci.yml`, unchanged resolution) and a newly-compiled `explorer-extra-py313.txt` (used by the Dockerfile, including the `standard-aifc`/`standard-sunau`/`standard-chunk` hashes), with `.github/requirements/README.md` documenting why the two can't be recombined
- **The Neo4j persistence example in `docs/quickstart.md` raised `AttributeError` when followed as written** (#1340, fixes #1135) by @Sameer6305 — the example passed a raw `Neo4jStore` backend directly to `GraphBuilder(graph_store=store)`, but `GraphBuilder` expects the `GraphStore` facade and calls `add_nodes()`/`add_edges()`, which the raw backend doesn't expose (`'Neo4jStore' object has no attribute 'add_nodes'`). Updated the example to construct `GraphStore(backend="neo4j", ...)` instead. New regression test in `tests/kg/test_graph_builder_with_graph_store.py` covering `GraphBuilder` against the `GraphStore` facade
- **`pip install semantica` failed on Python 3.9 across all three OSes** (#1329) by @KaifAhmad1`spacy` had no upper bound, so pip resolved spacy 3.8.16 whose `thinc>=8.3.12` requirement has no cp39 wheels and no working sdist build path either. Caps `spacy<3.8.8` and adds `thinc<8.3.5` for `python_version < '3.10'` (py3.10+ stays unconstrained); verified with a dry-run resolve against manylinux/win_amd64/macosx_arm64, all landing on prebuilt wheels (spacy 3.8.7 + thinc 8.3.4). Also pins Docker base images by digest and remaining unpinned CI tool installs, and adds Sigstore signing so `dist/*.sigstore.json` ships alongside release artifacts (OpenSSF Scorecard Pinned-Dependencies/Signed-Releases hardening)
- **FAISS vector store silently lost `vector_ids`/`metadata` across save/load, so a reloaded index reported zero vectors and `semantica store migrate --from faiss` silently copied zero records** (#1314, closes #1272) by @AhmadBilalDSA — loading a saved index reinitialized `vector_ids = []` and `metadata = {}`, so `scan_vectors()` returned `[]` and `count()` returned `0` despite a valid binary index on disk. Metadata now persists to an atomic companion `.meta.json` file written alongside the index, restored exactly on reload, with a `RuntimeWarning` plus a logged warning when the binary index exists but its sidecar is missing. New end-to-end regression test verifying `scan_vectors()` matches the original records across fresh store instances
- **Registered ontologies opened the Ontology Editor to an empty canvas, and ontology deep links didn't land on the Editor at all** (#1278, closes #1274) by @taoche — the app shell ignored `ontologyTab`/`ontologyEntity` URL state, and even when the Editor did open, it loaded registry metadata but never fetched the selected ontology's schema nodes and structural edges. Adds `GET /api/ontology/graph?uri=...` returning the bounded schema subgraph, wires deep-link state into startup tab selection, and maps the response into React Flow nodes/edges with loading/error/selection handling. 40 backend tests plus 77 explorer graph-workspace tests pass
- **Explorer's Full Graph view rendered small, multi-component graphs as unlabeled dots with relationships suppressed** (#1277, closes #1275) by @taoche — coordinate-free graphs of any size got the same large-graph seed layout, ForceAtlas2 stabilization, and overview edge LOD, which crushes node spacing and hides ordinary edges on a small graph. Adds a deterministic, component-aware layout path for coordinate-free graphs of up to 48 nodes — skips force stabilization, keeps labels visible, preserves relationship edges — while larger graphs and graphs with existing coordinates are unaffected. 81 explorer tests pass
- **Explorer graph-loading failures showed only a generic `Fetch failed: <status>` message, discarding the server's actionable error detail** (#1260, closes #1256) by @wanglin1111111 — e.g. an unconfigured `SEMANTICA_API_KEY` returns a specific remediation string in the response body's `detail` field, but the UI overlay showed a generic "check that the backend is running" hint instead, sending users down the wrong troubleshooting path. `useLoadGraph.ts` now reads the JSON body on a non-OK response and appends `detail` to the thrown error, degrading gracefully when the body isn't JSON
- **`ConsoleProgressDisplay` wrote progress bars to `sys.stdout`, corrupting the JSON-RPC protocol on stdio MCP servers** (#1254, closes #1134) by @dex0shubham — stdio MCP servers frame newline-delimited JSON-RPC on stdout, so an interleaved progress bar could make a response body unparseable. Progress now defaults to `sys.stderr` (resolved per-write via a property so a later rebinding, e.g. pytest capture, is honored), with an optional `stream` override; the cp1252 emoji-capability probe now inspects the actual target stream instead of always stdout. 9 new tests in `tests/utils/test_progress_stream.py`
- **`SlidingWindowChunker` accepted a zero or negative `stride`, and a failed `chunk_with_overlap()` call could leave chunker state un-restored** (#1245, closes #1244) by @HsienW — the fixed-size chunking path depends on `stride` to advance the cursor, but an explicit non-positive value passed validation; a temporary overlap override used internally by `chunk_with_overlap()` could also derive a non-positive stride, and the original overlap/custom stride weren't guaranteed to be restored if chunking raised. Non-positive stride/overlap values are now rejected before chunking, and the temporary override is restored via `try`/`finally` on both success and failure. 13 new/updated tests
- **Explorer's temporal scrubber sent duplicate snapshot requests and could apply a stale response over a newer one** (#1241, closes #1128) by @ALDRIN121 — repeated `onTimeChange` calls at the same timestamp (timeline recreation, play ticks, drag events) each fired a fresh `/api/temporal/snapshot` request with no dedup — 13+ identical-`at` requests observed at ~500ms cadence — and under variable network latency an older position's response could land after a newer one's, leaving the active-node chip visibly lagging the scrubber. New `temporalSnapshotGuards.ts` dedupes in-flight requests per scrubber position, caches and re-applies snapshots on revisit, and applies a response only while the scrubber is still on that position; state resets when the graph summary changes. 16 new unit tests
- **Distinct property spellings normalizing to the same ontology name produced duplicate property definitions, and object/data properties could collide under one IRI** (#1231) by @T1mn — follow-up to #1170/#1171. Same-kind properties normalizing to the same name are now merged, preserving their domains and ranges; a normalized name shared across an object and a data property now raises a structured `ValidationError` instead of silently colliding
- **Class inference could emit duplicate ontology classes for source types that normalize to the same name (e.g. `Person`/`person`), silently misassigning properties to the first class** (#1230) by @T1mn — follow-up to #1171. The collision is now detected and rejected with a structured `ValidationError` before duplicate classes or misassigned properties are emitted. New regression test for the `Person`/`person` case
- **`OntologyGenerator.infer_properties`'s public entry point still fell back to `owl:Thing` when relationship endpoints were given by entity ID or alias**, even though the main generation pipeline had already been fixed (#1229) by @T1mn — follow-up to #1170. The endpoint-resolution logic is now extracted into a shared `relationship_utils.py` helper used by both `PropertyGenerator` and the public inference path, so the two can't drift again
- **`auto_generate_id=False` on the six decision-model dataclasses was unreachable dead code** (#1153, fixes #1152) by @cxzg007`Decision`, `DecisionContext`, `Policy`, `PolicyException`, `Precedent`, and `ApprovalChain` declared `auto_generate_id` only as a plain `__post_init__` parameter rather than a dataclass field or `InitVar`, so the generated `__init__` never forwarded it — it was always `True`, and the "require a caller-supplied id" validation branch could never run. Declared as `InitVar[bool] = True` on each dataclass, restoring the intended contract with no serialization change (`InitVar` isn't a real field, so `to_dict()`/`from_dict()` are unaffected). 38 tests pass in `tests/context/test_decision_models.py`; 108 downstream tests unaffected
- **Three functions used mutable list-literal default arguments**, a classic Python pitfall where the same list object persists and can accumulate mutations across calls (#1068) by @yzxcj797`GraphAnalyzer.analyze_temporal_evolution(metrics=[...])`, `HierarchicalChunker.__init__(levels=[...])`, and `split_hierarchical(levels=[...])` now default to `None` with a fresh list built in-body. New regression tests in `tests/kg/test_kg.py` and `tests/split/test_chunkers.py`
- **`AgentMemory.find_by_entity()` defaulted to `limit=10`, silently truncating results** (#1024) by @yzxcj797 — the erasure workflow added in #1018 (`ErasureCoordinator`) computing what references an entity from a truncated page could leave the untruncated remainder live after a supposedly-complete erasure. Default changed to `limit=None` (all matches), with explicit limits still supported for pagination. New regression tests in `tests/context/test_agent_memory_find_by_entity.py`
- **Explorer SHACL validation error messages didn't name the environment variable that controls the limit being hit** (#1437, closes #1430) by @pkupt — the Turtle-size, triple-count, and timeout limit-exceeded messages in `validate_shacl` now name the specific env var to change, and `docs/guides/shacl-validation.md` documents all four resource-limit variables with their defaults. Existing message-assertion tests extended to also check the env var name appears.
- **Explorer's `POST /api/export` only supported `json`/`csv`, while the MCP `export_graph` tool already resolved Turtle, N-Triples, RDF/XML, JSON-LD, and GraphML through the same exporters** (#1157, closes #1131) by @13g4d0 — the Explorer route now reaches the same `semantica.export` exporters the MCP tool uses (`RDFExporter.export_to_rdf`, `GraphMLExporter.export`) rather than reimplementing anything, with an alias table shared with (and tested against) `mcp/tools/export.py`'s `_FORMAT_ALIASES`, correct media types/extensions per format, a 422 message that now names the supported formats instead of just saying the requested one isn't, and a missing optional dependency now returning 503 instead of a misleading 422. Parquet export is explicitly left out — it writes a file/path rather than a response body, and deserves its own review. Tests parse each of the seven RDF spellings with `rdflib` rather than asserting on strings, plus a canary that the Explorer and MCP alias tables agree; `tests/explorer/test_explorer_api.py`: 110 passed.
- **`semantica ingest` reported "✓ Ingested" while writing nothing to a configured Neo4j backend** (#1465, closes #1351) by @evgenyponomarev`ingest()`/`ingest_file()` never referenced a graph store at all, so `--store`/`GRAPH_STORE_DEFAULT_BACKEND` were accepted and silently discarded; the command now raises a clear error when a non-memory graph backend is configured, naming both this and the related `kg build` no-op (#1352) rather than recommending a workaround that fails the same way. `--output <file>.json` writes the ingested result instead (via the existing `_write_result_output` helper), and `_json_default` now expands dataclasses (`FileObject`) and decodes `bytes` so the written file holds real content, not a Python repr. 3 new regression tests; full `tests/test_cli_commands.py`: 270 passed
### Security
- **Five HIGH-severity Trivy findings in the built container image** (#1334) by @KaifAhmad1`setuptools` 70.3.0 (CVE-2025-47273, path traversal; base-image-bundled and never touched by our own build) upgraded explicitly to 78.1.1. `msgpack` 1.1.2 (GHSA-6v7p-g79w-8964, OOB read/crash on Unpacker reuse) shipped because the Dockerfile's bare `pip install ".[explorer]"` re-resolved dependencies from scratch instead of reusing the audited, hash-pinned `requirements-ci.txt` (which already pins `msgpack==1.2.1`) — the image now installs against a constraints file derived from `requirements-ci.txt` so it matches what's actually been audited. `openssl`/`libssl3t64` (CVE-2026-14456, QUIC server DoS) has no packaged fix yet in Debian's `trixie-security`; an upgrade step is added so the next rebuild picks it up automatically, documented as non-exploitable here since the image only serves plain HTTP via uvicorn and never opens a QUIC listener
- **Two npm advisories in `explorer/package-lock.json` flagged by OpenSSF Scorecard, plus over-broad workflow token permissions** (#1280) by @KaifAhmad1`brace-expansion` (transitive via `minimatch`) 5.0.8→5.0.9 and `nanoid` (transitive via `postcss`) 3.3.16→3.3.18 close GHSA-rgw5-rvv9-x895 and GHSA-2v37-7h3g-55p8 (both unbounded/looping-input DoS); lockfile-only, both versions already satisfy their parents' declared ranges. Also narrows `security-events: write`/`actions: read` from workflow-level to job-level scope in `codeql.yml` and `defender-for-devops.yml`, matching least-privilege token-permission guidance
- **12 Dependabot alerts against `aiohttp`** (request smuggling, websocket/parser bugs, cookie/redirect and deserialization issues, one rated High), pinned transitively via `checkov` in `.github/requirements/checkov.txt` (#1342) by @KaifAhmad1 — root cause: `checkov==3.3.1` itself constrained `aiohttp<3.14.0`, excluding every patched release. Bumping to `checkov==3.3.16` relaxes that to `aiohttp<3.15.0`, letting `aiohttp` resolve to the patched `3.14.3` and clearing all 12 alerts at once. Two related alerts are documented as left open rather than fixed here: `asteval` (checkov 3.3.16 still hard-pins `asteval==1.0.6` with no compatible range yet) and `ecdsa` (`0.19.2` is already latest; no fix exists yet for the Minerva timing-attack advisory GHSA-wj6h-64fc-37mp, which upstream has declared out of scope) — both assessed as non-exploitable here since these are checkov's own transitive dependencies used only for local static IaC analysis, with no network-signing or cloud-auth code path exercised
### Dependencies
- Routine version bump fixing 2 disclosed advisories with no application-facing behavior change: `browserslist` (transitive dev dependency in `explorer/`) 4.28.2→4.28.8, closing GHSA-73wf-gq98-2v4g and GHSA-c83g-rgw3-j3cx (#1382)
## [0.6.7] - 2026-08-28
### Added
@@ -128,6 +271,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- **Also fixed, on the JSON-LD paths**: the first fix covered the Turtle, N-Triples and RDF/XML serializers, and left both JSON-LD writers interpolating the entity's own text into `f"semantica:entity/{text}"` and the endpoints into `f"semantica:rel/{source}_{target}"`. Three consequences, all live in 0.6.5: an entity whose text contained a space produced an invalid IRI, and a JSON-LD parser dropped that node in full rather than reporting it, so the entity disappeared from the export; every relationship carrying `source`/`target` rather than `source_id`/`target_id` minted the identical `semantica:rel/_`, collapsing all of them onto one node whose types and endpoints merged; and the JSON-LD `@id` disagreed with the Turtle IRI for the same entity, so the two serializations of one knowledge graph were two different graphs. Both JSON-LD writers now use `mint_entity_iri`/`mint_relationship_iri`, and `JSONExporter.export_entities`/`export_relationships` declare the `semantica` prefix their `@context` was already writing `semantica:entities` against — without it a processor reads that as an IRI in the scheme `semantica`, which is the original #1101 defect on a third path
- `tests/export/test_jsonld_iri_minting.py` parses each export with a real JSON-LD processor and asserts the entity survives, the relationships stay distinct, no term expands into the `semantica` scheme, and the JSON-LD `@id` equals the Turtle IRI
- 236 export and ontology tests pass
- **`semantica.evals` runner gains per-metric objectives** (#1091)
- `evaluate()` now accepts `config={"<evaluator>": {"objective": {"direction": "maximize"|"minimize", "threshold": X}}}` to override the evaluator's default pass verdict with a threshold; `{"objective": {"expect": bool}}` expresses a Boolean expectation
- `minimize` requires a `threshold` — omitting it or setting it to `None` raises `ValueError`; `maximize` without a threshold is a no-op (the evaluator's own verdict stands); `expect` cannot be combined with `direction`/`threshold`; invalid config raises `ValueError` before any evaluator runs
- Error metrics are never affected by objectives (error wins over fail)
- Backward compatible: no `objective` key → existing behavior unchanged
- New tests in `tests/evals/test_runner.py::TestObjective`
- **`semantica.evals` is now a fully implemented evaluation module** (was a "Coming Soon" stub in the package layout)
- `evaluate(cases, evaluators, config=None, target_fn=None)` runner with per-case `pass`/`fail`/`error` status and an aggregate `pass_rate`, using a registry of named evaluators (`list_evaluators()`)
- 10 built-in evaluators: `exact_match`, `regex_match`, `numeric_range`, `temporal_range`, `length_range`, `keyword_check`, `levenshtein` (edit-distance similarity), `rouge` (in-house token F1, no new dependencies), `llm_as_judge` (lazy: caller-supplied `judge_fn`), and `decision_scores` (composite over `semantica.context.Decision`)
- `decision_scores` validates field-level (expected outcome, confidence bounds, non-empty maker/reasoning/scenario) and governance-level (provenance record presence; opt-in `PolicyEngine.check_compliance`) checks, coercing dict inputs via `Decision(**actual)` and never crashing on malformed input; an interface slot for causal-chain/embedding checks is reserved and raises `NotImplementedError` (V2)
- `__version__` is `0.1.0`, and the module ships a usage guide at `semantica/evals/usage.md` with worked import/run/interpret examples
- `semantica.evals` is reachable through the root package lazy module proxy (`semantica.evals`)
- 99 unit tests in `tests/evals/` covering every evaluator, registry errors, runner aggregation, decision coercion, and per-metric objectives; `python -m pytest tests/evals -q` → 99 passed
- **First-class CrewAI integration** (#988, closes #962) by @Shindevrp
- New `pip install semantica[crewai]` extra (`crewai>=0.80.0`) — crewai core provides `BaseTool`/`BaseKnowledgeSource`, so `crewai-tools` is intentionally not included, and the extra is intentionally **not** part of the `all` bundle: crewai hard-requires `chromadb~=1.1.0`, which is affected by the unpatched pre-auth code-injection CVE-2026-45829 (see `integrations/crewai/README.md`)
@@ -213,6 +369,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Fixed
- **RETE engine matched every fact against every rule — `AlphaNode._matches()` and `BetaNode._can_join()` were placeholder stubs that always returned `True`** (closes #300)
- `semantica/reasoning/rete_engine.py` shipped a Rete network whose per-condition alpha test and cross-condition beta join were both `return True` stubs, so `match_patterns()` fired every rule for every fact regardless of predicate, arity, or shared-variable consistency
- New module-level `unify_condition()` reuses the regex-based approach from `Reasoner._match_pattern()`: a condition pattern like `Person(?x)` / `Parent(?x, ?y)` is compiled against a fact's `predicate(arg, ...)` string, `?var` becomes a named capture group, and a variable seen twice within one condition (e.g. `Loves(?x, ?x)`) becomes a backreference, so it only unifies when both positions hold the same value. Returns the bindings dict or `None`
- Reworked propagation to carry partial-match **tokens** instead of bare facts: a new `Token` dataclass bundles the accumulated `facts` with the consistent `bindings`. `AlphaNode` emits a single-fact token per match; `BetaNode.join()` merges a left token with a right token, concatenating their facts in condition order and returning the merged token only when shared variables agree (conflicting values → `None`, no join). Terminal activations carry the full fact list and accumulated bindings through to the emitted match
- This fixes a P1 chained-join defect: rules with three or more conditions (e.g. `Person(?x)`, `Parent(?x, ?y)`, `Located(?y, ?z)`) previously lost bindings and accumulated wrong facts at the third join, and a conflicting third condition could spuriously fire. Beta nodes now keep both `left_tokens` and `right_tokens` memories and join each new token against every token on the opposite side, so deep chains stay binding-consistent and third-level conflicts are correctly suppressed
- Fixed an adjacent network-topology bug surfaced by the above: newly created beta nodes were never appended to their input nodes' `children`, so tokens could not propagate; propagation was reworked to support chained joins and to thread bindings end-to-end
- Reconciled with the rule-actions/provenance layer (#1096) merged after this fix was opened: `execute_matches()` still dedupes and fires `Rule.actions`/legacy `handler` through a bound `Reasoner` via `_make_activation_key`, now sourced from the Token model's own `bindings` instead of the interim `_bindings_for_rule()` regex re-extraction, which is removed as redundant
- New `tests/reasoning/test_rete_engine.py`: `unify_condition` unit cases (single/multi variable, literal args, predicate mismatch, repeated-variable equality), alpha match/reject, beta consistent-join vs conflict-reject, end-to-end rules (single-condition fires only the matching fact; multi-condition join fires only on consistent bindings), and a `TestThreeConditionChain` suite (valid three-condition match, third-level conflict suppression, insertion-order independence, `Match.facts` complete and in condition order, multiple left tokens joining one right fact, parity against `Reasoner._match_rule()`, and `reset()` clearing all token memory)
- **KG provenance tests asserted on generated ID strings instead of stored records, and `kg_provenance.py` was missed by the `utcnow` sweep** (closes #946) by @pravit-amp
- The KG workflow and integration suites checked that a tracker call returned an ID matching a prefix (`assert cent_id.startswith("centrality_")`) without ever reading the record back, so an ID generator that returned a well-formed string and wrote nothing would have passed. Worse, some of those calls named tracker methods that do not exist anywhere in `semantica/` (`track_layer_analysis`, `track_centrality_score`), so the assertions were satisfied with no real interaction behind them
- Those tests now read provenance back through `get_provenance()` and assert on algorithm metadata, and call the methods that actually persist records. Verified by mutation rather than by a green run alone: neutering the manager's storage write (`self.storage.store(...)` → no-op) fails 10 tests
+20
View File
@@ -0,0 +1,20 @@
cff-version: 1.2.0
message: "If you use this software, please cite it as below."
title: "Semantica: Graph-Native Infrastructure for Context and Accountable AI Systems"
type: software
authors:
- name: "Semantica"
repository-code: "https://github.com/semantica-agi/semantica"
url: "https://getsemantica.ai"
license: MIT
version: 0.6.8
date-released: 2026-09-05
keywords:
- knowledge-graph
- context-graph
- ai-agents
- llm
- decision-intelligence
- provenance
- explainability
- graph-rag
+38 -4
View File
@@ -1,5 +1,5 @@
# syntax=docker/dockerfile:1
FROM node:26-alpine AS frontend-builder
FROM node:26-alpine@sha256:2d984a15c9b54fd0aeb608b8e0d0d83529eb34d2966db27a1fb4f1edc3d298a3 AS frontend-builder
WORKDIR /app
COPY explorer/package*.json ./explorer/
@@ -9,7 +9,18 @@ RUN npm ci
COPY explorer/ ./
RUN mkdir -p /app/semantica && npm run build
FROM python:3.13-slim AS runtime
# CVE-2026-14456 (OpenSSL QUIC-server DoS, flagged against this base image's
# openssl/libssl3t64/openssl-provider-legacy): the Debian fix
# (3.5.7-1~deb13u2) is only in trixie-proposed-updates as of this writing,
# not yet promoted to trixie-security, so there's no package to pin here
# today. Deliberately NOT running `apt-get upgrade` to chase it - that
# breaks build reproducibility (terrascan AC_DOCKER_0052) and still
# wouldn't reach a proposed-updates-only package. Once Debian ships the fix
# and rebuilds this tag, the docker Dependabot ecosystem in
# .github/dependabot.yml opens a PR bumping the digest pin above. Also: this
# image only serves plain HTTP via uvicorn and never opens a QUIC listener,
# so the bug isn't reachable here regardless.
FROM python:3.13-slim@sha256:7ce4b6dfe35e55397b7cda544f8a13f191b7ae28dc5aad71fe664dbc9bc2623f AS runtime
ENV PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1 \
@@ -22,12 +33,35 @@ WORKDIR /app
RUN groupadd --system semantica \
&& useradd --system --gid semantica --home-dir /app --shell /usr/sbin/nologin semantica
COPY pyproject.toml README.md LICENSE MANIFEST.in ./
COPY pyproject.toml README.md LICENSE MANIFEST.in \
.github/requirements/explorer-extra-py313.txt .github/requirements/pep517-build.txt ./
COPY semantica/ ./semantica/
COPY integrations/ ./integrations/
COPY --from=frontend-builder /app/semantica/static ./semantica/static
RUN pip install --no-cache-dir ".[explorer]" \
# explorer-extra-py313.txt is `uv pip compile pyproject.toml --extra explorer
# --python-version 3.13 --constraint requirements-ci.txt --generate-hashes`
# (see ci.yml's explorer-extra-py311.txt for the CI counterpart, resolved
# for CI's python 3.11 instead - the two aren't interchangeable: audioread
# (via librosa) needs standard-aifc/standard-sunau only on python>=3.13,
# since aifc/sunau left stdlib there, so a 3.11-resolved lockfile is
# missing hashes pip needs on this image's actual 3.13 interpreter and
# --require-hashes fails outright rather than silently under-pinning).
# Every fetched package is hash-verified (Scorecard Pinned-Dependencies)
# and pinned to the same versions CI audited, e.g. msgpack==1.2.1 and
# setuptools==84.0.0 (which also replaces the base image's vulnerable
# 70.3.0, CVE-2025-47273 - nothing else in the tree pulls a newer copy).
# --no-deps on the local package itself: it's our own source tree, not a
# fetch, so there's nothing to hash-pin there - but `pip install .` still
# does a PEP 517 build, which by default creates an *isolated* build env
# and fetches [build-system] requires (setuptools, wheel) completely
# outside any hash checking. pep517-build.txt pins that exact
# build-system.requires; installing it first and passing
# --no-build-isolation makes pip reuse those hash-verified copies instead
# of fetching its own.
RUN pip install --no-cache-dir -r explorer-extra-py313.txt -r pep517-build.txt --require-hashes \
&& pip install --no-cache-dir --no-deps --no-build-isolation . \
&& rm -f explorer-extra-py313.txt pep517-build.txt \
&& chown -R semantica:semantica /app
USER semantica
+131
View File
@@ -0,0 +1,131 @@
# Growth & Distribution Playbook
North star: **10,000 developers who actually use Semantica in real projects**, not a raw PyPI download number. Downloads are a lagging indicator of distribution, not a target to optimize directly.
```
GitHub stars → Website visitors → PyPI installs → Weekly active users → Production deployments → Enterprise customers
```
The last two matter far more than the download count.
## Guardrails — do not do this
- No fake/looping CI jobs that repeatedly `pip install semantica` purely to inflate the graph. It's detectable, it produces zero real users, and it damages credibility with anyone doing diligence (investors, enterprise buyers, security reviewers).
- No package-splitting purely to multiply install counts — only split into `semantica-*` packages when there's a real architectural reason.
- No meaningless Docker pulls or notebook launches with no real content behind them.
- Every item below should get someone from "installed it" to "used it for something real." If a channel can't do that, it's not worth building.
## 30-day priority sprint
Ordered by leverage-to-effort ratio; do these first.
| # | Initiative | Target |
| - | ---------- | ------ |
| 1 | ✅ GitHub Actions example + reusable `setup-semantica` composite action + install-matrix badge | done |
| 2 | Google Colab notebooks | 10 |
| 3 | Docker images (RAG, Graph, Agent, API) | 4-5 |
| 4 | Hugging Face Spaces demos | 3-4 |
| 5 | LangChain integration + example | 1 |
| 6 | LlamaIndex integration + example | 1 |
| 7 | Vector/graph DB integrations (Qdrant, Weaviate, Neo4j) | 3 |
| 8 | MCP server + example | 1 (already have `mcp/` — package as a distributable example) |
| 9 | Production-quality starter repos (FastAPI, Streamlit, Gradio) | 3 |
| 10 | `awesome-rag` / `awesome-llm` / `awesome-knowledge-graph` list submissions | 3+ PRs |
Push everything through: GitHub → Discord (`sV34vps5hH`) → X (`@BuildSemantica`) → GitHub Discussions → Reddit → Hacker News → relevant newsletters.
## Full channel checklist
### CI/CD (highest-intent distribution — installs tied to real pipelines)
- [x] GitHub Actions example in `examples/ci/github-actions.yml`
- [x] Reusable composite GitHub Action — [`.github/actions/setup-semantica`](.github/actions/setup-semantica/action.yml), modeled on `actions/setup-python`; usable by any repo as `uses: semantica-agi/semantica/.github/actions/setup-semantica@main`
- [x] "pip install" status badge in the README, backed by [`.github/workflows/install-matrix.yml`](.github/workflows/install-matrix.yml) — verifies the *published* package installs cleanly on Ubuntu/macOS/Windows across Python 3.9-3.12, weekly + on every release
- [x] GitLab CI template — `examples/ci/gitlab-ci.yml`
- [x] CircleCI template — `examples/ci/circleci-config.yml`
- [ ] Jenkins, Azure DevOps, Bitbucket Pipelines, Buildkite, Travis CI equivalents
### Release pipeline hardening (already had Trusted Publishing/OIDC + SLSA attestation — this rounds it out to match top-tier OSS release practice)
- [x] `twine check` gate in `.github/workflows/release.yml` before publish — catches a broken PyPI long-description render before it goes live instead of after (a malformed README on the live PyPI page is a silent conversion killer)
- [x] `CITATION.cff` (see Academic & research below)
- [x] OpenSSF Scorecard (see Discoverability below)
- [ ] Considered and deliberately skipped: Release Drafter / auto-generated changelogs — this repo hand-curates `CHANGELOG.md` with far more detail (PR numbers, contributors, phase-1 limitations) than a bot would produce. Don't introduce this without checking with maintainers first.
- [ ] Renovate / Dependabot config templates that auto-bump the `semantica` version in downstream repos — real recurring CI runs on real adopters
- [ ] Nightly scheduled workflow template that tests a downstream project against `semantica@latest`
### Containers & dev environments
- [ ] Official Docker images: RAG, Graph, Agent, API, `+Postgres`, `+Neo4j`, `+Qdrant`
- [ ] `docker-compose` examples (repo already has `docker-compose.dev.yml` / `docker-compose.yml` as a base)
- [ ] `.devcontainer/devcontainer.json` for one-click "Reopen in Container"
- [ ] GitHub Codespaces-ready config
- [ ] Gitpod config
- [ ] "Use this template" GitHub repo button so new projects start with `semantica` in `requirements.txt`
### Notebooks & hosted demos
- [ ] 10-20 Google Colab notebooks (Graph RAG, agent memory, entity resolution, semantic search, document intelligence)
- [ ] Kaggle Notebooks/Kernels
- [ ] Binder / mybinder.org config for instant repo launch
- [ ] SageMaker Studio Lab / Databricks Community Edition / Paperspace Gradient examples
- [ ] Hugging Face Spaces (Streamlit/Gradio) demos with `semantica` in `requirements.txt`
- [ ] Public hosted playground (source on GitHub, install visible)
### Framework & data-store integrations
- [x] LangChain integration — `integrations/langchain/` (`SemanticaRetriever`, `SemanticaVectorStore`, `SemanticaKGTool`/`SemanticaDecisionTool`), `pip install semantica[langchain]`, shipped in 0.6.7
- [ ] LlamaIndex integration + example
- [ ] LangGraph example
- [ ] Neo4j integration/example (docs already list it as a supported graph store — turn into a runnable example repo)
- [ ] Vector DB examples: Qdrant, Weaviate, Milvus, Pinecone, Chroma, FAISS, pgvector, OpenSearch/Elasticsearch (FAISS/Pinecone/Weaviate/Qdrant/Milvus/PgVector already supported per `docs/community-projects.md` — package each as a standalone example)
- [ ] LLM provider quickstarts: OpenAI, Anthropic, Gemini, Groq, Ollama, HuggingFace, DeepSeek, LiteLLM (already-supported providers per docs — each gets its own copy-paste quickstart)
- [ ] CrewAI / Agno integration examples (already documented under `docs/integrations/`) — promote as standalone repos, not just docs pages
### Package managers & installers
- [ ] conda-forge feedstock
- [ ] Homebrew formula for the CLI
- [ ] Nix/nixpkgs packaging
- [ ] Chocolatey / Scoop (Windows)
- [ ] Document `uv add semantica` and `poetry add semantica` explicitly alongside `pip install`
### Downstream packages & CLI
- [ ] Genuinely useful `semantica-*` packages only where warranted (e.g. `semantica-rag`, `semantica-connectors`) — each pulls `semantica` as a real dependency
- [ ] Make sure `semantica init / ingest / index / query / serve` CLI flows are the default onboarding path in every tutorial
- [ ] VS Code extension wrapping the CLI (scaffold + run commands from the command palette)
- [ ] JetBrains plugin equivalent
### Templates & starters
- [ ] Cookiecutter templates: `cookiecutter-semantic-rag`, `cookiecutter-ai-agent`, `cookiecutter-enterprise-rag`
- [ ] Starter repos: FastAPI, Streamlit, Gradio, Next.js frontend + Semantica backend
- [ ] Cloud deploy templates: AWS, GCP, Azure, Modal, Railway, Render, Fly.io (repo already has `deploy/azure`, `deploy/gcp`, `deploy/fly`, `deploy/railway`, `deploy/render`, `deploy/kubernetes`, `deploy/helm` — link these prominently from the README/quickstart, they're already-built distribution surface)
- [ ] Terraform / Pulumi / Helm modules published to their respective registries
### Discoverability & curation
- [ ] Submit to `awesome-rag`, `awesome-llm`, `awesome-knowledge-graph`, `awesome-python`
- [ ] Pitch newsletters with engaged Python/AI audiences (Python Weekly, Import AI, TLDR AI, etc.)
- [x] PyPI trove classifiers/keywords and `project.urls` (Homepage/Docs/Repository/Changelog/Bug Tracker) — already complete in `pyproject.toml`
- [ ] Get listed on Papers With Code for any retrieval/graph-RAG benchmark work
- [x] [OpenSSF Scorecard](https://scorecard.dev/viewer/?uri=github.com/semantica-agi/semantica) badge + weekly workflow (`.github/workflows/scorecard.yml`) — a concrete trust signal security/procurement teams check before greenlighting adoption, which gates real (non-CI-bot) install growth at enterprises
### Academic & research
- [x] `CITATION.cff` at repo root — enables GitHub's native "Cite this repository" button, feeds Google Scholar/academic tooling; complements `docs/citation.md` (still needs a real Zenodo DOI to replace the `XXXXXXX` placeholder in both places once one is minted)
- [ ] arXiv paper if there's real architectural novelty to describe
- [ ] Zenodo DOI for citability (`docs/citation.md` already exists — make sure it points to a real DOI)
- [ ] Workshop/tutorial sessions at PyData/ODSC-style events with hands-on install steps
- [ ] University course material / bootcamp adoption outreach
### Content
- [ ] Reproducible benchmark repos (Graph RAG vs vector RAG, retrieval@k, enterprise-scale retrieval) with `pip install semantica && python benchmark.py`
- [ ] 20-30 real-world example applications (RAG, enterprise document intelligence, financial entity graphs, code knowledge graphs, research discovery, agent memory)
- [ ] Blog/tutorial posts on Dev.to, Medium, personal blogs — always with runnable code, not just prose
- [ ] Contribute integrations/PRs to other projects building RAG/agents/knowledge graphs — "I implemented Semantica support" beats "please use Semantica"
## Tracking
Don't just watch the raw PyPI number — use download analytics (e.g. PePy) to separate CI/bot traffic from real installs, and track the funnel above end-to-end where possible (stars → site visits → installs → weekly actives).
+66 -35
View File
@@ -14,21 +14,27 @@
### Graph-Native Infrastructure for Context and Accountable AI Systems
#### *The Open Source Palantir for AI Agents*
#### *Developer-first, knowledge infrastructure for AI, alternative to expensive enterprise platforms.*
> Ingest your enterprise data, extract what matters, build a Context Graph and knowledge graph (KG), and run graph analytics and causal reasoning over all of it, with full decision provenance baked in. Explainable, traceable, and trustworthy by design.
**Decision Intelligence &nbsp;·&nbsp; Context Management &nbsp;·&nbsp; Deterministic Reasoning &nbsp;·&nbsp; Ontology Management &nbsp;·&nbsp; Knowledge Modeling &nbsp;·&nbsp; End-to-End Traceability**
**Context Management &nbsp;·&nbsp; Knowledge Modeling &nbsp;·&nbsp; Deterministic Reasoning &nbsp;·&nbsp; Ontology Management &nbsp;·&nbsp; Decision Intelligence &nbsp;·&nbsp; End-to-End Traceability**
**Open Source &nbsp;·&nbsp; Self-Hostable &nbsp;·&nbsp; Auditable &nbsp;·&nbsp; Governed &nbsp;·&nbsp; Zero Vendor Lock-In**
**Open Source &nbsp;·&nbsp; Governed &nbsp;·&nbsp; Zero Vendor Lock-In**
**Polyglot Graph Storage &nbsp;·&nbsp; RDF & LPG Support &nbsp;·&nbsp; W3C Standards &nbsp;·&nbsp; Interoperable**
#### Built for High-Stakes, Regulated Domains
[![GitHub Stars](https://img.shields.io/github/stars/semantica-agi/semantica?style=flat-square&color=FFD700&logo=github&logoColor=white&label=Stars)](https://github.com/semantica-agi/semantica) [![GitHub Forks](https://img.shields.io/github/forks/semantica-agi/semantica?style=flat-square&color=6E40C9&logo=github&logoColor=white&label=Forks)](https://github.com/semantica-agi/semantica/network/members) [![Contributors](https://img.shields.io/github/contributors/semantica-agi/semantica?style=flat-square&color=2EA043&logo=github&logoColor=white)](https://github.com/semantica-agi/semantica/graphs/contributors) [![PyPI](https://img.shields.io/pypi/v/semantica.svg?style=flat-square&color=0066CC&logo=pypi&logoColor=white)](https://pypi.org/project/semantica/) [![Total Downloads](https://static.pepy.tech/badge/semantica?style=flat-square)](https://pepy.tech/project/semantica) [![Python 3.8+](https://img.shields.io/badge/python-3.8+-3776AB?style=flat-square&logo=python&logoColor=white)](https://www.python.org/) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg?style=flat-square)](https://opensource.org/licenses/MIT) [![CI](https://img.shields.io/github/actions/workflow/status/semantica-agi/semantica/ci.yml?style=flat-square&label=CI)](https://github.com/semantica-agi/semantica/actions) [![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/semantica-agi/semantica)
[![GitHub Stars](https://img.shields.io/github/stars/semantica-agi/semantica?style=flat-square&color=FFD700&logo=github&logoColor=white&label=Stars)](https://github.com/semantica-agi/semantica) [![GitHub Forks](https://img.shields.io/github/forks/semantica-agi/semantica?style=flat-square&color=6E40C9&logo=github&logoColor=white&label=Forks)](https://github.com/semantica-agi/semantica/network/members) [![Contributors](https://img.shields.io/github/contributors/semantica-agi/semantica?style=flat-square&color=2EA043&logo=github&logoColor=white)](https://github.com/semantica-agi/semantica/graphs/contributors) [![PyPI](https://img.shields.io/pypi/v/semantica.svg?style=flat-square&color=0066CC&logo=pypi&logoColor=white)](https://pypi.org/project/semantica/) [![Total Downloads](https://static.pepy.tech/badge/semantica?style=flat-square)](https://pepy.tech/project/semantica) [![Python 3.8+](https://img.shields.io/badge/python-3.8+-3776AB?style=flat-square&logo=python&logoColor=white)](https://www.python.org/) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg?style=flat-square)](https://opensource.org/licenses/MIT) [![CI](https://img.shields.io/github/actions/workflow/status/semantica-agi/semantica/ci.yml?style=flat-square&label=CI)](https://github.com/semantica-agi/semantica/actions) [![Install Matrix](https://img.shields.io/github/actions/workflow/status/semantica-agi/semantica/install-matrix.yml?style=flat-square&label=pip%20install)](https://github.com/semantica-agi/semantica/actions/workflows/install-matrix.yml) [![OpenSSF Scorecard](https://api.scorecard.dev/projects/github.com/semantica-agi/semantica/badge?style=flat-square)](https://scorecard.dev/viewer/?uri=github.com/semantica-agi/semantica) [![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/semantica-agi/semantica)
[![Website](https://img.shields.io/badge/Website-getsemantica.ai-000000?style=for-the-badge\&logo=googlechrome\&logoColor=white)](https://getsemantica.ai/)
[![Docs](https://img.shields.io/badge/Docs-docs.getsemantica.ai-0099FF?style=for-the-badge\&logo=readthedocs\&logoColor=white)](https://docs.getsemantica.ai/)
[![Community](https://img.shields.io/badge/Community-Join%20Discord-5865F2?style=for-the-badge\&logo=discord\&logoColor=white)](https://discord.gg/sV34vps5hH)
[![X](https://img.shields.io/badge/X-%40BuildSemantica-000000?style=for-the-badge\&logo=x\&logoColor=white)](https://x.com/BuildSemantica)
[![YouTube](https://img.shields.io/badge/YouTube-Watch%20Demos-FF0000?style=flat-square\&logo=youtube\&logoColor=white)](https://www.youtube.com/watch?v=QfnNZg4-dZA)
[![Website](https://img.shields.io/badge/Website-getsemantica.ai-000000?style=flat-square&logo=googlechrome&logoColor=white)](https://getsemantica.ai/) [![Docs](https://img.shields.io/badge/Docs-docs.getsemantica.ai-0099FF?style=flat-square&logo=readthedocs&logoColor=white)](https://docs.getsemantica.ai/) [![Discord](https://img.shields.io/badge/Discord-Join%20Community-5865F2?style=flat-square&logo=discord&logoColor=white)](https://discord.gg/sV34vps5hH) [![Twitter/X](https://img.shields.io/badge/Follow-%40BuildSemantica-000000?style=flat-square&logo=x&logoColor=white)](https://x.com/BuildSemantica) [![YouTube](https://img.shields.io/badge/YouTube-Watch%20Demos-FF0000?style=flat-square&logo=youtube&logoColor=white)](https://www.youtube.com/watch?v=QfnNZg4-dZA) [![Changelog](https://img.shields.io/badge/Changelog-View-6E40C9?style=flat-square&logo=keepachangelog&logoColor=white)](CHANGELOG.md)
```bash
pip install semantica
@@ -56,20 +62,18 @@ pip install semantica
---
Most AI agents act without a trail. They store embeddings, not meaning: context that can't be explained, decisions that can't be audited. In lending, that gap is a compliance exposure, not an inconvenience: an underwriting agent's approval has to survive a regulator's "why" months later.
Semantica sits underneath your LLM, vector store, and agent framework as a deterministic infrastructure layer: no LLM required for graph construction, reasoning, or provenance.
Most AI agents run on embeddings, not meaning: similarity scores with no structure, no relationships, and no way to explain why a result came back. Semantica is the semantic/context layer underneath your LLM, vector store, and agent framework: a deterministic infrastructure layer (no LLM required for graph construction, reasoning, or provenance) that turns fragmented enterprise data into a structured, queryable Context Graph and knowledge graph, governed by ontologies and controlled vocabularies (OWL, SHACL, SKOS) so the meaning of your data is explicit, not just its embedding. Decision provenance and audit trails fall out of that structure as a property, not the product itself; in domains a regulator can question, that same structure just happens to double as a straight answer to "why."
> ⚠️ **System-level explainability, not foundation-model explainability.** Semantica does not expose or reconstruct what happens *inside* the LLM — its internal reasoning or chain-of-thought stays opaque, as it does for any external system. Semantica explains what's *outside* the model: the context and data fed in, the decision produced, its provenance, relevant relationships, applied policies, and the full execution trail.
**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)** &nbsp;·&nbsp; **[Architecture](#architecture)** &nbsp;·&nbsp; **[What You Get](#what-semantica-gives-you)** &nbsp;·&nbsp; **[Why Semantica](#why-semantica)** &nbsp;·&nbsp; **[Decision Intelligence](#decision-intelligence)** &nbsp;·&nbsp; **[Context Graphs](#context-graphs)** &nbsp;·&nbsp; **[Recipe: Audit Trail](#recipe-audit-trail-for-a-regulated-decision)** &nbsp;·&nbsp; **[Module Reference](#module-reference)** &nbsp;·&nbsp; **[Integrations](#integrations)** &nbsp;·&nbsp; **[CLI](#cli)** &nbsp;·&nbsp; **[Performance](#performance)** &nbsp;·&nbsp; **[Install](#installation)**
@@ -83,7 +87,7 @@ Semantica sits underneath your LLM, vector store, and agent framework as a deter
- **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
@@ -141,10 +145,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.
@@ -169,7 +169,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
@@ -279,7 +279,7 @@ retrieved = ctx.retrieve("who approved the Acme contract?")
## Recipe: Audit Trail for a Regulated Decision
The flagship pattern: record a causally-linked decision chain, attach provenance to every entity, and export a regulator-ready audit trail.
One pattern built on the same Context Graph: record a causally-linked decision chain, attach provenance to every entity, and export a regulator-ready audit trail.
```python
from semantica.context import ContextGraph
@@ -322,7 +322,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 |
@@ -351,7 +351,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
@@ -402,7 +402,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`.
@@ -1030,7 +1030,7 @@ team = Team(agents=[researcher, analyst], mode="coordinate")
## More Recipes
The flagship audit-trail recipe is [above](#recipe-audit-trail-for-a-regulated-decision). Here are three more common patterns.
The audit-trail recipe is [above](#recipe-audit-trail-for-a-regulated-decision). Here are three more common patterns.
<details>
<summary><b>End-to-End GraphRAG Pipeline</b></summary>
@@ -1147,7 +1147,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 |
---
@@ -1461,20 +1461,36 @@ semantica-explorer --graph my_graph.json
For contributor / dev-server setup: **[explorer/README.md: Local Setup Guide](explorer/README.md)**
The CLI exposes the loaded `ContextGraph`. To also browse and edit an existing
`AgentMemory`, create the ASGI app programmatically with both live objects:
```python
from semantica.context import AgentMemory, ContextGraph
from semantica.explorer.app import create_app
from semantica.explorer.session import GraphSession
graph = ContextGraph()
memory = AgentMemory()
app = create_app(session=GraphSession(graph), agent_memory=memory)
```
The Memories workspace is shown only when `agent_memory` is provided. Apply
updates the supplied runtime object; it does not add disk persistence.
---
## What's New in v0.6.7
## What's New in v0.6.8
**Feature release**, plus one SSRF hardening fix and a large batch of correctness fixes across the RDF/ontology export pipeline:
**Every release from here on is cryptographically signed** — the build now runs SLSA build-provenance attestation plus Sigstore signing, and `.sigstore.json` bundles ship alongside the wheel/sdist on every GitHub Release, closing the OpenSSF Scorecard Signed-Releases gap. Beyond that, this is a large fix-and-hardening release plus a batch of vector-store and LLM-provider additions:
- **First-class LangChain integration** (`semantica[langchain]`): a `BaseRetriever` and `VectorStore` over `HybridSearch`, plus graph/decision-query tools
- **SAP OData ingestor** (`semantica[ingest-sap]`): OAuth2/Basic-auth, SSRF-guarded ingestion for Business Partners and Sales Orders, following the existing Snowflake/Databricks connector pattern
- **`ContextGraph` gains deterministic, human-editable Markdown round-trip persistence** alongside the existing JSON API, and the Explorer graph inspector gains a read-only Markdown content viewer
- **`reasoning` gains a structured Action layer**: rule-driven `Assert`/`Retract`/`Call`/`EmitEvent` actions with optional provenance, turning the reasoner into a production-rule system
- **`run_shacl_validation` is now a public, documented API**, and a dozen ontology/RDF export correctness fixes land: OWL property/class export, SHACL target-namespace resolution, one canonical confidence datatype across all four RDF formats, reachable OWL-Time reification, JSON-LD default-graph and content-derived document identity, and full metadata passthrough on every RDF serializer
- **Security**: Agno's `AgnoKnowledgeGraph.load_urls()` and OpenClaw's MCP tool now route outbound requests through the shared SSRF guard
- **Vector store gains real enumeration**: `scan_vectors()`/`iter_vectors()` land across FAISS, SQLiteVec, PgVector, Qdrant, Weaviate, and Milvus (each via the pagination primitive its API actually supports), making `semantica store migrate` functional between backends for the first time; Weaviate also gains `delete_vectors()` for `ErasureCoordinator` support
- **`semantica.llms` gains first-class `Anthropic`, `Gemini`, `Ollama`, `DeepSeek`, and `Novita` provider wrappers**, matching the existing `Groq`/`OpenAI` pattern
- **Ontology package gains a deterministic, CI-friendly quality gate** for ontologies and knowledge graphs, plus first-class Google ADK integration and a Salesforce ingestor
- **Explorer's read-only Markdown viewer becomes a full editor** for live `ContextGraph` nodes and host-supplied `AgentMemory` items
- **`ErasureCoordinator`** completes the erasure workflow `purge_node()` only started, so a purged entity no longer survives verbatim in `AgentMemory` or as an embedding
- **Security**: 12 Dependabot `aiohttp` alerts, 5 HIGH-severity Trivy container findings, and 2 npm advisories all resolved
Also fixes: `PipelineBuilder.set_parallelism()` now actually parallelizes independent pipeline steps, `flatten_dict()` no longer silently drops data on a key collision, `Config.get()` honors boolean environment overrides, and the MCP server's `export_graph` tool works again on every format.
Also fixes 35 correctness bugs (Python 3.9 install breakage, FAISS save/load metadata loss, `semantica ingest`'s silent no-op against a configured graph store, MCP persistence, Explorer graph rendering, ontology property-collision handling, and more) and a large batch of documentation corrections across the site.
→ [Full release notes](RELEASE_NOTES.md) · [Changelog](CHANGELOG.md)
@@ -1519,6 +1535,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
@@ -1526,7 +1543,7 @@ pip install semantica[watch] # Directory file watcher
pip install semantica[explorer] # Knowledge Explorer dashboard
```
For production deployments, use Docker or Kubernetes rather than a local `pip install`. Set `SEMANTICA_SECRET_KEY`, configure a persistent LPG graph store (Neo4j / FalkorDB / Apache AGE / AWS Neptune) and/or RDF triple store (Blazegraph / Apache Jena / Eclipse RDF4J), and point the vector store at a hosted backend (Qdrant / Pinecone). See [ARCHITECTURE.md](ARCHITECTURE.md) for the full deployment topology.
For production deployments, use Docker or Kubernetes rather than a local `pip install`. Set `SEMANTICA_API_KEY`, configure a persistent LPG graph store (Neo4j / FalkorDB / Apache AGE / AWS Neptune) and/or RDF triple store (Blazegraph / Apache Jena / Eclipse RDF4J), and point the vector store at a hosted backend (Qdrant / Pinecone). See [ARCHITECTURE.md](ARCHITECTURE.md) for the full deployment topology.
```bash
# From source
@@ -1534,6 +1551,20 @@ git clone https://github.com/semantica-agi/semantica.git
cd semantica && pip install -e ".[dev]" && pytest tests/
```
### CI & Deployment
Wiring `semantica` into your own CI is a two-minute job. On GitHub Actions, use the reusable composite action:
```yaml
- uses: semantica-agi/semantica/.github/actions/setup-semantica@main
with:
python-version: '3.11'
```
Copy-paste starting templates for GitHub Actions, GitLab CI, and CircleCI live in [examples/ci/](examples/ci/). The published package itself is verified installable across Ubuntu/macOS/Windows and Python 3.9-3.12 every week by the [Install Matrix workflow](.github/workflows/install-matrix.yml).
Ready-made deployment configs for AWS, GCP, Azure, Fly.io, Railway, Render, Kubernetes, and Helm are in [deploy/](deploy/).
---
## Enterprise
+2 -3
View File
@@ -153,7 +153,7 @@ that attack chain.
- **Risk**: a PR merges without its security/CI checks passing.
**Control**: merges require the `build`, `Analyze Python` (CodeQL), and `security-scan` checks to pass, in strict mode (checks must be re-run against the latest `main`).
- **Risk**: a compromised scanner job reaches secrets or write access.
**Control**: scanning jobs (`CodeQL`, `security-scan.yml`, `security.yml`, `defender-for-devops.yml`) run with read-only, least-privilege permissions (typically `contents: read` + `security-events: write` only) and never share a job, environment, or secret scope with the publish job.
**Control**: scanning jobs (`CodeQL`, `security-scan.yml`, `defender-for-devops.yml`) run with read-only, least-privilege permissions (typically `contents: read` + `security-events: write` only) and never share a job, environment, or secret scope with the publish job.
- **Risk**: secrets are committed accidentally.
**Control**: GitHub secret scanning and push protection are both enabled at the repository level, rejecting pushes that contain recognizable credential patterns before they land in history.
@@ -164,8 +164,7 @@ Every scan below runs continuously in CI, not just at release time:
- **CodeQL** (`security-and-quality` query pack) — Python source: injection, unsafe deserialization, and other code-level vulnerability classes. Runs in `codeql.yml` on every push/PR to `main` and weekly.
- **Bandit** — Python-specific security anti-patterns (hardcoded secrets, unsafe `eval`/`pickle`, weak crypto, etc.); CI fails on any HIGH-severity finding. Runs in `security-scan.yml` on every push/PR to `main` and twice weekly.
- **Semgrep** (`p/security` ruleset) — cross-language static-analysis security patterns. Runs in `security-scan.yml` on every push/PR to `main` and twice weekly.
- **Safety** — known CVEs in Semantica's own installed dependencies, including optional LLM-provider extras such as LiteLLM; CI fails on any match. Runs in `security-scan.yml` on every push/PR to `main` and twice weekly.
- **pip-audit** — independent, PyPA-maintained vulnerability database cross-check against installed dependencies (Safety and pip-audit use different advisory sources, so both run). Runs in `security.yml` weekly.
- **pip-audit** — PyPA-maintained, OSV-backed vulnerability database cross-check against Semantica's pinned dependency tree, including optional LLM-provider extras such as LiteLLM; CI fails on any match. Runs in `security-scan.yml` on every push/PR to `main` and twice weekly, and can be triggered on demand via `workflow_dispatch`.
- **Microsoft Defender for DevOps** (`eslint`, `templateanalyzer`, `terrascan`) — JavaScript/TypeScript lint-security rules and infrastructure-as-code misconfigurations. Runs in `defender-for-devops.yml` on every push/PR to `main` and weekly.
- **Checkov** — Kubernetes, Helm, Dockerfile, GitHub Actions, and secrets-pattern IaC scanning; results upload to the same Security tab as CodeQL. Runs in `defender-for-devops.yml` on every push/PR to `main` and weekly.
- **GitGuardian** — secret-detection check on every pull request, installed as a GitHub App integration (not a repo-local workflow). Runs on every PR.
@@ -1,222 +0,0 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/semantica-agi/semantica/blob/main/cookbook/advanced/09_Semantic_Layer_Construction.ipynb)\n",
"\n",
"# Semantic Layer Construction\n",
"\n",
"## Overview\n",
"\n",
"Build an enterprise semantic layer: construct knowledge graph, generate ontology, create semantic layer, export RDF, and store in triplet store.\n",
"\n",
"\n",
"**Documentation**: [API Reference](https://semantica.readthedocs.io/concepts/)\n",
"\n",
"## Installation\n",
"\n",
"Install Semantica from PyPI:\n",
"\n",
"```bash\n",
"pip install semantica\n",
"# Or with all optional dependencies:\n",
"pip install semantica[all]\n",
"```\n",
"\n",
"## Workflow: Build KG → Generate Ontology → Create Semantic Layer → Export RDF \n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"!pip install -qU semantica\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"from semantica.kg import GraphBuilder\n",
"from semantica.ontology import OntologyGenerator\n",
"from semantica.export import RDFExporter\n",
"from semantica.triplet_store import TripletStore\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Step 1: Build Knowledge Graph\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"builder = GraphBuilder()\n",
"\n",
"entities = [\n",
" {\"id\": \"e1\", \"type\": \"Person\", \"name\": \"Alice\", \"properties\": {\"age\": 30, \"role\": \"Engineer\"}},\n",
" {\"id\": \"e2\", \"type\": \"Person\", \"name\": \"Bob\", \"properties\": {\"age\": 35, \"role\": \"Manager\"}},\n",
" {\"id\": \"e3\", \"type\": \"Organization\", \"name\": \"Tech Corp\", \"properties\": {\"founded\": 2010}},\n",
" {\"id\": \"e4\", \"type\": \"Project\", \"name\": \"Project Alpha\", \"properties\": {\"status\": \"active\"}},\n",
"]\n",
"\n",
"relationships = [\n",
" {\"source\": \"e1\", \"target\": \"e2\", \"type\": \"reports_to\"},\n",
" {\"source\": \"e1\", \"target\": \"e3\", \"type\": \"works_for\"},\n",
" {\"source\": \"e2\", \"target\": \"e3\", \"type\": \"works_for\"},\n",
" {\"source\": \"e1\", \"target\": \"e4\", \"type\": \"works_on\"},\n",
"]\n",
"\n",
"knowledge_graph = builder.build(entities, relationships)\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Step 2: Generate Ontology\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"generator = OntologyGenerator()\n",
"ontology = generator.generate_from_graph(knowledge_graph)\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Step 3: Create Semantic Layer\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"def create_mappings(kg, ontology):\n",
" mappings = {\n",
" \"entity_type_mappings\": {},\n",
" \"relationship_type_mappings\": {},\n",
" \"property_mappings\": {}\n",
" }\n",
" \n",
" entity_types = set(e.get(\"type\") for e in entities)\n",
" ontology_classes = ontology.get(\"classes\", [])\n",
" \n",
" for entity_type in entity_types:\n",
" matching_class = next((cls for cls in ontology_classes if cls.get(\"name\") == entity_type), None)\n",
" if matching_class:\n",
" mappings[\"entity_type_mappings\"][entity_type] = matching_class.get(\"uri\", entity_type)\n",
" \n",
" relationship_types = set(r.get(\"type\") for r in relationships)\n",
" ontology_properties = ontology.get(\"properties\", [])\n",
" \n",
" for rel_type in relationship_types:\n",
" matching_prop = next((prop for prop in ontology_properties if prop.get(\"name\") == rel_type), None)\n",
" if matching_prop:\n",
" mappings[\"relationship_type_mappings\"][rel_type] = matching_prop.get(\"uri\", rel_type)\n",
" \n",
" return mappings\n",
"\n",
"mappings = create_mappings(knowledge_graph, ontology)\n",
"\n",
"semantic_layer = {\n",
" \"graph\": knowledge_graph,\n",
" \"ontology\": ontology,\n",
" \"mappings\": mappings,\n",
" \"metadata\": {\n",
" \"version\": \"1.0\",\n",
" \"created_at\": \"2024-01-01\",\n",
" \"description\": \"Enterprise semantic layer\"\n",
" }\n",
"}\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Step 4: Export RDF\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"exporter = RDFExporter()\n",
"# Export Knowledge Graph\n",
"exporter.export(knowledge_graph, \"knowledge_graph.ttl\", format=\"turtle\")\n",
"print(\"Exported knowledge graph to knowledge_graph.ttl\")\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Summary\n",
"\n",
"Enterprise semantic layer construction:\n",
"- Knowledge Graph Built\n",
"- Ontology Generated\n",
"- Semantic Layer Created with Mappings\n",
"- RDF Export Completed\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": []
},
{
"cell_type": "markdown",
"metadata": {},
"source": []
},
{
"cell_type": "markdown",
"metadata": {},
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.11.9"
}
},
"nbformat": 4,
"nbformat_minor": 2
}
@@ -1,435 +0,0 @@
{
"nbformat": 4,
"nbformat_minor": 5,
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"name": "python",
"version": "3.10.0"
}
},
"cells": [
{
"cell_type": "markdown",
"id": "cell-0",
"metadata": {},
"source": [
"[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/semantica-agi/semantica/blob/main/cookbook/advanced/13_Manual_Ontology_Snowflake_Mapping.ipynb)\n",
"\n",
"# Manual Ontology + Snowflake Mapping\n",
"\n",
"This notebook answers a specific workflow:\n",
"\n",
"> *\"I want to design the ontology myself — not have AI infer it from my tables — and then map Snowflake data to it explicitly.\"*\n",
"\n",
"### What this notebook demonstrates\n",
"\n",
"| Step | What happens | Who controls it |\n",
"|---|---|---|\n",
"| 1 | Design ontology classes and properties | **You** (Python dict) |\n",
"| 2 | Model n-ary facts with reification | **You** (`AssociativeClassBuilder`) |\n",
"| 3 | Pull rows from Snowflake | Semantica `SnowflakeIngestor` |\n",
"| 4 | Map columns → ontology-aligned graph | **You** (explicit transform) |\n",
"| 5 | Validate + export OWL / SHACL | Semantica `OntologyEngine` |\n",
"| 6 | Load to triplet store and query | Semantica `TripletStore` |\n",
"\n",
"### What this notebook does NOT do\n",
"\n",
"- No LLM-driven ontology generation\n",
"- No schema introspection or table-to-class inference\n",
"- No \"suggest ontology from my data\"\n",
"\n",
"### Standards coverage\n",
"\n",
"| Feature | Status |\n",
"|---|---|\n",
"| OWL 2 (Turtle / RDF-XML) | Supported |\n",
"| SHACL 1.1 shapes | Supported |\n",
"| SPARQL 1.1 | Supported |\n",
"| Reification / n-ary facts | Supported via `AssociativeClassBuilder` |\n",
"| SPARQL 1.2 (reifier annotation, `LATERAL`) | Planned |\n",
"| SHACL 1.2 (`sh:severity` extensions, SHACL-AF) | Planned |"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "cell-1",
"metadata": {},
"outputs": [],
"source": [
"!pip install -qU semantica"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "cell-2",
"metadata": {},
"outputs": [],
"source": [
"import os\n",
"from typing import Any, Dict, List\n",
"\n",
"from semantica.ingest import SnowflakeIngestor\n",
"from semantica.kg.methods import build_kg\n",
"from semantica.ontology import AssociativeClassBuilder, OntologyEngine\n",
"from semantica.triplet_store import TripletStore"
]
},
{
"cell_type": "markdown",
"id": "cell-3",
"metadata": {},
"source": [
"## Step 1: Hand-Design the Ontology in Python\n",
"\n",
"You define every class and property explicitly. Nothing is read from Snowflake at this stage.\n",
"\n",
"**Design decisions that belong to you:**\n",
"- Which classes exist and what they mean\n",
"- Which properties are datatype vs. object properties\n",
"- Domain, range, and cardinality constraints\n",
"- Which properties are required (later enforced by SHACL)\n",
"\n",
"This dict versions with your code. It does not change when your database schema changes."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "cell-4",
"metadata": {},
"outputs": [],
"source": "BASE_URI = \"https://example.com/hr/\"\n\n# Your ontology — designed by you, not inferred by Semantica.\nontology: Dict[str, Any] = {\n \"name\": \"EmploymentDomainOntology\",\n \"uri\": f\"{BASE_URI}EmploymentDomainOntology\",\n \"namespace\": {\"base_uri\": BASE_URI},\n\n # You decide the class taxonomy\n \"classes\": [\n {\"name\": \"Person\", \"uri\": f\"{BASE_URI}Person\"},\n {\"name\": \"Organization\", \"uri\": f\"{BASE_URI}Organization\"},\n {\"name\": \"Role\", \"uri\": f\"{BASE_URI}Role\"},\n # EmploymentEvent is a reification node.\n # It connects Person + Organization + Role and carries salary/date context.\n {\"name\": \"EmploymentEvent\", \"uri\": f\"{BASE_URI}EmploymentEvent\"},\n ],\n\n # Each property carries a full URI so TripletStore stores it as hr:<name>\n # rather than the default urn:property:<name>.\n # This ensures SPARQL queries using PREFIX hr: match what is actually stored.\n \"properties\": [\n # Datatype properties\n {\"name\": \"name\", \"uri\": f\"{BASE_URI}name\", \"type\": \"datatype\", \"domain\": \"Person\", \"range\": \"string\", \"required\": True},\n {\"name\": \"legalName\", \"uri\": f\"{BASE_URI}legalName\", \"type\": \"datatype\", \"domain\": \"Organization\", \"range\": \"string\", \"required\": True},\n {\"name\": \"title\", \"uri\": f\"{BASE_URI}title\", \"type\": \"datatype\", \"domain\": \"Role\", \"range\": \"string\", \"required\": True},\n {\"name\": \"startDate\", \"uri\": f\"{BASE_URI}startDate\", \"type\": \"datatype\", \"domain\": \"EmploymentEvent\", \"range\": \"date\"},\n {\"name\": \"endDate\", \"uri\": f\"{BASE_URI}endDate\", \"type\": \"datatype\", \"domain\": \"EmploymentEvent\", \"range\": \"date\"},\n {\"name\": \"salary\", \"uri\": f\"{BASE_URI}salary\", \"type\": \"datatype\", \"domain\": \"EmploymentEvent\", \"range\": \"decimal\"},\n\n # Object properties — reification spokes (required)\n {\"name\": \"employee\", \"uri\": f\"{BASE_URI}employee\", \"type\": \"object\", \"domain\": \"EmploymentEvent\", \"range\": \"Person\", \"required\": True},\n {\"name\": \"employer\", \"uri\": f\"{BASE_URI}employer\", \"type\": \"object\", \"domain\": \"EmploymentEvent\", \"range\": \"Organization\", \"required\": True},\n {\"name\": \"role\", \"uri\": f\"{BASE_URI}role\", \"type\": \"object\", \"domain\": \"EmploymentEvent\", \"range\": \"Role\", \"required\": True},\n\n # Shortcut edges — direct person→org / person→role without traversing the event node\n {\"name\": \"worksFor\", \"uri\": f\"{BASE_URI}worksFor\", \"type\": \"object\", \"domain\": \"Person\", \"range\": \"Organization\"},\n {\"name\": \"hasRole\", \"uri\": f\"{BASE_URI}hasRole\", \"type\": \"object\", \"domain\": \"Person\", \"range\": \"Role\"},\n ],\n}\n\nontology"
},
{
"cell_type": "markdown",
"id": "cell-5",
"metadata": {},
"source": [
"## Step 2: Reification — Modeling N-Ary Facts\n",
"\n",
"**The problem with binary triples:**\n",
"A simple triple `(Alice, worksFor, Acme)` cannot carry extra context such as salary, start date, or role.\n",
"Standard RDF reification and OWL n-ary patterns solve this by introducing an intermediate node.\n",
"\n",
"Semantica's `AssociativeClassBuilder` is the Pythonic API for this pattern:\n",
"\n",
"```\n",
"EmploymentEvent\n",
" ├── employee → Person (required)\n",
" ├── employer → Organization (required)\n",
" ├── role → Role (required)\n",
" ├── startDate → xsd:date\n",
" ├── endDate → xsd:date\n",
" └── salary → xsd:decimal\n",
"```\n",
"\n",
"**On SPARQL 1.1 vs. SPARQL 1.2:**\n",
"- **SPARQL 1.1 (current):** traverse the event node explicitly — `?event hr:employee ?person ; hr:salary ?salary`\n",
"- **SPARQL 1.2 (planned):** the draft reifier annotation syntax allows attaching context to triples directly, without a separate intermediate node. Semantica will adopt this once the spec is ratified.\n",
"\n",
"**On SHACL 1.1 vs. SHACL 1.2:**\n",
"- **SHACL 1.1 (current):** `sh:NodeShape` + `sh:PropertyShape` constraints are exported for all `required` properties and enforced at load time.\n",
"- **SHACL 1.2 (planned):** `sh:severity` profile extensions and SHACL-AF rules are on the roadmap."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "cell-6",
"metadata": {},
"outputs": [],
"source": "assoc_builder = AssociativeClassBuilder()\n\nemployment_assoc = assoc_builder.create_associative_class(\n name=\"EmploymentEvent\",\n connects=[\"Person\", \"Organization\", \"Role\"],\n temporal=True, # adds startDate / endDate handling\n properties={\n \"startDate\": \"xsd:date\",\n \"endDate\": \"xsd:date\",\n \"salary\": \"xsd:decimal\",\n },\n)\n\nvalidation_result = assoc_builder.validate_associative_class(employment_assoc)\n\n# AssociativeClass is a dataclass — use attribute access, not .get()\nprint(\"AssociativeClass structure:\")\nprint(f\" name: {employment_assoc.name}\")\nprint(f\" connects: {employment_assoc.connects}\")\nprint(f\" temporal: {employment_assoc.temporal}\")\nprint(f\" properties: {list(employment_assoc.properties.keys())}\")\nprint(f\"\\nValidation passed: {validation_result}\")"
},
{
"cell_type": "markdown",
"id": "cell-7",
"metadata": {},
"source": [
"## Step 3: Ingest Snowflake Rows (Extraction Only)\n",
"\n",
"`SnowflakeIngestor` retrieves rows — nothing more. It does **not**:\n",
"- Inspect your table schema\n",
"- Suggest classes or properties\n",
"- Infer relationships from column names\n",
"\n",
"Set `USE_LIVE_SNOWFLAKE=true` plus the env vars below to connect to a real warehouse.\n",
"Otherwise the stub data is used."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "cell-8",
"metadata": {},
"outputs": [],
"source": [
"def fetch_rows_from_snowflake() -> List[Dict[str, Any]]:\n",
" if os.getenv(\"USE_LIVE_SNOWFLAKE\", \"false\").lower() != \"true\":\n",
" return [\n",
" {\n",
" \"EMPLOYEE_ID\": \"E100\",\n",
" \"EMPLOYEE_NAME\": \"Alice Johnson\",\n",
" \"ORG_ID\": \"O10\",\n",
" \"ORG_NAME\": \"Acme Corp\",\n",
" \"ROLE_ID\": \"R7\",\n",
" \"ROLE_TITLE\": \"Senior Engineer\",\n",
" \"START_DATE\": \"2025-01-15\",\n",
" \"END_DATE\": None,\n",
" \"SALARY\": 160000,\n",
" },\n",
" {\n",
" \"EMPLOYEE_ID\": \"E101\",\n",
" \"EMPLOYEE_NAME\": \"Bob Singh\",\n",
" \"ORG_ID\": \"O10\",\n",
" \"ORG_NAME\": \"Acme Corp\",\n",
" \"ROLE_ID\": \"R9\",\n",
" \"ROLE_TITLE\": \"Data Architect\",\n",
" \"START_DATE\": \"2024-09-01\",\n",
" \"END_DATE\": None,\n",
" \"SALARY\": 185000,\n",
" },\n",
" ]\n",
"\n",
" ingestor = SnowflakeIngestor(\n",
" account=os.getenv(\"SNOWFLAKE_ACCOUNT\"),\n",
" user=os.getenv(\"SNOWFLAKE_USER\"),\n",
" password=os.getenv(\"SNOWFLAKE_PASSWORD\"),\n",
" warehouse=os.getenv(\"SNOWFLAKE_WAREHOUSE\"),\n",
" database=os.getenv(\"SNOWFLAKE_DATABASE\"),\n",
" schema=os.getenv(\"SNOWFLAKE_SCHEMA\", \"PUBLIC\"),\n",
" )\n",
" query = (\n",
" \"SELECT EMPLOYEE_ID, EMPLOYEE_NAME, \"\n",
" \"ORG_ID, ORG_NAME, ROLE_ID, ROLE_TITLE, \"\n",
" \"START_DATE, END_DATE, SALARY \"\n",
" \"FROM HR_EMPLOYMENT_FACT\"\n",
" )\n",
" data = ingestor.ingest_query(query)\n",
" ingestor.close()\n",
" return data.data\n",
"\n",
"\n",
"rows = fetch_rows_from_snowflake()\n",
"rows[:2]"
]
},
{
"cell_type": "markdown",
"id": "cell-9",
"metadata": {},
"source": [
"## Step 4: Map Rows to Ontology Concepts Explicitly\n",
"\n",
"This is the semantic transformation layer — the part that makes your ontology real.\n",
"\n",
"Semantica does not guess which column becomes which entity or property.\n",
"Every assignment is code you write and own:\n",
"\n",
"- **Stable node IDs** — deterministic, collision-safe, derived from business keys\n",
"- **Class assignment** — matches what you declared in Step 1\n",
"- **Property routing** — each column value goes to the correct ontology property\n",
"- **Reification wiring** — `EmploymentEvent` is linked to its three participants\n",
"\n",
"When your Snowflake schema changes, only this function needs updating. The ontology stays stable."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "cell-10",
"metadata": {},
"outputs": [],
"source": "def map_rows_to_kg(rows: List[Dict[str, Any]]) -> Dict[str, Any]:\n entities: Dict[str, Dict[str, Any]] = {}\n relationships: List[Dict[str, Any]] = []\n\n for row in rows:\n # Stable, deterministic node IDs derived from business keys\n person_id = f\"person:{row['EMPLOYEE_ID']}\"\n org_id = f\"org:{row['ORG_ID']}\"\n role_id = f\"role:{row['ROLE_ID']}\"\n # Event ID includes all three participants + start date so that\n # a re-hired employee gets a distinct event node, not an overwrite.\n event_id = f\"employment:{row['EMPLOYEE_ID']}:{row['ORG_ID']}:{row['START_DATE']}\"\n\n # Entities — \"type\" must match a class name from Step 1\n entities[person_id] = {\n \"id\": person_id,\n \"type\": \"Person\",\n \"properties\": {\"name\": row[\"EMPLOYEE_NAME\"]},\n }\n entities[org_id] = {\n \"id\": org_id,\n \"type\": \"Organization\",\n \"properties\": {\"legalName\": row[\"ORG_NAME\"]},\n }\n entities[role_id] = {\n \"id\": role_id,\n \"type\": \"Role\",\n \"properties\": {\"title\": row[\"ROLE_TITLE\"]},\n }\n\n # Reification node — filter out None values so TripletStore does not\n # stringify None as the literal \"None\" for open-ended employment.\n event_props = {\n \"startDate\": row[\"START_DATE\"],\n \"endDate\": row[\"END_DATE\"],\n \"salary\": row[\"SALARY\"],\n }\n entities[event_id] = {\n \"id\": event_id,\n \"type\": \"EmploymentEvent\",\n \"properties\": {k: v for k, v in event_props.items() if v is not None},\n }\n\n # Full URIs for relationship types so TripletStore stores hr:<type>\n # instead of the default urn:property:<type>, keeping SPARQL consistent.\n relationships.extend([\n # Shortcut edges — fast SPARQL when context is not needed\n {\"source\": person_id, \"target\": org_id, \"type\": f\"{BASE_URI}worksFor\"},\n {\"source\": person_id, \"target\": role_id, \"type\": f\"{BASE_URI}hasRole\"},\n # Reification spokes — full context via the event node\n {\"source\": event_id, \"target\": person_id, \"type\": f\"{BASE_URI}employee\"},\n {\"source\": event_id, \"target\": org_id, \"type\": f\"{BASE_URI}employer\"},\n {\"source\": event_id, \"target\": role_id, \"type\": f\"{BASE_URI}role\"},\n ])\n\n return build_kg([{\"entities\": list(entities.values()), \"relationships\": relationships}])\n\n\nkg = map_rows_to_kg(rows)\nprint(f\"Entities built: {len(kg.get('entities', []))}\")\nprint(f\"Relationships built: {len(kg.get('relationships', []))}\")\n\nsample = next((e for e in kg[\"entities\"] if e[\"type\"] == \"EmploymentEvent\"), None)\nprint(f\"\\nSample EmploymentEvent node: {sample}\")"
},
{
"cell_type": "markdown",
"id": "cell-11",
"metadata": {},
"source": [
"## Step 5: Validate Ontology and Export OWL + SHACL\n",
"\n",
"`OntologyEngine` validates your ontology dict and serialises it to standards-compliant files.\n",
"\n",
"**Output files:**\n",
"- `employment_manual_ontology.ttl` — OWL 2 Turtle\n",
"- `employment_manual_shapes.ttl` — SHACL 1.1 node and property shapes\n",
"\n",
"**Standards status:**\n",
"\n",
"| Standard | Semantica support |\n",
"|---|---|\n",
"| SPARQL 1.1 | Full |\n",
"| SHACL 1.1 (`sh:NodeShape`, `sh:PropertyShape`, `sh:minCount`, `sh:datatype`, `sh:class`) | Full |\n",
"| SPARQL 1.2 (reifier annotation syntax, `LATERAL`) | Tracked — not yet implemented |\n",
"| SHACL 1.2 (`sh:severity` profiles, SHACL-AF extensions) | Tracked — not yet implemented |"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "cell-12",
"metadata": {},
"outputs": [],
"source": [
"engine = OntologyEngine(base_uri=BASE_URI)\n",
"\n",
"validation = engine.validate(ontology)\n",
"owl_ttl = engine.to_owl(ontology, format=\"turtle\")\n",
"shacl_ttl = engine.to_shacl(ontology, format=\"turtle\")\n",
"\n",
"engine.export_owl(ontology, \"employment_manual_ontology.ttl\", format=\"turtle\")\n",
"engine.export_shacl(ontology, \"employment_manual_shapes.ttl\", format=\"turtle\")\n",
"\n",
"print(f\"Ontology valid: {validation.valid}\")\n",
"print(f\"Ontology consistent: {validation.consistent}\")\n",
"print(f\"OWL output: {len(owl_ttl):,} chars → employment_manual_ontology.ttl\")\n",
"print(f\"SHACL output: {len(shacl_ttl):,} chars → employment_manual_shapes.ttl\")\n",
"\n",
"print(\"\\n--- SHACL shapes (first 20 lines) ---\")\n",
"print(\"\\n\".join(shacl_ttl.splitlines()[:20]))"
]
},
{
"cell_type": "markdown",
"id": "cell-13",
"metadata": {},
"source": [
"## Best-Practice Architecture\n",
"\n",
"```\n",
"┌──────────────────────────────────┐\n",
"│ Ontology as code (Python dict) │ ← versioned alongside your application\n",
"│ + AssociativeClass for n-ary │\n",
"└───────────────┬──────────────────┘\n",
" │ validate + export\n",
" ▼\n",
"┌───────────────────────────────────┐\n",
"│ OWL 2 Turtle │ SHACL 1.1 │ ← standards-compliant artifacts\n",
"└───────────────┬───────────────────┘\n",
" │\n",
" ▼\n",
"┌──────────────────────────────────┐\n",
"│ Snowflake — raw data access │ ← no schema introspection\n",
"└───────────────┬──────────────────┘\n",
" │ explicit mapping layer\n",
" ▼\n",
"┌──────────────────────────────────┐\n",
"│ Ontology-aligned KG │ ← types, IDs, edges match Step 1\n",
"└───────────────┬──────────────────┘\n",
" │ optional\n",
" ▼\n",
"┌──────────────────────────────────┐\n",
"│ Triplet store + SPARQL 1.1 │\n",
"└──────────────────────────────────┘\n",
"```\n",
"\n",
"**Why this split matters:**\n",
"If Semantica inferred the ontology from your Snowflake schema, every schema migration would risk silently changing your semantic model.\n",
"With this pattern, schema changes only touch the mapping function in Step 4 — the ontology remains stable and under your control."
]
},
{
"cell_type": "markdown",
"id": "cell-14",
"metadata": {},
"source": [
"## SPARQL Query Patterns\n",
"\n",
"Two query styles are available because we wrote both shortcut edges and reification spokes.\n",
"\n",
"### Simple lookup — shortcut edge (no context needed)\n",
"\n",
"```sparql\n",
"PREFIX hr: <https://example.com/hr/>\n",
"\n",
"SELECT ?personName ?orgName\n",
"WHERE {\n",
" ?person a hr:Person ;\n",
" hr:name ?personName ;\n",
" hr:worksFor ?org .\n",
" ?org hr:legalName ?orgName .\n",
"}\n",
"```\n",
"\n",
"### Contextual lookup — via reification node (salary, dates, role)\n",
"\n",
"```sparql\n",
"PREFIX hr: <https://example.com/hr/>\n",
"\n",
"SELECT ?personName ?roleTitle ?salary ?startDate\n",
"WHERE {\n",
" ?event a hr:EmploymentEvent ;\n",
" hr:employee ?person ;\n",
" hr:role ?role ;\n",
" hr:salary ?salary ;\n",
" hr:startDate ?startDate .\n",
" ?person hr:name ?personName .\n",
" ?role hr:title ?roleTitle .\n",
"}\n",
"ORDER BY DESC(?salary)\n",
"```\n",
"\n",
"### Future: SPARQL 1.2 reifier syntax\n",
"\n",
"The SPARQL 1.2 draft introduces annotation syntax that lets you attach context directly to triples, without a separate intermediate node.\n",
"Once the spec is ratified Semantica will adopt it, and the contextual query above may be expressible more concisely."
]
},
{
"cell_type": "markdown",
"id": "cell-15",
"metadata": {},
"source": [
"## Step 6 (Optional): Load to Triplet Store and Run SPARQL\n",
"\n",
"Set `STORE_TO_TRIPLET=true` to load the KG into a live triplet store and run the contextual reification query."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "cell-16",
"metadata": {},
"outputs": [],
"source": [
"if os.getenv(\"STORE_TO_TRIPLET\", \"false\").lower() == \"true\":\n",
" store = TripletStore(\n",
" backend=os.getenv(\"TRIPLET_BACKEND\", \"blazegraph\"),\n",
" endpoint=os.getenv(\"TRIPLET_ENDPOINT\", \"http://localhost:9999/blazegraph\"),\n",
" namespace=os.getenv(\"TRIPLET_NAMESPACE\", \"kb\"),\n",
" )\n",
" store_result = store.store(knowledge_graph=kg, ontology=ontology)\n",
" print(\"Store result:\", store_result)\n",
"\n",
" # Contextual reification query — person + role + salary via EmploymentEvent\n",
" query = \"\"\"\n",
" PREFIX hr: <https://example.com/hr/>\n",
"\n",
" SELECT ?personName ?roleTitle ?salary ?startDate\n",
" WHERE {\n",
" ?event a hr:EmploymentEvent ;\n",
" hr:employee ?person ;\n",
" hr:role ?role ;\n",
" hr:salary ?salary ;\n",
" hr:startDate ?startDate .\n",
" ?person hr:name ?personName .\n",
" ?role hr:title ?roleTitle .\n",
" }\n",
" ORDER BY DESC(?salary)\n",
" LIMIT 10\n",
" \"\"\"\n",
" result = store.execute_query(query)\n",
" print(result)\n",
"else:\n",
" print(\"Skipping triplet-store load/query (set STORE_TO_TRIPLET=true to enable)\")"
]
}
]
}
@@ -10,15 +10,16 @@
"\n",
"## Overview\n",
"\n",
"This notebook demonstrates how to build knowledge graphs from entities and relationships using Semantica's graph building modules. You'll learn to use `GraphBuilder` and `EntityResolver`.\n",
"This notebook demonstrates how to build knowledge graphs from extracted entities and relationships using Semantica's graph building modules. You'll learn to use `GraphBuilder` and `EntityResolver`.\n",
"\n",
"**Documentation**: [API Reference](https://semantica.readthedocs.io/reference/kg/)\n",
"\n",
"### Learning Objectives\n",
"\n",
"- Use `GraphBuilder` to construct knowledge graphs\n",
"- Use `EntityResolver` to resolve entity conflicts\n",
"**Note**: For deduplication, use the `semantica.deduplication` module.\n",
"- Extract entity mentions and relations, and map them into graph records\n",
"- Use `GraphBuilder` to construct a graph whose edges come from the actual extracted relations\n",
"- Use `EntityResolver` to merge duplicate mentions and remap relationship endpoints\n",
"- Use the `semantica.deduplication` module and report the complete deduplicated entity set\n",
"\n",
"## Installation\n",
"\n",
@@ -32,120 +33,217 @@
"\n",
"---\n",
"\n",
"## Step 1: Build Knowledge Graph\n",
"## Step 1: Extract Entities and Relations\n",
"\n",
"Construct a knowledge graph from entities and relationships.\n"
"Extract entity mentions and relations from text. The sample text mentions `Apple Inc.` in two separate sentences, so we can later show how duplicate mentions are resolved into one canonical entity.\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"!pip install semantica\n"
]
"%pip install semantica\n",
"\n",
"# spaCy models are distributed separately from the spaCy library. This lesson\n",
"# relies on the English model to recognize standalone places such as Cupertino.\n",
"import sys\n",
"import subprocess\n",
"import spacy\n",
"\n",
"try:\n",
" spacy.load(\"en_core_web_sm\")\n",
"except OSError:\n",
" subprocess.check_call([sys.executable, \"-m\", \"spacy\", \"download\", \"en_core_web_sm\"])\n"
],
"execution_count": null,
"outputs": []
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"from semantica.kg import GraphBuilder\n",
"from semantica.semantic_extract import NERExtractor, RelationExtractor\n",
"\n",
"builder = GraphBuilder()\n",
"text = (\n",
" \"Apple Inc. is headquartered in Cupertino, California. \"\n",
" \"Tim Cook is the CEO of Apple Inc. \"\n",
" \"The company is a technology company.\"\n",
")\n",
"\n",
"ner_extractor = NERExtractor()\n",
"relation_extractor = RelationExtractor()\n",
"\n",
"text = \"Apple Inc. is a technology company. Tim Cook is the CEO of Apple Inc. Apple Inc. is headquartered in Cupertino, California.\"\n",
"mentions = ner_extractor.extract(text)\n",
"relations = relation_extractor.extract(text, mentions)\n",
"\n",
"entities_list = ner_extractor.extract(text)\n",
"relationships_list = relation_extractor.extract(text, entities_list)\n",
"print(\"Entity mentions:\")\n",
"for mention in mentions:\n",
" print(f\" {mention.text!r:<13} {mention.label:<7} span=[{mention.start_char}:{mention.end_char}]\")\n",
"\n",
"entities = []\n",
"for i, entity in enumerate(entities_list[:5], 1):\n",
" entities.append({\n",
" \"id\": f\"e{i}\",\n",
" \"type\": entity.label,\n",
" \"name\": entity.text,\n",
" \"properties\": {}\n",
" })\n",
"\n",
"relationships = []\n",
"for i, rel in enumerate(relationships_list[:3], 1):\n",
" relationships.append({\n",
" \"source\": f\"e{1}\",\n",
" \"target\": f\"e{i+1}\",\n",
" \"type\": rel.predicate,\n",
" \"properties\": {}\n",
" })\n",
"\n",
"knowledge_graph = builder.build(entities, relationships)\n",
"\n",
"print(f\"Built knowledge graph with {len(knowledge_graph.get('entities', []))} entities\")\n",
"print(f\"Relationships: {len(knowledge_graph.get('relationships', []))}\")"
]
"print(\"\\nExtracted relations:\")\n",
"for rel in relations:\n",
" print(f\" {rel.subject.text!r} --{rel.predicate}--> {rel.object.text!r}\")"
],
"execution_count": null,
"outputs": []
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Step 2: Entity Resolution\n",
"## Step 2: Build the Knowledge Graph\n",
"\n",
"Resolve entity conflicts and duplicates.\n"
"Give every mention a graph ID, then translate each relation's `subject` and `object` into those IDs. Building edges from the actual relation endpoints — rather than guessing endpoints from list positions — is what keeps the graph faithful to the text.\n"
]
},
{
"cell_type": "code",
"metadata": {},
"source": [
"from semantica.kg import GraphBuilder\n",
"\n",
"entities = []\n",
"span_to_id = {}\n",
"for i, mention in enumerate(mentions, 1):\n",
" graph_id = f\"e{i}\"\n",
" span_to_id[(mention.start_char, mention.end_char)] = graph_id\n",
" entities.append({\n",
" \"id\": graph_id,\n",
" \"type\": mention.label,\n",
" \"name\": mention.text,\n",
" \"properties\": {},\n",
" })\n",
"\n",
"relationships = []\n",
"for rel in relations:\n",
" source_id = span_to_id.get((rel.subject.start_char, rel.subject.end_char))\n",
" target_id = span_to_id.get((rel.object.start_char, rel.object.end_char))\n",
" if source_id is None or target_id is None:\n",
" print(f\"Skipping relation with unmapped endpoint: \"\n",
" f\"{rel.subject.text!r} --{rel.predicate}--> {rel.object.text!r}\")\n",
" continue\n",
" relationships.append({\n",
" \"source\": source_id,\n",
" \"target\": target_id,\n",
" \"type\": rel.predicate,\n",
" \"properties\": {},\n",
" })\n",
"\n",
"builder = GraphBuilder()\n",
"knowledge_graph = builder.build({\"entities\": entities, \"relationships\": relationships})\n",
"\n",
"id_to_name = {entity[\"id\"]: entity[\"name\"] for entity in entities}\n",
"\n",
"print(f\"Graph entities ({len(knowledge_graph['entities'])}):\")\n",
"for entity in knowledge_graph[\"entities\"]:\n",
" print(f\" {entity['id']}: {entity['name']} ({entity['type']})\")\n",
"\n",
"print(f\"\\nGraph relationships ({len(knowledge_graph['relationships'])}):\")\n",
"for relationship in knowledge_graph[\"relationships\"]:\n",
" print(f\" {id_to_name[relationship['source']]} \"\n",
" f\"--{relationship['type']}--> {id_to_name[relationship['target']]}\")\n",
"\n",
"edges = {\n",
" (id_to_name[r[\"source\"]], r[\"type\"], id_to_name[r[\"target\"]])\n",
" for r in knowledge_graph[\"relationships\"]\n",
"}\n",
"assert (\"Apple Inc.\", \"located_in\", \"Cupertino\") in edges\n",
"assert (\"Tim Cook\", \"works_for\", \"Apple Inc.\") in edges"
],
"execution_count": null,
"outputs": []
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Step 3: Entity Resolution\n",
"\n",
"The graph currently contains two nodes for the same organization. `EntityResolver` merges duplicate mentions into one canonical entity and records which source IDs were merged (`merged_from`), so relationship endpoints can be remapped onto the canonical entity.\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"from semantica.kg import EntityResolver\n",
"\n",
"entity_resolver = EntityResolver()\n",
"\n",
"resolved_entities = entity_resolver.resolve_entities(entities)\n",
"\n",
"print(f\"Original entities: {len(entities)}\")\n",
"print(f\"Resolved entities: {len(resolved_entities)}\")"
]
"canonical_id = {}\n",
"for entity in resolved_entities:\n",
" for source_id in entity.get(\"merged_from\", [entity[\"id\"]]):\n",
" canonical_id[source_id] = entity[\"id\"]\n",
" if entity.get(\"merged_from\"):\n",
" print(f\"Merged {entity['merged_from']} -> {entity['id']}: {entity['name']}\")\n",
"\n",
"print(f\"\\nMentions in: {len(entities)}, resolved entities out: {len(resolved_entities)}\")\n",
"\n",
"resolved_names = {entity[\"id\"]: entity[\"name\"] for entity in resolved_entities}\n",
"print(\"\\nRelationships remapped onto canonical entities:\")\n",
"for relationship in relationships:\n",
" source = canonical_id[relationship[\"source\"]]\n",
" target = canonical_id[relationship[\"target\"]]\n",
" print(f\" {resolved_names[source]} --{relationship['type']}--> {resolved_names[target]}\")\n",
"\n",
"canonical_entities = {(entity[\"name\"], entity[\"type\"]) for entity in resolved_entities}\n",
"assert canonical_entities == {\n",
" (\"Apple Inc.\", \"ORG\"),\n",
" (\"Tim Cook\", \"PERSON\"),\n",
" (\"Cupertino\", \"GPE\"),\n",
" (\"California\", \"GPE\"),\n",
"}\n",
"assert len(resolved_entities) == 4"
],
"execution_count": null,
"outputs": []
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Step 3: Deduplication\n",
"## Step 4: Deduplication\n",
"\n",
"Remove duplicate entities from the graph.\n"
"The `semantica.deduplication` module gives finer control over the same problem. Note that `merge_duplicates` returns one `MergeOperation` per duplicate *group* — the complete deduplicated collection is those merged entities plus every entity that was not part of any group.\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"from semantica.deduplication import DuplicateDetector, EntityMerger, MergeStrategy\n",
"\n",
"# Detect duplicates\n",
"detector = DuplicateDetector(similarity_threshold=0.8)\n",
"duplicate_groups = detector.detect_duplicate_groups(knowledge_graph.get('entities', []))\n",
"duplicate_groups = detector.detect_duplicate_groups(entities)\n",
"print(f\"Duplicate groups: {len(duplicate_groups)}\")\n",
"for group in duplicate_groups:\n",
" print(f\" {[entity['name'] for entity in group.entities]} \"\n",
" f\"(confidence={group.confidence:.2f})\")\n",
"\n",
"# Merge duplicates\n",
"merger = EntityMerger()\n",
"merge_operations = merger.merge_duplicates(\n",
" knowledge_graph.get('entities', []),\n",
" strategy=MergeStrategy.KEEP_MOST_COMPLETE\n",
" entities, strategy=MergeStrategy.KEEP_MOST_COMPLETE\n",
")\n",
"\n",
"deduplicated_entities = [op.merged_entity for op in merge_operations]\n",
"merged_source_ids = {\n",
" entity[\"id\"] for op in merge_operations for entity in op.source_entities\n",
"}\n",
"untouched_entities = [e for e in entities if e[\"id\"] not in merged_source_ids]\n",
"deduplicated_entities = untouched_entities + [\n",
" op.merged_entity for op in merge_operations\n",
"]\n",
"\n",
"print(f\"Original entities: {len(knowledge_graph.get('entities', []))}\")\n",
"print(f\"Deduplicated entities: {len(deduplicated_entities)}\")\n"
]
"print(f\"\\nMerge operations: {len(merge_operations)}\")\n",
"print(f\"Deduplicated entities ({len(deduplicated_entities)}):\")\n",
"for entity in deduplicated_entities:\n",
" print(f\" {entity['id']}: {entity['name']} ({entity['type']})\")\n",
"\n",
"assert len(merge_operations) == 1\n",
"assert len(deduplicated_entities) == 4"
],
"execution_count": null,
"outputs": []
},
{
"cell_type": "markdown",
@@ -155,9 +253,10 @@
"\n",
"You've learned how to build knowledge graphs:\n",
"\n",
"- **GraphBuilder**: Construct knowledge graphs from entities and relationships\n",
"- **EntityResolver**: Resolve entity conflicts and duplicates\n",
"- **Deduplication**: Use `semantica.deduplication` module for removing duplicate entities\n",
"- **Extraction to graph**: map each mention to a graph ID and build edges from the actual `Relation.subject` / `Relation.object` endpoints\n",
"- **GraphBuilder**: construct knowledge graphs from explicit `{\"entities\": ..., \"relationships\": ...}` input\n",
"- **EntityResolver**: merge duplicate mentions into canonical entities and remap relationship endpoints\n",
"- **Deduplication**: combine `MergeOperation` results with untouched entities to get the complete deduplicated set\n",
"\n",
"Next: Learn how to analyze graphs in the Graph_Analytics notebook.\n"
]
@@ -10,7 +10,7 @@
"\n",
"## Overview\n",
"\n",
"This notebook walks you through creating your first knowledge graph from a simple document. You'll learn the complete end-to-end workflow from ingesting a file to visualizing the resulting knowledge graph.\n",
"This notebook walks you through creating your first knowledge graph from a simple document. You'll learn the complete end-to-end workflow from ingesting a file to visualizing the resulting knowledge graph — and every step consumes the real output of the step before it.\n",
"\n",
"> [!TIP]\n",
"> This is the perfect starting point if you are new to Semantica. No prior knowledge of knowledge graphs is required!\n",
@@ -19,10 +19,10 @@
"\n",
"### 🎯 Learning Objectives\n",
"\n",
"- **Understand the Workflow**: Learn the `File → Parse → Extract → Graph` pipeline\n",
"- **Understand the Workflow**: Learn the `File → Parse → Extract → Graph → Visualize` pipeline\n",
"- **Ingest Data**: Load documents using `FileIngestor`\n",
"- **Parse Content**: Extract text using `DocumentParser`\n",
"- **Extract Knowledge**: Identify entities using `NERExtractor`\n",
"- **Extract Knowledge**: Identify entities and relations using `NERExtractor` and `RelationExtractor`\n",
"- **Build Graph**: Construct a graph using `GraphBuilder`\n",
"- **Visualize**: See your graph come to life with `KGVisualizer`\n",
"\n",
@@ -40,71 +40,76 @@
"\n",
"## 🔄 Simple End-to-End Workflow\n",
"\n",
"The complete workflow consists of four main steps:\n",
"The complete workflow consists of five main steps:\n",
"\n",
"1. **📥 Ingest** - Load data from files or other sources\n",
"2. **📄 Parse** - Extract and structure content from documents\n",
"3. **⛏️ Extract** - Identify entities and relationships\n",
"4. **🕸️ Build Graph** - Construct the knowledge graph\n",
"5. **📊 Visualize** - Render and analyze the graph\n",
"\n",
"Each step is demonstrated in the code cells below.\n",
"Each step is demonstrated in the code cells below, and each cell can be rerun on its own: the sample file is only removed by the optional cleanup cell at the very end.\n",
"\n",
"> [!TIP]\n",
"> **Alternative: Using Semantica Framework**\n",
"> \n",
">\n",
"> For a simpler, high-level approach, you can use the `Semantica` framework class which orchestrates all these steps:\n",
"> \n",
">\n",
"> ```python\n",
"> from semantica.core import Semantica\n",
"> \n",
">\n",
"> framework = Semantica()\n",
"> framework.initialize()\n",
"> \n",
">\n",
"> result = framework.build_knowledge_base(\n",
"> sources=[\"sample_document.txt\"],\n",
"> embeddings=True,\n",
"> graph=True\n",
"> )\n",
"> \n",
">\n",
"> framework.shutdown()\n",
"> ```\n",
"> \n",
">\n",
"> This notebook shows the step-by-step approach for learning. See [Core Module Usage Guide](../../../semantica/core/core_usage.md) for more details.\n",
"\n",
"---\n",
"\n",
"## 📂 Step 1: Ingest a File\n",
"\n",
"In this step, we'll use `FileIngestor` to load a document. The ingestor supports various file formats including PDF, DOCX, TXT, and more.\n"
"In this step, we'll use `FileIngestor` to load a document. The ingestor supports various file formats including PDF, DOCX, TXT, and more. Writing the sample file is idempotent, so this cell can be rerun at any time.\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"!pip install semantica"
]
"%pip install semantica\n",
"\n",
"# spaCy models are distributed separately from the spaCy library. This lesson\n",
"# relies on the English model to recognize standalone places such as Cupertino.\n",
"import sys\n",
"import subprocess\n",
"import spacy\n",
"\n",
"try:\n",
" spacy.load(\"en_core_web_sm\")\n",
"except OSError:\n",
" subprocess.check_call([sys.executable, \"-m\", \"spacy\", \"download\", \"en_core_web_sm\"])\n"
],
"execution_count": null,
"outputs": []
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"from semantica.ingest import FileIngestor\n",
"from pathlib import Path\n",
"\n",
"# Initialize the ingestor\n",
"ingestor = FileIngestor()\n",
"from semantica.ingest import FileIngestor\n",
"\n",
"# Create a sample document for demonstration\n",
"sample_text = \"\"\"\n",
"Apple Inc. is a technology company founded by Steve Jobs, Steve Wozniak, and Ronald Wayne in 1976.\n",
"The company is headquartered in Cupertino, California.\n",
"Tim Cook is the current CEO of Apple Inc.\n",
"Apple designs and manufactures consumer electronics, software, and online services.\n",
"sample_text = \"\"\"Apple Inc. is headquartered in Cupertino, California.\n",
"In 1976, Steve Jobs founded Apple Inc.\n",
"Tim Cook is the CEO of Apple Inc.\n",
"\"\"\"\n",
"\n",
"sample_file = Path(\"sample_document.txt\")\n",
@@ -113,12 +118,14 @@
"print(f\"File: {sample_file}\")\n",
"print(f\"Content length: {len(sample_text)} characters\")\n",
"\n",
"# Ingest the file\n",
"ingestor = FileIngestor()\n",
"file_object = ingestor.ingest_file(sample_file, read_content=True)\n",
"print(f\" File name: {file_object.name}\")\n",
"print(f\" File type: {file_object.file_type}\")\n",
"print(f\" Content available: {file_object.content is not None}\")\n"
]
"print(f\" Content available: {file_object.content is not None}\")"
],
"execution_count": null,
"outputs": []
},
{
"cell_type": "markdown",
@@ -126,64 +133,58 @@
"source": [
"## 📄 Step 2: Parse the Document\n",
"\n",
"After ingesting the file, we need to parse it to extract the text content. The `DocumentParser` handles various file formats and extracts structured content.\n"
"After ingesting the file, we need to parse it to extract the text content. `DocumentParser.parse_document()` returns the extracted text under the `\"text\"` key.\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"from semantica.parse import DocumentParser\n",
"\n",
"parser = DocumentParser()\n",
"# Parse the document to extract text\n",
"parsed_document = parser.parse_document(str(sample_file))\n",
"parsed_content = parsed_document.get(\"content\", \"\")\n",
"print(f\" Parsed content length: {len(parsed_content) if parsed_content else 0} characters\")\n",
"print(f\" Preview: {parsed_content[:200] if parsed_content else 'N/A'}...\")"
]
"\n",
"parsed_content = parsed_document.get(\"text\", \"\")\n",
"assert parsed_content.strip(), \"Parsing produced no text — check the input file\"\n",
"\n",
"print(f\"Parsed content length: {len(parsed_content)} characters\")\n",
"print(f\"Preview: {parsed_content[:120]}...\")"
],
"execution_count": null,
"outputs": []
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## ⛏️ Step 3: Extract Entities\n",
"## ⛏️ Step 3: Extract Entities and Relations\n",
"\n",
"Now we'll extract entities from the parsed text using Named Entity Recognition (NER). This identifies people, organizations, locations, dates, and other entities in the text.\n",
"\n",
"> [!NOTE]\n",
"> In a real scenario, you would use `NERExtractor` with an LLM or model backend. Here we simulate the output for demonstration purposes.\n"
"Now we'll extract entities and relations from the parsed text. `NERExtractor` identifies people, organizations, locations and dates; `RelationExtractor` finds relations between those mentions. Both operate on the *parsed content from Step 2* — not on a copy of the raw string.\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"from semantica.semantic_extract import NamedEntityRecognizer, NERExtractor\n",
"from semantica.semantic_extract import NERExtractor, RelationExtractor\n",
"\n",
"ner = NamedEntityRecognizer()\n",
"extractor = NERExtractor()\n",
"ner_extractor = NERExtractor()\n",
"relation_extractor = RelationExtractor()\n",
"\n",
"print(f\"\\nText: {parsed_content[:100]}...\")\n",
"mentions = ner_extractor.extract(parsed_content)\n",
"relations = relation_extractor.extract(parsed_content, mentions)\n",
"\n",
"# Simulated extraction results\n",
"expected_entities = [\n",
" {\"text\": \"Apple Inc.\", \"type\": \"Organization\", \"start\": 0, \"end\": 10},\n",
" {\"text\": \"Steve Jobs\", \"type\": \"Person\", \"start\": 50, \"end\": 60},\n",
" {\"text\": \"Steve Wozniak\", \"type\": \"Person\", \"start\": 62, \"end\": 75},\n",
" {\"text\": \"Ronald Wayne\", \"type\": \"Person\", \"start\": 81, \"end\": 93},\n",
" {\"text\": \"1976\", \"type\": \"Date\", \"start\": 97, \"end\": 101},\n",
" {\"text\": \"Cupertino, California\", \"type\": \"Location\", \"start\": 130, \"end\": 151},\n",
" {\"text\": \"Tim Cook\", \"type\": \"Person\", \"start\": 153, \"end\": 161},\n",
"]\n",
"print(\"Entity mentions:\")\n",
"for mention in mentions:\n",
" print(f\" {mention.text!r:<13} {mention.label:<7} span=[{mention.start_char}:{mention.end_char}]\")\n",
"\n",
"for entity in expected_entities:\n",
" print(f\" - {entity['text']} ({entity['type']})\")\n"
]
"print(\"\\nExtracted relations:\")\n",
"for rel in relations:\n",
" print(f\" {rel.subject.text!r} --{rel.predicate}--> {rel.object.text!r}\")"
],
"execution_count": null,
"outputs": []
},
{
"cell_type": "markdown",
@@ -191,58 +192,68 @@
"source": [
"## 🕸️ Step 4: Build the Knowledge Graph\n",
"\n",
"Using the extracted entities and relationships, we'll construct a knowledge graph. The graph represents entities as nodes and relationships as edges.\n"
"Using the extracted entities and relations, we construct a knowledge graph with `GraphBuilder`. Every mention gets a graph ID, and each edge is built from the actual `Relation.subject` / `Relation.object` endpoints.\n",
"\n",
"> [!NOTE]\n",
"> The graph will contain one node per *mention*, so `Apple Inc.` appears three times. Merging duplicate mentions into one canonical entity is covered in [07_Building_Knowledge_Graphs.ipynb](./07_Building_Knowledge_Graphs.ipynb).\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"from semantica.kg import GraphBuilder\n",
"import networkx as nx\n",
"\n",
"entities = []\n",
"span_to_id = {}\n",
"for i, mention in enumerate(mentions, 1):\n",
" graph_id = f\"e{i}\"\n",
" span_to_id[(mention.start_char, mention.end_char)] = graph_id\n",
" entities.append({\n",
" \"id\": graph_id,\n",
" \"type\": mention.label,\n",
" \"name\": mention.text,\n",
" \"properties\": {},\n",
" })\n",
"\n",
"relationships = []\n",
"for rel in relations:\n",
" source_id = span_to_id.get((rel.subject.start_char, rel.subject.end_char))\n",
" target_id = span_to_id.get((rel.object.start_char, rel.object.end_char))\n",
" if source_id is None or target_id is None:\n",
" print(f\"Skipping relation with unmapped endpoint: \"\n",
" f\"{rel.subject.text!r} --{rel.predicate}--> {rel.object.text!r}\")\n",
" continue\n",
" relationships.append({\n",
" \"source\": source_id,\n",
" \"target\": target_id,\n",
" \"type\": rel.predicate,\n",
" \"properties\": {},\n",
" })\n",
"\n",
"builder = GraphBuilder()\n",
"knowledge_graph = builder.build({\"entities\": entities, \"relationships\": relationships})\n",
"\n",
"# Prepare data for graph construction\n",
"entities_data = [\n",
" {\"id\": f\"entity_{i}\", \"name\": entity[\"text\"], \"type\": entity[\"type\"]}\n",
" for i, entity in enumerate(expected_entities)\n",
"]\n",
"id_to_name = {entity[\"id\"]: entity[\"name\"] for entity in entities}\n",
"\n",
"relationships_data = [\n",
" {\"source\": \"entity_0\", \"target\": \"entity_1\", \"type\": \"founded_by\"},\n",
" {\"source\": \"entity_0\", \"target\": \"entity_2\", \"type\": \"founded_by\"},\n",
" {\"source\": \"entity_0\", \"target\": \"entity_3\", \"type\": \"founded_by\"},\n",
" {\"source\": \"entity_0\", \"target\": \"entity_4\", \"type\": \"founded_in\"},\n",
" {\"source\": \"entity_0\", \"target\": \"entity_5\", \"type\": \"located_in\"},\n",
" {\"source\": \"entity_6\", \"target\": \"entity_0\", \"type\": \"ceo_of\"},\n",
"]\n",
"print(f\"Nodes (entities): {len(knowledge_graph['entities'])}\")\n",
"for entity in knowledge_graph[\"entities\"]:\n",
" print(f\" {entity['id']}: {entity['name']} ({entity['type']})\")\n",
"\n",
"# Build the graph using NetworkX\n",
"kg = nx.DiGraph()\n",
"print(f\"\\nEdges (relationships): {len(knowledge_graph['relationships'])}\")\n",
"for relationship in knowledge_graph[\"relationships\"]:\n",
" print(f\" {id_to_name[relationship['source']]} \"\n",
" f\"--{relationship['type']}--> {id_to_name[relationship['target']]}\")\n",
"\n",
"for entity in entities_data:\n",
" kg.add_node(entity[\"id\"], name=entity[\"name\"], type=entity[\"type\"])\n",
"\n",
"for rel in relationships_data:\n",
" source_name = entities_data[int(rel[\"source\"].split(\"_\")[1])][\"name\"]\n",
" target_name = entities_data[int(rel[\"target\"].split(\"_\")[1])][\"name\"]\n",
" kg.add_edge(rel[\"source\"], rel[\"target\"], type=rel[\"type\"])\n",
"\n",
"print(f\" Nodes (entities): {len(kg.nodes)}\")\n",
"print(f\" Edges (relationships): {len(kg.edges)}\")\n",
"\n",
"for node_id in kg.nodes():\n",
" node_data = kg.nodes[node_id]\n",
" print(f\" Node: {node_data['name']} ({node_data['type']})\")\n",
"\n",
"for source, target, data in kg.edges(data=True):\n",
" source_name = kg.nodes[source]['name']\n",
" target_name = kg.nodes[target]['name']\n",
" print(f\" {source_name} --[{data['type']}]--> {target_name}\")\n"
]
"edges = {\n",
" (id_to_name[r[\"source\"]], r[\"type\"], id_to_name[r[\"target\"]])\n",
" for r in knowledge_graph[\"relationships\"]\n",
"}\n",
"assert (\"Apple Inc.\", \"located_in\", \"Cupertino\") in edges\n",
"assert (\"Tim Cook\", \"works_for\", \"Apple Inc.\") in edges"
],
"execution_count": null,
"outputs": []
},
{
"cell_type": "markdown",
@@ -250,49 +261,81 @@
"source": [
"## 📊 Step 5: Visualize and Analyze\n",
"\n",
"Finally, we'll visualize the knowledge graph and analyze its structure. This helps you understand the relationships and entities in your data.\n"
"Finally, we render the knowledge graph with `KGVisualizer` and look at its structure. `visualize_network()` accepts the `GraphBuilder` result directly and can save an interactive HTML file.\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"from semantica.visualization import KGVisualizer\n",
"\n",
"visualizer = KGVisualizer()\n",
"\n",
"print(f\" Total entities: {len(kg.nodes)}\")\n",
"print(f\" Total relationships: {len(kg.edges)}\")\n",
"fig = visualizer.visualize_network(\n",
" knowledge_graph, output=\"html\", file_path=\"knowledge_graph.html\"\n",
")\n",
"print(\"Saved interactive visualization to knowledge_graph.html\")\n",
"\n",
"entity_types = {}\n",
"for node_id in kg.nodes():\n",
" entity_type = kg.nodes[node_id]['type']\n",
" entity_types[entity_type] = entity_types.get(entity_type, 0) + 1\n",
"for entity in knowledge_graph[\"entities\"]:\n",
" entity_types[entity[\"type\"]] = entity_types.get(entity[\"type\"], 0) + 1\n",
"\n",
"for etype, count in entity_types.items():\n",
" print(f\" - {etype}: {count}\")\n",
"print(\"\\nEntities by type:\")\n",
"for entity_type, count in sorted(entity_types.items()):\n",
" print(f\" - {entity_type}: {count}\")\n",
"\n",
"rel_types = {}\n",
"for _, _, data in kg.edges(data=True):\n",
" rel_type = data.get('type', 'unknown')\n",
" rel_types[rel_type] = rel_types.get(rel_type, 0) + 1\n",
"relationship_types = {}\n",
"for relationship in knowledge_graph[\"relationships\"]:\n",
" relationship_types[relationship[\"type\"]] = (\n",
" relationship_types.get(relationship[\"type\"], 0) + 1\n",
" )\n",
"\n",
"for rtype, count in rel_types.items():\n",
" print(f\" - {rtype}: {count}\")\n",
"print(\"\\nRelationships by type:\")\n",
"for relationship_type, count in sorted(relationship_types.items()):\n",
" print(f\" - {relationship_type}: {count}\")\n",
"\n",
"# Cleanup\n",
"if sample_file.exists():\n",
" sample_file.unlink()\n"
"fig"
],
"execution_count": null,
"outputs": []
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 🧹 Optional: Clean Up\n",
"\n",
"Run this cell only when you are done with the notebook. Earlier cells read `sample_document.txt`, so they stay rerunnable until you delete it here.\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
"source": [
"for path in [sample_file, Path(\"knowledge_graph.html\")]:\n",
" if path.exists():\n",
" path.unlink()\n",
" print(f\"Removed {path}\")"
],
"execution_count": null,
"outputs": []
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Summary\n",
"\n",
"You've built your first knowledge graph, end to end:\n",
"\n",
"- **FileIngestor** loaded the sample document\n",
"- **DocumentParser** returned its text under the `\"text\"` key\n",
"- **NERExtractor** / **RelationExtractor** produced real mentions and relations from that text\n",
"- **GraphBuilder** turned them into a graph whose edges come from the actual relation endpoints\n",
"- **KGVisualizer** rendered the result as an interactive network\n",
"\n",
"Next: merge duplicate mentions with `EntityResolver` in [07_Building_Knowledge_Graphs.ipynb](./07_Building_Knowledge_Graphs.ipynb), or explore graph metrics in the Graph Analytics notebook.\n"
]
}
],
"metadata": {
+2 -1
View File
@@ -497,7 +497,8 @@
"**Next Steps**:\n",
"* Try customizing the `NamespaceManager` to use your organization's URL.\n",
"* Explore `OntologyEvaluator` for deeper quality metrics.\n",
"* Feed the generated ontology into the **Knowledge Graph** module to start reasoning over your data!"
"* Feed the generated ontology into the **Knowledge Graph** module to start reasoning over your data!\n",
"* Put the graph, ontology, and explicit mappings together in [Semantic Layer Basics](./26_Semantic_Layer_Basics.ipynb)."
]
}
],
@@ -0,0 +1,418 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/semantica-agi/semantica/blob/main/cookbook/introduction/26_Semantic_Layer_Basics.ipynb)\n",
"\n",
"# Semantic Layer Basics: Putting the Knowledge Graph, Ontology, and Mappings Together\n",
"\n",
"## Overview\n",
"\n",
"This lesson connects three things you have already met — a knowledge graph, an ontology, and RDF export — into one minimal *semantic layer*: a knowledge graph whose types, relationships, and properties are **explicitly mapped** to ontology terms, so the resulting RDF can be queried with SPARQL against a shared vocabulary.\n",
"\n",
"**Documentation**: [API Reference](https://semantica.readthedocs.io/concepts/)\n",
"\n",
"### 🎯 Learning Objectives\n",
"\n",
"- Build a small knowledge graph with `GraphBuilder`\n",
"- Generate a starter ontology from the graph with `OntologyGenerator`\n",
"- Write **explicit** entity-type, relationship-type, and property mappings to ontology terms\n",
"- Produce ontology-aligned RDF and store it with `TripletStore`\n",
"- Answer a business question with one small SPARQL query\n",
"\n",
"### 📚 Prerequisites\n",
"\n",
"- [07_Building_Knowledge_Graphs.ipynb](./07_Building_Knowledge_Graphs.ipynb) — graphs from entities and relationships\n",
"- [14_Ontology.ipynb](./14_Ontology.ipynb) — ontology generation\n",
"- [20_Triplet_Store.ipynb](./20_Triplet_Store.ipynb) — triplet store backends\n",
"\n",
"> [!NOTE]\n",
"> **Teaching mappings vs. governed mappings.** The mappings in this lesson are a demo: they live in a Python dict and are derived from a generated ontology. A production semantic layer uses governed identifiers, hand-designed ontologies, explicit source mappings, validation (SHACL), provenance, and versioning — that workflow is covered in [Advanced: Manual Ontology + Snowflake Mapping](../advanced/13_Manual_Ontology_Snowflake_Mapping.ipynb).\n",
"\n",
"## Installation\n",
"\n",
"The triplet-store step uses the embedded Oxigraph backend, so install with that extra. Pin at least 0.6.7: earlier releases could generate ontology classes with no URI (#1103), which silently breaks the mappings below instead of failing loudly.\n",
"\n",
"```bash\n",
"pip install \"semantica[tripletstore-oxigraph]>=0.6.7\"\n",
"```\n",
"\n",
"---\n",
"\n",
"## Step 1: Build a Knowledge Graph\n",
"\n",
"Start from a small, explicit set of entities and relationships — two people, an organization, and a project.\n"
]
},
{
"cell_type": "code",
"metadata": {},
"source": [
"!pip install \"semantica[tripletstore-oxigraph]>=0.6.7\"\n"
],
"execution_count": null,
"outputs": []
},
{
"cell_type": "code",
"metadata": {},
"source": [
"from semantica.kg import GraphBuilder\n",
"\n",
"entities = [\n",
" {\"id\": \"e1\", \"type\": \"Person\", \"name\": \"Alice\", \"properties\": {\"age\": 30, \"role\": \"Engineer\"}},\n",
" {\"id\": \"e2\", \"type\": \"Person\", \"name\": \"Bob\", \"properties\": {\"age\": 35, \"role\": \"Manager\"}},\n",
" {\"id\": \"e3\", \"type\": \"Organization\", \"name\": \"Tech Corp\", \"properties\": {\"founded\": 2010}},\n",
" {\"id\": \"e4\", \"type\": \"Project\", \"name\": \"Project Alpha\", \"properties\": {\"status\": \"active\"}},\n",
"]\n",
"\n",
"relationships = [\n",
" {\"source\": \"e1\", \"target\": \"e2\", \"type\": \"reports_to\", \"properties\": {}},\n",
" {\"source\": \"e1\", \"target\": \"e3\", \"type\": \"works_for\", \"properties\": {}},\n",
" {\"source\": \"e2\", \"target\": \"e3\", \"type\": \"works_for\", \"properties\": {}},\n",
" {\"source\": \"e1\", \"target\": \"e4\", \"type\": \"works_on\", \"properties\": {}},\n",
"]\n",
"\n",
"builder = GraphBuilder()\n",
"knowledge_graph = builder.build({\"entities\": entities, \"relationships\": relationships})\n",
"\n",
"id_to_name = {entity[\"id\"]: entity[\"name\"] for entity in entities}\n",
"\n",
"print(f\"Entities ({len(knowledge_graph['entities'])}):\")\n",
"for entity in knowledge_graph[\"entities\"]:\n",
" print(f\" {entity['id']}: {entity['name']} ({entity['type']}) {entity['properties']}\")\n",
"\n",
"print(f\"\\nRelationships ({len(knowledge_graph['relationships'])}):\")\n",
"for relationship in knowledge_graph[\"relationships\"]:\n",
" print(f\" {id_to_name[relationship['source']]} \"\n",
" f\"--{relationship['type']}--> {id_to_name[relationship['target']]}\")"
],
"execution_count": null,
"outputs": []
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Step 2: Generate a Starter Ontology\n",
"\n",
"`OntologyGenerator` infers OWL classes and properties from graph records. Because `GraphBuilder` keeps business attributes inside each entity's `properties` dictionary while ontology inference reads record fields, we first create a flat **inference view**. The knowledge graph itself remains unchanged. Two settings matter here:\n",
"\n",
"- `base_uri` puts every generated term in *your* namespace\n",
"- `min_occurrences=1` includes classes that occur only once (the default of 2 would drop `Organization` and `Project` from this tiny demo graph)\n",
"\n",
"Note that the generator normalizes names: the relationship type `works_for` becomes the ontology property `worksFor`. That is exactly why the next step maps terms **explicitly** instead of matching names.\n"
]
},
{
"cell_type": "code",
"metadata": {},
"source": [
"from semantica.ontology import OntologyGenerator\n",
"\n",
"BASE_URI = \"https://example.org/company/\"\n",
"\n",
"# Adapt the property-graph representation to the record shape consumed by\n",
"# OntologyGenerator, so age/role/founded/status become declared properties.\n",
"ontology_input = {\n",
" \"entities\": [\n",
" {\n",
" **{key: value for key, value in entity.items() if key != \"properties\"},\n",
" **entity.get(\"properties\", {}),\n",
" }\n",
" for entity in knowledge_graph[\"entities\"]\n",
" ],\n",
" \"relationships\": knowledge_graph[\"relationships\"],\n",
"}\n",
"\n",
"generator = OntologyGenerator(base_uri=BASE_URI, min_occurrences=1)\n",
"ontology = generator.generate_from_graph(ontology_input)\n",
"\n",
"# OntologyGenerator calls datatype properties `data`; TripletStore's public\n",
"# ontology contract calls them `datatype`. Normalize that boundary explicitly.\n",
"store_ontology = {\n",
" **ontology,\n",
" \"properties\": [\n",
" {**prop, \"type\": \"datatype\" if prop[\"type\"] == \"data\" else prop[\"type\"]}\n",
" for prop in ontology[\"properties\"]\n",
" ],\n",
"}\n",
"\n",
"print(\"Classes:\")\n",
"for ontology_class in ontology[\"classes\"]:\n",
" print(f\" {ontology_class['name']:<14} {ontology_class['uri']}\")\n",
"\n",
"print(\"\\nProperties:\")\n",
"for prop in ontology[\"properties\"]:\n",
" print(f\" {prop['name']:<14} {prop['type']:<7} {prop['uri']} \"\n",
" f\"(domain={prop['domain']}, range={prop['range']})\")\n",
"\n",
"assert len(ontology[\"classes\"]) == 3"
],
"execution_count": null,
"outputs": []
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Step 3: Map the Graph to Ontology Terms\n",
"\n",
"The heart of a semantic layer is the mapping contract: which source type, relationship, and property corresponds to which ontology term.\n",
"\n",
"- **Entity types** and **relationship types**: each generated class/property records the source name it was inferred from (`metadata[\"inferred_from\"]`), so the mapping is read off the ontology itself — no fragile name matching between `works_for` and `worksFor`.\n",
"- **Properties**: the flat inference view makes `name`, `age`, `role`, `founded`, and `status` real generated datatype properties. Every mapping therefore points to a term declared in the ontology — no URI is invented only at mapping time.\n"
]
},
{
"cell_type": "code",
"metadata": {},
"source": [
"entity_type_mappings = {\n",
" ontology_class[\"metadata\"][\"inferred_from\"]: ontology_class[\"uri\"]\n",
" for ontology_class in ontology[\"classes\"]\n",
"}\n",
"\n",
"relationship_type_mappings = {\n",
" prop[\"metadata\"][\"inferred_from\"]: prop[\"uri\"]\n",
" for prop in ontology[\"properties\"]\n",
" if prop[\"type\"] == \"object\"\n",
"}\n",
"\n",
"datatype_property_uris = {\n",
" prop[\"metadata\"][\"inferred_from\"]: prop[\"uri\"]\n",
" for prop in ontology[\"properties\"]\n",
" if prop[\"type\"] != \"object\"\n",
"}\n",
"\n",
"property_mappings = datatype_property_uris\n",
"\n",
"semantic_layer = {\n",
" \"graph\": knowledge_graph,\n",
" \"ontology\": ontology,\n",
" \"mappings\": {\n",
" \"entity_type_mappings\": entity_type_mappings,\n",
" \"relationship_type_mappings\": relationship_type_mappings,\n",
" \"property_mappings\": property_mappings,\n",
" },\n",
"}\n",
"\n",
"for mapping_name, mapping in semantic_layer[\"mappings\"].items():\n",
" print(f\"{mapping_name}:\")\n",
" for source, target in mapping.items():\n",
" print(f\" {source:<12} -> {target}\")\n",
"\n",
"# Every type and relationship in the graph must have an ontology term\n",
"assert set(entity_type_mappings) == {entity[\"type\"] for entity in entities}\n",
"assert set(relationship_type_mappings) == {rel[\"type\"] for rel in relationships}\n",
"assert set(property_mappings) == {\"name\", \"age\", \"role\", \"founded\", \"status\"}\n",
"assert set(property_mappings.values()) <= {prop[\"uri\"] for prop in ontology[\"properties\"]}"
],
"execution_count": null,
"outputs": []
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Step 4: Apply the Mappings\n",
"\n",
"Applying the semantic layer means rewriting the graph so every type, relationship, and property key is an ontology term. This *aligned* graph — not the original one — is what gets exported and stored.\n"
]
},
{
"cell_type": "code",
"metadata": {},
"source": [
"aligned_graph = {\n",
" \"entities\": [\n",
" {\n",
" **entity,\n",
" \"type\": entity_type_mappings[entity[\"type\"]],\n",
" \"properties\": {\n",
" property_mappings[\"name\"]: entity[\"name\"],\n",
" **{\n",
" property_mappings[key]: value\n",
" for key, value in entity[\"properties\"].items()\n",
" },\n",
" },\n",
" }\n",
" for entity in knowledge_graph[\"entities\"]\n",
" ],\n",
" \"relationships\": [\n",
" {**rel, \"type\": relationship_type_mappings[rel[\"type\"]]}\n",
" for rel in knowledge_graph[\"relationships\"]\n",
" ],\n",
"}\n",
"\n",
"print(\"Aligned entity sample:\")\n",
"sample = aligned_graph[\"entities\"][0]\n",
"print(f\" id: {sample['id']}\")\n",
"print(f\" type: {sample['type']}\")\n",
"for key, value in sample[\"properties\"].items():\n",
" print(f\" {key} = {value}\")\n",
"\n",
"print(\"\\nAligned relationship sample:\")\n",
"print(f\" {aligned_graph['relationships'][0]['type']}\")"
],
"execution_count": null,
"outputs": []
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Step 5: Store and Export Complete Ontology-Aligned RDF\n",
"\n",
"`TripletStore.store()` materializes both the ontology declarations and the aligned instance graph. We then read those triples through the store's public API and serialize that complete RDF graph as Turtle. This avoids the compact `RDFExporter` entity projection, which does not include arbitrary entries from an entity's `properties` dictionary.\n"
]
},
{
"cell_type": "code",
"metadata": {},
"source": [
"from rdflib import Graph, Literal, URIRef\n",
"from rdflib.namespace import OWL, RDF\n",
"from semantica.triplet_store import TripletStore\n",
"\n",
"store = TripletStore(backend=\"oxigraph\")\n",
"result = store.store(aligned_graph, store_ontology)\n",
"print(f\"Stored triples: {result['processed']} (failed: {result['failed']})\")\n",
"\n",
"rdf_graph = Graph()\n",
"for triplet in store.get_triplets():\n",
" datatype = triplet.metadata.get(\"datatype\")\n",
" if datatype:\n",
" object_term = Literal(triplet.object, datatype=URIRef(datatype))\n",
" elif triplet.object.startswith((\"http://\", \"https://\", \"urn:\")):\n",
" object_term = URIRef(triplet.object)\n",
" else:\n",
" object_term = Literal(triplet.object)\n",
" rdf_graph.add((URIRef(triplet.subject), URIRef(triplet.predicate), object_term))\n",
"\n",
"rdf_graph.serialize(destination=\"semantic_layer.ttl\", format=\"turtle\")\n",
"turtle = open(\"semantic_layer.ttl\", encoding=\"utf-8\").read()\n",
"print(turtle[:600])\n",
"\n",
"# The exported RDF contains declarations plus mapped instance facts.\n",
"declared_datatype_properties = {\n",
" str(subject) for subject in rdf_graph.subjects(RDF.type, OWL.DatatypeProperty)\n",
"}\n",
"assert result[\"failed\"] == 0\n",
"assert set(property_mappings.values()) <= declared_datatype_properties\n",
"assert (\n",
" URIRef(BASE_URI + \"e1\"),\n",
" URIRef(property_mappings[\"role\"]),\n",
" Literal(\"Engineer\"),\n",
") in rdf_graph\n",
"assert (\n",
" URIRef(BASE_URI + \"e1\"),\n",
" URIRef(relationship_type_mappings[\"works_for\"]),\n",
" URIRef(BASE_URI + \"e3\"),\n",
") in rdf_graph\n",
"print(\"... exported semantic_layer.ttl\")"
],
"execution_count": null,
"outputs": []
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Step 6: Query the Semantic Layer\n",
"\n",
"The embedded Oxigraph backend runs in memory, so there is nothing to start beyond installing the `tripletstore-oxigraph` extra. The organization is constrained by its mapped `name` predicate; the query therefore means *Tech Corp*, rather than accidentally matching employees of every organization.\n"
]
},
{
"cell_type": "code",
"metadata": {},
"source": [
"query = f\"\"\"\n",
"SELECT ?name ?role WHERE {{\n",
" ?person <{BASE_URI}worksFor> ?org .\n",
" ?org <{BASE_URI}name> \"Tech Corp\" .\n",
" ?person <{BASE_URI}name> ?name .\n",
" ?person <{BASE_URI}role> ?role .\n",
"}}\n",
"ORDER BY ?name\n",
"\"\"\"\n",
"query_result = store.execute_query(query)\n",
"\n",
"print(\"\\nWho works for Tech Corp, and in which role?\")\n",
"for binding in query_result.bindings:\n",
" print(f\" {binding['name']['value']} — {binding['role']['value']}\")\n",
"\n",
"assert [(row[\"name\"][\"value\"], row[\"role\"][\"value\"]) for row in query_result.bindings] == [\n",
" (\"Alice\", \"Engineer\"),\n",
" (\"Bob\", \"Manager\"),\n",
"]"
],
"execution_count": null,
"outputs": []
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 🧹 Optional: Clean Up\n"
]
},
{
"cell_type": "code",
"metadata": {},
"source": [
"from pathlib import Path\n",
"\n",
"ttl_file = Path(\"semantic_layer.ttl\")\n",
"if ttl_file.exists():\n",
" ttl_file.unlink()\n",
" print(f\"Removed {ttl_file}\")"
],
"execution_count": null,
"outputs": []
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Summary\n",
"\n",
"A minimal semantic layer is a composition, and you have now built each part:\n",
"\n",
"1. **Knowledge graph** — `GraphBuilder` from explicit entities and relationships\n",
"2. **Ontology** — `OntologyGenerator` with your `base_uri`\n",
"3. **Explicit mappings** — entity types, relationship types, and properties, each tied to an ontology term\n",
"4. **Ontology-aligned RDF** — the mappings applied to the graph, materialized with `TripletStore`, and serialized to Turtle from the store's own triples\n",
"5. **Queryable store** — `TripletStore` (embedded Oxigraph) answering a SPARQL question over the shared vocabulary\n",
"\n",
"### Where to go next\n",
"\n",
"The production version of this workflow — hand-designed governed ontologies, explicit source-to-ontology mappings from a warehouse, n-ary modeling, SHACL validation, provenance, and versioning — is covered in [Advanced: Manual Ontology + Snowflake Mapping](../advanced/13_Manual_Ontology_Snowflake_Mapping.ipynb).\n"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.11.9"
}
},
"nbformat": 4,
"nbformat_minor": 2
}
+9 -9
View File
@@ -149,25 +149,25 @@ registry.register_plugin("my_plugin", MyPlugin, version="1.0.0")
<Accordion title="Modularity: use only what you need" icon="puzzle-piece">
Every component works standalone. `NERExtractor` runs without a graph store. `VectorStore` runs without decision tracking. The framework never forces a full stack instantiation: you pay only for what you import.
Every component works standalone. `NERExtractor` runs without a graph store. `VectorStore` runs without decision tracking. The framework never forces a full stack instantiation; you pay only for what you import.
</Accordion>
<Accordion title="Pluggability: extend without modifying core" icon="plug">
Custom ingestors, extractors, validators, and exporters follow the same base class pattern. Register them via `PluginRegistry` and they participate in the full pipeline: provenance tracking, retry policies, and parallel execution included: with no changes to core code.
Custom ingestors, extractors, validators, and exporters follow the same base class pattern. Register them via `PluginRegistry` and they participate in the full pipeline (provenance tracking, retry policies, and parallel execution included) with no changes to core code.
</Accordion>
<Accordion title="Provenance by default" icon="link">
Lineage tracking is built into graph construction at the lowest level. Every node and edge carries a `source_id` pointing back to the originating document, extraction method, and timestamp. There's no opt-in required: provenance is always on.
Lineage tracking is built into graph construction at the lowest level. Every node and edge carries a `source_id` pointing back to the originating document, extraction method, and timestamp. There is no opt-in required; provenance is always on.
</Accordion>
<Accordion title="Configuration over convention" icon="sliders">
Centralized `ConfigManager` with environment variable overrides. No magic defaults: all behavior is explicit and overridable. Suitable for multi-environment deployments where dev, staging, and production need different backends.
Centralized `ConfigManager` with environment variable overrides. No magic defaults; all behavior is explicit and overridable. Suitable for multi-environment deployments where dev, staging, and production need different backends.
</Accordion>
@@ -179,13 +179,13 @@ Centralized `ConfigManager` with environment variable overrides. No magic defaul
| Characteristic | Mechanism |
| :-------------- | :--------- |
| **Parallel execution** | `Pipeline(workers=N)` with configurable workers per stage |
| **Delta processing** | Incremental graph updates: no full recompute on new data |
| **Delta processing** | Incremental graph updates (no full recompute on new data) |
| **Streaming ingestion** | Process large corpora without loading everything into memory |
| **Backend flexibility** | Swap in-memory NetworkX for Neo4j / FalkorDB with no API changes |
| **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.
+4 -183
View File
@@ -1,14 +1,9 @@
/* ============================================================
SEMANTICA DOCS — PREMIUM DESIGN SYSTEM
SEMANTICA DOCS — DESIGN SYSTEM
Dark-first (#080C10 bg, #10B981 emerald accent)
Minimal, static styling — no decorative motion.
============================================================ */
/* ── Keyframes ─────────────────────────────────────────────── */
@keyframes pageFadeIn {
from { opacity: 0; transform: translateY(6px); }
to { opacity: 1; transform: translateY(0); }
}
/* ── Global ─────────────────────────────────────────────────── */
html {
scroll-behavior: smooth;
@@ -29,16 +24,7 @@ html {
}
::-webkit-scrollbar-thumb:hover { background: rgba(16, 185, 129, 0.4); }
/* ── Page entrance ──────────────────────────────────────────── */
main,
article,
[class*="content-area"],
[class*="ContentArea"],
[class*="prose"] {
animation: pageFadeIn 0.35s ease both;
}
/* ── Focus rings ─────────────────────────────────────────────── */
/* ── Focus rings (accessibility — kept) ─────────────────────── */
*:focus-visible {
outline: 2px solid rgba(16, 185, 129, 0.55) !important;
outline-offset: 3px !important;
@@ -59,7 +45,7 @@ h1::after {
left: 0;
width: 44px;
height: 2px;
background: linear-gradient(90deg, #10B981 0%, transparent 100%);
background: #10B981;
border-radius: 1px;
}
@@ -71,9 +57,6 @@ article a,
[class*="prose"] a {
text-decoration-color: rgba(16, 185, 129, 0.35);
text-underline-offset: 3px;
transition:
text-decoration-color 0.15s ease,
color 0.15s ease;
}
article a:hover,
@@ -89,14 +72,6 @@ blockquote {
padding: 0.9rem 1.2rem !important;
font-style: italic;
color: rgba(255, 255, 255, 0.68) !important;
transition:
border-color 0.2s ease,
background-color 0.2s ease !important;
}
blockquote:hover {
border-left-color: rgba(16, 185, 129, 0.65) !important;
background: rgba(16, 185, 129, 0.07) !important;
}
/* ── HR / Divider ────────────────────────────────────────────── */
@@ -123,165 +98,11 @@ table thead th {
border-bottom: 1px solid rgba(16, 185, 129, 0.18) !important;
}
table tbody tr {
transition: background-color 0.15s ease;
cursor: default;
}
table tbody tr:hover {
background-color: rgba(16, 185, 129, 0.06) !important;
}
table tbody tr:hover td {
background-color: transparent !important;
}
table td,
table th {
transition: background-color 0.15s ease;
}
/* ── CODE BLOCKS ─────────────────────────────────────────────── */
pre,
[class*="codeblock"],
[class*="code-group"],
[class*="CodeBlock"],
[data-rehype-pretty-code-fragment] {
transition:
box-shadow 0.25s cubic-bezier(0.4, 0, 0.2, 1),
border-color 0.25s cubic-bezier(0.4, 0, 0.2, 1),
transform 0.25s cubic-bezier(0.4, 0, 0.2, 1) !important;
}
pre:hover,
[class*="codeblock"]:hover,
[class*="CodeBlock"]:hover,
[data-rehype-pretty-code-fragment]:hover {
transform: translateY(-1px) !important;
box-shadow:
0 0 0 1px rgba(16, 185, 129, 0.18),
0 2px 12px rgba(16, 185, 129, 0.06),
0 8px 32px rgba(0, 0, 0, 0.2) !important;
border-color: rgba(16, 185, 129, 0.2) !important;
}
/* ── CARDS ───────────────────────────────────────────────────── */
[class*="card"],
[class*="Card"],
[data-card],
.group\/card {
transition:
transform 0.22s ease,
box-shadow 0.22s ease,
border-color 0.22s ease !important;
}
[class*="card"]:hover,
[class*="Card"]:hover,
[data-card]:hover,
.group\/card:hover {
transform: translateY(-3px) !important;
box-shadow:
0 8px 28px rgba(0, 0, 0, 0.18),
0 0 0 1px rgba(16, 185, 129, 0.22) !important;
border-color: rgba(16, 185, 129, 0.28) !important;
}
/* ── CALLOUTS / ADMONITIONS ──────────────────────────────────── */
[class*="callout"],
[class*="Callout"],
[class*="admonition"] {
transition:
box-shadow 0.2s ease,
border-color 0.2s ease !important;
}
[class*="callout"]:hover,
[class*="Callout"]:hover,
[class*="admonition"]:hover {
box-shadow: 0 2px 16px rgba(16, 185, 129, 0.08) !important;
border-color: rgba(16, 185, 129, 0.35) !important;
}
/* ── STEPS ───────────────────────────────────────────────────── */
[class*="step"],
[class*="Step"] {
transition: background-color 0.15s ease !important;
}
[class*="step"]:hover,
[class*="Step"]:hover {
background-color: rgba(16, 185, 129, 0.04) !important;
}
/* ── INLINE CODE ─────────────────────────────────────────────── */
:not(pre) > code {
transition:
background-color 0.15s ease,
color 0.15s ease !important;
cursor: text;
}
:not(pre) > code:hover {
background-color: rgba(16, 185, 129, 0.16) !important;
}
/* ── NAVIGATION / SIDEBAR ────────────────────────────────────── */
nav a,
[class*="sidebar"] a,
[class*="Sidebar"] a {
transition: color 0.15s ease !important;
text-decoration: none;
position: relative;
}
nav a::after,
[class*="sidebar"] a::after,
[class*="Sidebar"] a::after {
content: "";
position: absolute;
bottom: -1px;
left: 0;
width: 0;
height: 1px;
background: #10B981;
transition: width 0.2s ease;
}
nav a:hover::after,
[class*="sidebar"] a:hover::after,
[class*="Sidebar"] a:hover::after {
width: 100%;
}
/* ── TEXT / LIST ITEMS ───────────────────────────────────────── */
ul > li,
ol > li {
border-radius: 3px;
transition: background-color 0.12s ease;
}
ul > li:hover,
ol > li:hover {
background-color: rgba(16, 185, 129, 0.04);
}
/* ── PRIMARY BUTTON / CTA ────────────────────────────────────── */
button[class*="primary"],
a[class*="primary"],
[class*="btn-primary"],
[class*="ButtonPrimary"] {
transition:
box-shadow 0.2s ease,
transform 0.2s ease !important;
}
button[class*="primary"]:hover,
a[class*="primary"]:hover,
[class*="btn-primary"]:hover,
[class*="ButtonPrimary"]:hover {
box-shadow: 0 0 22px rgba(16, 185, 129, 0.28) !important;
transform: translateY(-1px) !important;
}
/* ── HIDE THEME TOGGLE ───────────────────────────────────────── */
+27 -27
View File
@@ -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
@@ -75,7 +75,7 @@ Pick your goal to see the minimum imports and a working skeleton.
sources = FileIngestor().ingest("report.pdf")
parsed = DocumentParser().parse_document("report.pdf")
# No API key required pattern-based extraction
# No API key required: pattern-based extraction
entities = NERExtractor(method="pattern").extract(parsed)
relationships = RelationExtractor(method="rule").extract(parsed, entities=entities)
@@ -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.
See the [Quickstart →](/quickstart) for a full pipeline with visualization and export.
</Tab>
<Tab title="Build GraphRAG">
@@ -109,7 +109,7 @@ Pick your goal to see the minimum imports and a working skeleton.
knowledge_graph=ContextGraph(advanced_analytics=True),
)
# Store facts retrieval uses both vectors and graph structure
# Store facts: retrieval uses both vectors and graph structure
context.store("Apple Inc. was co-founded by Steve Jobs in 1976 in Cupertino.")
# GraphRAG query with multi-hop reasoning trace
@@ -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">
@@ -206,11 +206,11 @@ Pick your goal to see the minimum imports and a working skeleton.
```python
from semantica.export import RDFExporter, ParquetExporter, LPGExporter, ArangoAQLExporter
# RDF multiple serialization formats
# RDF: multiple serialization formats
RDFExporter().export(graph, "graph.ttl", format="turtle")
RDFExporter().export(graph, "graph.jsonld", format="jsonld")
# Parquet for Spark, BigQuery, Databricks, Snowflake
# Parquet: for Spark, BigQuery, Databricks, Snowflake
ParquetExporter().export(graph, "output/graph.parquet")
# Neo4j / Memgraph via Cypher
@@ -222,18 +222,18 @@ 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.
<Tab title="MCP: Claude / Cursor">
Use Semantica from Claude Desktop, Cursor, VS Code, or any MCP-aware tool; no Python code required after setup. 15 tools are available.
**Step 1 Install:**
**Step 1: Install**
```bash
pip install semantica
```
**Step 2 Add to your MCP client config:**
**Step 2: Add to your MCP client config**
<CodeGroup>
@@ -268,30 +268,30 @@ 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>
## Still Unsure?
## Architecture Selection Guidance
<AccordionGroup>
<Accordion title="Knowledge graph vs. vector store — which do I need?" icon="scale-balanced">
<Accordion title="Knowledge graph vs. vector store selection" icon="scale-balanced">
Use a **knowledge graph** (`kg`) when you need structured reasoning, multi-hop traversal, provenance, or compliance audit trails.
Use a **vector store** (`vector_store`) when you need fast fuzzy similarity search over large text corpora and relationships between items don't matter.
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.
<Accordion title="Fast local pipeline setup" icon="rocket">
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">
Add `AgentContext`. It wraps your existing agent with memory, decision tracking, and precedent search no changes to your LLM provider or agent framework needed.
<Accordion title="Minimum configuration for existing agents" icon="plug">
Add `AgentContext` to equip an existing agent with memory, decision tracking, and precedent search, with no changes to your LLM provider or agent framework required.
```python
from semantica.context import AgentContext, ContextGraph
@@ -304,10 +304,10 @@ 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">
<Accordion title="Minimum stack for compliance-ready pipelines" icon="shield-check">
| Layer | Module | Key class |
| :---- | :------ | :--------- |
| Ingestion | `ingest` | `FileIngestor` |
@@ -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.
+3 -3
View File
@@ -43,10 +43,10 @@ icon: "quote-left"
## Share Your Research
Published research using Semantica? [Let us know](https://github.com/semantica-agi/semantica/issues): we may feature your work.
If you publish research using Semantica, [let us know](https://github.com/semantica-agi/semantica/issues) so we can feature your work.
## 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.
+14 -14
View File
@@ -18,13 +18,13 @@ After installation the following commands are available:
| Command | Entry point | What it does |
| :------- | :----------- | :------------ |
| `semantica` | `semantica.cli:main` | General-purpose CLI for pipeline runs, extraction, and graph operations |
| `semantica-server` | `semantica.server:main` | FastAPI/uvicorn REST API server bound to `0.0.0.0:8000` |
| `semantica-server` | `semantica.server:main` | FastAPI/uvicorn REST API server bound to `127.0.0.1:8000` by default (set `SEMANTICA_HOST` to override) |
| `semantica-worker` | `semantica.worker:main` | Background worker process entry point for Semantica deployments |
| `semantica-explorer` | `semantica.explorer:main` | Interactive browser dashboard for knowledge graph exploration |
| `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>
@@ -49,11 +49,11 @@ python -c "import semantica; print(semantica.__version__)"
## When to Use Each Command
- **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**: 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 `127.0.0.1:8000` by default; set `SEMANTICA_HOST` to expose beyond localhost. 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 15 tools and 3 resources to Claude Desktop, Cursor, Windsurf, or any MCP-aware client. See [MCP Server](/reference/mcp_server).
## Usage Examples
@@ -61,7 +61,7 @@ python -c "import semantica; print(semantica.__version__)"
<Tabs>
<Tab title="REST server">
```bash
# Starts FastAPI + uvicorn on 0.0.0.0:8000
# Starts FastAPI + uvicorn on 127.0.0.1:8000 (set SEMANTICA_HOST to change)
semantica-server
```
@@ -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.
+10 -10
View File
@@ -66,12 +66,12 @@ Production deployments span regulated and high-stakes industries where AI accoun
| :-------- | :---- |
| **OpenAI** | GPT-4o, GPT-4, GPT-3.5 |
| **Anthropic** | Claude Opus, Sonnet, Haiku |
| **Google Gemini** |: |
| **Groq** | LLaMA, Mixtral: fast inference |
| **Google Gemini** | Gemini Pro and other Gemini models |
| **Groq** | LLaMA, Mixtral (fast inference) |
| **Ollama** | Fully local, air-gapped |
| **HuggingFace** |: |
| **DeepSeek** |: |
| **Novita AI** |: |
| **HuggingFace** | Transformers-based local LLM models |
| **DeepSeek** | deepseek-chat and reasoning models |
| **Novita AI** | OpenAI-compatible gateway, DeepSeek-V3.2 default |
| **LiteLLM** | 100+ model gateway |
</Tab>
<Tab title="NLP Libraries">
@@ -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.
- [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.
- [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.
+9 -9
View File
@@ -9,10 +9,10 @@ Semantica is built in the open, with contributions from researchers, engineers,
## Get Help
- [GitHub Issues](https://github.com/semantica-agi/semantica/issues) — File bug reports and feature requests with full context.
- [GitHub Discussions](https://github.com/semantica-agi/semantica/discussions) — Ask questions, share ideas, and discuss design decisions.
- [Pull Requests](https://github.com/semantica-agi/semantica/pulls) — Browse open contributions and submit your own.
- [Security Issues](https://github.com/semantica-agi/semantica/security/advisories/new) — Report vulnerabilities privately: never in public issues.
- [GitHub Issues](https://github.com/semantica-agi/semantica/issues): file bug reports and feature requests with full context.
- [GitHub Discussions](https://github.com/semantica-agi/semantica/discussions): ask questions, share ideas, and discuss design decisions.
- [Pull Requests](https://github.com/semantica-agi/semantica/pulls): browse open contributions and submit your own.
- [Security Issues](https://github.com/semantica-agi/semantica/security/advisories/new): report vulnerabilities privately (never in public issues).
## Community Guidelines
@@ -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.
+125 -94
View File
@@ -5,19 +5,19 @@ 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.
Semantica transforms unstructured data (documents, web pages, reports, databases) into **knowledge graphs**: structured representations that AI systems can query, reason about, and trace back to sources.
At its core, Semantica adds a **context and accountability layer** on top of your existing AI stack. It doesn't replace LangChain, LlamaIndex, or your LLM provider: it makes their outputs **grounded**, **traceable**, and **auditable**.
At its core, Semantica adds a context and semantic layer on top of your existing AI stack. It doesn't replace LangChain, LlamaIndex, or your LLM provider. It makes their outputs grounded, traceable, and auditable.
- **Context Layer** Knowledge graphs, GraphRAG retrieval, semantic embeddings, and temporal intelligence ground every LLM response in structured, queryable facts.
- **Accountability Layer** Provenance tracking, decision intelligence, conflict detection, and W3C PROV-O compliance make every claim in your AI stack auditable and explainable.
- **Extension Layer** `PluginRegistry` and `MethodRegistry` let you replace or augment any component: ingestors, extractors, reasoning engines, backends: without changing framework code.
- **Context Layer.** Knowledge graphs, GraphRAG retrieval, semantic embeddings, and temporal intelligence ground every LLM response in structured, queryable facts.
- **Accountability Layer.** Provenance tracking, decision intelligence, conflict detection, and W3C PROV-O compliance make every claim in your AI stack auditable and explainable.
- **Extension Layer.** `PluginRegistry` and `MethodRegistry` let you replace or augment any component (ingestors, extractors, reasoning engines, backends) without changing framework code.
<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. In short, Semantica explains and audits *what the AI system did*, not the foundation model's private internal reasoning.
**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. In short, Semantica explains and audits *what the AI system did*, not the foundation model's private internal reasoning.
</Warning>
## Knowledge Graphs
@@ -30,7 +30,7 @@ The foundation of everything in Semantica. A knowledge graph stores information
- **Edges (relationships)**: `works_for`, `located_in`, `founded_by`
- **Properties**: name, date, confidence score, source URL
This structure makes knowledge **searchable**, **connectable**, **queryable**, and: critically: **explainable**: every answer can be traced back to the facts and relationships that produced it.
This structure makes knowledge searchable, connectable, and queryable. Critically, it's explainable: every answer can be traced back to the facts and relationships that produced it.
## Entity Extraction (NER)
@@ -38,18 +38,19 @@ This structure makes knowledge **searchable**, **connectable**, **queryable**, a
Scanning text to find and classify real-world entities:
```python
# Input: "Apple Inc. was founded by Steve Jobs in 1976 in Cupertino."
{
"entities": [
{"text": "Apple Inc.", "type": "ORGANIZATION", "confidence": 0.98},
{"text": "Steve Jobs", "type": "PERSON", "confidence": 0.99},
{"text": "1976", "type": "DATE", "confidence": 0.95},
{"text": "Cupertino", "type": "LOCATION", "confidence": 0.97}
]
}
# "Apple Inc. was founded by Steve Jobs in 1976 in Cupertino."
[
Entity(text="Apple Inc.", label="ORG", start_char=0, end_char=10, confidence=0.98),
Entity(text="Steve Jobs", label="PERSON", start_char=25, end_char=35, confidence=0.99),
Entity(text="1976", label="DATE", start_char=39, end_char=43, confidence=0.95),
Entity(text="Cupertino", label="GPE", start_char=47, end_char=56, confidence=0.97),
]
```
Each entity gets a type, confidence score, and a link to its source document. Three extraction methods are available:
`NERExtractor(method=...).extract(text)` returns a list of `Entity` objects, each
with a `label`, character offsets (`start_char` / `end_char`), a `confidence`
score, and a `metadata` dict recording the extraction method. Three methods are
available:
| Method | Speed | Accuracy | Requirements |
| :------ | :----- | :-------- | :------------ |
@@ -62,15 +63,19 @@ Each entity gets a type, confidence score, and a link to its source document. Th
Finding how entities connect to each other:
```python
{
"relationships": [
{"subject": "Steve Jobs", "predicate": "founded", "object": "Apple Inc.", "confidence": 0.92},
{"subject": "Apple Inc.", "predicate": "located_in", "object": "Cupertino", "confidence": 0.89}
]
}
jobs = Entity(text="Steve Jobs", label="PERSON", start_char=25, end_char=35)
apple = Entity(text="Apple Inc.", label="ORG", start_char=0, end_char=10)
[
Relation(subject=jobs, predicate="founded", object=apple, confidence=0.92),
Relation(subject=apple, predicate="located_in", object=Entity(text="Cupertino", label="GPE", start_char=47, end_char=56), confidence=0.89),
]
```
Relationships can be extracted via rule-based methods, ML models, or LLMs: each producing typed triplets with confidence scores and source attribution.
`RelationExtractor(method=...).extract(text, entities=entities)` returns a list of
`Relation` objects: typed subject-predicate-object triples (the endpoints are
`Entity` objects) with confidence scores and source attribution. Extraction runs
via pattern rules, ML models, or LLMs.
## Knowledge Graph vs. Vector Store
@@ -94,9 +99,10 @@ Both store information for AI retrieval: but they're built for different jobs.
```python
from semantica.kg import GraphBuilder, PathFinder
graph = GraphBuilder(merge_entities=True).build(entities=entities, relationships=rels)
finder = PathFinder()
path = finder.dijkstra_shortest_path(graph, "Steve Jobs", "Tim Cook")
graph = GraphBuilder(merge_entities=True).build(
{"entities": entities, "relationships": rels}
)
path = PathFinder().dijkstra_shortest_path(graph, "Steve Jobs", "Tim Cook")
```
</Tab>
@@ -140,8 +146,16 @@ Both store information for AI retrieval: but they're built for different jobs.
context = AgentContext(
vector_store=VectorStore(backend="faiss", dimension=768),
knowledge_graph=ContextGraph(advanced_analytics=True),
graph_expansion=True,
)
result = context.query("Who founded Apple?", mode="graphrag")
# store() extracts entities and populates the graph + vector index
context.store([{"content": "Steve Jobs co-founded Apple Inc. in 1976."}])
# retrieve() blends vector similarity with graph traversal
results = context.retrieve("Who founded Apple?", use_graph=True, expand_graph=True)
for r in results:
print(r["score"], r["content"], r["source"])
```
</Tab>
</Tabs>
@@ -203,7 +217,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
@@ -221,70 +235,80 @@ Inferred: Steve Jobs has a connection to Cupertino
Applies IF/THEN rules repeatedly until no new facts can be derived. Best for alert systems, compliance checks, and trigger-based workflows.
```python
from semantica.reasoning import Reasoner, Rule, Fact, RuleType
from semantica.reasoning import Reasoner
engine = Reasoner()
engine.add_fact(Fact(subject="Alice", predicate="is_a", obj="Manager"))
engine.add_rule(Rule(
rule_type=RuleType.FORWARD_CHAIN,
conditions=[{"subject": "?x", "predicate": "is_a", "object": "Manager"}],
conclusion={"subject": "?x", "predicate": "has_authority", "object": "true"}
))
result = engine.infer()
engine.add_fact("Manager(Alice)")
engine.add_rule("IF Manager(?x) THEN HasAuthority(?x)")
results = engine.forward_chain() # list of InferenceResult
for r in results:
print(r.conclusion) # "HasAuthority(Alice)"
```
</Tab>
<Tab title="Rete Network">
Efficient pattern matching for large rule sets: the Rete algorithm avoids re-evaluating rules whose preconditions haven't changed. Best for thousands of rules over millions of facts.
```python
from semantica.reasoning import ReteEngine
from semantica.reasoning import ReteEngine, Rule, Fact
engine = ReteEngine()
engine.load_rules("rules/domain_rules.json")
results = engine.run(kg)
engine.build_network([
Rule(rule_id="r1", name="manager_authority",
conditions=["Manager(?x)"], conclusion="HasAuthority(?x)"),
])
engine.add_fact(Fact(fact_id="f1", predicate="Manager", arguments=["Alice"]))
matches = engine.match_patterns()
results = engine.execute_matches(matches) # ["HasAuthority(?x)"]
```
</Tab>
<Tab title="Deductive & Abductive">
**Deductive**: classical syllogistic reasoning from premises to guaranteed conclusions.
**Abductive**: infers the most likely explanation for observed evidence. Best for diagnostic and investigative use cases.
<Tab title="LLM Reasoning">
`GraphReasoner` answers open-ended questions over a knowledge graph with an
LLM, returning a natural-language answer grounded in the graph's facts. Best
for exploratory and investigative questions that fixed rules can't anticipate.
```python
from semantica.reasoning import GraphReasoner
graph_reasoner = GraphReasoner(kg)
graph_reasoner.add_rule({"if": [{"subject": "?a", "predicate": "parent_of", "object": "?b"}], "then": {"subject": "?a", "predicate": "ancestor_of", "object": "?b"}})
inferences = graph_reasoner.infer(kg)
reasoner = GraphReasoner(provider="openai", model="gpt-4o-mini")
answer = reasoner.reason(kg, "Which suppliers are indirectly exposed to the Acme outage?")
```
</Tab>
<Tab title="Datalog (v0.4.0)">
Recursive Horn clause rules with fixpoint semantics: handles transitive closure and recursive relationships that forward chaining cannot express.
```python
from semantica.reasoning import DatalogReasoner, DatalogFact, DatalogRule
from semantica.reasoning import DatalogReasoner
reasoner = DatalogReasoner()
reasoner.add_fact(DatalogFact("parent", ("alice", "bob")))
reasoner.add_rule(DatalogRule("ancestor(?X, ?Y) :- parent(?X, ?Y)."))
reasoner.evaluate()
results = reasoner.query("ancestor(alice, ?Z)")
reasoner.add_fact("parent(alice, bob)")
reasoner.add_fact("parent(bob, charlie)")
reasoner.add_rule("ancestor(X, Y) :- parent(X, Y).")
reasoner.add_rule("ancestor(X, Z) :- parent(X, Y), ancestor(Y, Z).")
reasoner.derive_all()
results = reasoner.query("ancestor(alice, ?Z)") # {"Z": "bob"} and {"Z": "charlie"}, order not guaranteed
```
</Tab>
<Tab title="Engine Comparison">
| Engine | Description | Best For |
| :------ | :----------- | :-------- |
| Forward chaining | Applies rules until fixpoint | Alert systems, compliance checks |
| Rete network | Efficient pattern matching | Large rule sets, high fact throughput |
| Deductive | Classical syllogistic reasoning | Mathematical and logical inference |
| Abductive | Most likely explanation | Diagnostics, investigation |
| SPARQL | Query-based inference over RDF | Semantic web, ontology reasoning |
| Datalog (v0.4.0) | Recursive Horn clause rules | Transitive closure, graph reachability |
| Engine | Class | Best For |
| :------ | :----- | :-------- |
| Forward chaining | `Reasoner` | Alert systems, compliance checks |
| Rete network | `ReteEngine` | Large rule sets, high fact throughput |
| SPARQL expansion | `SPARQLReasoner` | Semantic web, ontology reasoning over RDF |
| Datalog (v0.4.0) | `DatalogReasoner` | Transitive closure, graph reachability |
| Temporal | `TemporalReasoningEngine` | Allen interval algebra, time-aware inference |
| LLM over the graph | `GraphReasoner` | Open-ended, investigative questions |
</Tab>
</Tabs>
All engines produce **explainable inference paths**: not black-box conclusions. Every derived fact includes the rules and premises that produced it.
`Reasoner.forward_chain()` returns `InferenceResult` objects that carry the rule
applied (`rule_used`) and the premises it fired on, and `ExplanationGenerator`
turns one into a step-by-step natural-language justification: reasoning here is
**not** a black box.
## Temporal Intelligence
@@ -313,13 +337,18 @@ Explore the semantic neighborhood of any entity in your graph: useful for unders
```python
from semantica.kg import SimilarityCalculator
calc = SimilarityCalculator()
scores = calc.calculate_similarity(entity_a, entity_b)
calc = SimilarityCalculator(method="cosine") # "cosine" | "euclidean" | "manhattan" | "correlation"
# Similarity for every unique pair of node embeddings: {(node_a, node_b): score}
pairs = calc.pairwise_similarity({"apple": vec_apple, "google": vec_google, "nest": vec_nest})
# Or rank a set of embeddings by closeness to one query vector
nearest = calc.find_most_similar(embeddings, query_embedding, top_k=10)
```
**Features:** N×N semantic distance matrices, ego-mode visualization, distance band classification (`near` / `mid` / `far`), embedding cache optimization for large graphs.
**Features:** N×N semantic distance matrices, ego-mode visualization, distance band classification (`direct` / `near` / `mid-range` / `distant`), 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
@@ -341,11 +370,11 @@ Real-world data contains the same entity under many names: "Apple", "Apple Inc."
```python
from semantica.deduplication import DuplicateDetector, EntityMerger
detector = DuplicateDetector(similarity_threshold=0.85)
duplicates = detector.detect_duplicates(entities)
detector = DuplicateDetector(similarity_threshold=0.85)
candidates = detector.detect_duplicates(entities)
merger = EntityMerger()
deduplicated_entities = merger.merge_duplicates(entities)
merger = EntityMerger()
operations = merger.merge_duplicates(entities, strategy="keep_most_complete")
```
</Tab>
</Tabs>
@@ -361,19 +390,21 @@ Every fact in Semantica links back to:
- The **reasoning steps** that produced any inferred fact
<Note>
This is W3C PROV-O compliant lineage: suitable for regulated industries that require audit trails (HIPAA, SOX, GDPR, FDA 21 CFR Part 11). Use `RDFExporter(include_provenance=True)` to embed provenance inline in any RDF export.
This is W3C PROV-O compliant lineage: suitable for regulated industries that require audit trails (HIPAA, SOX, GDPR, FDA 21 CFR Part 11). `ProvenanceManager.export_prov(format="turtle")` serialises the recorded lineage as PROV-O RDF.
</Note>
```python
from semantica.provenance import ProvenanceManager
prov = ProvenanceManager()
lineage = prov.get_entity_lineage("apple_inc")
prov = ProvenanceManager()
prov.track_entity("apple_inc", source="report.pdf",
metadata={"extractor": "NamedEntityRecognizer", "confidence": 0.98})
print(f"Source: {lineage.source_document}")
print(f"Method: {lineage.extraction_method}")
print(f"Extracted: {lineage.timestamp}")
print(f"Checksum: {lineage.checksum}")
record = prov.get_provenance("apple_inc") # dict; use get_lineage() for the full chain
print(record["source_document"])
print(record["timestamp"])
print(record["checksum"])
print(record["metadata"]) # extractor, confidence, and any custom keys
```
@@ -413,7 +444,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
@@ -456,32 +487,32 @@ Semantica is designed for extension. Any component: ingestor, extractor, graph b
**Extension points available:** ingestors, parsers, normalizers, extractors, reasoning engines, export formats, vector store backends, graph store backends, visualization renderers.
</Accordion>
<Accordion title="MethodRegistry: add domain-specific graph operations">
<Accordion title="MethodRegistry: swap a built-in graph operation for your own">
`MethodRegistry` lets you register custom methods on knowledge graph objects by name: useful for adding domain-specific graph operations without subclassing.
`method_registry` lets you register an alternative implementation for a
knowledge-graph task (`build`, `analyze`, `centrality`, `resolve`, …) under a
name, then select it wherever that task runs.
```python
from semantica.kg import MethodRegistry
from semantica.kg import method_registry
from semantica.kg.methods import calculate_centrality
registry = MethodRegistry()
def find_supply_chain_hops(graph, source_node, max_hops=3):
"""Custom BFS traversal for supply chain graphs."""
def fast_centrality(graph, **kwargs):
"""Custom centrality implementation."""
...
# Register under a string key
registry.register("supply_chain_hops", find_supply_chain_hops)
# register(task, name, func)
method_registry.register("centrality", "fast_centrality", fast_centrality)
# Call by name on any graph object
result = registry.call("supply_chain_hops", kg, source_node="Supplier_A", max_hops=5)
# The task wrappers consult method_registry, so the name is now selectable:
scores = calculate_centrality(kg, method="fast_centrality")
# List all registered methods
print(registry.list_methods()) # ["supply_chain_hops", ...]
print(method_registry.list_all("centrality")) # {"centrality": ["fast_centrality", ...]}
```
</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.
+9 -9
View File
@@ -4,7 +4,7 @@ description: "How to contribute code, documentation, tests, and community suppor
icon: "code-pull-request"
---
Contributions of all kinds are welcome: code, documentation, tests, and community support. Every contribution is recognized in release notes and the GitHub contributors list.
Contributions of all kinds are welcome (code, documentation, tests, and community support). Every contribution is recognized in release notes and the GitHub contributors list.
## Quick Start
@@ -17,15 +17,15 @@ pip install -e ".[dev]"
pytest
```
New to the project? Start with [`good-first-issue`](https://github.com/semantica-agi/semantica/labels/good-first-issue) labeled tickets: they're scoped to be completable in a few hours without deep codebase knowledge.
First-time contributors can start with [`good-first-issue`](https://github.com/semantica-agi/semantica/labels/good-first-issue) labeled tickets, which are scoped to be completable in a few hours without deep codebase knowledge.
## Ways to Contribute
- **Code** — Fix bugs, implement features, optimize performance, or add new ingestors, parsers, and exporters using the plugin registry.
- **Documentation** — Fix typos, improve clarity, add missing examples, write tutorials, or keep the API reference accurate as modules evolve.
- **Testing** — Add test coverage for untested modules or edge cases, reproduce reported bugs with minimal repros, or improve cross-platform reliability.
- **Community** — Answer questions in GitHub Issues and Discussions, review pull requests with constructive feedback, or share Semantica in blog posts and talks.
- **Code**: fix bugs, implement features, optimize performance, or add new ingestors, parsers, and exporters using the plugin registry.
- **Documentation**: fix typos, improve clarity, add missing examples, write tutorials, or keep the API reference accurate as modules evolve.
- **Testing**: add test coverage for untested modules or edge cases, reproduce reported bugs with minimal repros, or improve cross-platform reliability.
- **Community**: answer questions in GitHub Issues and Discussions, review pull requests with constructive feedback, or share Semantica in blog posts and talks.
## Development Setup
@@ -76,7 +76,7 @@ Before submitting a PR, confirm:
## Code of Conduct
All contributors are expected to follow the [Contributor Covenant Code of Conduct](https://github.com/semantica-agi/semantica/blob/main/CODE_OF_CONDUCT.md). Be respectful, patient, and constructive: especially toward newcomers. Report violations by opening an issue with the `[CoC]` prefix.
All contributors are expected to follow the [Contributor Covenant Code of Conduct](https://github.com/semantica-agi/semantica/blob/main/CODE_OF_CONDUCT.md). Be respectful, patient, and constructive, especially toward newcomers. Report violations by opening an issue with the `[CoC]` prefix.
## Help
@@ -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.
+26 -25
View File
@@ -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>
@@ -18,42 +18,43 @@ icon: "flask"
## Featured Recipe
- **[Your First Knowledge Graph](https://github.com/semantica-agi/semantica/blob/main/cookbook/introduction/08_Your_First_Knowledge_Graph.ipynb)** — Go from raw text to a queryable knowledge graph in 20 minutes. Topics: Extraction, Graph Construction, Visualization · *Beginner*
- **[Your First Knowledge Graph](https://github.com/semantica-agi/semantica/blob/main/cookbook/introduction/08_Your_First_Knowledge_Graph.ipynb)**: go from raw text to a queryable knowledge graph in 20 minutes. Topics: Extraction, Graph Construction, Visualization · *Beginner*
## Core Tutorials
Essential guides to master the Semantica framework.
- **[Welcome to Semantica](https://github.com/semantica-agi/semantica/blob/main/cookbook/introduction/01_Welcome_to_Semantica.ipynb)** — Interactive introduction to the framework's core philosophy and all modules. Topics: Framework Overview, Architecture · *Beginner*
- **[Data Ingestion](https://github.com/semantica-agi/semantica/blob/main/cookbook/introduction/02_Data_Ingestion.ipynb)** — Loading data from files, web, databases, streams, feeds, repositories, email, and MCP. Topics: FileIngestor, WebIngestor, DBIngestor · *Beginner*
- **[Document Parsing](https://github.com/semantica-agi/semantica/blob/main/cookbook/introduction/03_Document_Parsing.ipynb)** — Extracting clean text from complex formats like PDF, DOCX, and HTML. Topics: OCR, PDF Parsing, Text Extraction · *Beginner*
- **[Data Normalization](https://github.com/semantica-agi/semantica/blob/main/cookbook/introduction/04_Data_Normalization.ipynb)** — Pipelines for cleaning, normalizing, and preparing text. Topics: Text Cleaning, Unicode, Formatting · *Beginner*
- **[Entity Extraction](https://github.com/semantica-agi/semantica/blob/main/cookbook/introduction/05_Entity_Extraction.ipynb)** — Using NER to identify people, organizations, and custom entities. Topics: NER, spaCy, LLM Extraction · *Beginner*
- **[Relation Extraction](https://github.com/semantica-agi/semantica/blob/main/cookbook/introduction/06_Relation_Extraction.ipynb)** — Discovering and classifying relationships between entities. Topics: Relation Classification, Dependency Parsing · *Beginner*
- **[Embedding Generation](https://github.com/semantica-agi/semantica/blob/main/cookbook/introduction/12_Embedding_Generation.ipynb)** — Creating and managing vector embeddings for semantic search. Topics: Embeddings, OpenAI, HuggingFace · *Intermediate*
- **[Vector Store](https://github.com/semantica-agi/semantica/blob/main/cookbook/introduction/13_Vector_Store.ipynb)** — Setting up vector stores for similarity search and retrieval. *Intermediate*
- **[Graph Store](https://github.com/semantica-agi/semantica/blob/main/cookbook/introduction/09_Graph_Store.ipynb)** — Persisting knowledge graphs in Neo4j or FalkorDB. Topics: Neo4j, Cypher, Persistence · *Intermediate*
- **[Ontology](https://github.com/semantica-agi/semantica/blob/main/cookbook/introduction/14_Ontology.ipynb)** — Defining domain schemas and ontologies to structure your data. Topics: OWL, RDF, Schema Design · *Intermediate*
- **[Seed Data](https://github.com/semantica-agi/semantica/blob/main/cookbook/introduction/25_Seed_Data.ipynb)** — Bootstrapping a knowledge graph from trusted CSV, JSON, database, and API sources before extraction runs. Topics: SeedDataManager, Foundation Graphs · *Intermediate*
- **[Welcome to Semantica](https://github.com/semantica-agi/semantica/blob/main/cookbook/introduction/01_Welcome_to_Semantica.ipynb)**: interactive introduction to the framework's core philosophy and all modules. Topics: Framework Overview, Architecture · *Beginner*
- **[Data Ingestion](https://github.com/semantica-agi/semantica/blob/main/cookbook/introduction/02_Data_Ingestion.ipynb)**: loading data from files, web, databases, streams, feeds, repositories, email, and MCP. Topics: FileIngestor, WebIngestor, DBIngestor · *Beginner*
- **[Document Parsing](https://github.com/semantica-agi/semantica/blob/main/cookbook/introduction/03_Document_Parsing.ipynb)**: extracting clean text from complex formats like PDF, DOCX, and HTML. Topics: OCR, PDF Parsing, Text Extraction · *Beginner*
- **[Data Normalization](https://github.com/semantica-agi/semantica/blob/main/cookbook/introduction/04_Data_Normalization.ipynb)**: pipelines for cleaning, normalizing, and preparing text. Topics: Text Cleaning, Unicode, Formatting · *Beginner*
- **[Entity Extraction](https://github.com/semantica-agi/semantica/blob/main/cookbook/introduction/05_Entity_Extraction.ipynb)**: using NER to identify people, organizations, and custom entities. Topics: NER, spaCy, LLM Extraction · *Beginner*
- **[Relation Extraction](https://github.com/semantica-agi/semantica/blob/main/cookbook/introduction/06_Relation_Extraction.ipynb)**: discovering and classifying relationships between entities. Topics: Relation Classification, Dependency Parsing · *Beginner*
- **[Embedding Generation](https://github.com/semantica-agi/semantica/blob/main/cookbook/introduction/12_Embedding_Generation.ipynb)**: creating and managing vector embeddings for semantic search. Topics: Embeddings, OpenAI, HuggingFace · *Intermediate*
- **[Vector Store](https://github.com/semantica-agi/semantica/blob/main/cookbook/introduction/13_Vector_Store.ipynb)**: setting up vector stores for similarity search and retrieval. *Intermediate*
- **[Graph Store](https://github.com/semantica-agi/semantica/blob/main/cookbook/introduction/09_Graph_Store.ipynb)**: persisting knowledge graphs in Neo4j or FalkorDB. Topics: Neo4j, Cypher, Persistence · *Intermediate*
- **[Ontology](https://github.com/semantica-agi/semantica/blob/main/cookbook/introduction/14_Ontology.ipynb)**: defining domain schemas and ontologies to structure your data. Topics: OWL, RDF, Schema Design · *Intermediate*
- **[Seed Data](https://github.com/semantica-agi/semantica/blob/main/cookbook/introduction/25_Seed_Data.ipynb)**: bootstrapping a knowledge graph from trusted CSV, JSON, database, and API sources before extraction runs. Topics: SeedDataManager, Foundation Graphs · *Intermediate*
- **[Semantic Layer Basics](https://github.com/semantica-agi/semantica/blob/main/cookbook/introduction/26_Semantic_Layer_Basics.ipynb)**: capstone tutorial that combines a knowledge graph, generated ontology, explicit mappings, ontology-aligned RDF, and a SPARQL query. Topics: Semantic Layer, Ontology Mapping, Oxigraph, SPARQL · *Intermediate*
## Advanced Concepts
Deep dive into advanced features, customization, and complex workflows.
- **[Advanced Extraction](https://github.com/semantica-agi/semantica/blob/main/cookbook/advanced/01_Advanced_Extraction.ipynb)** — Custom extractors, LLM-based extraction, and complex pattern matching. Topics: Custom Models, Regex, LLMs · *Advanced*
- **[Advanced Graph Analytics](https://github.com/semantica-agi/semantica/blob/main/cookbook/advanced/02_Advanced_Graph_Analytics.ipynb)** — Centrality, community detection, and pathfinding algorithms. Topics: PageRank, Louvain, Shortest Path · *Advanced*
- **[Advanced Context Engineering](https://github.com/semantica-agi/semantica/blob/main/cookbook/advanced/11_Advanced_Context_Engineering.ipynb)** — Production-grade memory system for AI agents using FAISS and Neo4j. Topics: Agent Memory, GraphRAG, Entity Injection · *Advanced*
- **[Complete Visualization Suite](https://github.com/semantica-agi/semantica/blob/main/cookbook/advanced/03_Complete_Visualization_Suite.ipynb)** — Interactive, publication-ready visualizations of your graphs. Topics: PyVis, NetworkX, D3.js · *Intermediate*
- **[Conflict Resolution](https://github.com/semantica-agi/semantica/blob/main/cookbook/introduction/17_Conflict_Detection_and_Resolution.ipynb)** — Strategies for handling contradictory information from multiple sources. Topics: Truth Discovery, Voting, Confidence · *Advanced*
- **[Multi-Format Export](https://github.com/semantica-agi/semantica/blob/main/cookbook/advanced/05_Multi_Format_Export.ipynb)** — Exporting to RDF, OWL, JSON-LD, and NetworkX formats. Topics: Serialization, Interoperability · *Intermediate*
- **[Multi-Source Integration](https://github.com/semantica-agi/semantica/blob/main/cookbook/advanced/06_Multi_Source_Data_Integration.ipynb)** — Merging data from disparate sources into a unified graph. Topics: Entity Resolution, Merging, Fusion · *Advanced*
- **[Reasoning and Inference](https://github.com/semantica-agi/semantica/blob/main/cookbook/advanced/08_Reasoning_and_Inference.ipynb)** — Using logical reasoning to infer new knowledge from existing facts. Topics: Logic Rules, Inference Engines · *Advanced*
- **[Temporal Knowledge Graphs](https://github.com/semantica-agi/semantica/blob/main/cookbook/advanced/10_Temporal_Knowledge_Graphs.ipynb)** — Modeling and querying data that changes over time. Topics: Time Series, Temporal Logic, Allen Algebra · *Advanced*
- **[Provenance Tracking](https://github.com/semantica-agi/semantica/blob/main/cookbook/introduction/22_Provenance_Tracking.ipynb)** — Audit-grade, W3C PROV-O-aligned tracking of where every entity, relationship, and chunk came from. Topics: PROV-O, Lineage, Checksums, Invalidation · *Advanced*
- **[Reasoning Module](https://github.com/semantica-agi/semantica/blob/main/cookbook/introduction/23_Reasoning.ipynb)** — Deriving new knowledge from existing facts with forward chaining, backward chaining, and Datalog strategies. Topics: Reasoner, Datalog, Explanations · *Advanced*
- **[Change Management](https://github.com/semantica-agi/semantica/blob/main/cookbook/introduction/24_Change_Management.ipynb)** — Versioning, audit trails, and data-integrity checks for knowledge graphs and ontologies. Topics: ChangeLogEntry, Version Storage, Data Integrity · *Advanced*
- **[Advanced Extraction](https://github.com/semantica-agi/semantica/blob/main/cookbook/advanced/01_Advanced_Extraction.ipynb)**: custom extractors, LLM-based extraction, and complex pattern matching. Topics: Custom Models, Regex, LLMs · *Advanced*
- **[Advanced Graph Analytics](https://github.com/semantica-agi/semantica/blob/main/cookbook/advanced/02_Advanced_Graph_Analytics.ipynb)**: centrality, community detection, and pathfinding algorithms. Topics: PageRank, Louvain, Shortest Path · *Advanced*
- **[Advanced Context Engineering](https://github.com/semantica-agi/semantica/blob/main/cookbook/advanced/11_Advanced_Context_Engineering.ipynb)**: persistent memory system for AI agents using FAISS and Neo4j. Topics: Agent Memory, GraphRAG, Entity Injection · *Advanced*
- **[Complete Visualization Suite](https://github.com/semantica-agi/semantica/blob/main/cookbook/advanced/03_Complete_Visualization_Suite.ipynb)**: interactive network, analytics, and temporal visualizations for graphs. Topics: PyVis, NetworkX, D3.js · *Intermediate*
- **[Conflict Resolution](https://github.com/semantica-agi/semantica/blob/main/cookbook/introduction/17_Conflict_Detection_and_Resolution.ipynb)**: strategies for handling contradictory information from multiple sources. Topics: Truth Discovery, Voting, Confidence · *Advanced*
- **[Multi-Format Export](https://github.com/semantica-agi/semantica/blob/main/cookbook/advanced/05_Multi_Format_Export.ipynb)**: exporting to RDF, OWL, JSON-LD, and NetworkX formats. Topics: Serialization, Interoperability · *Intermediate*
- **[Multi-Source Integration](https://github.com/semantica-agi/semantica/blob/main/cookbook/advanced/06_Multi_Source_Data_Integration.ipynb)**: merging data from disparate sources into a unified graph. Topics: Entity Resolution, Merging, Fusion · *Advanced*
- **[Reasoning and Inference](https://github.com/semantica-agi/semantica/blob/main/cookbook/advanced/08_Reasoning_and_Inference.ipynb)**: using logical reasoning to infer new knowledge from existing facts. Topics: Logic Rules, Inference Engines · *Advanced*
- **[Temporal Knowledge Graphs](https://github.com/semantica-agi/semantica/blob/main/cookbook/advanced/10_Temporal_Knowledge_Graphs.ipynb)**: modeling and querying data that changes over time. Topics: Time Series, Temporal Logic, Allen Algebra · *Advanced*
- **[Provenance Tracking](https://github.com/semantica-agi/semantica/blob/main/cookbook/introduction/22_Provenance_Tracking.ipynb)**: W3C PROV-O-aligned lineage tracking and checksum verification for entities, relationships, and chunks. Topics: PROV-O, Lineage, Checksums, Invalidation · *Advanced*
- **[Reasoning Module](https://github.com/semantica-agi/semantica/blob/main/cookbook/introduction/23_Reasoning.ipynb)**: deriving new knowledge from existing facts with forward chaining, backward chaining, and Datalog strategies. Topics: Reasoner, Datalog, Explanations · *Advanced*
- **[Change Management](https://github.com/semantica-agi/semantica/blob/main/cookbook/introduction/24_Change_Management.ipynb)**: versioning, audit trails, and data-integrity checks for knowledge graphs and ontologies. Topics: ChangeLogEntry, Version Storage, Data Integrity · *Advanced*
## How to Run
+30 -29
View File
@@ -2,7 +2,7 @@
"$schema": "https://mintlify.com/docs.json",
"theme": "mint",
"name": "Semantica",
"description": "The Accountability and Context Layer for AI — Context Graphs · Decision Intelligence · Full Provenance",
"description": "The Context and Semantic Layer for AI in High-Stakes Domains — Context Graphs · Decision Intelligence · Full Provenance",
"colors": {
"primary": "#10B981",
"light": "#10B981",
@@ -43,7 +43,7 @@
"raiseIssue": true
},
"metadata": {
"og:title": "Semantica — Accountability & Context Layer for AI",
"og:title": "Semantica — Context & Semantic Layer for AI in High-Stakes Domains",
"og:description": "Build explainable, auditable knowledge graphs with full provenance. Open source. MIT licensed.",
"og:image": "/assets/img/semantica-logo.png",
"twitter:card": "summary_large_image",
@@ -121,6 +121,23 @@
"pages": [
"vector_stores/pgvector"
]
},
{
"group": "FAQ",
"pages": [
"faq"
]
},
{
"group": "Community",
"pages": [
"community",
"community-projects",
"contributing-guide",
"governance",
"citation",
"project-license"
]
}
]
},
@@ -167,7 +184,17 @@
"guides/policy-engine",
"guides/visualization",
"guides/distance-intelligence",
"guides/graph-analytics",
"guides/graph-analytics"
]
}
]
},
{
"tab": "API Reference",
"groups": [
{
"group": "Context & Intelligence",
"pages": [
"reference/context",
"reference/kg",
"reference/temporal",
@@ -236,32 +263,6 @@
]
}
]
},
{
"tab": "FAQ",
"groups": [
{
"group": "FAQ",
"pages": [
"faq"
]
},
{
"group": "Community",
"pages": [
"community",
"community-projects",
"contributing-guide",
"governance",
"citation",
"project-license"
]
}
]
},
{
"tab": "Changelog",
"href": "https://github.com/semantica-agi/semantica/releases"
}
]
},
+7 -7
View File
@@ -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
@@ -109,7 +109,7 @@ Explorer loads a graph from a JSON file on disk. You need to create that file fi
</Steps>
<Tip>
Already have a graph from a pipeline run? Skip straight to Step 2. The only requirement is that the file was saved with `ContextGraph.save_to_file()`.
Pipelines that already produced a saved graph can skip straight to Step 2, provided the file was saved with `ContextGraph.save_to_file()`.
</Tip>
@@ -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).
+15 -15
View File
@@ -16,8 +16,8 @@ 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 |
| Latest version? | **v0.6.7** (August 2026) |
| 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.8** (September 2026) |
| Local LLMs? | Yes: Ollama via LiteLLM, HuggingFaceLLM for air-gapped |
@@ -27,7 +27,7 @@ icon: "circle-question"
<Accordion title="What is Semantica?" icon="info-circle">
Semantica is an open-source framework for building context graphs and decision intelligence layers for AI. It transforms unstructured data: documents, APIs, databases: into structured knowledge graphs with full provenance tracking, making AI systems explainable and auditable.
Semantica is an open-source framework for building context graphs and decision intelligence layers for AI. It transforms unstructured data (documents, APIs, databases) into structured knowledge graphs with full provenance tracking, making AI systems explainable and auditable.
It's not a replacement for LangChain or LlamaIndex. It's the **accountability layer** that goes on top: recording decisions, tracing facts to sources, and making reasoning transparent.
@@ -46,7 +46,7 @@ It's not a replacement for LangChain or LlamaIndex. It's the **accountability la
<Accordion title="What makes Semantica different from LangChain or LlamaIndex?" icon="scale-balanced">
Most frameworks stop at retrieval or generation. Semantica adds an **accountability layer**: every decision is recorded, every fact links to a source, and every reasoning step is explainable. It's designed for environments where you need to audit *why* an AI reached a conclusion: not just what it said.
Most frameworks stop at retrieval or generation. Semantica adds an **accountability layer**: every decision is recorded, every fact links to a source, and every reasoning step is explainable. It's designed for environments where you need to audit *why* an AI reached a conclusion, not just what it said.
Semantica works alongside these frameworks, not against them.
@@ -54,11 +54,11 @@ Semantica works alongside these frameworks, not against them.
<Accordion title="Does Semantica explain an LLM's internal reasoning or chain-of-thought?" icon="triangle-exclamation">
No. 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.
No. 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: what context and data were used, what decision was produced, the provenance behind it, the relevant relationships, the policies applied, and the resulting decision trail.
In short: Semantica explains and audits *what the AI system did* not the foundation model's private internal reasoning.
In short, Semantica explains and audits *what the AI system did*, not the foundation model's private internal reasoning.
</Accordion>
@@ -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.8**: released September 2026.
Highlights: Ontology Hub, Distance Intelligence, Parquet/XML ingestion, 12 security fixes, Graph Explorer redesign, NER gateway fix.
Highlights: every release is now cryptographically signed (SLSA build provenance + Sigstore, closing the OpenSSF Scorecard Signed-Releases gap), real vector-store enumeration (`scan_vectors()`/`iter_vectors()`) across FAISS/SQLiteVec/PgVector/Qdrant/Weaviate/Milvus making `store migrate` functional, first-class Anthropic/Gemini/Ollama/DeepSeek/Novita LLM provider wrappers, a CI-friendly ontology quality gate, and 35 correctness fixes. The 0.6.x line also added first-class LangChain and 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.83.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>
@@ -348,6 +348,6 @@ set PYTHONIOENCODING=utf-8
## Support
- [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.
- [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.
+47 -39
View File
@@ -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
@@ -42,7 +42,7 @@ icon: "rocket"
Verify installation:
```python
import semantica
print(semantica.__version__) # 0.6.7
print(semantica.__version__) # 0.6.8
```
</Check>
</Step>
@@ -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.
@@ -84,13 +84,13 @@ icon: "rocket"
# 1. Ingest
sources = FileIngestor().ingest("data/report.pdf")
# 2. Parse
parsed = DocumentParser().parse(sources[0])
# 2. Parse (extract_text returns a plain string for any supported format)
text = DocumentParser().extract_text(sources[0].path)
# 3. Extract
# 3. Extract (extractors take text, return Entity / Relation objects)
ner = NERExtractor(method="pattern") # no API key needed
entities = ner.extract(parsed)
relationships = RelationExtractor().extract(parsed, entities=entities)
entities = ner.extract(text)
relationships = RelationExtractor(method="pattern").extract(text, entities=entities)
# 4. Build
graph = GraphBuilder(merge_entities=True).build(
@@ -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">
@@ -144,24 +144,32 @@ icon: "rocket"
context = AgentContext(
vector_store=VectorStore(backend="faiss", dimension=768),
knowledge_graph=ContextGraph(advanced_analytics=True),
graph_expansion=True, # blend graph traversal into retrieval
max_expansion_hops=3, # how far to walk from the seed nodes
)
# Load your knowledge graph
context.load_graph("company_kg.json")
# store() runs extraction and populates both the vector index and the graph
context.store([
{"content": "Steve Wozniak co-founded Apple with Steve Jobs in 1976."},
{"content": "Tony Fadell led the iPod team at Apple, then founded Nest."},
])
# Multi-hop GraphRAG query
result = context.query(
# GraphRAG retrieval: seed from vector matches, expand along graph edges
results = context.retrieve(
"What companies were founded by people who worked at Apple?",
mode="graphrag",
reasoning=True,
use_graph=True,
expand_graph=True,
)
# Every claim links back to a source node
for claim in result.claims:
print(f"{claim.text} → source: {claim.source_node}")
for r in results:
print(f"[{r['score']:.3f}] {r['content'][:70]} (source: {r['source']})")
```
**Next:** [GraphRAG concepts →](concepts#graphrag)
Each result carries `content`, `score`, `source`, and `metadata`. For a
grounded natural-language answer plus an auditable traversal, use
`context.query_with_reasoning(query, llm_provider=...)` — it returns
`response`, `reasoning_path`, `sources`, and `confidence`.
**Next:** [GraphRAG concepts →](/concepts#graphrag)
</Tab>
<Tab title="MCP Integration">
@@ -183,9 +191,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 +202,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.
+32 -32
View File
@@ -23,16 +23,16 @@ A persistent, queryable graph of everything an agent knows, decides, and reasons
A first-class object in Semantica: a recorded agent choice with category, scenario, reasoning, outcome, confidence score, causal chain, and source provenance. Stored and searchable via `context.record_decision()`.
**Entity**
A distinct object or concept in the real world: a person, organization, location, event, or abstract concept. Entities are nodes in a knowledge graph, each with typed properties and a source provenance record.
A distinct object or concept in the real world (person, organization, location, event, or abstract concept). Entities are nodes in a knowledge graph, each with typed properties and a source provenance record.
**Knowledge Graph (KG)**
A structured representation of knowledge using entities (nodes) and relationships (edges). Knowledge graphs enable reasoning, querying, semantic search, and traceable inference: unlike flat vector stores.
A structured representation of knowledge using entities (nodes) and relationships (edges). Knowledge graphs enable reasoning, querying, semantic search, and traceable inference, unlike flat vector stores.
**Relationship**
A directed, typed connection between two entities: e.g., `works_for`, `located_in`, `founded_by`. Relationships carry confidence scores and provenance back to the source document.
A directed, typed connection between two entities (e.g., `works_for`, `located_in`, `founded_by`). Relationships carry confidence scores and provenance back to the source document.
**Semantic**
Relating to meaning in language or logic. Semantic understanding captures context and intent: going beyond keyword matching to understand what text *means*.
Relating to meaning in language or logic. Semantic understanding captures context and intent, going beyond keyword matching to understand what text *means*.
## Data Processing
@@ -41,19 +41,19 @@ Relating to meaning in language or logic. Semantic understanding captures contex
Splitting large documents into smaller pieces while preserving semantic context. Semantica supports recursive, semantic boundary, entity-aware, relation-aware, sliding window, structural, and table-aware chunking strategies.
**Ingestion**
Loading data from external sources: files, databases, APIs, streams: into the pipeline as a unified `SourceDocument`. The first stage in every Semantica pipeline.
Loading data from external sources (files, databases, APIs, streams) into the pipeline as a unified `SourceDocument`. The first stage in every Semantica pipeline.
**Normalization**
Standardizing data into a consistent canonical form: converting dates to ISO format, canonicalizing entity names, fixing encoding issues, stripping noise. Ensures downstream extraction works on clean, consistent text.
Standardizing data into a consistent canonical form by converting dates to ISO format, canonicalizing entity names, fixing encoding issues, and stripping noise. Ensures downstream extraction works on clean, consistent text.
**Parsing**
Extracting structured text, layout, and metadata from unstructured or semi-structured documents: PDFs, Word files, HTML, PPTX. `DoclingParser` additionally handles multi-column layouts, merged-cell tables, and OCR.
Extracting structured text, layout, and metadata from unstructured or semi-structured documents (PDFs, Word files, HTML, PPTX). `DoclingParser` additionally handles multi-column layouts, merged-cell tables, and OCR.
## Artificial Intelligence
**Abductive Reasoning**
Inference to the most plausible explanation for observed facts. One of six reasoning engines in `semantica.reasoning`: returns the most likely hypothesis given available evidence.
Inference to the most plausible explanation for observed facts. One of six reasoning engines in `semantica.reasoning`, returning the most likely hypothesis given available evidence.
**Datalog**
A declarative logic programming language for knowledge base queries. Semantica's `DatalogEngine` supports recursive Horn clause rules with bottom-up semi-naive fixpoint semantics. Added in v0.4.0.
@@ -62,7 +62,7 @@ A declarative logic programming language for knowledge base queries. Semantica's
An advanced RAG approach that combines vector similarity search with knowledge graph traversal. Every LLM response is grounded in structured graph context, with each claim traceable to a source node. Eliminates hallucination without source attribution.
**Inference**
Deriving new facts or conclusions from existing knowledge using logical rules: without the derived facts being explicitly present in the source data.
Deriving new facts or conclusions from existing knowledge using logical rules, without the derived facts being explicitly present in the source data.
**LLM (Large Language Model)**
An AI model trained on large text corpora, capable of understanding and generating natural language. Semantica integrates with 8+ LLM providers for entity extraction, relation extraction, and reasoning.
@@ -74,7 +74,7 @@ A technique that enhances LLM outputs by retrieving relevant context from a know
## Knowledge Graph Components
**Allen Interval Algebra**
A system of 13 relations for describing how two time intervals relate: before, after, meets, overlaps, during, starts, finishes, equals, and their inverses. Supported in `TemporalKnowledgeGraph` since v0.4.0.
A system of 13 relations for describing how two time intervals relate (before, after, meets, overlaps, during, starts, finishes, equals, and their inverses). Supported in `TemporalKnowledgeGraph` since v0.4.0.
**BiTemporalFact**
A fact with two independent time dimensions: *valid time* (when it was true in the world) and *transaction time* (when it was recorded in the system). Enables full audit trails for slowly changing data.
@@ -86,43 +86,43 @@ A directed connection between two nodes in a graph, representing a typed relatio
A vertex in a knowledge graph representing an entity or concept. Nodes carry typed properties, a confidence score, and provenance linking back to the source document.
**Property**
An attribute or characteristic of an entity or relationship: name, date, URI, confidence score, source URL.
An attribute or characteristic of an entity or relationship, such as name, date, URI, confidence score, or source URL.
**Temporal Graph**
A knowledge graph where nodes and edges carry `valid_from` / `valid_until` time windows, enabling point-in-time queries and historical state reconstruction.
**Triplet**
The atomic unit of knowledge: a `(subject, predicate, object)` triple: e.g., `(Apple_Inc, founded_by, Steve_Jobs)`. The building block of RDF and SPARQL-based storage.
The atomic unit of knowledge: a `(subject, predicate, object)` triple (e.g., `(Apple_Inc, founded_by, Steve_Jobs)`). The building block of RDF and SPARQL-based storage.
## Entity Recognition & Extraction
**Coreference Resolution**
Determining when multiple expressions in text refer to the same entity: e.g., "Apple" and "the company" both referring to Apple Inc. Handled by `CoreferenceResolver` in `semantica.semantic_extract`.
Determining when multiple expressions in text refer to the same entity (e.g., "Apple" and "the company" both referring to Apple Inc.). Handled by `CoreferenceResolver` in `semantica.semantic_extract`.
**Entity Resolution**
Determining when two entity mentions across different documents refer to the same real-world entity. Also called entity linking or deduplication. Uses similarity scoring, blocking, and semantic embeddings.
**Event Detection**
Identifying and classifying events in text: acquisitions, partnerships, product launches, regulatory decisions. Handled by `EventDetector` in `semantica.semantic_extract`.
Identifying and classifying events in text (acquisitions, partnerships, product launches, regulatory decisions). Handled by `EventDetector` in `semantica.semantic_extract`.
**Named Entity Recognition (NER)**
Identifying and classifying named entities in text into predefined categories: persons, organizations, locations, dates, products, and custom types. Three modes: pattern-based, ML-based, and LLM-based.
Identifying and classifying named entities in text into predefined categories (persons, organizations, locations, dates, products, and custom types). Three modes: pattern-based, ML-based, and LLM-based.
**Relationship Extraction**
Identifying and extracting typed semantic relationships between entities: e.g., `(Google, acquired, DeepMind)`: from raw text.
Identifying and extracting typed semantic relationships between entities (such as `(Google, acquired, DeepMind)`) from raw text.
## Ontology & Schema
**Axiom**
A statement accepted as true in an ontology, used to define logical constraints: e.g., "every Person must have a name", "Organization can have at most one CEO at a time".
A statement accepted as true in an ontology, used to define logical constraints (e.g., "every Person must have a name", "Organization can have at most one CEO at a time").
**Class**
A category or type of entity in an ontology: `Person`, `Organization`, `Location`. Classes form a hierarchy and carry constraints validated by SHACL.
A category or type of entity in an ontology (`Person`, `Organization`, `Location`). Classes form a hierarchy and carry constraints validated by SHACL.
**Ontology**
A formal specification of domain concepts, relationships, and constraints: typically expressed in OWL. Semantica can auto-generate ontologies from knowledge graphs or import existing OWL/RDF/Turtle files.
A formal specification of domain concepts, relationships, and constraints, typically expressed in OWL. Semantica can auto-generate ontologies from knowledge graphs or import existing OWL/RDF/Turtle files.
**Ontology Hub**
Semantica's v0.5.0 visual browser UI for the full ontology lifecycle: visual class editor, SHACL Studio, alignment authoring, health dashboard, and version-controlled diffs.
@@ -140,13 +140,13 @@ A W3C standard for representing controlled vocabularies, taxonomies, and thesaur
## Storage & Retrieval
**Embedding**
A dense numerical vector that represents text, images, or other data in a continuous semantic space. Entities with similar meaning produce vectors that are close together: enabling similarity search and semantic matching.
A dense numerical vector that represents text, images, or other data in a continuous semantic space. Entities with similar meaning produce vectors that are close together, enabling similarity search and semantic matching.
**Graph Database**
A database optimized for storing and querying graph-structured data using node and edge primitives. Semantica supports Neo4j, FalkorDB, Apache AGE, and Amazon Neptune.
**Hybrid Search**
A retrieval strategy combining vector similarity search with keyword or metadata filtering: higher accuracy than either approach alone.
A retrieval strategy combining vector similarity search with keyword or metadata filtering, achieving higher accuracy than either approach alone.
**Triplet Store**
A database designed specifically for storing and querying RDF `(subject, predicate, object)` triples. Semantica supports embedded Oxigraph as well as Blazegraph, Apache Jena, and RDF4J.
@@ -158,19 +158,19 @@ A database optimized for storing and searching high-dimensional embedding vector
## Graph Analytics
**Centrality**
A measure of a node's importance in the graph. Common metrics: PageRank (link-based importance), betweenness centrality (bridge nodes), closeness centrality (average distance to all others).
A measure of a node's importance in the graph. Common metrics include PageRank (link-based importance), betweenness centrality (bridge nodes), and closeness centrality (average distance to all others).
**Community Detection**
Identifying groups of densely connected nodes: clusters that share more internal links than external ones. Used for finding subject communities, fraud rings, and organizational clusters.
Identifying groups of densely connected nodes (clusters that share more internal links than external ones). Used for finding subject communities, fraud rings, and organizational clusters.
**Distance Band**
A classification of a node's semantic proximity to a target: `near`, `mid`, or `far`, based on embedding distance thresholds. Part of Distance Intelligence (v0.5.0).
A classification of a node's semantic proximity to a target (`near`, `mid`, or `far`) based on embedding distance thresholds. Part of Distance Intelligence (v0.5.0).
**Distance Intelligence**
Semantica's v0.5.0 feature for semantic neighborhood exploration: N×N distance matrices, ego-mode visualization centered on a single entity, and distance band classification across the graph.
Semantica's v0.5.0 feature for semantic neighborhood exploration, including N×N distance matrices, ego-mode visualization centered on a single entity, and distance band classification across the graph.
**PageRank**
An algorithm measuring node importance based on the structure of incoming relationships: originally designed for web pages, applicable to any directed graph.
An algorithm measuring node importance based on the structure of incoming relationships; originally designed for web pages, but applicable to any directed graph.
## Query Languages & Standards
@@ -194,13 +194,13 @@ The W3C query language for RDF data. Semantica's `SparqlReasoner` uses SPARQL fo
Handling contradictory facts from multiple sources in the same knowledge graph. Semantica's `ConflictDetector` surfaces conflicts; resolution strategies include prefer-most-recent, prefer-most-reliable, majority-vote, and flag-for-review.
**Data Provenance**
Complete information about the origin, history, and lineage of every fact: source document, extraction method, timestamp, confidence score. W3C PROV-O compliant in Semantica.
Complete information about the origin, history, and lineage of every fact (source document, extraction method, timestamp, confidence score). W3C PROV-O compliant in Semantica.
**Deduplication**
Identifying and merging duplicate entity records. Semantica v2 strategies (`blocking_v2`, `hybrid_v2`, `semantic_v2`) are up to 7x faster than v1.
**W3C PROV-O**
The W3C provenance ontology standard. Semantica tracks lineage across all modules in PROV-O compliant format: suitable for HIPAA, SOX, GDPR, and FDA 21 CFR Part 11 compliance.
The W3C provenance ontology standard. Semantica tracks lineage across all modules in PROV-O compliant format, suitable for HIPAA, SOX, GDPR, and FDA 21 CFR Part 11 compliance.
## Security Terms
@@ -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 with 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.
+11 -11
View File
@@ -9,9 +9,9 @@ icon: "scale-balanced"
## Roles
- **Maintainers** Semantica team: review and merge PRs, manage releases and code quality, set project direction and community standards.
- **Contributors** — Submit code, documentation, and bug reports. Help with issues and reviews. Recognized in [CONTRIBUTORS.md](https://github.com/semantica-agi/semantica/blob/main/CONTRIBUTORS.md).
- **Community Members** — Use Semantica, provide feedback, share use cases, and participate in GitHub Discussions and Discord.
- **Maintainers**: Semantica team. Review and merge PRs, manage releases and code quality, set project direction and community standards.
- **Contributors**: submit code, documentation, and bug reports. Help with issues and reviews. Recognized in [CONTRIBUTORS.md](https://github.com/semantica-agi/semantica/blob/main/CONTRIBUTORS.md).
- **Community Members**: use Semantica, provide feedback, share use cases, and participate in GitHub Discussions and Discord.
## Decision Process
@@ -65,19 +65,19 @@ Semantica follows **Semantic Versioning** (`MAJOR.MINOR.PATCH`):
## Project Goals
- **Usability** — Easy to use and understand: sensible defaults, clear documentation, minimal ceremony.
- **Reliability** — Production-ready quality: tested across Python versions, platforms, and real-world workloads.
- **Performance** — Efficient and scalable: from single-machine notebooks to enterprise graph databases.
- **Extensibility** — Easy to extend with plugins and custom modules via the `PluginRegistry` pattern.
- **Community** — Welcoming and inclusive: all backgrounds and experience levels contribute and are recognized.
- **Usability**: easy to use and understand with sensible defaults, clear documentation, and minimal ceremony.
- **Reliability**: production-ready quality tested across Python versions, platforms, and real-world workloads.
- **Performance**: efficient and scalable from single-machine notebooks to enterprise graph databases.
- **Extensibility**: easy to extend with plugins and custom modules via the `PluginRegistry` pattern.
- **Community**: welcoming and inclusive. All backgrounds and experience levels contribute and are recognized.
## 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.
+5 -5
View File
@@ -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`.
+2 -2
View File
@@ -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
+3 -3
View File
@@ -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
+3 -3
View File
@@ -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
+35 -11
View File
@@ -102,9 +102,8 @@ The `Decision` dataclass that backs this node has the following fields — these
from semantica.context import Decision
from datetime import datetime
# Constructing a Decision explicitly (alternative to record_decision)
d = Decision(
decision_id = "dec_001", # UUID — auto-generated if omitted via record_decision
decision_id = None, # required arg — None/"" auto-generates a UUID
category = "threat_classification",
scenario = "Unattributed C2 cluster",
reasoning = "Infrastructure overlaps APT29 ASN",
@@ -117,9 +116,29 @@ d = Decision(
valid_until = "2025-09-30T23:59:59", # ISO datetime
metadata = {"source_feed": "isac_partner_b"},
)
graph.add_decision(d)
```
To actually store a decision built this way, pass its fields to `ContextGraph.add_decision()` as keyword arguments — this is the alternative to `record_decision()` for cases where you want `valid_from`/`valid_until` or extra metadata fields alongside the required ones:
```python
decision_id = graph.add_decision(
category = "threat_classification",
scenario = "Unattributed C2 cluster",
reasoning = "Infrastructure overlaps APT29 ASN",
outcome = "classified_as_apt29_cluster",
confidence = 0.88, # float 0.01.0
decision_maker = "cti_pipeline_v2",
# optional fields:
valid_from = "2025-07-01T00:00:00", # ISO datetime
valid_until = "2025-09-30T23:59:59", # ISO datetime
source_feed = "isac_partner_b", # extra kwargs are stored as metadata
)
```
<Warning>
Only pass keyword arguments to `add_decision()`, not a pre-built `Decision` object. `add_decision(Decision(...))` stores the node directly and skips the indexing step that `record_decision()` performs, so the decision becomes invisible to `find_precedents()`, `get_causal_chain()`, and `get_decision_insights()`, and `trace_decision_causality()` raises `ValueError` if you call it on one. The keyword-argument form above does not have this problem — it delegates to `record_decision()` internally. Note that, like `record_decision()`, it always generates its own `decision_id` (returned from the call); there is no way to force a specific ID.
</Warning>
## Searching Precedents Before Deciding
Before making a significant call, the system should search past decisions for similar scenarios. This is how you prevent the same cluster being classified differently across two agent runs — the second agent finds the first agent's decision and uses it as a prior.
@@ -137,7 +156,7 @@ for p in precedents:
print(" Similarity: {:.3f}".format(p.metadata.get("similarity_score", 0)))
```
Hybrid search blends two signals: semantic similarity over the `scenario` and `reasoning` text (weight 0.7), and structural graph proximity via Node2Vec embeddings (weight 0.3). The result is a ranked list of `Decision` objects — the most similar past decisions float to the top regardless of how differently they were phrased.
Hybrid search blends two signals: lexical overlap between the query and each decision's `scenario`, `reasoning`, and `entities` text (weight 0.7 — word-level Jaccard similarity, with a character-bigram fallback for CJK-style queries), and structural similarity based on how many other nodes each decision connects to in the graph (weight 0.3, only computed when the graph was built with `advanced_analytics=True`). The result is a ranked list of `Decision` objects, filtered to those scoring at least `similarity_threshold` (default 0.5) — because the match is lexical rather than embedding-based, precedents phrased very differently from the query may not surface even if they describe a similar scenario.
## Building a Causal Chain
@@ -262,8 +281,13 @@ d = Decision(
)
if engine.check_compliance(d, "cti_confidence_gate"):
graph.add_decision(d)
engine.record_policy_application(d.decision_id, "cti_confidence_gate", "1.0")
# Pass fields as kwargs, not the Decision object itself — see the
# warning above. add_decision() generates its own decision_id.
decision_id = graph.add_decision(
category=d.category, scenario=d.scenario, reasoning=d.reasoning,
outcome=d.outcome, confidence=d.confidence, decision_maker=d.decision_maker,
)
engine.record_policy_application(decision_id, "cti_confidence_gate", "1.0")
print("Decision recorded — policy compliant.")
else:
print("Decision blocked — confidence 0.62 below policy minimum 0.80.")
@@ -590,7 +614,7 @@ if engine.check_compliance(d, "lending_policy_v3"):
decision_maker=d.decision_maker,
)
graph.add_causal_relationship(stress_id, loan_id, "INFLUENCED")
engine.record_policy_application(d.decision_id, "lending_policy_v3", "3.0")
engine.record_policy_application(loan_id, "lending_policy_v3", "3.0")
print("Loan decision recorded — policy compliant.")
# SR 11-7 explainability report
@@ -638,8 +662,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
+2 -2
View File
@@ -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
+4 -4
View File
@@ -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
+2 -2
View File
@@ -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`
+4 -4
View File
@@ -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.60.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
+55 -42
View File
@@ -1,9 +1,9 @@
---
title: "GraphRAG Graph-Augmented Retrieval"
title: "GraphRAG: Graph-Augmented Retrieval"
description: "Go beyond vector search: retrieve facts, trace reasoning paths, and ground LLM responses in your knowledge graph."
---
GraphRAG combines vector similarity with knowledge graph traversal so retrieval finds structurally connected facts, not just text that sounds related. When a `ContextGraph` is attached to `AgentContext`, every retrieval call automatically blends semantic search with multi-hop graph expansion and `query_with_reasoning()` returns an auditable reasoning path alongside the LLM answer.
GraphRAG combines vector similarity with knowledge graph traversal so retrieval finds structurally connected facts, not just text that sounds related. When a `ContextGraph` is attached to `AgentContext`, every retrieval call automatically blends semantic search with multi-hop graph expansion, and `query_with_reasoning()` returns an auditable reasoning path alongside the LLM answer.
## What Is GraphRAG?
@@ -11,7 +11,7 @@ GraphRAG (Graph-Augmented Retrieval-Augmented Generation) enhances traditional R
**GraphRAG vs. traditional vector-only RAG:** Vector RAG finds documents similar to your query text. GraphRAG finds documents similar to your query AND documents connected to those through entity relationships, even if they don't mention your query terms directly.
**The role of graph traversal:** Starting from entities found in vector-similar documents, GraphRAG expands outward through relationship edges to discover related facts. This reveals connections that pure text similarity would miss like finding that a threat actor targets healthcare by following the path: Actor → Tool → Victim Organization → Industry Sector.
**The role of graph traversal:** Starting from entities found in vector-similar documents, GraphRAG expands outward through relationship edges to discover related facts. This reveals connections that pure text similarity would miss, like finding that a threat actor targets healthcare by following the path: Actor → Tool → Victim Organization → Industry Sector.
## Why Use GraphRAG?
@@ -96,7 +96,7 @@ context = AgentContext(
)
```
Now ingest your documents. `store()` with `extract_entities=True` runs the full extraction pipeline internally Named Entity Recognition (NER), relation extraction, and entity linking and populates both the vector index and the graph simultaneously:
Now ingest your documents. `store()` with `extract_entities=True` runs the full extraction pipeline internally (Named Entity Recognition, relation extraction, and entity linking) and populates both the vector index and the graph simultaneously:
```python
intel_documents = [
@@ -132,16 +132,17 @@ stats = context.store(
print("Graph built: {} nodes, {} edges".format(
stats["graph_nodes"], stats["graph_edges"]
))
# Graph built: 18 nodes, 14 edges
# Nodes: APT29, HAMMERTOSS, NATO, LifeCare, AS59796, CISA Sector 6, ...
# Edges: deployed, observed_on, classified_as, targets, operates_in, ...
```
The graph now contains a connected subgraph linking APT29 to healthcare infrastructure across four document boundaries — something that would be invisible to a pure vector search.
`store()` returns a dict with `stored_count`, `memory_ids`, `graph_nodes`, and
`graph_edges`. The extracted nodes (APT29, HAMMERTOSS, LifeCare, AS59796, …) and
edges (`deployed`, `observed_on`, `classified_as`, …) now span all four documents.
The graph now contains a connected subgraph linking APT29 to healthcare infrastructure across four document boundaries, something that would be invisible to a pure vector search.
## Retrieving the relevant subgraph
With the graph populated, a plain `retrieve()` call already does more than vector search. When `use_graph=True`, the retriever seeds the graph traversal from the top-k vector matches and expands outward by following edges, collecting connected facts within `max_hops`:
With the graph populated, a plain `retrieve()` call already does more than vector search. When `use_graph=True`, the retriever seeds the graph traversal from the top-k vector matches and expands outward by following edges. Expansion depth is set once, by `max_expansion_hops` on the `AgentContext` constructor:
```python
results = context.retrieve(
@@ -149,7 +150,6 @@ results = context.retrieve(
use_graph=True,
max_results=10,
expand_graph=True,
max_hops=3,
)
for r in results:
@@ -169,17 +169,25 @@ Notice the top results: while pure vector search might rank connected facts lowe
When you know specifically which entity you want to anchor the traversal to, pass `anchor_node`:
```python
# Anchor on APT29 explicitly proximity scores are calculated from this node
# Anchor on APT29 explicitly: proximity scores are calculated from this node
apt29_intel = context.retrieve(
"C2 infrastructure beaconing patterns",
use_graph=True,
anchor_node="APT29",
proximity_weight=0.7, # strongly favour nodes close to APT29
max_hops=3,
max_hops=3, # with an anchor, this bounds the proximity radius
max_results=8,
)
```
<Note>
`max_hops` on `retrieve()` only takes effect when `anchor_node` is set: it
bounds the proximity radius used for scoring and drops results farther than
`max_hops` from the anchor. Without an `anchor_node` it is ignored. It does
**not** change how far graph expansion reaches: that is fixed by
`max_expansion_hops` on the constructor.
</Note>
## Getting a grounded LLM answer with a reasoning path
`retrieve()` gives you the grounded context. `query_with_reasoning()` goes one step further: it passes that subgraph context to an LLM and returns the answer together with the multi-hop path the retrieval system traced through the graph. That path is your audit trail.
@@ -187,7 +195,7 @@ apt29_intel = context.retrieve(
```python
from semantica.llms import LiteLLM
llm = LiteLLM(model="anthropic/claude-sonnet-4-20250514")
llm = LiteLLM(model="anthropic/claude-sonnet-5")
result = context.query_with_reasoning(
"What are APT29's known TTPs against healthcare infrastructure, "
@@ -197,7 +205,7 @@ result = context.query_with_reasoning(
max_hops=3,
)
# The LLM answer grounded in graph-retrieved context, not training memory
# The LLM answer, grounded in graph-retrieved context, not training memory
print(result["response"])
# The multi-hop trace: APT29 → deployed → HAMMERTOSS → observed_on → LifeCare → ...
@@ -213,7 +221,7 @@ for src in result["sources"]:
print(" [{:.3f}] {}".format(src["score"], src["content"][:80]))
```
The `reasoning_path` field is what separates GraphRAG from a black-box LLM call. When an analyst asks "how do you know APT29 targeted healthcare?", you can show them the exact traversal the system made across your own documents not a claim the model generated from training data.
The `reasoning_path` field is what separates GraphRAG from a black-box LLM call. When an analyst asks "how do you know APT29 targeted healthcare?", you can show them the exact traversal the system made across your own documents, not a claim the model generated from training data.
The full return structure from `query_with_reasoning()`:
@@ -232,11 +240,11 @@ The full return structure from `query_with_reasoning()`:
<Tabs>
<Tab title="Defense CTI/Threat">
<Tab title="Defense: CTI/Threat">
Multi-INT intelligence fusion: OSINT threat feeds, NVD CVE data, and HUMINT summaries ingested into a single graph, then queried with multi-hop reasoning to trace C2 infrastructure chains and attribute campaigns to specific actors.
In classified environments the graph can be partitioned by data handling caveat each `AgentContext` operates over the subset of documents cleared for the querying user. The `reasoning_path` output doubles as a sanitisable audit trail for downgraded reporting.
In classified environments the graph can be partitioned by data handling caveat: each `AgentContext` operates over the subset of documents cleared for the querying user. The `reasoning_path` output doubles as a sanitisable audit trail for downgraded reporting.
```python
from semantica.context import AgentContext, ContextGraph
@@ -273,7 +281,7 @@ context.store(
link_entities=True,
)
llm = LiteLLM(model="anthropic/claude-sonnet-4-20250514")
llm = LiteLLM(model="anthropic/claude-sonnet-5")
result = context.query_with_reasoning(
"Trace the C2 infrastructure chain for APT29 operations targeting "
"ITAR-controlled contractors in 2025. Include IP ranges, ASNs, and TTPs.",
@@ -300,11 +308,11 @@ proximate = context.retrieve(
</Tab>
<Tab title="Security SOC/Incident">
<Tab title="Security: SOC/Incident">
Security operations: real-time alert triage against a graph containing hosts, CVEs, user accounts, runbooks, and historical incidents. GraphRAG retrieves the relevant runbook and similar past incidents in a single call, reducing mean-time-to-respond.
The `decision_tracking=True` flag records every triage query as an auditable decision, with the full context that was provided to the LLM essential for post-incident review and SOC metrics.
The `decision_tracking=True` flag records every triage query as an auditable decision, with the full context that was provided to the LLM. That's essential for post-incident review and SOC metrics.
```python
from semantica.context import AgentContext, ContextGraph
@@ -343,7 +351,7 @@ Parent: wmiprvse.exe
Sigma match: T1053.005 Scheduled Task/Job
"""
llm = LiteLLM(model="anthropic/claude-sonnet-4-20250514")
llm = LiteLLM(model="anthropic/claude-sonnet-5")
triage = soc_context.query_with_reasoning(
"Triage this SIEM alert and identify the correct response runbook:\n{}".format(alert_text),
llm_provider=llm,
@@ -369,7 +377,7 @@ for inc in similar:
</Tab>
<Tab title="Life Science Clinical/Pharma">
<Tab title="Life Science: Clinical/Pharma">
Clinical decision support: FDA drug labels, clinical guidelines, and trial summaries ingested into a graph where drug-enzyme-metabolite-interaction chains become traversable paths. A three-hop query (drug → enzyme → metabolite → contraindication) surfaces interaction risks that no single document would make explicit.
@@ -417,7 +425,7 @@ Patient: 68F, AF, CKD stage 3b (eGFR 32). On warfarin (INR target 2.03.0).
Presenting for elective hip replacement. Concurrent: amiodarone 200mg, atorvastatin 40mg.
"""
llm = LiteLLM(model="anthropic/claude-sonnet-4-20250514")
llm = LiteLLM(model="anthropic/claude-sonnet-5")
answer = clinical_context.query_with_reasoning(
"What is the evidence-based warfarin bridging protocol for this patient "
"given CKD and amiodarone interaction risk?\n\n{}".format(patient_context),
@@ -443,7 +451,7 @@ contra_chain = clinical_context.retrieve(
</Tab>
<Tab title="Banking Risk/Compliance">
<Tab title="Banking: Risk/Compliance">
Regulatory compliance: Basel III (CRE20), BCBS 239, SR 11-7, and EBA IRRBB guidelines ingested as a graph where regulation articles cross-reference each other as edges. Multi-hop queries traverse those cross-references automatically, so a question about commercial real estate RWA pulls the relevant CRE20 paragraphs and the BCBS 239 data quality requirements that govern their calculation in a single call.
@@ -466,12 +474,17 @@ compliance_context = AgentContext(
retention_days=2555, # 7-year regulatory retention
)
# In production these come from ingest_file() — shown as strings here for brevity
basel_cre20_text = "CRE20.32: For income-producing real estate where repayment depends on "
"property cash flows, RWA = exposure × risk weight, where risk weight "
"is determined by LTV bucket per Table CRE20.3..."
bcbs239_text = "Principle 3: Risk data should be accurate and have a single authoritative source. "
"Where data is aggregated across systems, reconciliation must be documented..."
# In production the text comes from a parsed file, e.g. FileIngestor().ingest_file(path).text;
# inline strings here for brevity
basel_cre20_text = (
"CRE20.32: For income-producing real estate where repayment depends on "
"property cash flows, RWA = exposure × risk weight, where risk weight "
"is determined by LTV bucket per Table CRE20.3..."
)
bcbs239_text = (
"Principle 3: Risk data should be accurate and have a single authoritative source. "
"Where data is aggregated across systems, reconciliation must be documented..."
)
compliance_context.store(
[
@@ -482,7 +495,7 @@ compliance_context.store(
extract_relationships=True,
)
llm = LiteLLM(model="anthropic/claude-sonnet-4-20250514")
llm = LiteLLM(model="anthropic/claude-sonnet-5")
answer = compliance_context.query_with_reasoning(
"Under Basel III CRE20, what are the RWA calculation requirements for "
"commercial real estate exposures with LTV > 80%? "
@@ -496,7 +509,7 @@ print(answer["response"])
print("Regulatory sources cited: {}".format(answer["num_sources"]))
print("Confidence: {:.1%}".format(answer["confidence"]))
# The reasoning path is the audit log show it to the regulator
# The reasoning path is the audit log: show it to the regulator
print("\n--- Reasoning Path (audit log) ---")
print(answer["reasoning_path"])
```
@@ -524,18 +537,18 @@ The `hybrid_alpha` parameter set in the `AgentContext` constructor establishes a
When targeting a specific `anchor_node`, you can apply `proximity_weight` in `retrieve()` to dynamically blend structural distance from the anchor into the final score:
```python
# Anchor node provided let vector semantics lead, graph proximity only slightly boosts
# Anchor node provided: let vector semantics lead, graph proximity only slightly boosts
results = context.retrieve(
query, use_graph=True, anchor_node="APT29", proximity_weight=0.2
)
# Known-entity tracing topology drives the retrieval
# Known-entity tracing: topology drives the retrieval
results = context.retrieve(
query, use_graph=True, anchor_node="APT29", proximity_weight=0.8
)
```
Each additional hop in `max_hops` exponentially increases the subgraph size. Practical defaults by domain:
Each additional expansion hop exponentially increases the subgraph size. Practical defaults by domain:
```text
General Q&A max_expansion_hops=2 (95% of useful facts within 2 hops)
@@ -544,7 +557,7 @@ Drug interactions max_expansion_hops=3 (drug → enzyme → metabolite
Regulatory cross-ref max_expansion_hops=2 (rule → article → article)
```
Set globally in the constructor; override per call with the `max_hops` argument to `retrieve()`.
Expansion depth is a constructor setting only (`max_expansion_hops`); there is no per-call override on `retrieve()`. `query_with_reasoning()` does take a per-call `max_hops` argument.
## How GraphRAG works internally
@@ -576,9 +589,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
- [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
- [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](/guides/reasoning): derive new facts and run inference rules over the graph
- [Decision Intelligence](/guides/decision-intelligence): causal chains, policy enforcement, decision tracking
- [LLM Integrations](/guides/llm-integrations): connect Groq, OpenAI, Anthropic, HuggingFace, and 100+ more
+2 -2
View File
@@ -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
+164 -36
View File
@@ -28,6 +28,7 @@ The `semantica.llms` module provides a unified interface for connecting to Large
## When To Use / When Not To Use
**Use LLM integrations for:**
- Text generation, summarization, and question-answering tasks
- Complex reasoning that requires natural language understanding
- Structured data extraction from unstructured text
@@ -35,6 +36,7 @@ The `semantica.llms` module provides a unified interface for connecting to Large
- Tasks where context, ambiguity, or domain knowledge matter
**Deterministic tools may be better for:**
- Pattern matching that regular expressions can handle
- Simple rule-based classification with clear criteria
- Mathematical calculations or statistical analysis
@@ -42,6 +44,7 @@ The `semantica.llms` module provides a unified interface for connecting to Large
- Data transformations with known logic
**A full LLM may be unnecessary for:**
- Simple keyword search or exact string matching
- Deterministic workflows with predefined decision trees
- High-frequency, low-latency operations where inference overhead matters
@@ -59,7 +62,7 @@ Four factors drive provider selection, each optimized for different use cases:
**Accuracy** matters most in high-stakes decisions: clinical contraindication checks, credit committee reasoning, and legal document analysis. Frontier models like Claude or GPT-4 available through `LiteLLM` provide the strongest reasoning capabilities.
**Data residency** constraints eliminate cloud providers for classified or HIPAA-regulated workloads. `HuggingFaceLLM` with local model paths enables fully air-gapped deployments without network calls.
**Data residency** constraints eliminate cloud providers for classified or HIPAA-regulated workloads. `HuggingFaceLLM` with local model paths, or `Ollama` pointed at a local server, both enable fully air-gapped deployments without network calls.
**Cost at scale** favors high-throughput providers like Novita AI for bulk extraction pipelines processing thousands of documents per hour where per-token costs accumulate quickly.
@@ -143,24 +146,149 @@ risk_data = oai.generate_structured(
The default model `gpt-3.5-turbo` is fine for classification and light extraction. Switch to `gpt-4o` for complex multi-step regulatory reasoning or document understanding.
## Anthropic — Complex Reasoning and Structured Extraction
**Anthropic** provides the Claude model family, built with an emphasis on careful, instruction-following behavior and strong performance on multi-step reasoning, long-document analysis, and code-related tasks. Claude models tend to be more cautious about ambiguous instructions than other providers. That matters when the cost of a confidently wrong answer is high.
The `Anthropic` provider wraps the Claude API. Reach for it when the task involves reasoning through several dependent steps (not just single-turn extraction), when you're processing long source documents that need to stay in context, or when you need schema-validated structured output rather than best-effort JSON.
Install with `pip install "semantica[llm-anthropic]"` (or just `pip install anthropic`) before using this provider.
```python
from semantica.llms import Anthropic
claude = Anthropic(model="claude-sonnet-4-6", api_key="YOUR_ANTHROPIC_KEY")
# api_key falls back to the ANTHROPIC_API_KEY environment variable
# is_available() only confirms a client was constructed from some key.
# It does not validate the key or check network reachability - an
# invalid or expired key still passes this check and fails at generate().
if not claude.is_available():
raise RuntimeError("Anthropic provider not configured - set ANTHROPIC_API_KEY")
# Plain generation - multi-step reasoning over a contract clause
verdict = claude.generate(
"A vendor contract has a 30-day termination-for-convenience clause "
"but a 90-day data-return obligation that survives termination. "
"If the customer terminates on day 1, when must vendor-held data "
"be returned? Answer with the date basis only.",
temperature=0.1,
)
print(verdict)
# "Day 120 from termination notice. The 90-day return period runs from
# the termination date (day 30), not from the notice date."
# Structured, schema-validated output
from pydantic import BaseModel
class ContractRisk(BaseModel):
clause: str
risk_level: str
days_to_deadline: int
risk = claude.generate_typed(
"Extract the termination clause risk from: vendor contract, "
"30-day termination for convenience, 90-day post-termination "
"data return obligation.",
schema=ContractRisk,
)
print(risk.risk_level, risk.days_to_deadline)
# "medium" 90
```
Model selection follows the same tier structure as the other providers: a Haiku model for high-volume classification where cost matters more than depth, a Sonnet model as the default for most extraction and reasoning tasks, an Opus model when a task genuinely needs the deepest reasoning available and latency/cost are secondary. Check Anthropic's docs for the current model identifiers, since they're versioned and change over time.
## Gemini — Long Context and Multimodal Input
**Gemini** is Google's model family, with a context window large enough to hold entire codebases or long regulatory filings in a single call, and native support for image and document input alongside text. Reach for it when a task needs to reference a large amount of source material at once, or when the input isn't plain text.
The `Gemini` provider tries the newer `google-genai` SDK first and falls back to the older `google-generativeai` package if that's what's installed. Install with `pip install "semantica[llm-gemini]"` (or `pip install google-genai`) before using this provider.
```python
from semantica.llms import Gemini
gemini = Gemini(model="gemini-pro", api_key="YOUR_GEMINI_KEY")
# api_key falls back to the GEMINI_API_KEY environment variable
if not gemini.is_available():
raise RuntimeError("Gemini provider not configured - set GEMINI_API_KEY")
response = gemini.generate(
"Summarize the key obligations in a standard NDA in three bullet points."
)
print(response)
data = gemini.generate_structured(
"Extract the party names and effective date from: "
"This Agreement is entered into between Acme Corp and Globex LLC, "
"effective January 1, 2026."
)
print(data)
```
## Ollama — Local, Air-Gapped Inference
**Ollama** runs models entirely on your own machine, with no API key and no outbound network call. It's the right choice for air-gapped environments, offline development, or any workload where the source data can't leave the local network.
Unlike the other providers here, `Ollama` takes a `base_url` instead of an `api_key`. It talks to a local Ollama server over HTTP. Start the server with `ollama serve` and pull a model with `ollama pull llama2` before using this provider. Install the Python client with `pip install "semantica[llm-ollama]"` (or `pip install ollama`).
```python
from semantica.llms import Ollama
llm = Ollama(model="llama2", base_url="http://localhost:11434")
if not llm.is_available():
raise RuntimeError("Ollama provider not configured - is 'ollama serve' running?")
response = llm.generate("Explain the difference between a hash map and a tree map.")
print(response)
```
`is_available()` for Ollama does a real connectivity check (it calls the server's `list()` endpoint), unlike the API-key-based providers above, so a `False` here usually means the server isn't running rather than a missing credential.
## DeepSeek — Budget Reasoning at Scale
**DeepSeek** exposes an OpenAI-compatible API at a fraction of the cost of the larger US providers, with reasoning quality that holds up well for extraction and classification work. It's a reasonable default when you're processing a large volume of documents and don't need the deepest reasoning tier.
Install with `pip install "semantica[llm-deepseek]"` (or `pip install openai`, since DeepSeek is accessed through the OpenAI client pointed at a different base URL).
```python
from semantica.llms import DeepSeek
llm = DeepSeek(model="deepseek-chat", api_key="YOUR_DEEPSEEK_KEY")
# api_key falls back to the DEEPSEEK_API_KEY environment variable
if not llm.is_available():
raise RuntimeError("DeepSeek provider not configured - set DEEPSEEK_API_KEY")
response = llm.generate("List three risks of using a floating IP in a Kubernetes ingress.")
print(response)
data = llm.generate_structured(
"Extract the CVE ID and affected product from: "
"CVE-2024-3400 affects PAN-OS GlobalProtect gateways."
)
print(data)
```
## LiteLLM — One Interface, 100+ Providers
**LiteLLM** is a universal adapter that provides a single interface to over 100 different LLM providers, including Anthropic Claude, Azure OpenAI, AWS Bedrock, Google Vertex AI, and local Ollama instances. It acts as a translation layer, converting your unified API calls into provider-specific requests, enabling easy switching between providers without code changes.
`LiteLLM` is the Swiss Army knife. It wraps the `litellm` library, which speaks to every major provider using a unified completion API. The model string encodes both provider and model name: `"anthropic/claude-sonnet-4-20250514"`, `"azure/gpt-4o"`, `"bedrock/anthropic.claude-3-5-sonnet-20241022-v2:0"`, `"ollama/llama3.2"`. Change the string, change the provider — no other code changes needed.
`LiteLLM` is the Swiss Army knife. It wraps the `litellm` library, which speaks to every major provider using a unified completion API. The model string encodes both provider and model name: `"anthropic/claude-sonnet-5"`, `"azure/gpt-4o"`, `"bedrock/anthropic.claude-sonnet-4-5-20250929-v1:0"`, `"ollama/llama3.2"`. Change the string, change the provider — no other code changes needed.
```python
from semantica.llms import LiteLLM
# Anthropic Claude — highest accuracy for complex reasoning
llm = LiteLLM(model="anthropic/claude-sonnet-4-20250514")
llm = LiteLLM(model="anthropic/claude-sonnet-5")
# Reads ANTHROPIC_API_KEY from environment
# Azure OpenAI — compliance and data-residency requirements
llm = LiteLLM(model="azure/gpt-4o", api_key="YOUR_AZURE_KEY")
# AWS Bedrock — existing cloud agreement, no new vendor
llm = LiteLLM(model="bedrock/anthropic.claude-3-5-sonnet-20241022-v2:0")
llm = LiteLLM(model="bedrock/anthropic.claude-sonnet-4-5-20250929-v1:0")
# Google Vertex AI
llm = LiteLLM(model="vertex_ai/gemini-1.5-pro")
@@ -178,7 +306,7 @@ The environment-variable convention for each provider: `ANTHROPIC_API_KEY`, `AZU
import os
PROVIDER_MAP = {
"prod": "anthropic/claude-sonnet-4-20250514",
"prod": "anthropic/claude-sonnet-5",
"staging": "openai/gpt-4o-mini",
"local": "ollama/llama3.2",
"azure": "azure/gpt-4o",
@@ -250,7 +378,7 @@ print("FAST: {} (conf={:.0%})".format(fast_result["response"], fast_result["con
# Tier 2: deep answer with Claude if confidence is below threshold
if fast_result["confidence"] < 0.85:
deep_llm = LiteLLM(model="anthropic/claude-sonnet-4-20250514")
deep_llm = LiteLLM(model="anthropic/claude-sonnet-5")
deep_result = context.query_with_reasoning(
query, llm_provider=deep_llm, max_results=15, max_hops=3
)
@@ -306,30 +434,32 @@ for t in triplets:
## Novita AI — Cost-Efficient Bulk Extraction
Novita AI exposes an OpenAI-compatible API and is available as a built-in provider for the extraction layer. It is accessed differently from the `semantica.llms` classes — through `create_provider` from `semantica.semantic_extract.providers` — making it the right choice for high-volume NER pipelines where per-call cost matters.
**Novita AI** exposes an OpenAI-compatible API at low per-call cost, making it a reasonable choice for high-volume NER pipelines where cost matters more than getting the single best answer.
Install with `pip install "semantica[llm-novita]"` (or `pip install openai`, since Novita is accessed through the OpenAI client pointed at a different base URL).
```python
from semantica.llms import Novita
llm = Novita(model="deepseek/deepseek-v3.2", api_key="YOUR_NOVITA_KEY")
# api_key falls back to the NOVITA_API_KEY environment variable
if not llm.is_available():
raise RuntimeError("Novita provider not configured - set NOVITA_API_KEY")
response = llm.generate("Summarize the Basel III leverage ratio requirement.")
data = llm.generate_structured(
"Extract drug names and dosages from: "
"Patient received warfarin 5mg daily, aspirin 75mg daily, metformin 500mg twice daily."
)
```
Novita is also reachable as a provider name string for the NER interface, without going through the `Novita` class directly:
```python
from semantica.semantic_extract.providers import create_provider
from semantica.semantic_extract import NamedEntityRecognizer
# create_provider pools instances — same key reuses the same object
provider = create_provider(
"novita",
api_key="YOUR_NOVITA_KEY", # or set NOVITA_API_KEY env var
model="deepseek/deepseek-v3.2", # default model
)
if provider.is_available():
# Plain generation
response = provider.generate("Summarise the Basel III leverage ratio requirement.")
# Structured extraction — returns parsed dict
data = provider.generate_structured(
"Extract drug names and dosages from: "
"Patient received warfarin 5mg daily, aspirin 75mg daily, metformin 500mg twice daily."
)
# Use Novita through the NER interface — provider name as string
ner = NamedEntityRecognizer(
methods=["llm"],
provider="novita",
@@ -339,11 +469,9 @@ entities = ner.extract_entities(
"CVE-2024-3400 is exploited by UNC3886 targeting PAN-OS GlobalProtect."
)
for e in entities:
print("{} ({}) conf={:.2f}".format(e.text, e.label, e.confidence))
print("{} ({}) conf={:.2f}".format(e.text, e.label, e.confidence))
```
Novita requires the `openai` Python client under the hood — install with `pip install "semantica[llm-openai]"` or `pip install openai`.
## Domain Examples
<Tabs>
@@ -446,7 +574,7 @@ print("TRIAGE: {} (conf={:.0%})".format(triage["response"], triage["confidence"]
# Tier 2: escalate to Claude for deep analysis if Tier 1 is uncertain
if triage["confidence"] < 0.88:
deep_llm = LiteLLM(model="anthropic/claude-sonnet-4-20250514")
deep_llm = LiteLLM(model="anthropic/claude-sonnet-5")
deep = context.query_with_reasoning(
"Full MITRE ATT&CK analysis of this alert: identify the attack chain, "
"blast radius, affected systems, and recommended containment steps.",
@@ -502,7 +630,7 @@ for d in drugs:
# trastuzumab (conf=0.98), pertuzumab (conf=0.97), docetaxel (conf=0.96)
# Report synthesis with Claude — switch to azure/gpt-4o for HIPAA by changing one string
report_llm = LiteLLM(model="anthropic/claude-sonnet-4-20250514")
report_llm = LiteLLM(model="anthropic/claude-sonnet-5")
# For HIPAA-constrained Azure deployment:
# report_llm = LiteLLM(model="azure/gpt-4o", api_key="YOUR_AZURE_KEY")
@@ -554,7 +682,7 @@ question = (
# Two-provider consensus — same query, same graph, different LLMs
gpt4o = OpenAI(model="gpt-4o", api_key="YOUR_OAI_KEY")
claude = LiteLLM(model="anthropic/claude-sonnet-4-20250514")
claude = LiteLLM(model="anthropic/claude-sonnet-5")
answer_a = context.query_with_reasoning(question, llm_provider=gpt4o, max_results=10)
answer_b = context.query_with_reasoning(question, llm_provider=claude, max_results=10)
@@ -591,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()`
+6 -4
View File
@@ -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
+9 -9
View File
@@ -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
@@ -197,7 +197,7 @@ reasoning_agent.load("./pipeline/enriched_intel/")
# All memories, graph nodes, and vector embeddings from both ingestion agents are now available.
# Use a high-capability model for the synthesis step
llm = LiteLLM(model="anthropic/claude-sonnet-4-20250514")
llm = LiteLLM(model="anthropic/claude-sonnet-5")
synthesis = reasoning_agent.query_with_reasoning(
"Summarize the APT29 exploitation of CVE-2024-3400: affected products, "
@@ -428,7 +428,7 @@ tier1.store(
# --- Tier 2: deep investigation when Tier 1 confidence is low ---
if triage["confidence"] < 0.90:
deep_llm = LiteLLM(model="anthropic/claude-sonnet-4-20250514")
deep_llm = LiteLLM(model="anthropic/claude-sonnet-5")
investigation = tier2.query_with_reasoning(
"Full MITRE ATT&CK analysis of incident {}. "
@@ -533,7 +533,7 @@ t1.start(); t2.start()
t1.join(); t2.join()
# Chief agent synthesizes across literature and experimental data
llm = LiteLLM(model="anthropic/claude-sonnet-4-20250514")
llm = LiteLLM(model="anthropic/claude-sonnet-5")
synthesis = chief.query_with_reasoning(
"Identify the top two candidate compounds for KRAS G12C NSCLC that show "
@@ -576,7 +576,7 @@ credit_officer = make_desk_agent()
committee_chair = make_desk_agent()
app_id = "LOAN-2025-88421"
llm = LiteLLM(model="anthropic/claude-sonnet-4-20250514")
llm = LiteLLM(model="anthropic/claude-sonnet-5")
# --- Risk Desk: PD/LGD/EL analysis ---
risk_desk.store(
@@ -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
+5 -5
View File
@@ -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.
---
@@ -477,7 +477,7 @@ regs = [
]
# Use an LLM to extract the conceptual model from regulatory prose
llm_gen = LLMOntologyGenerator(provider="anthropic", model="claude-sonnet-4-20250514")
llm_gen = LLMOntologyGenerator(provider="anthropic", model="claude-sonnet-5")
ontology = llm_gen.generate_ontology_from_text(
"\n\n".join(r.text[:8000] for r in regs) # token-safe excerpt per document
)
@@ -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
+6 -4
View File
@@ -127,7 +127,7 @@ engine = ExecutionEngine(max_workers=4, retry_on_failure=True)
result = engine.execute_pipeline(pipeline)
print(f"Success: {result.success}")
print(f"Output: {result.output}") # {"node_count": 312, "edge_count": 847}
print(f"Output: {result.output}") # the final step's return value, e.g. {"node_count": ..., "edge_count": ...}
print(f"Duration: {result.metrics['execution_time']:.2f}s")
print(f"Steps completed: {result.metrics['steps_executed']}")
```
@@ -197,7 +197,9 @@ engine = ExecutionEngine(
max_workers = 4,
retry_on_failure = True,
)
# The engine uses handler.get_retry_policy(step.step_type) when a step fails
# ExecutionEngine builds its own FailureHandler; replace it with the configured one
engine.failure_handler = handler
# The engine now calls engine.failure_handler.get_retry_policy(step.step_type) on failure
```
`handler.classify_error()` distinguishes `ValidationError` (low severity, usually don't retry), `ProcessingError` (high severity), and timeout/connection errors (medium severity, always retry). You can inspect the classification:
@@ -717,6 +719,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
+4 -4
View File
@@ -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
+2 -2
View File
@@ -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
+5 -5
View File
@@ -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
+25 -20
View File
@@ -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
@@ -100,14 +100,15 @@ ner = NamedEntityRecognizer(
methods=["llm", "ml", "pattern"],
confidence_threshold=0.75,
provider="anthropic",
llm_model="claude-sonnet-4-6",
llm_model="claude-sonnet-5",
)
entities = ner.extract_entities(report)
for e in entities:
print("[{:>5.2f}] {:15s} {}".format(e.confidence, e.label, e.text))
# Expected output (abbreviated):
# Illustrative output — exact labels and scores depend on the method and model.
# Abbreviated:
# [ 0.94] THREAT_ACTOR GAMMA-7
# [ 0.91] THREAT_ACTOR DELTA-3
# [ 0.97] MALWARE HAMMERTOSS
@@ -262,16 +263,18 @@ from semantica.semantic_extract import TripletExtractor
tri = TripletExtractor(
method="llm",
provider="anthropic",
llm_model="claude-sonnet-4-6",
llm_model="claude-sonnet-5",
include_temporal=True, # attach time context to triplets when available
include_provenance=True, # embed source document reference in each triplet
validate=False, # return raw triplets; validate explicitly below
)
# Feed in the entities and relations you already extracted — the extractor
# uses them to constrain and validate what it produces
# uses them to constrain what it produces
triplets = tri.extract_triplets(report, entities, relations)
# Filter malformed triplets before serialisation
# (extract_triplets validates automatically unless validate=False, as above)
valid = tri.validate_triplets(triplets)
print("Valid: {}/{}".format(len(valid), len(triplets)))
@@ -320,7 +323,7 @@ def ingest_intel_report(
methods=[method, "pattern"],
confidence_threshold=0.70,
provider="anthropic",
llm_model="claude-sonnet-4-6",
llm_model="claude-sonnet-5",
)
entities = ner.extract_entities(text)
classified = ner.classify_entities(entities)
@@ -335,7 +338,7 @@ def ingest_intel_report(
relation_types=["deployed", "targets", "exploits", "operates_from", "provided_to"],
confidence_threshold=0.65,
provider="anthropic",
llm_model="claude-sonnet-4-6",
llm_model="claude-sonnet-5",
)
relations = rel.extract_relations(text, entities)
@@ -347,9 +350,10 @@ def ingest_intel_report(
tri = TripletExtractor(
method=method,
provider="anthropic",
llm_model="claude-sonnet-4-6",
llm_model="claude-sonnet-5",
include_temporal=True,
include_provenance=True,
validate=False, # keep raw triplets so the summary can report rejections
)
triplets = tri.extract_triplets(text, entities, relations)
valid = tri.validate_triplets(triplets)
@@ -377,6 +381,7 @@ def ingest_intel_report(
"coref_chains": len(chains),
"relations": len(relations),
"events": len(events),
"triplets_total": len(triplets),
"triplets_valid": len(valid),
"graph_nodes": graph_stats.get("graph_nodes", 0),
"graph_edges": graph_stats.get("graph_edges", 0),
@@ -402,7 +407,7 @@ for text, doc_id in reports:
summary["relations"],
summary["events"],
summary["triplets_valid"],
len(summary["rdf_turtle"]),
summary["triplets_total"],
))
```
@@ -421,7 +426,7 @@ ner = NamedEntityRecognizer(
methods=["llm", "pattern"],
confidence_threshold=0.75,
provider="anthropic",
llm_model="claude-sonnet-4-6",
llm_model="claude-sonnet-5",
)
entities = ner.extract_entities(fintel_text)
grouped = ner.classify_entities(entities)
@@ -438,14 +443,14 @@ rel = RelationExtractor(
relation_types=["operates_from", "deployed", "targets", "exploits"],
confidence_threshold=0.70,
provider="anthropic",
llm_model="claude-sonnet-4-6",
llm_model="claude-sonnet-5",
)
relations = rel.extract_relations(fintel_text, entities)
tri = TripletExtractor(
method="llm",
provider="anthropic",
llm_model="claude-sonnet-4-6",
llm_model="claude-sonnet-5",
include_temporal=True,
include_provenance=True,
)
@@ -544,14 +549,14 @@ rel = RelationExtractor(
relation_types=["treats", "causes_adverse_event", "has_efficacy", "evaluated_in"],
confidence_threshold=0.65,
provider="anthropic",
llm_model="claude-sonnet-4-6",
llm_model="claude-sonnet-5",
)
relations = rel.extract_relations(paper, entities)
tri = TripletExtractor(
method="llm",
provider="anthropic",
llm_model="claude-sonnet-4-6",
llm_model="claude-sonnet-5",
triplet_types=["treats", "has_efficacy", "causes_adverse_event"],
include_temporal=True,
include_provenance=True,
@@ -595,7 +600,7 @@ ner = NamedEntityRecognizer(
methods=["llm", "ml", "pattern"],
confidence_threshold=0.70,
provider="anthropic",
llm_model="claude-sonnet-4-6",
llm_model="claude-sonnet-5",
)
entities = ner.extract_entities(credit_memo)
grouped = ner.classify_entities(entities)
@@ -612,14 +617,14 @@ rel = RelationExtractor(
relation_types=["guaranteed_by", "secured_by", "classified_as", "exposed_to"],
confidence_threshold=0.65,
provider="anthropic",
llm_model="claude-sonnet-4-6",
llm_model="claude-sonnet-5",
)
relations = rel.extract_relations(credit_memo, entities)
tri = TripletExtractor(
method="llm",
provider="anthropic",
llm_model="claude-sonnet-4-6",
llm_model="claude-sonnet-5",
include_temporal=True,
include_provenance=True,
)
@@ -664,8 +669,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
+18 -2
View File
@@ -707,6 +707,22 @@ report_dict = report.to_dict()
---
## Resource limits
Live SHACL validation in the Explorer enforces four resource limits, all configurable
through environment variables. When a limit trips, the error message names the
variable that controls it.
| Environment variable | Default | What it bounds |
| --- | --- | --- |
| `SEMANTICA_MAX_SHACL_TURTLE_BYTES` | `262144` (256 KB) | Size of the submitted SHACL Turtle |
| `SEMANTICA_MAX_SHACL_TRIPLES` | `1000` | Triple count of the parsed shapes graph |
| `SEMANTICA_MAX_SHACL_TIMEOUT` | `15.0` | Validation timeout in seconds |
| `SEMANTICA_MAX_SHACL_CONCURRENCY` | `4` | Concurrent validations per process |
The first three are surfaced in the validation error message when exceeded; the
concurrency limit applies as a semaphore and does not appear in responses.
## Using SHACL validation as a CI/CD gate
Call this function as a pre-publish gate; exit code 1 blocks the pipeline.
@@ -740,5 +756,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
+3 -3
View File
@@ -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
+25 -304
View File
@@ -1,109 +1,31 @@
---
title: "Semantica"
description: "The Accountability and Context Layer for AI: Context Graphs · Decision Intelligence · Full Provenance"
title: "Welcome to Semantica"
description: "The Context and Semantic Layer for AI in High-Stakes Domains: Context Graphs · Decision Intelligence · Full Provenance"
---
```bash
pip install semantica
```
Your AI agent just made a decision. Now someone needs to explain it.
Most AI agents run on embeddings, not meaning. A similarity score has no structure, no relationships, and no way to explain why a result came back.
*What did it know at the time? Which facts shaped the outcome? Where did those facts come from? Has it made the same call before: and did that go well?*
Semantica is the semantic and context layer underneath your LLM, vector store, and agent framework: deterministic infrastructure, not a model. Graph construction, reasoning, and provenance all run without an LLM in the loop. It turns fragmented enterprise data into a structured, queryable context graph and knowledge graph, governed by ontologies, taxonomies, and controlled vocabularies (OWL, SHACL, SKOS), so your data's meaning is explicit rather than approximated by an embedding.
If your stack can't answer those questions with a traceable record, you have a gap. Not a capability gap: an **accountability gap**. It's the reason AI hasn't landed at scale in healthcare, finance, legal, and government. And it's why teams building for those markets keep rebuilding the same guardrails from scratch.
Provenance and audit trails aren't a bolt-on. They fall out naturally once your data has that structure, so the same graph that powers retrieval and reasoning also gives you a straight answer when a regulator asks why.
**Semantica closes that gap.** It's the context and accountability layer that sits beneath your existing agent framework: not a replacement for LangChain or LlamaIndex, but the infrastructure that makes their outputs trustworthy.
## What you get
## The Problem Every Production AI Team Hits
Powerful agents aren't automatically trustworthy ones. Five structural blind spots make modern AI systems impossible to deploy in regulated environments:
**No memory structure** — agents store embeddings, not meaning
- No way to ask *why* a fact was recalled
- No link from a recalled fact back to its source document
- Context is a black box that resets on every run
**No decision trail** — agents act continuously but record nothing
- No history to hand to a regulator or auditor
- No way to replay or reproduce a past decision
- Debugging means re-running, not reviewing
**No provenance** — outputs can't be traced to source facts
- In healthcare, finance, and legal: this is a hard compliance blocker
- No lineage from inference back to the original document
- Impossible to demonstrate what the agent actually relied on
**No reasoning transparency** — black-box answers with no explanation
- Impossible to validate the reasoning path
- Impossible to contest a specific conclusion
- No basis for improving or correcting future behavior
**No conflict detection** — contradictory facts silently coexist in vector stores
- No detection when two sources disagree
- Outputs become inconsistent and unpredictable over time
- Silent failures compound as the knowledge base grows
<Note>
These aren't edge cases. They're why enterprise AI pilots stall: and why your compliance team keeps saying *not yet*.
</Note>
## What Semantica Adds to Your Stack
Semantica gives every agent the infrastructure it needs to be accountable. Drop it into your existing setup in minutes:
**Context Graphs** — a structured, queryable graph of everything your agent knows, decides, and reasons about
- Persistent across agent runs: no context loss between sessions
- Queryable with SPARQL and full graph algorithms
- Temporal model with `valid_from` / `valid_until` on nodes and edges
- Point-in-time snapshots of the full knowledge state
**Decision Intelligence** — every decision is a first-class object in your system
- `record_decision()` captures full lifecycle and causal chain
- Hybrid precedent search over past decisions for consistency
- `analyze_decision_impact()` shows downstream consequences
- Causal chain visualization from trigger to outcome
**Full Provenance** — every fact links to its source document and ingestion event
- W3C PROV-O compliant lineage across all modules
- Full traceability from raw input to final inference
- `recorded_at` stamping with OWL-Time export
- Audit-ready for HIPAA, SOX, GDPR, FDA 21 CFR Part 11
**Reasoning Engines** — explainable reasoning paths, not black boxes
- Forward chaining, Rete, deductive, abductive
- SPARQL query-based inference over RDF graphs
- Datalog with recursive Horn clause rules
- Every conclusion backed by a traceable derivation path
**Temporal Intelligence** — your graph knows not just *what*, but *when*
- Allen interval algebra: all 13 temporal relations
- Point-in-time queries over historical graph states
- Temporal provenance stamping on every fact
- OWL-Time export for standards-compliant archiving
**Ontology Hub** — full ontology lifecycle in the browser
- Visual editor for schema design and editing
- SHACL Studio for constraint authoring and validation
- Alignment authoring across multiple ontologies
- Health dashboard and version control built in
- **[Context graphs](/guides/context-graphs)**: a persistent, queryable graph of everything your agent knows, decides, and reasons about
- **Decision intelligence**: `record_decision()` captures the full lifecycle and causal chain of every decision
- **[Full provenance](/guides/provenance)**: every fact links back to its source, W3C PROV-O compliant and audit-ready for HIPAA, SOX, and GDPR
- **[Explainable reasoning](/guides/reasoning)**: forward chaining, Datalog, and SPARQL, each with a derivation path you can inspect
- **Temporal intelligence**: Allen interval algebra and point-in-time snapshots, so the graph knows not just *what* but *when*
<Tip>
Works alongside any LLM provider and any agent framework: add it to an existing stack without changing your architecture.
Works alongside any LLM provider and any agent framework, and ingests directly from enterprise data platforms like Databricks, SAP, Salesforce, and Snowflake. Add it to an existing stack without changing your architecture.
</Tip>
<img src="/assets/img/diagrams/architecture-overview.svg" alt="Semantica four-layer architecture: Ingestion → Processing → Intelligence → Application" style={{ width: '100%', borderRadius: '12px', margin: '24px 0' }} />
## See It In Action
One pip install. A few lines to connect your agent. Everything else becomes traceable.
```bash
pip install semantica
```
## Try it
<CodeGroup>
@@ -185,229 +107,28 @@ decision_id = context.record_decision(
</CodeGroup>
- [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
## Built for Where Mistakes Have Consequences
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.
</Warning>
**Healthcare & Life Sciences**
- Clinical decision support with full audit trails
- Drug interaction and contraindication graphs
- Patient safety event tracking and root-cause analysis
- HIPAA-compliant provenance chains out of the box
**Finance & Risk**
- Fraud detection knowledge graphs
- Risk assessment trails built to survive an audit
- SOX, GDPR, and MiFID II compliance infrastructure
- Model decision lineage for regulatory reporting
**Legal & Compliance**
- Evidence-backed research with every cited fact provenance-linked
- Contract analysis with traceable clause extraction
- Regulatory change tracking across jurisdictions
- Full reasoning paths ready for court-admissible documentation
**Cybersecurity**
- Threat attribution graphs linking actors, TTPs, and indicators
- Incident response timelines with full event provenance
- Security audit trails across the complete kill chain
- MITRE ATT&CK-aligned knowledge graph integration
**Government & Defense**
- Policy decision trails from brief to outcome
- Classified information handling with provenance chains
- Chain-of-custody scrutiny for intelligence reporting
- Air-gapped deployment with local LLM support
**Critical Infrastructure**
- Power grid state tracking with temporal intelligence
- Transportation safety event graphs
- Emergency response coordination with decision audit trails
- Consequence modeling for high-stakes operational decisions
## Start Here
## Start here
<Steps>
<Step title="Install Semantica">
<Step title="Install">
```bash
pip install semantica
```
See [Installation](installation) for optional extras (`[all]`, `[neo4j]`, `[pinecone]`) and environment setup.
Optional extras: `[all]`, `[neo4j]`, `[pinecone]`. See [Installation](/installation).
</Step>
<Step title="Run the 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 title="Build a pipeline">
Follow the [Quickstart](/quickstart) to ingest documents, extract entities, build a graph, and record a decision in 5 minutes.
</Step>
<Step title="Learn the mental model">
[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 title="Learn the model">
[Core Concepts](/concepts) covers knowledge graphs vs. vector stores, GraphRAG, and how provenance and decisions fit together.
</Step>
<Step title="Go deep on any module">
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 title="Go deep">
Every module has a [reference page](/reference/context) with full API docs and runnable examples.
</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
- [Changelog](https://github.com/semantica-agi/semantica/releases) — Release history
## Full Capabilities
<AccordionGroup>
<Accordion title="Context & Decision Intelligence" icon="brain">
### Context Graphs
- Structured, persistent graph of entities, relationships, and decisions
- Temporal model with `valid_from` / `valid_until` on every node and edge
- Point-in-time queries across historical graph states
- Distance Intelligence: semantic neighborhoods and N×N distance matrices
### Decision Tracking
- `record_decision()` with full lifecycle management and causal chains
- Hybrid similarity search over past decisions for consistency enforcement
- `analyze_decision_impact()` and `analyze_decision_influence()` for consequence modeling
- Ego-mode exploration for targeted neighborhood investigation
More: the [Cookbook](/cookbook) for real-world notebooks, [Discord](https://discord.gg/sV34vps5hH) for help.
<Accordion title="Full module list">
`semantica.ingest`, `semantica.parse`, `semantica.split`, `semantica.normalize`, `semantica.semantic_extract`, `semantica.kg`, `semantica.ontology`, `semantica.reasoning`, `semantica.embeddings`, `semantica.vector_store`, `semantica.graph_store`, `semantica.triplet_store`, `semantica.context`, `semantica.provenance`, `semantica.change_management`, `semantica.deduplication`, `semantica.conflicts`, `semantica.export`, `semantica.visualization`, `semantica.pipeline`, `semantica.seed`, `semantica.llms`, `semantica.mcp_server`, `semantica.explorer`, `semantica.evals`, `semantica.utils`, `semantica.core`. See the [API Reference](/reference/context) for full docs on each.
</Accordion>
<Accordion title="Knowledge Engineering" icon="diagram-project">
### Entity & Relation Extraction
- Named entity recognition: pattern, ML, or LLM methods
- Typed triplet extraction via LLM or rule-based pipelines
- Event extraction with temporal and causal linking
### Ontology & Schema
- Ontology Hub: visual editor, SHACL Studio, alignments, health dashboard
- Deduplication v2: `blocking_v2`, `hybrid_v2`, `semantic_v2`: up to 7x faster
- Datalog reasoning: recursive Horn clause rules with fixpoint semantics
- SPARQL reasoning: query-based inference over RDF graphs
</Accordion>
<Accordion title="Provenance & Auditability" icon="shield-check">
### Lineage Tracking
- W3C PROV-O lineage across all modules: every fact has a source
- `recorded_at` stamping with full OWL-Time export
- Change management with SHA-256 checksums and version control
- Full audit trails from ingestion event to final inference
### Compliance Infrastructure
- HIPAA: patient data handling with audit-ready provenance chains
- SOX / MiFID II: financial decision records with full traceability
- GDPR: data lineage for subject access and right-to-erasure workflows
- FDA 21 CFR Part 11: electronic records and signature compliance
</Accordion>
<Accordion title="Data Ingestion & Export" icon="database">
### Ingestion Formats
- Documents: PDF, DOCX, HTML, PPTX, Docling layout analysis
- Structured data: JSON, CSV, Excel, Parquet, XML
- Sources: web crawl, SQL, Snowflake, feeds, email, code repositories, MCP
### Vector Stores
- FAISS, Pinecone, Weaviate, Qdrant, Milvus, PgVector, in-memory
### Graph Stores
- Neo4j, FalkorDB, Apache AGE, Amazon Neptune
### Export Formats
- RDF: Turtle, JSON-LD, N-Triples, RDF/XML
- Tabular: Parquet, CSV, Arrow
- Graph: GraphML, GEXF, DOT, ArangoDB AQL
- Ontology: OWL, SKOS, SHACL
</Accordion>
</AccordionGroup>
## Module Reference
| Module | What it provides |
| :-------- | :----------------- |
| `semantica.context` | Context graphs, agent memory, decision tracking, causal analysis, precedent search |
| `semantica.kg` | KG construction, graph algorithms, temporal model, Allen interval algebra |
| `semantica.semantic_extract` | NER, relation extraction, event extraction, triplet generation |
| `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.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 |
| `semantica.ingest` | Files, web, feeds, databases, Snowflake, Parquet, XML, MCP |
| `semantica.parse` | Document parsing: PDF, DOCX, HTML, PPTX, Docling layout analysis |
| `semantica.split` | Text chunking: sentence, paragraph, token, semantic boundary strategies |
| `semantica.normalize` | Text normalization, entity canonicalization, whitespace and encoding cleanup |
| `semantica.embeddings` | Sentence-Transformers, FastEmbed, OpenAI, BGE, Ollama local embeddings |
| `semantica.pipeline` | Pipeline DSL, parallel workers, retry policies, failure handling |
| `semantica.export` | RDF, Parquet, ArangoDB AQL, CSV, OWL, Arrow, GraphML, GEXF, DOT |
| `semantica.visualization` | Programmatic graph rendering: force, hierarchical, circular, spring layouts |
| `semantica.deduplication` | Entity deduplication v1/v2, similarity scoring, blocking, merging |
| `semantica.conflicts` | Conflict detection and resolution across overlapping knowledge sources |
| `semantica.provenance` | W3C PROV-O lineage tracking, source attribution, audit trails |
| `semantica.change_management` | Version control with SHA-256 checksums, diff, rollback |
| `semantica.llms` | Groq, OpenAI, Anthropic, Gemini, Ollama, DeepSeek, Novita AI, LiteLLM, HuggingFace |
| `semantica.seed` | Foundation graph seeding from CSV, JSON, SQL, API, and RDF sources |
| `semantica.evals` | Evaluation harness: KG quality, extraction F1, pipeline benchmarking, regression tracking |
| `semantica.core` | Orchestration, ConfigManager, LifecycleManager, PluginRegistry, MethodRegistry |
| `semantica.utils` | Logging, validation, progress tracking, hash utilities, nested dict helpers |
## Why Semantica?
**Open Source, MIT** — No vendor lock-in. No paywalled features.
- Full source available on GitHub
- Every line auditable by your security team
- Fork, extend, and self-host with no restrictions
- No telemetry, no usage reporting
**Production Ready** — Built for teams that can't afford surprises.
- 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
**Modular by Design** — Import only what you need.
- Use `NERExtractor` without a graph store
- Use `ContextGraph` without vector storage
- Every component independently swappable and testable
- No framework lock-in: works with any agent stack
+3 -3
View File
@@ -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.
+1 -1
View File
@@ -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.
+4 -4
View File
@@ -12,13 +12,13 @@ icon: "link"
pip install "semantica[langchain]"
```
Requires `langchain-core >= 0.3`. If langchain-core is not installed, the integration still imports — every class carries the full Semantica API and degrades gracefully (`build()` returns `None`; branch on `LANGCHAIN_AVAILABLE`).
Requires `langchain-core >= 0.3`. If langchain-core is not installed, the integration still imports. Every class carries the full Semantica API and degrades gracefully (`build()` returns `None`; branch on `LANGCHAIN_AVAILABLE`).
## Components at a Glance
- **SemanticaRetriever** `BaseRetriever`: hybrid-search seeds retrieval, then graph edges are walked `hops` steps (default 2) for GraphRAG-style results.
- **SemanticaVectorStore** `VectorStore`: `add_texts` / `similarity_search` / `similarity_search_with_score` / `from_texts` over `HybridSearch`.
- **SemanticaKGTool** / **SemanticaDecisionTool** `BaseTool` subclasses: `semantica_query_graph` and `semantica_query_decisions` for LangGraph / tool-calling agents.
- **SemanticaRetriever** (`BaseRetriever`): hybrid-search seeds retrieval, then graph edges are walked `hops` steps (default 2) for GraphRAG-style results.
- **SemanticaVectorStore** (`VectorStore`): `add_texts` / `similarity_search` / `similarity_search_with_score` / `from_texts` over `HybridSearch`.
- **SemanticaKGTool** / **SemanticaDecisionTool** (`BaseTool` subclasses): `semantica_query_graph` and `semantica_query_decisions` for LangGraph / tool-calling agents.
## Component Details
+2 -2
View File
@@ -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.
+1 -1
View File
@@ -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.
+14 -14
View File
@@ -9,9 +9,9 @@ Whether you're running your first pipeline or deploying Semantica in production,
## Learning Paths
- **Beginner (12 hrs)** — New to Semantica and knowledge graphs. [Start with Installation →](installation)
- **Intermediate (46 hrs)** — Comfortable with basics, building real applications. [Start with Modules →](modules)
- **Advanced (8+ hrs)** — Enterprise deployments, customization, and extension. [Start with Architecture →](architecture)
- **Beginner (12 hrs)**: new to Semantica and knowledge graphs. [Start with Installation →](/installation)
- **Intermediate (46 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 (12 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.
@@ -116,7 +116,7 @@ pip install "semantica[gpu]" # GPU acceleration
<Accordion title="AuthenticationError" icon="lock">
Set your API key as an environment variable never hardcode keys in source files:
Set your API key as an environment variable (never hardcode keys in source files):
```bash
export OPENAI_API_KEY="sk-..."
@@ -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.
+200 -159
View File
@@ -5,30 +5,32 @@ icon: "puzzle-piece"
---
<Info>
Looking for a quick reference? Jump to the [Module Index](#module-index) at the bottom.
Jump to the [Module Index](#module-index) for a quick reference.
</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.
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.
## Architecture Overview
- **Input Layer** — Data ingestion and preparation. Modules: `ingest`, `parse`, `split`, `normalize`
- **Core Processing** — Intelligence and understanding. Modules: `semantic_extract`, `kg`, `ontology`, `reasoning`
- **Storage** — Persistent data storage. Modules: `embeddings`, `vector_store`, `graph_store`, `triplet_store`
- **Quality Assurance** — Data quality and consistency. Modules: `deduplication`, `conflicts`
- **Context & Memory** — Agent memory and decision tracking. Modules: `context`, `provenance`, `change_management`
- **Output & Orchestration** — Export, visualization, and workflows. Modules: `export`, `visualization`, `pipeline`, `explorer`
- **Input Layer**: data ingestion and preparation. Modules: `ingest`, `parse`, `split`, `normalize`
- **Core Processing**: intelligence and understanding. Modules: `semantic_extract`, `kg`, `ontology`, `reasoning`
- **Storage**: persistent data storage. Modules: `embeddings`, `vector_store`, `graph_store`, `triplet_store`
- **Quality Assurance**: data quality and consistency. Modules: `deduplication`, `conflicts`
- **Context & Memory**: agent memory and decision tracking. Modules: `context`, `provenance`, `change_management`
- **Output & Orchestration**: export, visualization, and workflows. Modules: `export`, `visualization`, `pipeline`, `explorer`
## Input Layer
### Ingest
Loads data from files, web, databases, and streams into a unified `SourceDocument` format.
Loads data from files, web, databases, and streams. Each ingestor returns its own
result type (`FileIngestor``FileObject`, `WebIngestor``WebContent`, …);
document-oriented ones expose a `.text` payload and `.metadata`.
```python
from semantica.ingest import FileIngestor, WebIngestor, ParquetIngestor, XMLIngestor, DatabricksIngestor
@@ -37,7 +39,7 @@ from semantica.ingest import FileIngestor, WebIngestor, ParquetIngestor, XMLInge
ingestor = FileIngestor()
documents = ingestor.ingest_directory("data/")
# Web crawl
# Web page: returns a WebContent with .text, .title, .links, .metadata
web_ingestor = WebIngestor()
page = web_ingestor.ingest_url("https://example.com")
@@ -49,7 +51,7 @@ sources = parquet.ingest("data/events.parquet")
xml = XMLIngestor()
sources = xml.ingest("data/records/", schema_path="schema.xsd")
# Enterprise lakehouse/warehouse Unity Catalog + Delta Lake, or a Snowflake warehouse
# Enterprise lakehouse/warehouse: Unity Catalog + Delta Lake, or a Snowflake warehouse
databricks = DatabricksIngestor(host="...", token="...", http_path="...")
customers = databricks.ingest_table("customers")
```
@@ -57,7 +59,7 @@ customers = databricks.ingest_table("customers")
**Available ingestors:** `FileIngestor`, `WebIngestor`, `ParquetIngestor`, `XMLIngestor`, `RESTIngestor`, `PublicAPIIngestor`, `DBIngestor`, `DatabricksIngestor`, `SnowflakeIngestor`, `EmailIngestor`, `FeedIngestor`, `MCPIngestor`, `OntologyIngestor`, `RepoIngestor`, `StreamIngestor`, `ArrowIngestor`, `CloudStorageIngestor`
<Note>
`DuckDBIngestor`, `ElasticIngestor`, `GDriveIngestor`, `HuggingFaceIngestor`, `MongoIngestor`, and `PandasIngestor` also ship but aren't re-exported from the top-level `semantica.ingest` namespace yet import them directly, e.g. `from semantica.ingest.duckdb_ingestor import DuckDBIngestor`.
`DuckDBIngestor`, `ElasticIngestor`, `GDriveIngestor`, `HuggingFaceIngestor`, `MongoIngestor`, and `PandasIngestor` also ship but aren't re-exported from the top-level `semantica.ingest` namespace yet; import them directly, e.g. `from semantica.ingest.duckdb_ingestor import DuckDBIngestor`.
</Note>
### Parse
@@ -67,13 +69,13 @@ Extracts structured text and layout metadata from raw documents.
```python
from semantica.parse import DocumentParser, DoclingParser
# Standard parser: all common formats
# Standard parser: all common formats. parse() takes a path, returns a dict
parser = DocumentParser()
parsed = parser.parse_document("document.pdf")
parsed = parser.parse("document.pdf") # {"full_text": ..., "metadata": ..., ...}
# Advanced parser: multi-column PDFs, merged-cell tables, OCR
parser = DoclingParser(extract_tables=True, extract_images=True, output_format="markdown")
parsed = parser.parse("data/annual_report.pdf")
# Advanced parser (pip install semantica[parse-docling]): tables, OCR, layout
parser = DoclingParser(export_format="markdown", enable_ocr=True)
parsed = parser.parse("data/annual_report.pdf") # dict with full_text, tables, pages
```
**Available parsers:** `DocumentParser`, `DoclingParser`, `CodeParser`, `CSVParser`, `DocxParser`, `EmailParser`, `ExcelParser`, `HTMLParser`, `ImageParser`, `JSONParser`, `MCPParser`, `MediaParser`, `PDFParser`, `PPTXParser`, `StructuredDataParser`, `WebParser`, `XMLParser`
@@ -85,11 +87,12 @@ Chunks text for embedding and RAG pipelines with awareness of semantic boundarie
```python
from semantica.split import TextSplitter
splitter = TextSplitter(method="semantic_transformer")
chunks = splitter.split(text, chunk_size=1000, chunk_overlap=200)
# chunk_size / chunk_overlap are constructor arguments
splitter = TextSplitter(method="semantic_transformer", chunk_size=1000, chunk_overlap=200)
chunks = splitter.split(text)
```
**Chunking strategies:** `recursive`, `semantic_transformer`, `entity_aware`, `relation_aware`, `sliding_window`, `structural`
**Chunking methods:** `recursive`, `token`, `sentence`, `paragraph`, `semantic_transformer`, `entity_aware`, `relation_aware`, `graph_based`, `ontology_aware`, `hierarchical`, `community_detection`, `centrality_based`, `llm`
### Normalize
@@ -115,17 +118,18 @@ Named entity recognition, relation extraction, and triplet generation.
```python
from semantica.semantic_extract import NERExtractor, RelationExtractor, TripletExtractor
ner = NERExtractor(method="llm", llm_provider=llm)
entities = ner.extract("Apple Inc. was founded by Steve Jobs.")
# LLM method: provider + llm_model select the backend; the API key comes from the env
ner = NERExtractor(method="llm", provider="groq", llm_model="llama-3.3-70b-versatile")
entities = ner.extract("Apple Inc. was founded by Steve Jobs.") # list[Entity]
rel = RelationExtractor(method="llm", llm_provider=llm)
relationships = rel.extract(text, entities=entities)
rel = RelationExtractor(method="llm", provider="groq", llm_model="llama-3.3-70b-versatile")
relationships = rel.extract(text, entities=entities) # list[Relation]
trip = TripletExtractor(method="llm", llm_provider=llm)
triplets = trip.extract(text)
trip = TripletExtractor(method="pattern")
triplets = trip.extract(text) # list[Triplet]
```
**Extraction methods:** `"pattern"` (no API key), `"ml"` (local model), `"llm"` (any of the 8 supported providers)
**Extraction methods:** `"pattern"` (no API key), `"ml"` (local spaCy model), `"llm"` (any of the 9 supported providers)
**Additional extractors:** `CoreferenceResolver`, `EventDetector`, `SemanticAnalyzer`, `SemanticNetworkExtractor`
@@ -137,17 +141,17 @@ Graph construction, graph algorithms, temporal model, and distance intelligence.
from semantica.kg import GraphBuilder, GraphAnalyzer, TemporalGraphQuery, SimilarityCalculator
from datetime import datetime
# Build
# Build: build() takes a {"entities": ..., "relationships": ...} dict
builder = GraphBuilder(merge_entities=True)
kg = builder.build(entities=entities, relationships=relationships)
kg = builder.build({"entities": entities, "relationships": relationships})
# Temporal graphs (v0.4.0)
query_engine = TemporalGraphQuery(enable_temporal_reasoning=True)
snapshot = query_engine.query_at_time(kg, query="", at_time=datetime(2021, 6, 15))
# Semantic similarity (v0.5.0)
calc = SimilarityCalculator()
scores = calc.calculate_similarity(entity_a, entity_b)
# Semantic similarity (v0.5.0): operates on embedding vectors
calc = SimilarityCalculator(method="cosine")
score = calc.cosine_similarity(vec_a, vec_b)
```
**Graph algorithms available:** centrality calculation, community detection, connectivity analysis, entity resolution, link prediction, path finding, similarity calculation
@@ -175,19 +179,23 @@ Derives new facts from existing knowledge using multiple inference strategies.
```python
from semantica.reasoning import Reasoner, DatalogReasoner
# Rule-based reasoning
# Forward chaining: facts and rules as predicate(args) / IF-THEN strings
engine = Reasoner()
engine.apply_transitivity("located_in")
engine.apply_symmetry("knows")
result = engine.infer()
engine.add_fact("Manager(Alice)")
engine.add_rule("IF Manager(?x) THEN HasAuthority(?x)")
results = engine.forward_chain() # list[InferenceResult] with .conclusion, .rule_used
# Datalog: recursive Horn clause rules (v0.4.0)
datalog = DatalogEngine()
datalog = DatalogReasoner()
datalog.add_fact("parent(tom, bob)")
datalog.add_fact("parent(bob, ann)")
datalog.add_rule("ancestor(X, Y) :- parent(X, Y).")
datalog.add_rule("ancestor(X, Z) :- parent(X, Y), ancestor(Y, Z).")
results = datalog.query("ancestor(alice, ?)")
datalog.derive_all()
results = datalog.query("ancestor(tom, ?Z)") # [{"Z": "bob"}, {"Z": "ann"}], order not guaranteed
```
**Engines:** forward chaining, Rete network, deductive, abductive, SPARQL, Datalog: all produce explainable inference paths
**Engines:** `Reasoner` (forward/backward chaining), `ReteEngine`, `SPARQLReasoner`, `DatalogReasoner`, `TemporalReasoningEngine`, `GraphReasoner` (LLM)
## Storage
@@ -199,9 +207,9 @@ Generates and manages vector embeddings for semantic similarity.
```python
from semantica.embeddings import EmbeddingGenerator
generator = EmbeddingGenerator(model="sentence-transformers")
embeddings = generator.generate(["text1", "text2"])
similarity = generator.similarity(embeddings[0], embeddings[1])
generator = EmbeddingGenerator()
embeddings = generator.generate_embeddings(["text1", "text2"]) # np.ndarray
similarity = generator.compare_embeddings(embeddings[0], embeddings[1])
```
**Supported models:** Sentence-Transformers, FastEmbed, OpenAI, BGE
@@ -215,12 +223,18 @@ Multi-backend vector database with hybrid search support.
```python
from semantica.vector_store import VectorStore
store = VectorStore(backend="faiss", dimension=768)
store.add_vectors(embeddings, ids)
results = store.search(query_vector, top_k=10)
store = VectorStore(backend="faiss", dimension=768)
# Raw vectors
ids = store.store_vectors(embeddings) # returns generated ids
hits = store.search_vectors(query_vector, k=10)
# Or store text and let the store embed it
store.add_documents(["Apple was founded in 1976.", "Google was founded in 1998."])
results = store.search("tech company founding dates", limit=10)
```
**Backends:** FAISS, Pinecone, Weaviate, Qdrant, Milvus, PgVector, in-memory
**Backends:** FAISS, Pinecone, Weaviate, Qdrant, Milvus, PgVector, SQLite, in-memory
**Search modes:** semantic top-k, hybrid (vector + keyword), metadata-filtered
@@ -232,8 +246,8 @@ Connects to graph databases for persistent, query-able storage.
from semantica.graph_store import GraphStore
store = GraphStore(backend="neo4j")
store.add_nodes(entities)
store.add_edges(relationships)
store.add_nodes([{"id": "acme", "type": "Organization", "properties": {"name": "Acme"}}])
store.add_edges([{"source": "alice", "target": "acme", "type": "works_for"}])
results = store.query("MATCH (n)-[r]->(m) RETURN n, r, m")
```
@@ -246,9 +260,9 @@ RDF triple-based storage with SPARQL query support.
```python
from semantica.triplet_store import TripletStore
store = TripletStore(backend="blazegraph")
store.add_triplets(subject, predicate, obj)
results = store.sparql("SELECT ?s ?p ?o WHERE { ?s ?p ?o }")
store = TripletStore(backend="oxigraph")
store.add_triplets(triplets) # list of Triplet objects (or add_triplet for one)
results = store.execute_query("SELECT ?s ?p ?o WHERE { ?s ?p ?o }")
```
**Backends:** Oxigraph (embedded), Blazegraph, Apache Jena, RDF4J
@@ -261,15 +275,18 @@ results = store.sparql("SELECT ?s ?p ?o WHERE { ?s ?p ?o }")
Detects, scores, and merges duplicate entities across sources.
```python
from semantica.deduplication import EntityResolver
from semantica.deduplication import DuplicateDetector, EntityMerger
resolver = EntityResolver()
merged = resolver.resolve(entities, strategy="semantic_v2")
detector = DuplicateDetector(similarity_threshold=0.85)
candidates = detector.detect_duplicates(entities)
merger = EntityMerger()
operations = merger.merge_duplicates(entities, strategy="keep_most_complete")
```
**v2 strategies** (`blocking_v2`, `hybrid_v2`, `semantic_v2`) are up to 7x faster than v1.
**v2 candidate-generation modes** (`blocking_v2`, `hybrid_v2`, `semantic_v2`) are up to 7x faster than v1.
**Components:** `EntityResolver`, `DuplicateDetector`, `EntityMerger`, `SimilarityCalculator`, `ClusterBuilder`
**Components:** `DuplicateDetector`, `EntityMerger`, `ClusterBuilder`, `MergeStrategyManager`
**`DuplicateDetector` options:** `max_results`, `top_k_per_entity`, `min_similarity`, `sort_by`
@@ -278,14 +295,13 @@ merged = resolver.resolve(entities, strategy="semantic_v2")
Detects and resolves fact conflicts across overlapping knowledge sources.
```python
from semantica.conflicts import ConflictDetector
from semantica.conflicts import ConflictDetector, ConflictResolver
detector = ConflictDetector()
conflicts = detector.detect_conflicts(kg)
resolved = detector.resolve(conflicts, strategy="most_recent")
conflicts = ConflictDetector().detect_conflicts(entities) # list of entity dicts
resolved = ConflictResolver().resolve_conflicts(conflicts, strategy="most_recent")
```
**Detection types:** value conflicts, type conflicts, temporal conflicts, logical conflicts
**Detection types:** value conflicts, type conflicts, relationship conflicts, temporal conflicts, logical conflicts
**Resolution strategies:** prefer most recent, prefer most reliable source, majority vote, flag for manual review
@@ -298,6 +314,7 @@ Agent context graphs, decision tracking, causal chains, and precedent search.
```python
from semantica.context import AgentContext, ContextGraph
from semantica.vector_store import VectorStore
context = AgentContext(
vector_store=VectorStore(backend="faiss", dimension=768),
@@ -328,7 +345,7 @@ W3C PROV-O compliant lineage tracking across all modules.
from semantica.provenance import ProvenanceManager
manager = ProvenanceManager()
manager.track_entity("entity_1", "document.pdf", "person")
manager.track_entity("entity_1", source="document.pdf", metadata={"type": "person"})
lineage = manager.get_lineage("entity_1")
```
@@ -364,8 +381,8 @@ RDFExporter().export(graph, file_path="graph.ttl", format="turtle")
# Analytics
ParquetExporter().export(graph, file_path="output/graph.parquet")
# ArangoDB
aql = ArangoAQLExporter().export(graph)
# ArangoDB: writes AQL INSERT statements to the given path
ArangoAQLExporter().export(graph, file_path="graph.aql")
```
**Export formats:** RDF (Turtle, JSON-LD, N-Triples, XML), Parquet, ArangoDB AQL, CSV, OWL, Arrow, LPG, YAML, distance matrices
@@ -390,16 +407,24 @@ viz.visualize_network(graph, output="html", file_path="graph.html")
Pipeline DSL with parallel workers, retry policies, and failure handling.
```python
from semantica.pipeline import Pipeline
from semantica.pipeline import PipelineBuilder, ExecutionEngine
from semantica.ingest import FileIngestor
from semantica.semantic_extract import NERExtractor
pipeline = Pipeline()
pipeline.add_step("ingest", FileIngestor())
pipeline.add_step("extract", NERExtractor())
pipeline.add_step("build", GraphBuilder())
result = pipeline.run("data/")
builder = PipelineBuilder()
# Each step type dispatches to a handler you register (or supply explicitly)
builder.register_step_handler("ingest", lambda data, **c: FileIngestor().ingest(c["source"]))
builder.register_step_handler("extract", lambda docs, **c: NERExtractor(method="pattern").extract(docs[0].text))
builder.add_step("ingest", step_type="ingest", source="data/")
builder.add_step("extract", step_type="extract")
pipeline = builder.connect_steps("ingest", "extract").build(name="docs_to_entities")
result = ExecutionEngine().execute_pipeline(pipeline)
```
**Components:** `Pipeline`, `PipelineBuilder`, `ExecutionEngine`, `FailureHandler`, `PipelineValidator`, `ParallelismManager`, `ResourceScheduler`
**Components:** `PipelineBuilder`, `Pipeline`, `ExecutionEngine`, `FailureHandler`, `PipelineValidator`, `ParallelismManager`, `ResourceScheduler`
### Explorer
@@ -428,7 +453,7 @@ llm = OpenAI(model="gpt-4o", api_key=os.getenv("OPENAI_API_KEY"))
llm = LiteLLM(model="anthropic/claude-opus-4-7", api_key=os.getenv("ANTHROPIC_API_KEY"))
```
**Supported providers:** OpenAI, Anthropic, Google Gemini, Groq, Ollama, DeepSeek, Novita AI, LiteLLM (20+ models via one interface)
**Supported providers:** OpenAI, Anthropic, Google Gemini, Groq, Ollama, DeepSeek, Novita AI, HuggingFace, plus LiteLLM (100+ models via one interface)
### MCP Server
@@ -438,51 +463,50 @@ 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 are exposed.
### Seed
Bootstrap knowledge graphs from verified structured sources: fixed-point reference data, controlled vocabularies, and domain anchors.
```python
from semantica.seed import SeedManager
from semantica.seed import SeedDataManager
seed = SeedManager()
seed.populate(kg, dataset="companies", count=100)
seed = SeedDataManager()
# Load domain seeds from file or built-in datasets
seed.load_from_file("seed_data/industries.json")
seed.inject(kg) # merges seed nodes without duplicating existing entities
# Load trusted reference data from CSV / JSON / a database / an API
seed_data = seed.load_from_csv("seed_data/industries.csv", entity_type="Industry")
# Merge seed data with extraction output (seed values win on conflict by default)
combined = seed.integrate_with_extracted(
{"entities": seed_data, "relationships": []},
{"entities": extracted_entities, "relationships": extracted_relationships},
merge_strategy="seed_first",
)
```
**Use cases:** anchoring extraction with known entities, pre-populating ontology classes, deterministic test graph generation.
### Evals
Evaluation framework for measuring KG quality, extraction accuracy, and pipeline performance.
Scores decision-intelligence outputs (decision records, audit trails, reasoning
text) with a registry of deterministic and model-backed evaluators plus a small
run harness.
```python
from semantica.evals import KGEvaluator, ExtractionEvaluator, PipelineEvaluator, RegressionTracker
from semantica.evals import evaluate, list_evaluators
# KG quality
report = KGEvaluator().evaluate(kg, ontology=ontology)
print(f"Completeness: {report.completeness:.2%} Consistency: {report.consistency:.2%}")
list_evaluators()
# ['decision_scores', 'exact_match', 'keyword_check', 'length_range',
# 'levenshtein', 'llm_as_judge', 'numeric_range', 'regex_match', 'rouge',
# 'temporal_range']
# Extraction accuracy
report = ExtractionEvaluator().evaluate_ner(predictions=extracted, gold_standard=annotated)
print(f"Precision: {report.precision:.3f} Recall: {report.recall:.3f} F1: {report.f1:.3f}")
# Pipeline throughput and latency
metrics = PipelineEvaluator().benchmark(pipeline, data="data/", bench_runs=5)
print(f"Throughput: {metrics.docs_per_second:.1f} docs/sec")
# Regression tracking across runs
tracker = RegressionTracker(db_path="eval_history.db")
run_id = tracker.record_run(pipeline_version="v1.2.0", metrics=metrics)
diff = tracker.compare(run_id, baseline_run_id="run_abc123")
cases = [("apple", "aple"), ("night", "nacht")]
summary = evaluate(cases, evaluators=["levenshtein"])
print(summary.total, summary.passed, summary.pass_rate)
```
**Components:** `KGEvaluator`, `ExtractionEvaluator`, `PipelineEvaluator`, `RegressionTracker`
**Public API:** `evaluate(cases, evaluators, config=None)`, `list_evaluators()`, `get_evaluator(name)`, and the `EvalMetric` / `CaseResult` / `EvalSummary` result types. See the [Evals reference](/reference/evals).
### Core
@@ -491,20 +515,20 @@ Base classes, shared data models, and the plugin registry used across all module
```python
from semantica.core import Semantica, PluginRegistry, ConfigManager
# Top-level orchestrator
sem = Semantica(config_path="config.yaml")
# ConfigManager loads a Config; Config.get() does dotted lookups
config = ConfigManager().load_from_file("config.yaml")
batch = config.get("processing.batch_size", default=32)
# Top-level orchestrator: pass the Config object (or a dict), not a path
sem = Semantica(config=config)
sem.initialize()
# Plugin registry: register custom components
# Plugin registry: register custom components under a name
registry = PluginRegistry()
registry.register("my_ingestor", MyCustomIngestor)
# Config management
config = ConfigManager(config_path="config.yaml")
batch = config.get("processing.batch_size", default=32)
registry.register_plugin("my_ingestor", MyCustomIngestor, version="1.0.0")
```
**Components:** `Semantica`, `PluginRegistry`, `ConfigManager`, `LifecycleManager`, `HealthMonitor`, `Config`
**Components:** `Semantica`, `PluginRegistry`, `ConfigManager`, `Config`, `LifecycleManager`, `HealthStatus`, `MethodRegistry`
### Utils
@@ -532,11 +556,13 @@ from semantica.semantic_extract import NERExtractor, RelationExtractor
from semantica.kg import GraphBuilder
sources = FileIngestor().ingest("data/")
parsed = DocumentParser().parse(sources[0])
entities = NERExtractor(method="llm", llm_provider=llm).extract(parsed)
relationships = RelationExtractor(method="llm", llm_provider=llm).extract(parsed, entities=entities)
text = DocumentParser().parse(sources[0].path)["full_text"]
ner = NERExtractor(method="llm", provider="groq", llm_model="llama-3.3-70b-versatile")
rel = RelationExtractor(method="llm", provider="groq", llm_model="llama-3.3-70b-versatile")
entities = ner.extract(text)
relationships = rel.extract(text, entities=entities)
graph = GraphBuilder(merge_entities=True).build(
entities=entities, relationships=relationships
{"entities": entities, "relationships": relationships}
)
```
@@ -555,16 +581,20 @@ from semantica.vector_store import VectorStore
context = AgentContext(
vector_store=VectorStore(backend="faiss", dimension=768),
knowledge_graph=ContextGraph(advanced_analytics=True),
graph_expansion=True,
)
context.load_graph("company_kg.json")
result = context.query(
# store() extracts entities and populates the graph + vector index
context.store([{"content": "Steve Wozniak co-founded Apple with Steve Jobs."}])
# retrieve() blends vector similarity with multi-hop graph traversal
results = context.retrieve(
"What companies did Apple alumni found?",
mode="graphrag",
reasoning=True,
use_graph=True,
expand_graph=True,
)
for claim in result.claims:
print(f"{claim.text} → {claim.source_node}")
for r in results:
print(f"[{r['score']:.3f}] {r['content']} (source: {r['source']})")
```
**Best for:** question-answering systems, RAG with source attribution, research assistants
@@ -606,18 +636,22 @@ precedents = context.find_precedents("model selection", limit=5)
```python
from semantica.ingest import FileIngestor
from semantica.parse import DocumentParser
from semantica.semantic_extract import NERExtractor
from semantica.kg import GraphBuilder
from semantica.provenance import ProvenanceManager
from semantica.export import RDFExporter
sources = FileIngestor().ingest("records/")
entities = NERExtractor(method="llm", llm_provider=llm).extract(sources)
graph = GraphBuilder(merge_entities=True).build(entities=entities, relationships=[])
prov = ProvenanceManager()
lineage = prov.get_entity_lineage("entity_id")
ner = NERExtractor(method="llm", provider="groq", llm_model="llama-3.3-70b-versatile")
entities = ner.extract(DocumentParser().parse(sources[0].path)["full_text"])
graph = GraphBuilder(merge_entities=True).build({"entities": entities, "relationships": []})
RDFExporter(include_provenance=True).export(graph, file_path="audit.ttl", format="turtle")
prov = ProvenanceManager()
prov.track_entity("entity_id", source="records/filing.pdf", metadata={"extractor": "llm"})
lineage = prov.get_lineage("entity_id")
RDFExporter().export(graph, file_path="audit.ttl", format="turtle")
```
**Best for:** HIPAA, SOX, GDPR, FDA 21 CFR Part 11 deployments
@@ -632,18 +666,25 @@ RDFExporter(include_provenance=True).export(graph, file_path="audit.ttl", format
from semantica.ingest import WebIngestor
from semantica.normalize import TextNormalizer
from semantica.semantic_extract import NERExtractor, RelationExtractor
from semantica.graph_store import Neo4jStore
from semantica.graph_store import GraphStore
from semantica.kg import GraphBuilder
pages = WebIngestor(max_depth=2).ingest("https://example.com")
ingestor = WebIngestor()
normalizer = TextNormalizer()
store = Neo4jStore(uri="bolt://localhost:7687", user="neo4j", password="password")
ner = NERExtractor(method="pattern")
rel = RelationExtractor(method="pattern")
for page in pages:
# The generic GraphStore wrapper exposes the add_nodes/add_edges interface
# GraphBuilder persists through; a raw Neo4jStore does not
store = GraphStore(backend="neo4j", uri="bolt://localhost:7687", user="neo4j", password="password")
builder = GraphBuilder(merge_entities=True, graph_store=store)
for url in ["https://example.com/a", "https://example.com/b"]:
page = ingestor.ingest_url(url) # WebContent, has .text
text = normalizer.normalize_text(page.text)
entities = NERExtractor().extract(text)
relationships = RelationExtractor().extract(text, entities=entities)
store.add_nodes(entities)
store.add_edges(relationships)
entities = ner.extract(text)
relationships = rel.extract(text, entities=entities)
builder.build({"entities": entities, "relationships": relationships})
```
**Best for:** competitive intelligence, news monitoring, research aggregation
@@ -680,34 +721,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 | `DuplicateDetector`, `EntityMerger`, `ClusterBuilder`, `MergeStrategyManager` |
| [conflicts](/reference/conflicts) | Conflict resolution | `ConflictDetector`, `ConflictResolver`, `SourceTracker` |
| [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 | `SeedDataManager` |
| [evals](/reference/evals) | Decision-intelligence evaluation | `evaluate`, `list_evaluators`, `EvalSummary` |
| [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.
+2 -2
View File
@@ -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.
+94 -68
View File
@@ -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.8**: cryptographically signed releases (SLSA provenance + Sigstore), real vector-store enumeration across FAISS/Qdrant/Weaviate/Milvus, and first-class Anthropic/Gemini/Ollama/DeepSeek/Novita LLM provider wrappers. <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.8
```
@@ -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,26 @@ 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["full_text"][:200]) # extracted text
print(parsed["metadata"]) # document properties (fields vary by format)
```
`parse()` returns a `dict`. `full_text` and `metadata` are present for every
format; other keys depend on the parser (`pages` for PDF, `tables` and
`paragraphs` for DOCX, `tables` for `DoclingParser`).
<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 +109,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["full_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["full_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 +192,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 +267,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)["full_text"]
entities = ner.extract(text)
rels = rel.extract(text, entities=entities)
all_entities.extend(entities)
all_rels.extend(rels)
@@ -327,10 +329,11 @@ print(f"Relationships active in 2023: {result_2023['num_relationships']}")
<Accordion title="Persistent graph store: Neo4j, FalkorDB, Apache AGE" icon="database">
```python
from semantica.graph_store import Neo4jStore
from semantica.graph_store import GraphStore
from semantica.kg import GraphBuilder
store = Neo4jStore(
store = GraphStore(
backend="neo4j",
uri="bolt://localhost:7687",
user="neo4j",
password="password",
@@ -358,7 +361,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>
@@ -372,32 +376,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"])["full_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">
@@ -428,7 +454,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, and 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.

Some files were not shown because too many files have changed in this diff Show More