Skip to main content
A minimal guide to rendering your current knowledge graph to an interactive HTML file. Use it when you want to see what your memory actually contains — a bounded, readable subgraph by default, or the whole graph on demand.

Before You Start

  • Complete Quickstart to understand basic operations
  • Ensure you have LLM Providers and Embedding Providers configured
  • Read Core Concepts Overview for how Cognee builds knowledge graphs
  • No data is required up front — the script ingests its own sample passages, but it starts with cognee.forget(everything=True), which wipes all existing Cognee data; run it against a setup you can afford to reset

Code in Action

What Just Happened

Step 1: Create Your Knowledge Graph

This starts from a clean state, then uses remember() to ingest the passages into the graph_visualization_guide dataset and build the graph in one call. Every render below scopes itself to that same dataset.

Step 2: Render the Default Bounded Subgraph

A bare call no longer renders the whole graph: it seeds on well-connected nodes, expands their 2-hop neighborhood, and caps the result at 500 nodes. That keeps the HTML fast and readable no matter how large the graph grows. Degree seeding is deliberately approximate — on the built-in adapters the ranking runs inside the database over a bounded edge sample, so opening the view never reads the whole graph into memory (see the “Bounded subgraph by default” section below).

Step 3: Seed the View From a Query

Passing query seeds the subgraph from that query’s nearest vector hits instead, so the render shows the neighborhood the question actually lands in.

Step 4: Render the Whole Graph

full=True restores the legacy unbounded render of every node and edge. The script writes all three files side by side under .artifacts/graph_visualization/ so you can open them and compare the seeding modes.

What Graph Visualization Shows

  • Nodes (entities, types, chunks, summaries) with color coding
  • Edges with labels and weights; edge weights control line thickness, and tooltips show extra edge properties
  • Interactive features: drag nodes, zoom/pan, hover edges for details
  • Output is static, self-contained HTML — open it in any modern browser or share it as an artifact

Tabs

Every rendered HTML file opens with a tab bar of four views — Graph, Schema, Memory, and Semantic — all computed from the same graph payload: For what each view shows in detail — layout modes, the label budget, search, the Schema tab’s inspector and operations overlay, and the theme toggle — see Reading the Visualization.

Advanced Usage

visualize_graph() renders a bounded, relevant subgraph by default instead of the entire graph: it picks a small set of seed nodes, expands their k-hop neighborhood, and caps the result at max_nodes. This keeps renders fast and readable on large graphs. Pass full=True to restore the legacy whole-graph render.Seeds are resolved by priority — the first of these that produces nodes wins:
  1. seed_node_ids — explicit node ids you pass.
  2. recall_result — a recall() or search result whose graph provenance (used_graph_element_ids) seeds the subgraph, i.e. “show me the subgraph behind this answer”.
  3. query — a query string whose nearest vector hits (distance-ranked, nearest first) seed the subgraph.
  4. Well-connected nodes by approximate degree — the fallback so a bare visualize_graph() call still shows a representative view.
If none of these resolve any seeds, an empty graph is rendered.On the built-in adapters — Ladybug, Neo4j, Neptune, Postgres, and Turso — degree seeding (option 4) is ranked inside the database over a bounded sample of at most 200,000 edges, so a bare call never loads the whole graph into memory to pick ten ids. The sample is a physical prefix of the stored edges, not a random or recency-weighted one: seeds skew toward earlier-ingested data, and hubs created recently in a large graph can stay invisible. Isolated nodes are valid seeds too — when the edge sample is sparse or the graph has no edges at all, the seeds are topped up with node ids so the view is still non-empty. An adapter that does not implement the in-database ranking falls back to reading the whole graph and counting degree in Python — see Seed selection.
Degree seeding is approximate, not an exact ranking. Treat its result as “some nodes worth starting from”, and pass query, seed_node_ids, or recall_result when you need a specific part of the graph.
The new parameters are keyword-only, so existing positional calls keep working unchanged:
Nodes are always returned seeds first, then by hop distance, and edges survive only when both endpoints do, so no dangling edges remain. On the Postgres graph backend, the max_nodes cap is applied inside the database rather than after reading the whole neighborhood.Entities keep their semantic type. An entity’s type (Person, Repository, …) is only knowable from its is_a edge to an EntityType node, and a bounded read returns only the edges between its own members, so the EntityType node of an entity near the edge of the window is often not in the read. The read therefore asks the store for each entity’s is_a target instead of inferring it from the edges it happened to fetch, and every node in the payload carries the result as entity_type. Only Entity nodes are looked up — the lookup never starts from a type node, which would pull in every entity of that type. If the store cannot answer the lookup it is logged once and not retried for the rest of the read — the render still returns, with those entities typed only from the is_a edges the read itself contains.Over HTTP. GET /api/v1/visualize exposes the same controls as query params: full, query, seed_node_ids, neighborhood_depth, neighborhood_seed_top_k, and max_nodes (recall_result is Python-only). For example, GET /api/v1/visualize?dataset_id=<id>&full=true returns the whole-graph render. GET /api/v1/visualize/json accepts the same controls and returns the payload behind that render instead of HTML. For graphs too large to hold in one response, it can also stream the subgraph in chunks as server-sent events, which raises the max_nodes ceiling from 5000 to 20000.

Troubleshooting

If visualize_graph() logs No nodes found in the database (or the HTML opens empty) even though add() and cognify() ran without errors, the most common causes are:
  • Graph path mismatch. With the default Ladybug backend, the graph is stored on disk under <SYSTEM_ROOT_DIRECTORY>/databases/. By default, SYSTEM_ROOT_DIRECTORY is an absolute .cognee_system path under Cognee’s package root. In notebooks like Colab, it is safer to set explicit absolute paths before running add(), cognify(), and visualize_graph() so every step uses the same persisted location across cells and runtime changes:
  • cognify() produced no nodes. A run can finish “successfully” yet extract nothing — for example if no data was actually ingested, or graph extraction silently returned empty results (often a misconfigured or failing LLM/embedding provider). Don’t rely on the absence of an error; verify the graph was populated.
  • Data was pruned in between. Calling cognee.forget(everything=True) (or cognee.prune) after cognify() clears the graph, so a later visualize_graph() sees nothing.

Verify the graph was populated

Before visualizing, query the graph engine directly. get_graph_data() returns a (nodes, edges) tuple, and get_graph_metrics() reports the node/edge counts:
If len(nodes) is 0 here, the problem is upstream in add()/cognify() (or a path mismatch), not in visualization. A non-zero count from the same process that then reports No nodes found points to a path/config mismatch between steps.Read len(nodes) as the primary signal — len(edges) being 0 while len(nodes) is non-zero is a legitimate reading, not a visualization fault. It means entities were extracted but no relationships between them were, which is something to investigate upstream in extraction (the graph renders as unconnected nodes). On the Ladybug backend this used to be hidden: get_graph_data() padded an edgeless graph with one synthetic SELF edge per node, so len(edges) was never 0 while nodes existed, even though get_graph_metrics() — which counts edges in the store directly — correctly reported num_edges: 0. That padding is gone, so the two counts now agree — if you still see len(edges) equal to len(nodes) with every edge a SELF self-loop, you are on a release from before the fix.
Two companion projections summarize your memory without rendering every node — both run end-to-end without an LLM:
  • Schema Inventory — get_schema_inventory() summarizes the graph by semantic type: per-type counts, sample names, and relationship distribution.
  • Memory Provenance — visualize_memory_provenance() renders the ownership and data-flow story (Tenant → User → Agent → Dataset → file) from the relational database.

Full Examples

Additional examples about Cognee are available on our github.

Reading the Visualization

What each tab shows, and which one to reach for.

Core Concepts Overview

Understand how Cognee builds and stores knowledge graphs.

Visualization Payloads

The JSON payloads and HTTP endpoints behind the render.