LlamaIndex AgensGraph
This plugin integrates AgensGraph
with LlamaIndex, persisting graphs and vectors
directly in AgensGraph. It powers PropertyGraphIndex and VectorStoreIndex,
so you can store and query property graphs and embeddings in one database.
- Property Graph Store:
AgensPropertyGraphStore - Vector Store:
AgensgraphVectorStore - Connection pool:
AgensEngine(optional, shared across stores)
Requirements
- Python 3.11+
agensgraph-python2.0, installed with the package- AgensGraph 2.17 or later with the
vectorextension (for vector / HNSW search). Themetaextension is used for schema introspection when present, with a catalog fallback otherwise.
Installation
pip install llama-index llama-index-agensgraph
Usage
Property Graph Store
import os
import urllib.request
import nest_asyncio
from llama_index.core import SimpleDirectoryReader, PropertyGraphIndex
from llama_index.embeddings.openai import OpenAIEmbedding
from llama_index.llms.openai import OpenAI
from llama_index.core.indices.property_graph import SchemaLLMPathExtractor
from llama_index_agensgraph.graph_stores.agensgraph import AgensPropertyGraphStore
os.environ[
"OPENAI_API_KEY"
] = "<YOUR_API_KEY>" # Replace with your OpenAI API key
url = (
"https://raw.githubusercontent.com/run-llama/llama_index/main/docs/"
"examples/data/paul_graham/paul_graham_essay.txt"
)
output_path = "data/paul_graham/paul_graham_essay.txt"
os.makedirs("data/paul_graham/", exist_ok=True)
urllib.request.urlretrieve(url, output_path)
nest_asyncio.apply()
# Nothing to escape: every value reaches the server as a bound parameter, so an
# apostrophe in the text is just an apostrophe.
documents = SimpleDirectoryReader("./data/paul_graham/").load_data()
# Setup AgensGraph connection (ensure AgensGraph is running)
conf = {
"dbname": "",
"user": "",
"password": "",
"host": "",
"port": 5432,
}
# Pass vector_dimension to enable the HNSW vector index (match your embedding
# model's dimension, e.g. 1536 for text-embedding-3-small). Without it, vector
# search still works but is unindexed.
graph_store = AgensPropertyGraphStore(
graph_name="graph",
conf=conf,
vector_dimension=1536,
)
index = PropertyGraphIndex.from_documents(
documents,
embed_model=OpenAIEmbedding(model_name="text-embedding-3-small"),
kg_extractors=[
SchemaLLMPathExtractor(
llm=OpenAI(model="gpt-4o-mini", temperature=0.0),
# strict=True can yield zero triplets with some models; strict=False is more forgiving
strict=False,
)
],
property_graph_store=graph_store,
show_progress=True,
)
query_engine = index.as_query_engine(include_text=True)
response = query_engine.query("What happened at Interleaf and Viaweb?")
print("\nDetailed Query Response:")
print(str(response))
Natural-language queries (Text2Cypher)
TextToCypherRetriever turns a question into AgensGraph Cypher using the store's
built-in dialect prompt — no custom prompt needed:
from llama_index.core.indices.property_graph import TextToCypherRetriever
retriever = TextToCypherRetriever(
graph_store=graph_store, llm=OpenAI(model="gpt-4o-mini")
)
nodes = retriever.retrieve("How many entities of each type are there?")
print(nodes[0].node.text) # the generated Cypher and its result
Vector Store
import os
import urllib.request
from llama_index.core import SimpleDirectoryReader, VectorStoreIndex, StorageContext
from llama_index_agensgraph.vector_stores.agensgraph import AgensgraphVectorStore
# Set your OpenAI API key
os.environ["OPENAI_API_KEY"] = "<YOUR_API_KEY>" # Replace with your key
# Download example data
os.makedirs("data/paul_graham/", exist_ok=True)
url = (
"https://raw.githubusercontent.com/run-llama/llama_index/main/docs/"
"examples/data/paul_graham/paul_graham_essay.txt"
)
output_path = "data/paul_graham/paul_graham_essay.txt"
urllib.request.urlretrieve(url, output_path)
# Load documents
documents = SimpleDirectoryReader("./data/paul_graham").load_data()
# Setup AgensGraph connection (ensure AgensGraph is running)
url = "postgresql://username:password@host:port/database_name"
embed_dim = 1536
# Initialize vector store
vector_store = AgensgraphVectorStore(url=url, embedding_dimension=embed_dim)
# Build index
storage_context = StorageContext.from_defaults(vector_store=vector_store)
index = VectorStoreIndex.from_documents(documents, storage_context=storage_context)
# Query
query_engine = index.as_query_engine()
response = query_engine.query("What happened at Interleaf?")
print("\nQuery Response:")
print(str(response))
For hybrid (vector + keyword) search, build the store with hybrid_search=True
and query with a query_str:
hybrid_store = AgensgraphVectorStore(
url=url, embedding_dimension=embed_dim, hybrid_search=True
)
index = VectorStoreIndex.from_vector_store(hybrid_store)
index.as_retriever(vector_store_query_mode="hybrid").retrieve(
"What happened at Interleaf?"
)
Async & connection pooling
By default each store opens a single dedicated connection. For concurrent
workloads, share an AgensEngine (a driver connection pool) across stores so
each request checks out its own connection instead of serializing on one:
from llama_index_agensgraph.engine import AgensEngine
from llama_index_agensgraph.graph_stores.agensgraph import AgensPropertyGraphStore
from llama_index_agensgraph.vector_stores.agensgraph import AgensgraphVectorStore
engine = AgensEngine.from_url(
"postgresql://user:pwd@host:5432/db", min_size=2, max_size=20
)
graph_store = AgensPropertyGraphStore(graph_name="graph", conf=conf, engine=engine)
vector_store = AgensgraphVectorStore(
url="postgresql://user:pwd@host:5432/db",
embedding_dimension=1536,
engine=engine,
)
# ... use the stores ...
engine.close() # await engine.aclose() if you used the async pool
The stores also provide true-async hot paths backed by the driver's AsyncConnection
(no thread-pool wrapping):
- Vector store:
async_add,aquery,adelete - Property graph store:
aupsert_nodes,aupsert_relations,aget,avector_query,astructured_query
These work with or without an AgensEngine; with one, they draw from the async
pool.
Performance & indexing
The stores are indexed for their hot paths out of the box:
- Ingest (
MERGE-by-id) is backed by a btree index onid, so bulkupsert/addstays near-linear rather than O(N²). - Vector search uses the HNSW index on the embedding.
- Lookups by id (
get/get_nodes/delete_nodes) and the vector store'sdelete(ref_doc_id)are index-backed. - Relation upserts are UNWIND-batched per relationship type (not one query per relation).
Metadata-filtered vector search. A metadata filter cannot use the HNSW index for the filter itself, so a filter on an un-indexed property degrades to a sequential scan over the embedded nodes. Index the keys you filter on to keep it fast:
# Property graph store
graph_store.create_property_index("country")
# Vector store
vector_store.create_property_index("topic")
With the index present, the planner preselects matching rows via an index/bitmap scan and then ranks them — instead of scanning every node.
Counting and type filters. Prefer count(*) over count(n) in aggregations:
count(n) materializes each matched node (including its embedding), so it is much
slower on a graph that stores embeddings. For "all nodes of type X", match the label
itself with MATCH (n:X), which reads only that label's storage.
Demos & guides
Start here: the examples/demos/ suite — runnable,
end-to-end demos on real datasets (arXiv, Wikipedia, CC-News) that show how to
build with this integration at realistic scale. Its
README has a quickstart and copy-paste building
blocks.
| Demo | What you build |
|---|---|
| 01 · arXiv | a property graph + vector search + GraphRAG in one store, with get/get_triplets and an upsert→delete lifecycle |
| 02 · Wikipedia | an LLM-built knowledge graph + natural-language (Text2Cypher) Q&A |
| 03 · News | vector RAG: semantic, metadata-filtered (full operator set), hybrid, and cited, plus the store-mutation lifecycle |
| 04 · Router | one AgensEngine routing questions to the graph or the vector store — router and FunctionAgent |
Each demo folder also ships a pre-executed notebook — a narrated, end-to-end tour with real embedded outputs.
Short, single-feature notebooks:
What's new in 0.3.0
Every statement goes through the agensgraph-python 2.0 driver.
- An element is written on the label naming what it is.
MATCH (n:Author)reads that label's storage and nothing else, so nothing has to keep a list of types or a scalar copy of one beside every element. Measured on twenty thousand of each of two types, counting one of them: 217 buffers by label against 335 through a btree over such a copy, which also cost an index entry on every write. Each label carries its own uniqueness onid, because a constraint on the label they inherit does not reach them. - The embedding has a column of its own. Read out of the property map it is
text in a bag that has to come out of TOAST and be parsed before a distance can
be taken, once per element a filter kept -- which is where a metadata-filtered
search spent its time. Over 20,000 entities with a filter keeping one in ten:
1,209 ms against 171 ms for the same complete answer -- 7x, and 111x against
the un-promoted path once both are asked for every row they were asked for.
The 180x first published here compared a complete answer against a partial one.
Decline it with
promote_embedding=False. - The query's mode is read.
VectorStoreQuery.modewas never looked at, so every mode got a plain vector search. Hybrid, text search and MMR are answered now,alpha/sparse_top_k/hybrid_top_kdo what they say, a metadata filter reaches both halves of a hybrid search, anddistance_strategytakesl2andinner_productas well ascosine. - A generated statement runs read-only.
SafeTextToCypherRetrieverruns what a model wrote in a transaction the server will not let write, so what it may do is the server's decision. A list of Cypher's write keywords is not that: it is PostgreSQL underneath, soINSERT,TRUNCATE,GRANTandCOPYare all available and none of them is on such a list, while a read whose text merely mentions DELETE looks like a write. - True async. All twelve async methods the contract declares are implemented here. The base class answers most of them by calling the synchronous one, which holds the event loop for the whole round trip: twelve concurrent rel maps ran in 76.9 ms with the loop doing nothing else at all, against 31.8 ms with it still running other work. See Async & connection pooling.
- Nothing is installed in your database. Opening a store used to create three plpgsql functions there. The catalogs answer the same questions, and faster: on twenty thousand elements carrying embeddings, 1,213 ms of walking every one of them against 19 ms.
- Performance. Ingest is index-backed and near-linear (a btree index on the
idMERGE key; bulkadd/upsertbatched). Id-keyed lookups (get,get_nodes,get_triplets,get_rel_map,delete_nodes) and the vector store'sdelete(ref_doc_id)are index-backed rather than sequential scans, relation upserts are UNWIND-batched per type, and schema introspection no longer materializes every distinct property value. Metadata-filter keys can be indexed withcreate_property_index(...). See Performance & indexing. - AgensGraph 2.17 or later; an older server is refused at connect. Python 3.11 to 3.14.
Changes in 0.2.0
AgensPropertyGraphStore.vector_queryworks. It hard-coded a 3-dimension cast and ordered by a fixed literal vector, so results ignored the query embedding and it errored at any other dimension. Repairing that left it emittingORDER BYafter aWITHinside a SQL sub-query, which the grammar does not allow, so every call raised until the ordering moved onto the finalRETURN-- where it still reaches the HNSW index, which is the part worth checking rather than assuming.- Metadata-filtered vector search. Both
AgensPropertyGraphStore.vector_queryandAgensgraphVectorStore.queryhonorMetadataFilters, translated into a fully parameterized (injection-safe) CypherWHERE. All 14FilterOperatorvalues are supported —EQ,NE,GT,GTE,LT,LTE,IN,NIN,CONTAINS,TEXT_MATCH,TEXT_MATCH_INSENSITIVE,ANY,ALL,IS_EMPTY— along withAND/OR/NOTconditions and nested filter groups. - Hybrid search.
AgensgraphVectorStore(hybrid_search=True)fuses HNSW semantic search with full-text keyword search by reciprocal rank fusion — each modality is queried against its own index (so both stay index-backed) and the two rankings are merged. - AgensGraph-dialect Text2Cypher. The property graph store sets a default
text_to_cypher_templatethat knows the storage model -- an element is written on the label naming what it is, and every such label inherits"__Node__"-- and avoids Neo4j-only syntax, soTextToCypherRetrievergenerates runnable Cypher out of the box. - Lazy schema introspection.
AgensPropertyGraphStore(refresh_schema=False)defers the (O(N)) schema scan to the firstget_schema()/get_schema_str()call, so opening a large existing graph is instant. - Correctness fixes. Entity embeddings are persisted on
upsert_nodeseven when the entity has no source chunk;get(ids=[])returns nothing (instead of the whole graph); and depth-1get_rel_mapuses a fixed pattern (AgensGraph's variable-length edges are far slower). - Modern vector-store node management.
AgensgraphVectorStoreimplementsget_nodes(node_ids, filters),delete_nodes(node_ids, filters)andclear()(plus asyncaget_nodes/adelete_nodes/aclear). - Richer enhanced schema. With
enhanced_schema=True, numeric properties getmin/max/distinct_count, list properties getmin_size/max_size, and other properties get example values +distinct_count(computed exhaustively under a row threshold, sampled above it). - Breaking change. The deprecated triplet
AgensGraphStore(Knowledge Graph Store) has been removed. UseAgensPropertyGraphStorewithPropertyGraphIndex.
License
Apache-2.0.
Release files for llama-index-agensgraph 0.3.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| llama_index_agensgraph-0.3.1.tar.gz | 92.8 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| llama_index_agensgraph-0.3.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 162.7 kB
Release files / llama_index_agensgraph-0.3.1.tar.gz
| Download URL | llama_index_agensgraph-0.3.1.tar.gz |
|---|---|
| Size | 92.8 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
4286e1b2618e540be93f28d27686e68617f3437074c983c9c86a0f6ddb574bc5
|
|
BLAKE2b-256 checksum How to use checksums |
07d6870568128679018d653eadb848ad50d2f9cc6d082119fe2ba505896e88a1
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.5
|
Release files / llama_index_agensgraph-0.3.1-py3-none-any.whl
| Download URL | llama_index_agensgraph-0.3.1-py3-none-any.whl |
|---|---|
| Size | 69.8 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
be76b19f715f38c16a83f8d9d65a00b24207b1e6a2a2f153de80a0fb03a184f8
|
|
BLAKE2b-256 checksum How to use checksums |
5adae946cb3a59e216c3a4d89c28c5ce04a32c3429cb6aabb3fe2eef399a7c0b
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.5
|