Skip to main content
Version: 0.0.4

Configure storage backends

This guide sets up an embedder, a vector store, and a graph store. Use it before you store or search embeddings.

Install​

Install the core package. Then install one extra for each backend you use.

uv pip install agentic-graphrag
uv pip install "agentic-graphrag[embed-local]" # local embedding models
uv pip install "agentic-graphrag[qdrant]" # Qdrant vector store
uv pip install "agentic-graphrag[weaviate]" # Weaviate vector store
uv pip install "agentic-graphrag[milvus]" # Milvus vector store
uv pip install "agentic-graphrag[neo4j]" # Neo4j graph store

You must install at least one vector store extra to use VectorStore. You must install the neo4j extra to use GraphStore.

Set up the embedder​

build_embedder takes a sentence-transformers model name. It returns a ready-to-use embedder. The default model is ibm-granite/granite-embedding- small-english-r2.

from agrag.embedding import build_embedder

embedder = build_embedder("ibm-granite/granite-embedding-small-english-r2")
vectors = await embedder.embed(["first passage", "second passage"])
print(embedder.dimensions) # the vector length this model produces

To use a different model, or to set the device or batch size, build a SentenceTransformerEmbedder yourself. Pass its own EmbeddingSettings.

from agrag.embedding import EmbeddingSettings, SentenceTransformerEmbedder

embedder = SentenceTransformerEmbedder(
settings=EmbeddingSettings(model="BAAI/bge-small-en-v1.5", device="cuda"),
)

Set embedder options with environment variables​

Set these variables instead of passing EmbeddingSettings fields in code.

VariableMeaning
EMBEDDING_MODELThe sentence-transformers model name.
EMBEDDING_DEVICEThe device to load the model on.
EMBEDDING_NORMALIZEWhether to normalize output vectors.
EMBEDDING_BATCH_SIZEThe number of texts per encode call.

Set up a vector store​

build_vector_store takes a backend name. It reads connection settings from environment variables.

from agrag.vectordb import build_vector_store
from agrag.common.data_models.vector_record import Distance

store = build_vector_store("qdrant")
await store.initialize()
await store.ensure_collection(
"chunks", dimensions=embedder.dimensions, distance=Distance.COSINE, hybrid=True
)

Set dimensions to the value your embedder produces. ensure_collection raises an error if a collection with that name already exists with a different dimension. Pass hybrid=True to search the collection with hybrid_search later. Leave it out if you only need search.

For full control, build a backend class yourself with its own settings.

from agrag.vectordb.qdrant import QdrantVectorStore
from agrag.vectordb.settings import QdrantSettings

store = QdrantVectorStore(
settings=QdrantSettings(url="https://xyz.cloud.qdrant.io", api_key="...")
)

Set vector store credentials with environment variables​

Set the variables for the one backend you use.

BackendVariables
QdrantQDRANT_URL, QDRANT_API_KEY, QDRANT_REQUIRE_TLS
WeaviateWEAVIATE_URL, WEAVIATE_API_KEY, WEAVIATE_REQUIRE_TLS
MilvusMILVUS_URI, MILVUS_TOKEN, MILVUS_REQUIRE_TLS

The default URLs point at plaintext localhost, for the Docker Compose services make dev-services-up starts. Settings construction raises if a URL uses a plaintext scheme (http), points at a non-local host, and carries an API key or token: use https:// for any remote deployment.

An unauthenticated backend on a private network (a VPC, a cluster-internal service) is not flagged by default, since the settings cannot tell a private host from a public one from the URL alone. Set *_REQUIRE_TLS=true to reject plaintext to any non-local host regardless of credential, for a deployment that wants every remote connection encrypted.

Write and search vectors​

Write vectors with upsert. Each record needs an id, a vector, and a payload.

from uuid import uuid4
from agrag.common.data_models.vector_record import VectorRecord

record = VectorRecord(
id=uuid4(), vector=vectors[0], payload={"text": "first passage"}
)
await store.upsert("chunks", [record])

Search by vector alone with search. Search by vector and keyword together with hybrid_search.

hits = await store.search("chunks", query_vector=vectors[0], limit=5)
hits = await store.hybrid_search(
"chunks", query_vector=vectors[0], query_text="sepsis management", limit=5,
)
for hit in hits:
print(hit.id, hit.score, hit.payload)

Set up a graph store​

Build a Neo4jGraphStore with Neo4jSettings. Call connect before you use it.

from agrag.graphdb.neo4j import Neo4jGraphStore
from agrag.graphdb.settings import Neo4jSettings

store = Neo4jGraphStore(settings=Neo4jSettings())
await store.connect()

Set graph store credentials with environment variables​

VariableMeaning
NEO4J_URIThe Bolt connection URI. Use neo4j+s://... for Aura.
NEO4J_USERNAMEThe database username.
NEO4J_PASSWORDThe database password.
NEO4J_DATABASEThe target database name.

Neo4j always authenticates with a password. Settings construction raises an error if NEO4J_URI uses a plaintext scheme (bolt:// or neo4j://) and points at a non-local host. Use neo4j+s:// (or bolt+s://) for a remote instance.

Write nodes​

Write nodes with upsert_nodes. Each record needs an id, its labels, and a properties map. Call setup_constraints after your first write of a label, so Neo4j knows which labels to constrain.

from uuid import uuid4
from agrag.common.data_models.graph_record import NodeRecord

node = NodeRecord(
id=uuid4(),
labels=["Chunk"],
properties={"text": "first passage", "embedding": vectors[0]},
)
await store.upsert_nodes("Chunk", [node])
await store.setup_constraints()

A node can carry more than one label. List every label the node should have in labels; upsert_nodes writes all of them, not only the label argument. Records with different label sets in the same call are grouped and written separately.

tagged_node = NodeRecord(
id=uuid4(),
labels=["Chunk", "Entity"],
properties={"text": "sepsis protocol"},
)
await store.upsert_nodes("Chunk", [tagged_node])

Labels only ever accumulate. If a later upsert of the same node id lists fewer labels, the labels already on the node are kept, not removed.

Handle partial write failures​

upsert_nodes and upsert_relations return an UpsertResult. Check failures for records that did not write. The rest of the call still lands.

nodes = [
NodeRecord(id=uuid4(), labels=["Chunk"], properties={"text": "example"})
]
result = await store.upsert_nodes("Chunk", nodes)
if result.failures:
for failure in result.failures:
print(f"{failure.id} failed: {failure.error_message}")
print(f"{result.written} of {len(nodes)} nodes written")

A connectivity or authentication failure still raises instead of returning a partial result. Only a failure specific to one record's data is isolated and reported this way.

Search vectors stored in the graph​

You can search vectors stored as node properties, without a separate vector store. First create a vector index on the property. Then search it.

await store.ensure_vector_index(
label="Chunk",
vector_property="embedding",
dimensions=embedder.dimensions,
distance=Distance.COSINE,
)
hits = await store.vector_search(
label="Chunk", vector_property="embedding", query_vector=vectors[0], limit=5
)

vector_search returns the same hit type VectorStore.search returns. Code that reads hits does not need to know which store produced them.

Mirror ingestion into a vector store​

Graph.open accepts an optional vector_store. Pass one and every embedding ingestion writes to the graph store is also upserted to the vector store: entity, chunk, and community vectors all land in the collections RetrievalSettings names (agrag_entities, agrag_chunks, agrag_communities by default). open() creates a missing collection with your embedder's dimensions. After entity merges, the absorbed entities' vectors are deleted there. Each detect_communities(apply=True) run replaces the community vectors after stale-vector cleanup succeeds. That cleanup clears every Community vector in the collection, the way the graph-side recompute clears every Community node in the database, so give each graph its own Neo4j database (NEO4J_DATABASE) and its own collections. A cleanup failure can leave old community vectors in the collection and is reported to the caller.

from agrag.ingestion import Graph
from agrag.vectordb import build_vector_store

vector_store = build_vector_store("qdrant")
graph = await Graph.open(
schema=schema,
graph_store=graph_store,
embedder=embedder,
extractor=extractor,
vector_store=vector_store,
)

A SearchEngine pointed at the same vector_store then searches the same data the graph-native path serves. Ingestion reports failed vector-store writes in each stage's failures: a failed mirror is recorded as a StageFailure such as chunk_vector_store under ErrorPolicy.SKIP/QUARANTINE and propagates under the default ErrorPolicy.RAISE.

Next steps​

See the API Reference for every parameter.