Compare commits

...
Author SHA1 Message Date
KaifAhmad1 68f4aa4aef docs: simplify custom.css to a static, professional style
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:55:46 +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
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
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 321 additions and 285 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>
+23 -15
View File
@@ -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(
@@ -144,23 +144,31 @@ 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']})")
```
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>
+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})