mirror of
https://github.com/semantica-agi/semantica.git
synced 2026-08-29 04:26:20 +00:00
17 KiB
17 KiB
title, description, icon
| title | description | icon |
|---|---|---|
| Graph Store Module | Unified interface for Neo4j, FalkorDB, Apache AGE, and Amazon Neptune graph databases. | server |
semantica.graph_store provides a single API for persisting and querying knowledge graphs in production graph databases. Swap backends with a one-line change — no application code changes needed.
Exported Classes
| Class | Role |
|---|---|
GraphStore |
Unified interface: create_node, create_relationship, query, get_neighbors, shortest_path |
QueryEngine |
Parameterized Cypher execution with result caching |
GraphAnalytics |
degree_centrality, connected_components, shortest_path, get_neighbors |
Neo4jStore |
Production workloads via Bolt — supports APOC and GDS plugins |
ApacheAgeStore |
PostgreSQL + AGE extension — no separate graph server needed |
AmazonNeptuneStore |
AWS Neptune — OpenCypher via Bolt protocol |
FalkorDBStore |
Redis-based — sub-millisecond latency for real-time applications |
What You Get
Unified interface across Neo4j, FalkorDB, Apache AGE, and Amazon Neptune. Parameterized Cypher construction and optional result caching. Degree centrality, connected components, shortest path, and neighbor traversal. Batched node and edge loading — faster than individual writes. Create indexes to optimize query performance. Find shortest paths between nodes and walk neighbors by depth.Getting Started
GraphStore wraps the backend of your choice behind a single API. Call connect() (or use it as a context manager) before running any queries:
from semantica.graph_store import GraphStore
store = GraphStore(
backend="neo4j",
uri="bolt://localhost:7687",
user="neo4j",
password="password",
)
store.connect()
# Create a node
node = store.create_node(
labels=["Person"],
properties={"name": "Alice", "role": "Engineer"},
)
print(node["id"]) # Neo4j internal integer ID
# Create a relationship
store.create_relationship(
start_node_id=node1_id,
end_node_id=node2_id,
rel_type="WORKS_FOR",
properties={"since": 2022},
)
# Execute a Cypher query
results = store.query(
"MATCH (p:Person)-[:WORKS_FOR]->(o:Organization) WHERE o.name = $org RETURN p",
parameters={"org": "Acme Corp"},
)
store.close()
Use as a context manager to close the connection automatically:
with GraphStore(backend="neo4j", uri="bolt://localhost:7687", user="neo4j", password="password") as store:
store.create_node(labels=["Person"], properties={"name": "Bob"})
Quick Start
```python from semantica.graph_store import GraphStorestore = GraphStore(
backend="neo4j",
uri="bolt://localhost:7687",
user="neo4j",
password="password",
)
store.connect()
```
GraphStore Methods
| Method | Returns | Description |
|---|---|---|
create_node(labels, properties) |
dict |
Create a single node, returns node dict with "id" |
create_nodes(nodes) |
List[dict] |
Batch-create nodes from list of {"labels", "properties"} dicts |
get_node(node_id) |
dict | None |
Retrieve a node by its backend ID |
get_nodes(labels, properties, limit) |
List[dict] |
Query nodes matching label/property criteria |
update_node(node_id, properties, merge) |
dict |
Update node properties; merge=True (default) merges, merge=False replaces |
delete_node(node_id, detach) |
bool |
Delete node; detach=True (default) removes relationships too |
create_relationship(start_node_id, end_node_id, rel_type, properties) |
dict |
Create a directed relationship |
get_relationships(node_id, rel_type, direction, limit) |
List[dict] |
Get relationships for a node |
delete_relationship(rel_id) |
bool |
Delete a relationship by ID |
execute_query(query, parameters) |
dict |
Execute raw Cypher, returns {"success", "records", "keys", "metadata"} |
query(query, parameters) |
List[dict] |
Execute Cypher and return records list directly |
create_index(label, property_name) |
bool |
Create an index for faster lookups |
get_neighbors(node_id, rel_type, direction, depth) |
List[dict] |
Get neighboring nodes |
shortest_path(start_node_id, end_node_id, rel_type, max_depth) |
dict | None |
Find shortest path between two nodes |
get_stats() |
dict |
Get graph statistics from the backend |
Backends
```bash pip install neo4j ``````python
from semantica.graph_store import GraphStore
store = GraphStore(
backend="neo4j",
uri="bolt://localhost:7687",
user="neo4j",
password="password",
database="neo4j", # optional — targets default database
)
store.connect()
```
Best for: production workloads, complex Cypher queries, Bloom visualization.
```python
store = GraphStore(
backend="falkordb",
host="localhost",
port=6379,
graph_name="semantica",
)
store.connect()
```
Best for: ultra-low latency queries over Redis protocol, edge deployments.
```python
store = GraphStore(
backend="age", # or "apache_age"
connection_string="host=localhost dbname=agedb user=postgres password=secret",
graph_name="semantica",
)
store.connect()
```
Best for: teams already running PostgreSQL who want graph queries without a separate service.
```python
store = GraphStore(
backend="neptune", # or "amazon_neptune"
endpoint="your-cluster.cluster-xxxx.us-east-1.neptune.amazonaws.com",
port=8182,
region="us-east-1",
iam_auth=True, # uses boto3 default credential chain
)
store.connect()
# OpenCypher queries via Bolt protocol
results = store.query(
"MATCH (p:Person)-[:WORKS_FOR]->(o:Organization) RETURN p, o"
)
```
Best for: managed AWS deployments. Neptune uses the Bolt protocol for OpenCypher queries — the same query API used for Neo4j.
| Backend | Query Language | Deployment | IAM Auth | Best For |
| ------- | -------------- | ---------- | -------- | -------- |
| Neo4j | Cypher | Self-hosted / Aura | No | Production, complex traversals, Bloom UI |
| FalkorDB | OpenCypher | Redis-based | No | Ultra-low latency, edge deployments |
| Apache AGE | OpenCypher | PostgreSQL extension | No | Teams already on Postgres |
| Amazon Neptune | OpenCypher | AWS managed | Yes | Cloud-native, managed, compliance |
Graph Operations
# Create a single node
node = store.create_node(
labels=["Organization"],
properties={"name": "Apple Inc.", "founded": 1976},
)
# Create a directed relationship (both node IDs required)
rel = store.create_relationship(
start_node_id=jobs_id,
end_node_id=node["id"],
rel_type="FOUNDED",
properties={"year": 1976},
)
# Batch-create nodes
store.create_nodes([
{"labels": ["Person"], "properties": {"name": "Steve Jobs"}},
{"labels": ["Organization"], "properties": {"name": "NeXT"}},
])
# Update node properties (merge=True merges, merge=False replaces)
store.update_node(node["id"], {"employees": 164000}, merge=True)
# Delete (detach=True also removes connected relationships)
store.delete_node(node["id"], detach=True)
store.delete_relationship(rel["id"])
# Get neighbors
neighbors = store.get_neighbors(
node["id"],
rel_type="HAS_EMPLOYEE",
direction="in", # "in" | "out" | "both"
depth=1,
)
# Shortest path — returns {"length", "nodes", "relationships"} or None
path = store.shortest_path(
start_node_id=jobs_id,
end_node_id=cook_id,
rel_type="WORKS_WITH",
max_depth=5,
)
if path:
print(f"Hops: {path['length']}")
QueryEngine
QueryEngine handles query execution and optional caching. Access it via store.query_engine:
from semantica.graph_store import GraphStore
store = GraphStore(backend="neo4j", uri="bolt://localhost:7687", user="neo4j", password="password")
store.connect()
# Get the query engine from the store
engine = store.query_engine
# Execute a parameterized query
result = engine.execute(
"MATCH (p:Person) WHERE p.department = $dept RETURN p",
parameters={"dept": "Engineering"},
)
# result is {"success": True, "records": [...], "keys": [...], "metadata": {...}}
# Execute with caching — repeated identical calls return cached result
result = engine.execute(
"MATCH (p:Person) RETURN count(p) as total",
use_cache=True,
)
# Clear cached results
engine.clear_cache()
# Toggle caching
engine.disable_cache()
engine.enable_cache()
QueryEngine Methods
| Method | Description |
|---|---|
execute(query, parameters, use_cache) |
Execute Cypher, optionally using in-process cache |
clear_cache() |
Flush all cached query results |
enable_cache() |
Turn on caching (on by default) |
disable_cache() |
Turn off caching |
GraphAnalytics
Access analytics via store._manager.analytics or construct directly with the backend store instance:
from semantica.graph_store import GraphStore, GraphAnalytics
store = GraphStore(backend="neo4j", uri="bolt://localhost:7687", user="neo4j", password="password")
store.connect()
# GraphAnalytics takes the backend store, not the GraphStore facade
analytics = GraphAnalytics(store._store_backend)
# Degree centrality — returns list of {"id", "degree"} dicts ordered by degree DESC
scores = analytics.degree_centrality(
labels=["Person"],
rel_type="KNOWS",
direction="both", # "in" | "out" | "both"
)
for entry in scores[:5]:
print(f"Node {entry['id']}: degree {entry['degree']}")
# Connected components (requires GDS for Neo4j, NetworkX for in-process)
components = analytics.connected_components(labels=["Person"])
# Shortest path — returns {"length", "nodes", "relationships"} or None
path = analytics.shortest_path(
start_node_id=alice_id,
end_node_id=charlie_id,
rel_type="KNOWS",
max_depth=4,
)
# Neighbor traversal
neighbors = analytics.get_neighbors(
node_id=alice_id,
rel_type="KNOWS",
direction="out",
depth=2,
)
GraphAnalytics Methods
| Method | Description |
|---|---|
degree_centrality(labels, rel_type, direction) |
Degree-based node importance — returns list of records ordered by degree |
connected_components(labels) |
Connected component assignment (requires GDS on Neo4j) |
shortest_path(start_node_id, end_node_id, rel_type, max_depth) |
Returns path dict or None |
get_neighbors(node_id, rel_type, direction, depth) |
Neighbor nodes up to depth hops |
Schema Management
# Index for fast label-property lookups — use property_name= not property=
store.create_index(label="Person", property_name="name")
store.create_index(label="Organization", property_name="id")
# Graph statistics
stats = store.get_stats()
Common Workflows
```python from semantica.graph_store import GraphStorestore = GraphStore(backend="neo4j", uri="bolt://localhost:7687", user="neo4j", password="password")
store.connect()
# Create indexes first for speed
store.create_index("Person", property_name="name")
store.create_index("Organization", property_name="name")
# Load entities as nodes
created = store.create_nodes([
{"labels": [e["type"]], "properties": {"name": e["text"], "id": e["id"]}}
for e in entities
])
# Map entity IDs to backend node IDs
id_map = {e["id"]: node["id"] for e, node in zip(entities, created)}
# Load relationships
for rel in relationships:
if rel["source_id"] in id_map and rel["target_id"] in id_map:
store.create_relationship(
start_node_id=id_map[rel["source_id"]],
end_node_id=id_map[rel["target_id"]],
rel_type=rel["type"],
)
store.close()
```