Compare commits

...
Author SHA1 Message Date
KaifAhmad1 ed89651034 docs: restructure nav — drop FAQ/Changelog tabs, add API Reference tab
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:14:59 +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
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
pkupt df42a015b0 test(weaviate): cover delete_vectors and erasure integration 2026-09-02 20:45:22 +08:00
pkupt 9df54ffcd0 feat(weaviate): add delete_vectors to WeaviateStore 2026-09-02 20:02:37 +08:00
6 changed files with 326 additions and 297 deletions
+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 ───────────────────────────────────────── */
+111 -80
View File
@@ -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>
@@ -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,11 +337,16 @@ 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.
@@ -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
```
@@ -456,27 +487,27 @@ 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>
+28 -27
View File
@@ -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"
}
]
},
+9 -7
View File
@@ -78,11 +78,13 @@ from semantica.parse import DocumentParser
parser = DocumentParser()
parsed = parser.parse(sources[0].path) # parse() takes a path string
print(parsed["text"][:200]) # extracted text
print(parsed["metadata"]) # file_path, encoding, size, and format-specific keys
print(parsed["full_text"][:200]) # extracted text
print(parsed["metadata"]) # document properties (fields vary by format)
```
`parse()` returns a `dict` with `text`, `full_text`, and `metadata` keys.
`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` (`pip install semantica[parse-docling]`): it applies advanced layout analysis and returns structured table data alongside text.
@@ -107,7 +109,7 @@ Identify named entities and extract typed relationships between them.
```python Pattern-based (fast, no API key)
from semantica.semantic_extract import NERExtractor, RelationExtractor
text = parsed["text"]
text = parsed["full_text"]
ner = NERExtractor(method="pattern")
entities = ner.extract(text)
@@ -122,7 +124,7 @@ relationships = rel.extract(text, entities=entities)
from semantica.semantic_extract import NERExtractor, RelationExtractor
# Reads GROQ_API_KEY from the environment; provider/llm_model select the backend
text = parsed["text"]
text = parsed["full_text"]
ner = NERExtractor(method="llm", provider="groq", llm_model="llama-3.3-70b-versatile")
entities = ner.extract(text)
@@ -277,7 +279,7 @@ builder = GraphBuilder(merge_entities=True)
all_entities, all_rels = [], []
for source in FileIngestor().ingest("data/reports/"):
text = parser.parse(source.path)["text"]
text = parser.parse(source.path)["full_text"]
entities = ner.extract(text)
rels = rel.extract(text, entities=entities)
all_entities.extend(entities)
@@ -413,7 +415,7 @@ store = GraphStore(backend="neo4j", uri="bolt://localhost:7687",
builder = GraphBuilder(merge_entities=True, graph_store=store)
for info in ingestor.scan_directory("data/reports/", recursive=True):
text = parser.parse(info["path"])["text"] # one document loaded at a time
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})
+32
View File
@@ -416,6 +416,38 @@ class WeaviateStore:
)
raise ProcessingError(f"Failed to add objects: {str(e)}")
def delete_vectors(self, vector_ids: List[str], **options) -> Dict[str, Any]:
"""Delete vectors (objects) from the collection by their ids.
Args:
vector_ids: Object uuids to delete
**options: Additional options (ignored, kept for API parity)
Returns:
A dict with the number of successfully deleted objects
(``delete_count``).
"""
if self.collection is None or not WEAVIATE_AVAILABLE:
raise ProcessingError("Collection not initialized or Weaviate unavailable")
if not vector_ids:
return {"delete_count": 0}
deleted = 0
try:
data = self.collection.data
for vector_id in vector_ids:
if not vector_id:
continue
# delete_by_id returns False (not an error) for a uuid that is
# not present, and True when an object was deleted. Count only
# actual deletes so delete_count never over-reports.
if data.delete_by_id(vector_id):
deleted += 1
return {"delete_count": deleted}
except Exception as e:
raise ProcessingError(f"Failed to delete vectors: {str(e)}")
def get_vector(self, vector_id: str) -> Optional[np.ndarray]:
"""Get vector by ID."""
if self.collection is None or not WEAVIATE_AVAILABLE:
@@ -0,0 +1,142 @@
"""Tests for WeaviateStore.delete_vectors (#1374)."""
from unittest import TestCase
from unittest.mock import MagicMock, patch
from semantica.context.erasure import STATUS_ERASED, ErasureCoordinator
from semantica.utils.exceptions import ProcessingError
from semantica.vector_store import VectorStore
from semantica.vector_store.weaviate_store import WeaviateStore
class WeaviateStoreDeleteVectorsTest(TestCase):
def setUp(self):
self.patches = [
patch("semantica.vector_store.weaviate_store.WEAVIATE_AVAILABLE", True)
]
for p in self.patches:
p.start()
def tearDown(self):
for p in reversed(self.patches):
p.stop()
def _store(self, error=None):
"""Return (store, data) where data records delete_by_id calls."""
data = MagicMock()
data.delete_by_id = MagicMock()
coll = MagicMock()
coll.data = data
if error is not None:
data.delete_by_id.side_effect = error
store = WeaviateStore()
store.collection = coll
return store, data
def test_delete_single_id_calls_delete_by_id(self):
store, data = self._store()
ret = store.delete_vectors(["abc"])
data.delete_by_id.assert_called_once_with("abc")
self.assertEqual(ret, {"delete_count": 1})
def test_delete_many_ids_calls_each(self):
store, data = self._store()
ret = store.delete_vectors(["a", "b", "c"])
self.assertEqual(data.delete_by_id.call_count, 3)
self.assertEqual(ret, {"delete_count": 3})
def test_delete_skips_ids_that_report_missing(self):
store, data = self._store()
def _fake(uuid):
return uuid != "missing"
data.delete_by_id.side_effect = _fake
ret = store.delete_vectors(["present", "missing", "also-here"])
self.assertEqual(data.delete_by_id.call_count, 3)
self.assertEqual(ret, {"delete_count": 2})
def test_delete_drops_empty_ids(self):
store, data = self._store()
store.delete_vectors(["", "abc"])
data.delete_by_id.assert_called_once_with("abc")
self.assertEqual(data.delete_by_id.call_count, 1)
def test_delete_empty_ids_is_noop(self):
store, data = self._store()
ret = store.delete_vectors([])
self.assertEqual(ret, {"delete_count": 0})
data.delete_by_id.assert_not_called()
def test_delete_without_collection_raises(self):
store = WeaviateStore()
with self.assertRaises(ProcessingError):
store.delete_vectors(["a"])
def test_delete_backend_error_raises_processing_error(self):
store, _ = self._store(error=RuntimeError("connection reset"))
with self.assertRaises(ProcessingError):
store.delete_vectors(["a"])
class WeaviateErasureIntegrationTest(TestCase):
"""ErasureCoordinator reaches the real WeaviateStore.delete_vectors path."""
def setUp(self):
self._patch = patch(
"semantica.vector_store.weaviate_store.WEAVIATE_AVAILABLE", True
)
self._patch.start()
def tearDown(self):
self._patch.stop()
def _bind_weaviate_as_vector_store(self):
vs = VectorStore(backend="weaviate", config={"dimension": 3})
weaviate = WeaviateStore()
data = MagicMock()
coll = MagicMock()
coll.data = data
weaviate.collection = coll
vs._backend_store = weaviate
return vs, data
def test_erasure_reports_erased_when_delete_runs(self):
vs, data = self._bind_weaviate_as_vector_store()
coord = ErasureCoordinator(vector_store=vs)
receipt = coord.erase_entity("customer-4471")
data.delete_by_id.assert_called()
self.assertEqual(receipt.stores["vectors"]["status"], STATUS_ERASED)
def test_erasure_reports_erased_when_nothing_was_found(self):
"""delete_by_id returns False (404) for an id that is not in the store.
For erasure that still means the goal is met: nothing remains under
that id. The receipt keeps the honest zero count in backend_result
instead of raising a false failed status.
"""
vs, data = self._bind_weaviate_as_vector_store()
data.delete_by_id.return_value = False
coord = ErasureCoordinator(vector_store=vs)
receipt = coord.erase_entity("customer-4471")
self.assertEqual(receipt.stores["vectors"]["status"], STATUS_ERASED)
self.assertEqual(
receipt.stores["vectors"]["backend_result"], {"delete_count": 0}
)
def test_erasure_backend_name_is_weaviate(self):
vs, _ = self._bind_weaviate_as_vector_store()
coord = ErasureCoordinator(vector_store=vs)
receipt = coord.erase_entity("customer-4471")
self.assertEqual(receipt.stores["vectors"]["backend"], "weaviate")
def test_facade_delete_vectors_forwards_to_weaviate(self):
vs, data = self._bind_weaviate_as_vector_store()
def _fake(uuid):
return uuid != "missing"
data.delete_by_id.side_effect = _fake
ret = vs.delete_vectors(["present", "missing"])
self.assertEqual(data.delete_by_id.call_count, 2)
self.assertEqual(ret, {"delete_count": 1})