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.graphsbased 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_backendcan 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_querywith 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 }
}
}
]
}