Graph Database Integration — Client Developer Documentation
Introduction
The Graph Database add-on gives agents a unified, pluggable way to work with graphs for knowledge, provenance, and workflow linkage. It supports:
- Remote graph DBs (e.g., JanusGraph via Gremlin, ArangoDB via AQL)
- Local in-memory graph for tests/transient runs
- A common abstract interface for custom backends (e.g., Neo4j)
Agents declare graph capabilities via an Add-On entry in the Subject Specification (similar to how MemoryItem
declares memories).
Typical workflow:
- Declare an AddonItem with
addon_type="graph_db"
and your chosen backend. - Initialize a graph client from
agent_sdk.graphs
based on that backend. - Write vertices/edges, query, and manage graph data.
- Optionally swap backends (e.g., in-memory for local tests → JanusGraph in prod) without code changes.
Registering Graph DB in Subject Specification
Graph DBs are registered using AddonItem
.
Data Class Reference
from dataclasses import dataclass, field
from typing import Dict, Any
@dataclass
class AddonItem:
addon_id: str
addon_type: str
addon_backend: str
addon_config: Dict[str, Any] = field(default_factory=dict)
For graphs: set
addon_type="graph_db"
.addon_backend
can be"memory"
,"janusgraph"
,"arangodb"
,"custom"
(e.g., your Neo4j client), etc.
Minimal Required Configuration
{
"addon_id": "graphs-primary",
"addon_type": "graph_db",
"addon_backend": "janusgraph",
"addon_config": {
"host": "janusgraph.default.svc.cluster.local",
"port": 8182,
"options": {}
}
}
Field Descriptions
Field | Location | Type | Description |
---|---|---|---|
addon_id | root | str |
Unique identifier for this add-on. |
addon_type | root | str |
Must be "graph_db" to enable the graphs module. |
addon_backend | root | str |
"memory" | "janusgraph" | "arangodb" | "custom" (your own implementation). |
addon_config.host / port | config | str /int |
Remote endpoint for JanusGraph (Gremlin) or ArangoDB (HTTP). |
addon_config.db_name | config | str |
(ArangoDB) Database name. |
addon_config.username/password | config | str |
(ArangoDB) Credentials if required. |
addon_config.options | config | dict |
Extra backend-specific options (TLS, timeouts, namespaces, etc.). |
Import and Setup
Choosing a backend at runtime
from agent_sdk.graphs.db import JanusGraphDBClient, ArangoDBClient
from agent_sdk.graphs.local import InMemoryGraphDB
def build_graph_client(addon: dict):
backend = addon["addon_backend"]
cfg = addon.get("addon_config", {})
if backend == "janusgraph":
return JanusGraphDBClient(host=cfg.get("host", "localhost"),
port=cfg.get("port", 8182))
elif backend == "arangodb":
return ArangoDBClient(host=cfg.get("host", "localhost"),
port=cfg.get("port", 8529),
username=cfg.get("username", "root"),
password=cfg.get("password", ""),
db_name=cfg.get("db_name", "graph_db"))
elif backend == "memory":
return InMemoryGraphDB(directed=cfg.get("directed", True))
else:
# e.g., your custom client import/constructor
raise NotImplementedError(f"Unsupported backend: {backend}")
Usage Guide
A. JanusGraph (Gremlin)
Import
from agent_sdk.graphs.db import JanusGraphDBClient
Initialize
client = JanusGraphDBClient(host="localhost", port=8182)
Common operations
v = client.add_vertex("Person", {"name": "Alice", "age": 30})
u = client.add_vertex("Person", {"name": "Bob", "age": 31})
e = client.add_edge(v["id"], u["id"], "KNOWS", {"since": 2024})
read_v = client.get_vertex_by_id(v["id"])
read_e = client.get_edge_by_id(e["id"])
client.delete_edge(e["id"])
client.delete_vertex(v["id"])
client.close()
Custom Gremlin
client.execute_query("g.V().has('Person','name', name)", bindings={"name": "Alice"})
B. ArangoDB (AQL)
Import
from agent_sdk.graphs.db import ArangoDBClient
Initialize
client = ArangoDBClient(
host="localhost", port=8529,
username="root", password="openSesame",
db_name="graph_db"
)
Common operations
client.add_document("persons", {"_key": "alice", "name": "Alice"})
doc = client.get_document("persons", "alice")
client.update_document("persons", "alice", {"age": 30})
client.delete_document("persons", "alice")
client.close()
Custom AQL
client.execute_query(
"FOR p IN persons FILTER p.name == @name RETURN p",
bind_vars={"name": "Alice"}
)
C. Local In-Memory Graph
Import & Init
from agent_sdk.graphs.local import InMemoryGraphDB
graph = InMemoryGraphDB(directed=True)
Common operations
graph.add_vertex("alice")
graph.add_vertex("bob")
graph.add_edge("alice", "bob", relation="KNOWS", since=2023)
graph.find_edges_between("alice", "bob")
graph.find_edges_by_property(relation="KNOWS")
graph.dfs("alice")
graph.find_path("alice", "bob")
graph.remove_edge("alice", "bob")
graph.remove_vertex("alice")
Custom Backends
All custom backends must implement the abstract interface:
from abc import ABC, abstractmethod
class GraphDBInterface(ABC):
@abstractmethod
def add_vertex(self, *args, **kwargs): ...
@abstractmethod
def add_edge(self, *args, **kwargs): ...
@abstractmethod
def get_vertex_by_id(self, *args, **kwargs): ...
@abstractmethod
def get_edge_by_id(self, *args, **kwargs): ...
@abstractmethod
def delete_vertex(self, *args, **kwargs): ...
@abstractmethod
def delete_edge(self, *args, **kwargs): ...
@abstractmethod
def execute_query(self, *args, **kwargs): ...
@abstractmethod
def close(self, *args, **kwargs): ...
Example: Neo4j port
Install
pip install neo4j
Implement
from neo4j import GraphDatabase
from agent_sdk.graphs.abs import GraphDBInterface
class Neo4jDBClient(GraphDBInterface):
def __init__(self, uri, user, password):
self.driver = GraphDatabase.driver(uri, auth=(user, password))
def close(self):
self.driver.close()
def _run(self, query, parameters=None):
with self.driver.session() as session:
result = session.run(query, parameters or {})
return [record.data() for record in result]
def add_vertex(self, label, properties):
props = ', '.join(f"{k}: ${k}" for k in properties)
return self._run(f"CREATE (n:{label} {{{props}}}) RETURN n", properties)
def add_edge(self, out_v, in_v, label, properties=None):
props = ', '.join(f"{k}: ${k}" for k in (properties or {}))
q = ("MATCH (a), (b) WHERE id(a)=$out_id AND id(b)=$in_id "
f"CREATE (a)-[r:{label} {{{props}}}]->(b) RETURN r")
return self._run(q, {**(properties or {}), "out_id": int(out_v), "in_id": int(in_v)})
def get_vertex_by_id(self, vertex_id):
return self._run("MATCH (n) WHERE id(n)=$id RETURN n", {"id": int(vertex_id)})
def get_edge_by_id(self, edge_id):
return self._run("MATCH ()-[r]->() WHERE id(r)=$id RETURN r", {"id": int(edge_id)})
def delete_vertex(self, vertex_id):
return self._run("MATCH (n) WHERE id(n)=$id DETACH DELETE n", {"id": int(vertex_id)})
def delete_edge(self, edge_id):
return self._run("MATCH ()-[r]->() WHERE id(r)=$id DELETE r", {"id": int(edge_id)})
def execute_query(self, query, bindings=None):
return self._run(query, bindings)
Use
neo = Neo4jDBClient(uri="bolt://localhost:7687", user="neo4j", password="password")
neo.add_vertex("Person", {"name": "Neo"})
neo.close()
Best-Practice Patterns
- Dev/CI: use
addon_backend="memory"
for fast tests → swap to remote in staging/prod. - Schema discipline: standardize labels/collections and edge predicates early.
- Observability: wrap
execute_query
with timing/logging; add retry/backoff for remotes. - Security: prefer TLS (Gremlin WS / HTTPS) and secrets management for credentials.
- Migration: encapsulate backend choice behind your own factory (see Import and Setup).
Quick Subject Spec Example (two add-ons)
{
"subject_id": "agent-graph-user",
"subject_type": "agent",
"addons": [
{
"addon_id": "graphs-dev",
"addon_type": "graph_db",
"addon_backend": "memory",
"addon_config": { "directed": true }
},
{
"addon_id": "graphs-prod",
"addon_type": "graph_db",
"addon_backend": "janusgraph",
"addon_config": {
"host": "janusgraph.default.svc.cluster.local",
"port": 8182,
"options": { "tls": false }
}
}
]
}