Skip to main content
A minimal guide to how Cognee talks to a graph database without your code ever depending on which one is actually configured. You’ll create two nodes and an edge, read them back, and clean up — using only interface methods, on an isolated local graph.

Before You Start

  • This is a low-level infrastructure exercise, not a replacement for cognee.remember() or Cognee pipelines — direct graph writes like this skip vector indexing, relational records, dataset provenance, and normal access-control behavior.
  • This guide defines a custom Person(DataPoint) model — see DataPoints for the concept, or Custom Data Models and Custom Graph Model to go deeper.

Code in Action

What Just Happened

Step 1: Configure an Isolated Graph and Get the Engine

graph_file_path points Ladybug at a throwaway directory so this exercise never touches your default Cognee graph. graph_database_subprocess_enabled=False keeps Ladybug running inside your Python process instead of as a separate background process — simpler for a short script like this. get_graph_engine() is async and returns whatever adapter the configuration selected — here, a Ladybug adapter — typed as GraphDBInterface. Everything from this point on is written against that interface, not against Ladybug specifically.

Step 2: Define a Minimal Node Model

Person inherits from DataPoint, Cognee’s base model for graph nodes. Declaring "identity_fields": ["name"] gives each Person a deterministic id based on its name, so Person(name="Alice") always resolves to the same node id instead of a random one.

Step 3: Create Nodes and an Edge

add_nodes(nodes) takes a list of DataPoint instances and writes them in a single batch. add_edge(source_id, target_id, relationship_name, edge_properties) creates one directed edge — here, Alice -[knows]-> Bob with a small property dictionary. Both take plain string ids, so DataPoint.id (a UUID) is converted with str(...) before being passed in.

Step 4: Read Data Back

get_node(node_id) returns a single node’s properties as a dictionary, or None if it does not exist. get_neighbors(node_id) returns the properties of every node connected to the given node — here, Bob’s only neighbor is Alice. has_edge(source_id, target_id, relationship_name) checks whether a specific directed, labeled edge exists, returning a plain bool.

Step 5: Clean Up

delete_nodes(node_ids) removes the listed nodes and any edges attached to them. Running it in a finally block ensures the example nodes are removed even if an earlier step raises, leaving the isolated graph empty again.

One Interface, Many Databases

Cognee can store its graph in several different backends — Ladybug (the default local, file-based engine), Postgres, Neo4j, and others (see Graph Stores for how to configure each one). Application code that queries or writes to the graph should not need a different code path for each one. Cognee solves this with three pieces:
  • Configuration picks which provider is active (e.g. "ladybug" or "postgres").
  • get_graph_engine() is a factory function. It reads the configuration and returns an object implementing the interface — you never construct an adapter yourself.
  • GraphDBInterface is an abstract base class that declares the methods every adapter must provide (add_node, get_node, has_edge, and so on).
  • Concrete adapters (LadybugAdapter, PostgresAdapter, …) implement that interface against a specific database.
Because every adapter satisfies the same interface, code written against get_graph_engine() works unchanged no matter which provider is configured.
Do not import or instantiate LadybugAdapter, PostgresAdapter, or any other adapter directly. Always go through get_graph_engine() — that is what keeps your code portable across providers.

Comparing Adapters

LadybugAdapter and PostgresAdapter both implement add_nodes, add_edge, get_node, get_neighbors, and has_edge from GraphDBInterface — but the two implementations look nothing alike internally. Ladybug builds parameterized Cypher-style statements against an embedded Kuzu database; Postgres issues SQL against relational tables that model nodes and edges. Neither difference is visible to code written against the interface, which is the point: swapping graph_database_provider from "ladybug" to "postgres" (with matching connection settings) does not require changing any of the code above.
Every adapter also has a raw query() method for running commands written directly in that database’s own language — Cypher for Ladybug/Neo4j, SQL for Postgres. Using it ties your code to one specific database, which is exactly what this guide is trying to avoid. That’s why it’s left out here — everything above uses only the shared GraphDBInterface methods, which work the same way no matter which database is configured.

DataPoints

Learn more about DataPoint, the base model Person builds on

BaseRetriever Guide

See the same abstract-contract pattern applied to retrievers