# Usage This is a hands-on guide to `cognee-agensgraph`. It assumes you have completed [Installation](installation.md) — a running AgensGraph 2.17 or later with `pgvector` available, and the model environment variables set **before** `import cognee` (cognee reads them at import). cognee is configured process-globally: you point its **graph** store and **vector** store at an AgensGraph database, then drive the pipeline `add` → `cognify` → `search`. `SearchType` lives at `cognee.modules.search.types.SearchType`. ## Quickstart Point both stores at one AgensGraph database. The database must exist; the adapters create the `cognee` graph in it, and the vector adapter enables `vector` if the role may. ```python import asyncio import cognee import cognee_agensgraph # registers the AgensGraph adapters from cognee.modules.search.types import SearchType DSN = "postgresql://localhost:55432/cognee" # One AgensGraph database backs BOTH the knowledge graph and the embeddings. cognee.config.set_graph_db_config({"graph_database_url": DSN, "graph_database_provider": "agensgraph"}) cognee.config.set_vector_db_config({"vector_db_url": DSN, "vector_db_provider": "agensgraph"}) ``` Then build a memory and ask it a question. `cognify` and `search` call the models you configured in [Installation](installation.md): ```python async def main(): await cognee.add( [ "AgensGraph is a multi-model graph database built on PostgreSQL.", "cognee builds a knowledge graph and embeddings from your data.", ], dataset_name="quickstart", ) await cognee.cognify(["quickstart"]) # chunk → LLM-extract → summarize → embed answer = await cognee.search( query_text="What is AgensGraph and how does cognee use it?", query_type=SearchType.GRAPH_COMPLETION, ) print(answer) # Close the connection pool before the event loop ends; otherwise the program can # hang at exit while asyncio cancels the pool's tasks. from cognee.infrastructure.databases.graph import get_graph_engine await (await get_graph_engine()).finalize() asyncio.run(main()) ``` ## Search modes `cognee.search` answers the same memory in many ways via `query_type`. Build once, then compare modes: ```python question = "What is anarchism, and what ideas, people, and movements is it connected to?" for mode in ( SearchType.GRAPH_COMPLETION, # graph-grounded answer (entities + relations + chunks) SearchType.GRAPH_SUMMARY_COMPLETION, # the graph answer, condensed SearchType.GRAPH_COMPLETION_COT, # graph answer with chain-of-thought SearchType.GRAPH_COMPLETION_CONTEXT_EXTENSION, # graph answer + extra retrieved context SearchType.RAG_COMPLETION, # plain chunk RAG, no graph (baseline) SearchType.INSIGHTS, # entity→relation→entity triplets (no LLM) SearchType.CHUNKS, # raw matching text chunks (vector search) SearchType.SUMMARIES, # pre-computed per-document summaries SearchType.NATURAL_LANGUAGE, # question → generated Cypher → graph rows ): try: print(mode.value, "->", await cognee.search(query_text=question, query_type=mode)) except IndexError: # cognee 0.2 raises this when a search matches nothing (see below) print(mode.value, "->", []) ``` For `NATURAL_LANGUAGE` the model is not given the relationship names or any values: the node labels and properties come from a fixed list in cognee's prompt, and only the edge property keys from the graph reach the prompt. It guesses names and values, so an open question often gets no rows. A question that names a node and asks what it is connected to gives it what it needs. `SearchType.CYPHER` is different — you pass a Cypher query (not a question) and get graph rows back: ```python rows = await cognee.search( query_text="MATCH (n:Entity) RETURN n.name AS name LIMIT 5", query_type=SearchType.CYPHER, ) ``` Each cognee class (`Entity`, `EntityType`, `DocumentChunk`, `TextDocument`, `TextSummary`, ...) is a vertex label of its own, and all of them are children of `__node__`. So `MATCH (n:Entity)` reads the entities and `MATCH (n:__Node__)` reads every node. Label names are lower case. Cypher folds an unquoted label to lower case, so `Entity`, `entity` and `"entity"` all find it; a quoted `"Entity"` or `"__Node__"` does not. The class name as written stays in the `type` property. A node whose id already exists on another label stays on that label, so `MATCH (n:Entity)` can return a node whose `type` is `EntityType`. Every relationship is an edge label named after it (`contains`, `is_a`, `is_part_of`, ...). `GRAPH_*` and `RAG_COMPLETION` modes return an answer (often a string); `INSIGHTS` returns `(source, edge, target)` triplets; `CHUNKS` returns matching text chunks. ## Typed extraction with an ontology Pass a domain ontology (OWL/Turtle `.ttl`) to `cognify` via `ontology_file_path`. Ontology classes become `EntityType` nodes, and extracted entities that match an ontology individual are flagged `ontology_valid` — so the graph follows your vocabulary instead of drifting. ```python import pathlib ONTOLOGY = str(pathlib.Path("ontology.ttl").resolve()) await cognee.add(["...your domain documents..."], dataset_name="typed") await cognee.cognify(["typed"], ontology_file_path=ONTOLOGY) # Inspect alignment from cognee.infrastructure.databases.graph import get_graph_engine nodes, _ = await (await get_graph_engine()).get_graph_data() vocabulary = {p.get("name") for _, p in nodes if p.get("type") == "EntityType"} aligned = [p.get("name") for _, p in nodes if p.get("ontology_valid") and p.get("type") == "Entity"] print("vocabulary:", vocabulary, "\naligned entities:", aligned[:15]) ``` In the ontology file, OWL classes (`:Person a owl:Class .`) define the vocabulary and individuals (`:aristotle a :Person .`) are known entities — an individual's URI fragment must match the extracted entity name. ## Multi-dataset memory cognee is a memory layer: data lives in named **datasets**, built incrementally in one database, each optionally tagged with a `node_set`. A single search draws on the whole unified memory. ```python from cognee.infrastructure.databases.graph import get_graph_engine from cognee.modules.engine.models.node_set import NodeSet # Build two datasets incrementally — the graph grows, no rebuild. await cognee.add(encyclopedia_docs, dataset_name="encyclopedia", node_set=["reference"]) await cognee.cognify(["encyclopedia"]) await cognee.add(news_docs, dataset_name="news", node_set=["current_events"]) await cognee.cognify(["news"]) # Pull back one node_set's slice of the unified memory. g = await get_graph_engine() for tag in ("reference", "current_events"): ns_nodes, ns_edges = await g.get_nodeset_subgraph(NodeSet, [tag]) print(tag, len(ns_nodes), "nodes") # One query retrieves across BOTH datasets. await cognee.search(query_text="notable people and recent events", query_type=SearchType.CHUNKS) ``` > **Note.** The `datasets=[...]` argument to `cognee.search` is a read-*permission* scope, not > a retrieval filter — retrieval runs over the whole unified memory regardless. For hard > per-tenant isolation, use a separate database (DSN) per tenant. ## Loading a large corpus Inserting into an HNSW index is the main cost of a vector write. `bulk_ingest()` on the vector adapter pays off on a first load: collections created inside the block get no HNSW index until the block exits, then each is built once. Searches inside the block on those collections read every row. With cognee 0.2, a collection that already exists keeps its index during the block, because cognee's indexing step creates the collection first; rows then go into the index as usual. ```python from cognee.infrastructure.databases.vector import get_vector_engine vector_engine = get_vector_engine() # the AgensgraphVectorAdapter configured above # A first load into a fresh database, so the collections are created inside the block. await cognee.add(corpus_docs, dataset_name="corpus") async with vector_engine.bulk_ingest(): await cognee.cognify(["corpus"]) ``` ## Building a code graph cognee has a dedicated pipeline that parses a Python repository into a graph of modules/classes/functions, then searches it with `SearchType.CODE`. Ingest uses `run_code_graph_pipeline` (an async generator), not `add`/`cognify`. The pipeline clears everything cognee has stored first (`prune_data()` and `prune_system(metadata=True)`), so run it against its own database. It parses Python with tree-sitter: install `tree-sitter` and `tree-sitter-python` (both are in `examples/demos/requirements-demos.txt`). ```python from cognee.api.v1.cognify.code_graph_pipeline import run_code_graph_pipeline from cognee.modules.search.types import SearchType target = "/path/to/a/python/package" async for status in run_code_graph_pipeline(target, include_docs=False): if "Error" in str(getattr(status, "status", "")): raise SystemExit(getattr(status, "payload", status)) results = await cognee.search( query_text="How does the library send an HTTP request?", query_type=SearchType.CODE, ) for r in results if isinstance(results, (list, tuple)) else [results]: if isinstance(r, dict): print(r.get("name"), "->", (r.get("content") or "")[:110]) ``` ## Inspecting and visualizing the graph The memory is a real AgensGraph property graph. Reach it through `get_graph_engine()`, or render it to a standalone HTML file. ```python from cognee.infrastructure.databases.graph import get_graph_engine g = await get_graph_engine() # Ten keys: num_nodes, num_edges, mean_degree, edge_density, num_connected_components, # sizes_of_connected_components, and four optional ones (num_selfloops, diameter, # avg_shortest_path_length, avg_clustering) that read -1 unless include_optional=True. # With include_optional=True an optional value can be None, and above 5,000 nodes the # diameter and path length are estimated and an 11th key, approximate, is True. metrics = await g.get_graph_metrics(include_optional=False) nodes, edges = await g.get_graph_data() # everything disconnected = await g.get_disconnected_nodes() entities, ent_edges = await g.get_filtered_graph_data([{"type": ["Entity"]}]) # Raw AgensGraph Cypher — top entities by degree rows = await g.query(''' MATCH (n:Entity) OPTIONAL MATCH (n)-[r]-() RETURN n.id AS id, n.name AS name, count(r) AS degree ORDER BY degree DESC LIMIT 12 ''') # Traversal from the top entity, and an interactive HTML view if rows: neighbors = await g.get_neighbors(str(rows[0]["id"])) connections = await g.get_connections(rows[0]["id"]) await cognee.visualize_graph("graph.html") ``` ### `query()` is read-only by default `g.query(cypher)` is what `SearchType.CYPHER` and `SearchType.NATURAL_LANGUAGE` run — a statement written by a user or by a language model. It takes one statement at a time and runs it in a read-only transaction, so a statement that writes fails. To let it write: ```python g = await get_graph_engine() g.query_read_only = False # query(), CYPHER and NATURAL_LANGUAGE may now write ``` A read-only transaction does not stop a superuser from running a program on the database host. The default is `query_allow_server_programs = True`, unlike the LangChain, LlamaIndex and MCP paths that run generated Cypher, which refuse such a role by default. With it, a superuser role lets `query()`, and so `CYPHER` and `NATURAL_LANGUAGE`, run a program on the database host through `COPY … TO PROGRAM`, read-only or not. For anything but local development, connect as a role that is not a superuser and not a member of `pg_execute_server_program`, and set `g.query_allow_server_programs = False`; `query()` then refuses to run if the role can still do this (while `query_read_only` is on). `query()` also accepts Neo4j's edge shorthand — `(a)--(b)`, `(a)-->(b)`, `(a)<--(b)` — which language models write in most statements. In AgensGraph `--` starts a comment, so between two pattern nodes it is rewritten to `-[]-`, `-[]->` and `<-[]-`. Everything else is sent as written. ## Resetting a memory To clear what cognee has built (local data plus the graph, vector, and metadata stores): ```python await cognee.prune.prune_data() await cognee.prune.prune_system(metadata=True) ``` ## Run the demos The repository ships a runnable [demo suite](https://github.com/skaiworldwide-oss/agensgraph-ai/tree/main/cognee/examples/demos) on real public data, each with a pre-executed notebook. Each demo creates its own database, so they never collide. | Demo | What it shows | Scripts | | ---- | ------------- | ------- | | `01_search_modes` | Build a KG; query it ten ways (all modes + Cypher) | `build.py` → `ask.py` | | `02_typed` | Ontology-guided extraction (`ontology_file_path`) | `build.py` → `ask.py` | | `03_memory` | Multiple datasets with `node_set` tags | `build.py` → `ask.py` | | `04_code_graph` | Code graph over a repo + `SearchType.CODE` | `build.py` → `ask.py` | | `05_explore` | Inspect the KG: metrics, Cypher, HTML viz | `explore.py` | Run each script from the `cognee/` directory of a repository checkout, and run `01_search_modes` before `05_explore`, which reads its graph. Setup, with [`uv`](https://github.com/astral-sh/uv) and Python 3.11 to 3.13: ```bash uv venv .venv --python 3.13 uv pip install -e . -r examples/demos/requirements-demos.txt cp examples/demos/.env.example examples/demos/.env # then set OPENAI_API_KEY ``` The `*_LIMIT` knobs keep a first run small (`cognify` calls the LLM on every chunk); the `*_RESET` knobs (on by default) prune before each build. > **Empty results.** In cognee 0.2's core, a `search` that matches nothing can raise > `IndexError` (an upstream quirk in its history logging, not in this integration). If a query > may have no hits, wrap it in a `try/except IndexError` returning `[]`.