# Usage This is a hands-on guide to `lightrag-agensgraph`. It assumes you have completed [Installation](installation.md) — a running AgensGraph with the `pgvector` extension, and the `AGENSGRAPH_*` connection variables set. You use LightRAG exactly as normal; you just name the AgensGraph storages. Importing `lightrag_agensgraph` registers all four. **The storages read the `AGENSGRAPH_*` environment variables**, so set them before you create `LightRAG`. ## Quickstart Import the package and tell the storages how to reach AgensGraph: ```python import os import asyncio import lightrag_agensgraph # registers the four AgensGraph storages from lightrag import LightRAG, QueryParam from lightrag.kg.shared_storage import initialize_pipeline_status from lightrag.llm.openai import gpt_4o_mini_complete, openai_embed os.environ["AGENSGRAPH_DB"] = "lightrag" os.environ["AGENSGRAPH_USER"] = "postgres" os.environ["AGENSGRAPH_PASSWORD"] = "postgres" # must be set; may be empty under trust auth os.environ["AGENSGRAPH_HOST"] = "localhost" os.environ["AGENSGRAPH_PORT"] = "5432" os.environ["OPENAI_API_KEY"] = "sk-..." ``` Then build LightRAG on the four storages, ingest a document, and query it: ```python async def main(): rag = LightRAG( working_dir="./rag_storage", # local working files; data lives in AgensGraph llm_model_func=gpt_4o_mini_complete, embedding_func=openai_embed, # text-embedding-3-small, 1536-d graph_storage="AgensgraphStorage", vector_storage="AgensgraphVectorStorage", kv_storage="AgensgraphKVStorage", doc_status_storage="AgensgraphDocStatusStorage", ) await rag.initialize_storages() # creates the graph and the tables on first run await initialize_pipeline_status() await rag.ainsert("Marie Curie discovered radium and won two Nobel Prizes.") print(await rag.aquery("What did Marie Curie discover?", param=QueryParam(mode="mix"))) await rag.finalize_storages() asyncio.run(main()) ``` The four `*_storage` arguments are independent — set only the ones you want on AgensGraph and leave the rest at LightRAG's defaults. Any LightRAG `llm_model_func` and `embedding_func` works; the OpenAI helpers are only the quickest start. ## Retrieval modes LightRAG's strength is dual-level retrieval. Ask the same question through every mode to see the difference: ```python question = "Tell me about Marie Curie and the people, places, and events around her." for mode in ("naive", "local", "global", "hybrid", "mix"): answer = await rag.aquery(question, param=QueryParam(mode=mode)) print(f"\n[{mode}]\n{answer}") ``` | Mode | What it retrieves | | ---- | ----------------- | | `naive` | Vector search over chunks only (no graph). | | `local` | Entity-centric: specific entities and their facts. | | `global` | Relationship-centric: cross-document themes. | | `hybrid` | `local` + `global` merged. | | `mix` | Graph retrieval **and** chunks together — the default, most thorough. | To see *what* a mode retrieved (the dual-level signal — entities, relationships, chunks, and the high-/low-level keywords), use `aquery_data`; to get the retrieved context without an LLM answer, pass `only_need_context=True`: ```python result = await rag.aquery_data(question, param=QueryParam(mode="mix")) data = result["data"] print(len(data["entities"]), "entities,", len(data["relationships"]), "relationships") ctx = await rag.aquery(question, param=QueryParam(mode="mix", only_need_context=True)) ``` > If you haven't configured a rerank model, pass `enable_rerank=False` in `QueryParam` to > silence a no-op rerank warning. ## Incremental ingestion and document status New documents extend the same graph; entities recurring across documents merge into one node. Tag each ingestion wave with a `track_id` and give documents stable `ids` so a re-ingested document is not extracted again. The doc-status pipeline tracks every document. ```python from lightrag.base import DocStatus await rag.ainsert( ["Acme Corp opened a plant in Phoenix. CEO Jane Roe announced 500 jobs."], ids=["news-1"], file_paths=["url-1"], track_id="wave-1", ) counts = await rag.doc_status.get_all_status_counts() print("processed:", counts.get("processed"), "failed:", counts.get("failed")) processed = await rag.doc_status.get_docs_by_status(DocStatus.PROCESSED) rows, total = await rag.doc_status.get_docs_paginated( page=1, page_size=10, sort_field="updated_at", sort_direction="desc", ) ``` > Duplicate documents (by filename/content hash) are recorded as `failed` rather than > re-extracted — so a non-zero `failed` count during bulk ingestion is the dedup pipeline > working, not an error. ## Loading many documents at once The vector storage keeps the records LightRAG hands it while a document is merged, then embeds them in batches of `embedding_batch_num` and writes up to 500 rows per statement, the vectors in binary. Inserting into an HNSW index still costs far more than building the index afterwards. For a large one-off load, wrap the work in `bulk_ingest()` on each vector store: the index is dropped for the duration and rebuilt at the end with `maintenance_work_mem` raised (`1GB` by default). To change it, set the attribute on the store before the load, for example `rag.chunks_vdb.maintenance_work_mem = "4GB"`. ```python documents = [ "Acme Corp opened a plant in Phoenix.", "Jane Roe is the CEO of Acme Corp.", ] async with rag.entities_vdb.bulk_ingest(), rag.relationships_vdb.bulk_ingest(), rag.chunks_vdb.bulk_ingest(): await rag.ainsert(documents) ``` ## Exploring the knowledge graph The extracted graph is a first-class property graph. Reach it through `rag.chunk_entity_relation_graph` (the `AgensgraphStorage` instance): ```python g = rag.chunk_entity_relation_graph labels = await g.get_all_labels() # every entity name popular = await g.get_popular_labels(limit=12) # most-connected entities print(await g.search_labels("Curie", limit=10)) # case-insensitive substring search for name in popular: print(name, "degree:", await g.node_degree(name)) # Export an ego-network (bounded BFS) around a hub entity. kg = await g.get_knowledge_graph(popular[0], max_depth=2, max_nodes=40) print(f"subgraph: {len(kg.nodes)} nodes, {len(kg.edges)} edges") ``` Other available methods include `get_node`, `has_node`, `has_edge`, `get_edge`, `get_node_edges`, and `node_degrees_batch`. LightRAG treats a relation as undirected, so the store keeps each relation once per pair of entities, and `has_edge("A", "B")` and `has_edge("B", "A")` find the same edge. Edge reads are answered from the edge table by graph id; `has_edge` and `get_edge` are one index probe on the pair, so they cost the same on an entity with hundreds of relations as on one with two. ## Curating the graph A LightRAG KG is maintainable, not write-once. These LightRAG core methods update both the graph and the vector store: ```python # Merge duplicate entities (fold sources into a target). await rag.amerge_entities(source_entities=["Open AI"], target_entity="OpenAI") # Edit an entity (re-embeds it in the vector DB). await rag.aedit_entity("OpenAI", {"description": "OpenAI (verified).", "entity_type": "organization"}) # Edit a relation. await rag.aedit_relation("OpenAI", "Microsoft", {"description": "partnership", "weight": 9.0}) # Delete an entity (and purge its vectors), or a whole document. await rag.adelete_by_entity("Some Entity") await rag.adelete_by_doc_id("doc-0") ``` `amerge_entities` accepts a `merge_strategy` dict (deprecated since lightrag-hku 1.5.0) that maps a field to `concatenate`, `keep_first`, `keep_last`, or `join_unique` (for example `{"description": "concatenate", "entity_type": "keep_first"}`). ## Multi-tenancy with workspaces A `workspace` partitions an entire LightRAG knowledge base — all four stores — inside one database. The graph store gives each workspace a graph of its own (`_chunk_entity_relation`, with the workspace lower-cased, other characters turned into `_`, a `w_` prefix if it starts with a digit or `pg_`, and the name cut to 63 characters), and the other three stores keep a `workspace` column, so tenants never see each other's data. Pass it to each `LightRAG`: ```python def build(workspace: str) -> LightRAG: return LightRAG( working_dir=f"./rag_storage/{workspace}", workspace=workspace, llm_model_func=gpt_4o_mini_complete, embedding_func=openai_embed, graph_storage="AgensgraphStorage", vector_storage="AgensgraphVectorStorage", kv_storage="AgensgraphKVStorage", doc_status_storage="AgensgraphDocStatusStorage", ) rag_a, rag_b = build("tenant_a"), build("tenant_b") for tenant in (rag_a, rag_b): await tenant.initialize_storages() print(rag_a.chunk_entity_relation_graph.graph_name) # tenant_a_chunk_entity_relation print(rag_b.chunk_entity_relation_graph.graph_name) # tenant_b_chunk_entity_relation # ingest into and query each one independently for tenant in (rag_a, rag_b): await tenant.finalize_storages() ``` An empty workspace, the default, uses the graph `chunk_entity_relation`, so a single-tenant setup needs nothing. `AGENSGRAPH_WORKSPACE` also sets the workspace, and when it is set it takes precedence over the `workspace` you pass to `LightRAG`, for every store. Leave it unset when one process serves several tenants. ## Run the demos The repository ships a runnable [demo suite](https://github.com/skaiworldwide-oss/agensgraph-ai/tree/main/lightrag/examples/demos) on real public data (Wikipedia, CC-News), each with a pre-executed notebook. Run from the `lightrag/` directory; each demo picks its own database. | Demo | What it shows | Scripts | | ---- | ------------- | ------- | | `01_kg_modes` | Build a KG; compare all five retrieval modes | `build.py` → `ask.py` | | `02_incremental` | Incremental waves, doc-status, entity merging | `ingest.py` | | `03_kg_explore` | Top entities, search, subgraph export, multi-hop | `explore.py` | | `04_curation` | Merge / edit / delete entities, relations, docs | `curate.py` | | `05_workspace` | Two isolated tenants in one database | `tenants.py` | Setup: from `lightrag/`, `uv venv .venv --python 3.13` then `uv pip install -e . -r examples/demos/requirements-demos.txt`, and copy `examples/demos/.env.example` to `examples/demos/.env` (the only `.env` the demos load) and fill in your `OPENAI_API_KEY`. The template points at `localhost:55432`; change `AGENSGRAPH_PORT` if your server listens elsewhere. Don't set `AGENSGRAPH_DB`; each demo chooses its own. Run `01_kg_modes` before `03_kg_explore`, which reads its graph. LightRAG calls the LLM on every chunk at insert, so start with a small `*_LIMIT` and scale up once a demo runs end to end.