LlamaIndex VelesDB Integration
VelesDB vector store integration for LlamaIndex — the vector-recall face of VelesDB, the explainable, local-first memory engine for AI agents. For the connected why() recall trail across typed links, see velesdb-memory and the LangGraph integration.
Features
- 🚀 Sub-millisecond search — SIMD-optimized vector retrieval
- 📦 Self-contained — Single VelesDB binary, no external services required
- 🔒 Local-first — All data stays on your machine
- 🧠 RAG-ready — Built for Retrieval-Augmented Generation
- 🔀 Multi-Query Fusion — Native MQG support with RRF/Weighted strategies
- 🌊 Streaming inserts — Backpressure-aware streaming ingestion through VelesDB's bounded-channel API
Installation
pip install llama-index-vector-stores-velesdb
Quick Start
from llama_index.core import SimpleDirectoryReader, StorageContext, VectorStoreIndex
from llamaindex_velesdb import VelesDBVectorStore
# Create vector store
vector_store = VelesDBVectorStore(
path="./velesdb_data",
collection_name="my_docs",
metric="cosine",
)
# Wrap it in a StorageContext. This step is required: from_documents()
# ignores a bare vector_store= keyword and would silently index into an
# in-memory store, leaving the VelesDB collection empty.
storage_context = StorageContext.from_defaults(vector_store=vector_store)
# Load and index documents — chunks are written to VelesDB
documents = SimpleDirectoryReader("./data").load_data()
index = VectorStoreIndex.from_documents(
documents,
storage_context=storage_context,
)
# Query
query_engine = index.as_query_engine()
response = query_engine.query("What is VelesDB?")
print(response)
Usage with Existing Index
from llama_index.core import VectorStoreIndex
from llamaindex_velesdb import VelesDBVectorStore
# Connect to existing data
vector_store = VelesDBVectorStore(path="./existing_data")
index = VectorStoreIndex.from_vector_store(vector_store)
# Query
query_engine = index.as_query_engine()
response = query_engine.query("Summarize the key points")
API Reference
VelesDBVectorStore
VelesDBVectorStore(
path: str = "./velesdb_data", # Database directory
collection_name: str = "llamaindex", # Collection name
metric: str = "cosine", # Distance metric
storage_mode: str = "full", # Storage / quantization mode
search_quality: str = None, # Quality preset (see below)
)
Parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
path |
str |
"./velesdb_data" |
Path to database directory |
collection_name |
str |
"llamaindex" |
Name of the collection |
metric |
str |
"cosine" |
Distance metric: cosine, euclidean, dot (aliases: dotproduct, inner, ip), hamming, jaccard |
storage_mode |
str |
"full" |
Storage mode: full/f32, sq8/int8 (4× compression), binary/bit (32× compression), pq (2 × dim / m compression), rabitq (32× with scalar correction) |
search_quality |
str | None |
None |
Quality preset: fast, balanced, accurate, perfect, autotune, custom:N, adaptive:MIN:MAX |
Methods:
| Method | Description |
|---|---|
| Core Operations | |
add(nodes) |
Add nodes with embeddings |
add_bulk(nodes) |
Bulk insert (2-3x faster for large batches) |
delete(ref_doc_id) |
Delete by document ID |
get_nodes(node_ids) |
Retrieve nodes by their IDs |
flush() |
Flush pending changes to disk |
| Search | |
query(query) |
Query with vector |
batch_query(queries) |
Batch query multiple vectors in parallel |
multi_query_search(query_embeddings, ...) |
Multi-query fusion search ⭐ NEW |
hybrid_query(query_str, query_embedding, ...) |
Hybrid vector+BM25 search |
text_query(query_str, ...) |
Full-text BM25 search |
velesql(query_str, params) |
Execute VelesQL query |
| Utilities | |
get_collection_info() |
Get collection metadata |
is_empty() |
Check if collection is empty |
scroll(batch_size) |
Iterate all points in stable batches without a query vector |
Advanced Features
Multi-Query Fusion (MQG)
Search with multiple query embeddings and fuse results using various strategies. Perfect for RAG pipelines using Multiple Query Generation (MQG).
from llamaindex_velesdb import VelesDBVectorStore
vector_store = VelesDBVectorStore(path="./velesdb_data")
# Basic usage with RRF (Reciprocal Rank Fusion)
results = vector_store.multi_query_search(
query_embeddings=[emb1, emb2, emb3], # Multiple query reformulations
similarity_top_k=10,
fusion="rrf",
fusion_params={"k": 60}
)
# With weighted fusion (like SearchXP's scoring)
results = vector_store.multi_query_search(
query_embeddings=[emb1, emb2],
similarity_top_k=10,
fusion="weighted",
fusion_params={
"avg_weight": 0.6, # Average score weight
"max_weight": 0.3, # Maximum score weight
"hit_weight": 0.1, # Hit ratio weight
}
)
for node in results.nodes:
print(f"{node.metadata}: {node.text[:50]}...")
Fusion Strategies:
"rrf"- Reciprocal Rank Fusion (default, robust to score scale differences)"average"- Mean score across all queries"maximum"- Maximum score from any query"weighted"- Custom combination of avg, max, and hit ratio"relative_score"- Linear blend of dense and sparse scores
# Relative Score Fusion
results = vector_store.multi_query_search(
query_embeddings=[emb1, emb2],
similarity_top_k=10,
fusion="relative_score",
fusion_params={"dense_weight": 0.7, "sparse_weight": 0.3}
)
Similarities are higher-is-closer on every search path. A raw score of a
distance metric (euclidean, hamming) becomes 1 / (1 + d); cosine, dot and
Jaccard scores pass through. Fused and text scores keep the direction core
gives them: rrf and relative_score fusion, hybrid_query (RRF of vector and
BM25 ranks), text_query (BM25) and dense + sparse query(..., sparse_vector=...)
scores are already higher-is-better and pass through unchanged, while
average, maximum and weighted fusion keep the metric's units and are
mapped like a raw score on a distance metric. velesql() reads its scores in
the metric's direction, the direction of a NEAR score.
Advanced Search
search_quality — Quality Presets
Control the recall/latency trade-off for all queries with a single parameter set
at construction time or overridden per-call via query() kwargs.
# Set once on the store — applies to every query() call
vector_store = VelesDBVectorStore(
path="./velesdb_data",
search_quality="accurate", # higher recall at the cost of latency
)
q = VectorStoreQuery(query_embedding=embedding, similarity_top_k=10)
results = vector_store.query(q)
# Override per-call via kwargs
results = vector_store.query(q, search_quality="fast")
Accepted values:
| Value | Description |
|---|---|
"fast" |
Lowest latency, reduced recall |
"balanced" |
Balanced latency/recall |
"accurate" |
Higher recall, higher latency |
"perfect" |
Exhaustive search, maximum recall |
"autotune" |
Runtime-adaptive quality |
"custom:N" |
Explicit ef_search (e.g. "custom:256") |
"adaptive:MIN:MAX" |
Adaptive ef range (e.g. "adaptive:32:512") |
query_with_ef(query_embedding, ef_search, top_k)
Search with an explicit HNSW ef_search parameter to trade query latency for recall.
# Higher ef_search = better recall, slower query
results = vector_store.query_with_ef(
query_embedding=embedding,
ef_search=256,
top_k=10
)
query_ids(query_embedding, top_k)
Search returning only node IDs — no payloads transferred.
Faster than query() when only IDs are needed (e.g., for post-processing pipelines).
ids = vector_store.query_ids(query_embedding=embedding, top_k=50)
# ["abc", "def", ...] -- flat list of node-id strings, ordered by descending similarity
Hybrid Search (Vector + BM25)
from llamaindex_velesdb import VelesDBVectorStore
vector_store = VelesDBVectorStore(path="./velesdb_data")
# Hybrid search combining semantic and keyword matching
results = vector_store.hybrid_query(
query_str="machine learning optimization",
query_embedding=embedding_model.get_query_embedding("machine learning optimization"),
similarity_top_k=10,
vector_weight=0.7 # 70% vector, 30% BM25
)
for node in results.nodes:
print(node.text)
Full-Text Search (BM25)
# Pure keyword-based search without embeddings
results = vector_store.text_query(
query_str="VelesDB performance",
similarity_top_k=5
)
Cross-Collection MATCH
The velesql() method runs single-collection VelesQL/MATCH queries against the
vector store's own collection:
results = vector_store.velesql(
"MATCH (p:Product)-[:STORED_IN]->(w:Warehouse) RETURN p.name, w.city LIMIT 20"
)
for row in results:
print(row["p.name"], row["w.city"])
Cross-collection
@collectionMATCH is not available through the LlamaIndex integration.velesql()delegates to a singlevelesdb.Collection, which cannot resolve@collection-annotated nodes from other collections — that requiresDatabase-level routing. For cross-collection MATCH, use the corevelesdb.DatabaseAPI or the REST server directly. (Tracked indocs/reference/ECOSYSTEM_PARITY.md, action item #7.)
Graph Retrieval
Three graph helpers ship with the package: GraphLoader (build a knowledge
graph over stored nodes), GraphRetriever (vector seed + graph expansion)
and GraphQARetriever (adds depth-based re-ranking). Native mode talks to
the VelesDB graph layer directly — no server required. Create the graph
collection once with the core API; the loader then writes nodes and edges
into it. Core refuses an edge whose source or target is not a node of that
graph collection, so add both endpoints first. For GraphRetriever to
expand from a stored LlamaIndex node, its graph node id must be that node's
VelesDB point ID (the hash of its LlamaIndex node id, via
velesdb_common.ids.stable_hash_id):
from llamaindex_velesdb import VelesDBVectorStore, GraphLoader
vector_store = VelesDBVectorStore(path="./velesdb_data", collection_name="docs")
vector_store._get_db().create_graph_collection("kg") # once
loader = GraphLoader(vector_store, graph_collection_name="kg")
loader.add_node(node_id=source_id, label="DOCUMENT")
loader.add_node(node_id=target_id, label="DOCUMENT")
loader.add_edge(edge_id=1, source=source_id, target=target_id, label="RELATES_TO")
edges = loader.get_edges(label="RELATES_TO")
load_from_nodes loads LlamaIndex nodes as graph nodes in one call, labelled
node_label (stored in _labels, so MATCH (n:DOCUMENT) finds them). In
native mode each graph node's id is the node's VelesDB point ID,
stable_hash_id(node.node_id), so GraphRetriever expands from a vector hit
straight into the loaded graph. With extract_relations=True it also writes
each node's relationships as edges, labelled with the relationship kind:
SOURCE, PREVIOUS, NEXT, PARENT, CHILD (one edge per child):
vector_store.add(nodes) # the vector-store side: what GraphRetriever seeds from
counts = loader.load_from_nodes(nodes, extract_relations=True)
# e.g. {"nodes": 4, "edges": 6, "skipped_relations": 1}
- A relationship is linked when its target is in the graph: loaded by the same
call or, in native mode, by an earlier one. Any other target (typically
SOURCE, when the source document is never loaded) is counted inskipped_relationsand not written: no dangling edge. - Edge ids are deterministic, so loading the same nodes again writes nothing
new and returns the same counts.
edgescounts the distinct relationship edges the graph holds for the call, written now or by an earlier load; an edge that fails to write is logged and not counted. - With
extract_relations=False(the default) no edge is written and bothedgesandskipped_relationsare 0. - Without a graph collection, nodes go to the vector store's own collection
and edges to an in-memory graph store. There the id stays
stable_hash_id(f"{node_label}:{node.node_id}"), because the point ID would overwrite the stored node, and only same-call targets are linked.
Upgrading from 7.1.0: with a graph collection, the 7.1.0 loader wrote its nodes into the vector store's collection, as metadata-only points at
stable_hash_id(f"{node_label}:{node.node_id}")(or at the id given toadd_node). 8.0.0 writes them into the graph collection and no longer reads those points; search andgetnever return them, but they stay stored and counted. Delete them with the nodes you loaded and thenode_labelyou passed:from velesdb_common.ids import stable_hash_id node_label = "DOCUMENT" # the node_label passed to load_from_nodes collection = vector_store.client.get_collection(vector_store.collection_name) collection.delete([stable_hash_id(f"{node_label}:{n.node_id}") for n in nodes])Only do this for a store that used
graph_collection_name: without one, those points are still the loader's nodes.
GraphRetriever finds seed nodes by vector search, then expands the context
through the graph:
from llama_index.core import VectorStoreIndex
from llamaindex_velesdb import GraphRetriever
index = VectorStoreIndex.from_vector_store(vector_store)
retriever = GraphRetriever(
index=index,
mode="native",
graph_collection_name="kg",
seed_k=3, # initial vector hits
expand_k=10, # max nodes after expansion
max_depth=2, # BFS traversal depth
)
nodes = retriever.retrieve("What is VelesDB?")
The result holds the seed_k vector hits first, best score first, then the
nodes the graph reaches from them, seed by seed in traversal order, until
expand_k nodes in all. A node already returned, or one the vector store
cannot return, is skipped and takes no slot. Seeds carry graph_depth=0;
a neighbour carries its hops from the nearest seed as graph_depth, and
score=None: it was reached through the graph, not scored, so a score
threshold downstream should let None through or drop it deliberately.
GraphQARetriever re-ranks results by graph depth (seeds first) for Q&A:
from llamaindex_velesdb import GraphQARetriever
retriever = GraphQARetriever(
index=index,
mode="native",
graph_collection_name="kg",
rerank_by_depth=True,
)
nodes = retriever.retrieve("How are these concepts related?")
Pass low_latency=True to either retriever to skip graph expansion and
return vector-only seeds (no graph collection required).
Performance
Measured on CI runners (ubuntu-latest, 2-core). Local hardware will be faster.
Source: benchmarks/baseline.json.
| Operation | Latency | Notes |
|---|---|---|
| Search (10K / 128D, k=10) | ~0.34 ms | HNSW + SIMD cosine |
| Hybrid (vector + filter) | ~0.27 ms | Filtered vector search |
| Batch insert (10K / 128D) | ~9 s total | Sequential HNSW build |
Agent Memory (optional)
llama-index-vector-stores-velesdb re-exports three agent-memory wrappers around VelesDB's
native memory subsystems. They are imported lazily — if the underlying
velesdb native extension isn't installed, the import becomes a no-op and
the symbols are exposed as None.
from llamaindex_velesdb import (
VelesDBSemanticMemory, # long-term knowledge store
VelesDBEpisodicMemory, # time-sequenced event recall
VelesDBProceduralMemory, # learned procedures with reinforcement
)
See llamaindex_velesdb/memory.py for the full per-class API. The
companion GraphLoader, GraphRetriever, and GraphQARetriever exports
(top of the public namespace) are documented in the Graph Retrieval
section above.
Comparison with Other Stores
| Feature | VelesDB | Chroma | Pinecone |
|---|---|---|---|
| Deployment | Local binary | Docker | Cloud |
| Cost | Free | Free | $$$ |
| Offline | ✅ | ✅ | ❌ |
License
MIT License (this integration)
VelesDB Core and Server are licensed under VelesDB Core License 1.0 (source-available). See the root LICENSE for details.
Metadata
Release files for llama-index-vector-stores-velesdb 8.0.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| llama_index_vector_stores_velesdb-8.0.0.tar.gz | 75.5 kB | Without provenance | Details for llama_index_vector_stores_velesdb-8.0.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| llama_index_vector_stores_velesdb-8.0.0-py3-none-any.whl | Python 3 | none | any | Details for llama_index_vector_stores_velesdb-8.0.0-py3-none-any.whl |
Total release size: 123.4 kB
Release files / llama_index_vector_stores_velesdb-8.0.0.tar.gz
| Download URL | llama_index_vector_stores_velesdb-8.0.0.tar.gz |
|---|---|
| Size | 75.5 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
f6236778a6ceffe756527657a2e9c3fe946d564ff9b20fc78422bbc6fe865dee
|
|
BLAKE2b-256 checksum How to use checksums |
57e1f518a4924b9b96e666c5c72d42325693f8edc7038576154d5693e33f33b4
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Release files / llama_index_vector_stores_velesdb-8.0.0-py3-none-any.whl
| Download URL | llama_index_vector_stores_velesdb-8.0.0-py3-none-any.whl |
|---|---|
| Size | 48.0 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
453cab0bac23f675955435d8cca00d223c8ffea04209021e57169a6b6f619d12
|
|
BLAKE2b-256 checksum How to use checksums |
7310e7db4786ac747cf94f6accc7d4835b234fbff3b1e49cac60c18d6e8e4359
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Security for llama-index-vector-stores-velesdb 8.0.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.