RagLeap Packages
Looking for the self-hosted AI employee system? This page documents the open-source pip install ragleap-vectorstores library. To install and run ragleap-core (AI employees, channels, Docker), see docs.ragleap.com; the project home is ragleap.com.
48 / 48 tests passing · live-verified against real infrastructure

ragleap-vectorstores

Pluggable vector backends beyond ragleap-rag core's six — install only the client SDK you actually need, not the whole ecosystem.

0.5.1
PyPI version
48
Tests passing
5
Backends implemented

Install

Each backend's real client SDK is an optional extra — installing the bare package alone pulls in no heavy dependencies beyond ragleap-rag itself.

pip install ragleap-vectorstores[chroma]
# or, with uv
uv add ragleap-vectorstores[chroma]

pip install ragleap-vectorstores[lancedb]
# or, with uv
uv add ragleap-vectorstores[lancedb]

pip install ragleap-vectorstores[redis]
# or, with uv
uv add ragleap-vectorstores[redis]

pip install ragleap-vectorstores[upstash]
# or, with uv
uv add ragleap-vectorstores[upstash]

Quick start

database_url= is still required even when using one of these backends for vectors — ragleap-rag's conversation memory is always Postgres-backed regardless of which vector_backend is chosen; only vector storage moves. Swap ChromaBackend for LanceDBBackend(uri="./lancedb_data") to use LanceDB instead, or RedisBackend(redis_url="redis://localhost:6379/0") for Redis (requires a real Redis Stack instance, or plain Redis + the RediSearch module), or UpstashBackend(url="...", token="...") for Upstash Vector (requires an already-created dense-type index — see the Upstash console) — same interface, same rest of the setup.

from ragleap_vectorstores import ChromaBackend
from ragleap import RagLeap, ProviderConfig, EmbeddingConfig

backend = ChromaBackend(persist_directory="./chroma_data")

rag = RagLeap(
    database_url="postgresql://...",   # still required — conversation memory
    vector_backend=backend,             # vectors go to Chroma instead
    primary=ProviderConfig(provider="gemini", api_key="..."),
    embedder=EmbeddingConfig(provider="gemini", api_key="..."),
)
rag.init_schema()
rag.ingest("report.pdf")
answer = rag.ask("What were Q3's key findings?")

Full public API surface

Confirmed via direct source reading, not inspect().

from ragleap_vectorstores import VectorBackend, ChromaBackend, LanceDBBackend, RedisBackend, UpstashBackend, OpenSearchBackend, __version__

ChromaBackend — implements VectorBackend

ChromaBackend(persist_directory, collection_name="ragleap")
Embedded/local via chromadb's PersistentClient — no server required. persist_directory is required; both Chroma's on-disk index and a small document-registry SQLite sidecar live there.
init_schema(dimensions)
Creates or opens the Chroma collection (cosine similarity space).
insert_document / insert_chunk
Chroma persists chunk text and metadata natively — no sidecar needed for chunks. The sidecar is only for the document registry (filename, upload time), since Chroma has no native parent-document concept.
search_dense(embedding, top_k, metadata_filter=None)
Cosine similarity search. Multi-key metadata_filters are automatically wrapped in Chroma's required $and form.
search_sparse / search_hybrid
Not natively supported — honestly falls back to the VectorBackend default (empty / dense-only) rather than claiming unimplemented capability.
supports_sparse()
Returns False — see Known limitations below.
list_documents / delete_document / get_document_filename
Operate against the local SQLite document registry.

LanceDBBackend — implements VectorBackend

LanceDBBackend(uri, table_name="ragleap")
Embedded/local via a directory path — no server required. uri is required; both LanceDB's own on-disk table and a small document-registry SQLite sidecar live there.
init_schema(dimensions)
Idempotent — opens the existing table via LanceDB's real open_table ValueError-on-missing behavior, or creates one from an explicit pyarrow schema if it doesn't exist yet.
insert_document / insert_chunk
LanceDB persists chunk text and metadata natively — no sidecar needed for chunks. insert_chunk uses real upsert semantics via merge_insert() — re-inserting the same id updates the row in place rather than duplicating it.
search_dense(embedding, top_k, metadata_filter=None)
Cosine similarity search (.metric("cosine") set explicitly — LanceDB defaults to L2 otherwise). Multi-key metadata_filters combine directly with native SQL AND, unlike Chroma's $and requirement.
search_sparse / search_hybrid
Not natively supported in this release — honestly falls back to the VectorBackend default (empty / dense-only) rather than claiming unimplemented capability.
supports_sparse()
Returns False — see Known limitations below.
list_documents / delete_document / get_document_filename
Operate against the local SQLite document registry.

RedisBackend — implements VectorBackend

RedisBackend(redis_url, index_name="ragleap_idx", key_prefix="ragleap:chunk:")
Requires a real running server — Redis Stack, or plain Redis with the RediSearch module loaded. Unlike Chroma/LanceDB, there is no embedded/local mode. redis_url is required.
init_schema(dimensions)
Checks MODULE LIST for the search module and raises a clear RuntimeError immediately if it's missing, rather than failing confusingly later at query time. Creates a RediSearch HNSW vector index (cosine distance) if one doesn't already exist, or reuses it via ft().info().
insert_document / insert_chunk
Chunk text, vector, and metadata are stored natively in a Redis HASH per chunk — no sidecar needed for chunks. A small SQLite sidecar is used only for the document registry, same pattern as Chroma/LanceDB. Re-inserting the same document_id/chunk_index overwrites the HASH in place (Redis's native upsert behavior).
search_dense(embedding, top_k, metadata_filter=None)
KNN vector search via RediSearch's query-string syntax. metadata_filter only supports narrowing by document_id (a declared TAG field) — RediSearch requires fields to be predeclared in the schema, unlike Chroma's flexible where=. Other filter keys are accepted but ignored rather than raising.
search_sparse / search_hybrid
Not natively supported in this release — honestly falls back to the VectorBackend default (empty / dense-only) rather than claiming unimplemented capability.
supports_sparse()
Returns False — see Known limitations below.
list_documents / delete_document / get_document_filename
Operate against the local SQLite document registry.

UpstashBackend — implements VectorBackend

UpstashBackend(url, token, namespace="")
Managed serverless REST API — no local/embedded mode at all, unlike every other backend here. The index itself (fixed dimension, dense-vs-hybrid type) must already exist, created via the Upstash console; there is no create-index call in the SDK. url and token are required.
init_schema(dimensions)
Cannot create anything — verifies the real index's dimension and type via info() and raises a clear RuntimeError on mismatch (wrong dimension, or a hybrid/sparse-type index) rather than failing confusingly on the first insert.
insert_document / insert_chunk
Chunk text and metadata are stored natively in Upstash's per-vector JSON metadata dict — no sidecar needed for chunks. A small SQLite sidecar is used only for the document registry, same pattern as every other backend. Re-inserting the same document_id/chunk_index overwrites in place via Upstash's native upsert semantics.
search_dense(embedding, top_k, metadata_filter=None)
Vector search via Upstash's real SQL-like filter= string. Unlike Redis, arbitrary multi-key metadata_filters work natively — no predeclared schema needed, and multi-key filters combine with AND without Chroma's $and wrapping.
search_sparse / search_hybrid
Not natively supported in this release — honestly falls back to the VectorBackend default (empty / dense-only) rather than claiming unimplemented capability.
supports_sparse()
Returns False — see Known limitations below.
list_documents / delete_document / get_document_filename
Operate against the local SQLite document registry; chunk-counting and deletion use Upstash's native prefix= parameter on range()/delete().

OpenSearchBackend — implements VectorBackend

OpenSearchBackend(opensearch_url, index_name="ragleap_idx", documents_index_name=None, use_ssl=False, verify_certs=False, http_auth=None, **client_kwargs)
Requires a real running OpenSearch instance — no embedded/local mode. opensearch_url is required (for example http://localhost:9200). The client SDK is imported lazily, so a missing opensearch extra raises a clear ImportError at construction.
init_schema(dimensions)
Creates a k-NN index using the lucene engine with cosinesimil space. Idempotent: reusing an existing index with a matching dimension just reuses it, while a mismatched dimension raises a clear RuntimeError.
insert_document / insert_chunk
Chunks are stored under the id <document_id>:<chunk_index>, so re-inserting the same chunk overwrites it rather than duplicating. The document registry is a second real OpenSearch index (<index_name>_documents), not a SQLite sidecar. Writes use refresh=True for immediate read-after-write consistency, trading indexing throughput.
search_dense(embedding, top_k, metadata_filter=None)
k-NN search via OpenSearch's native query DSL. metadata_filter supports arbitrary term filters over mapped fields through bool.filter — every key is applied, so a nonexistent field narrows results to zero rather than being silently ignored.
search_sparse / search_hybrid
Not implemented in this release — honestly falls back to the VectorBackend default (empty / dense-only).
supports_sparse()
Returns False — see Known limitations below.
list_documents / delete_document / get_document_filename
Operate against the registry index, using OpenSearch's own sorting and paging. delete_document() issues a delete_by_query on the chunks index plus a direct delete on the registry index — two requests, not one atomic operation.

Known limitations

Drawn from live testing against the real installed chromadb==1.5.9, lancedb==0.37.1, redis-py==7.3.0, upstash-vector==0.8.0, and opensearch-py==3.2.0 packages, plus a real Redis Stack instance, a real Upstash Vector index, and a real OpenSearch 3.8.0 instance — not from documentation alone.

Applies to all backends

  • No native sparse/keyword search. supports_sparse() honestly reports False for all five — none has a tested full-text/keyword search path in this release, so search_hybrid() falls back to dense-only.
  • database_url= is still required. Conversation memory is always Postgres-backed in ragleap-rag, regardless of which vector backend is chosen — these backends only replace vector storage.

Chroma-specific

  • where= filters need exactly one top-level operator. A plain multi-key metadata filter dict raises ValueError against real chromadb — ChromaBackend wraps multi-key filters in $and automatically, but this is a real constraint worth knowing if you're debugging filter behavior directly against chromadb.
  • Dependency footprint. chromadb's own transitive dependencies are non-trivial (kubernetes client libs, OpenTelemetry, uvicorn extras) — correctly gated behind the optional [chroma] extra, but worth knowing before installing into a size-sensitive environment.

LanceDB-specific

  • Default metric is L2, not cosine. LanceDBBackend sets .metric("cosine") explicitly on every query to avoid this — worth knowing if you're querying LanceDB directly outside this backend.
  • LanceDB does support full-text search via tantivy (fts_columns=), but that surface wasn't implemented or tested in this release.

Redis-specific

  • Requires a real running server. Unlike Chroma/LanceDB, there's no embedded/local mode — plain Redis has no vector search at all. init_schema() checks MODULE LIST and raises a clear error if the search module (RediSearch/Redis Stack) isn't loaded.
  • Metadata filtering is limited to document_id. RediSearch requires fields to be predeclared in the index schema up front — unlike Chroma's flexible where=, arbitrary metadata keys can't be filtered on without a schema change.
  • TAG field values must be escaped. An un-escaped UUID-style document_id (containing hyphens) inside a TAG query silently returns zero matches — RediSearch parses hyphens as query syntax, not literal characters. Handled automatically, but worth knowing if querying Redis directly.
  • Redis does support full-text search via TextField/BM25 scorers, but that surface wasn't implemented or tested in this release.

Upstash-specific

  • No local mode at all. Unlike every other backend here (including self-hostable Redis), there is no way to run Upstash Vector locally — it's a managed cloud service only, and the index must already exist, created via the Upstash console.
  • An index can be "dense" or "hybrid" at creation time. A hybrid-type index rejects dense-only upserts with a confusing error — live-verified. init_schema() checks this via info() and fails clearly up front instead.
  • Writes are eventually consistent. A short delay after insert_chunk() may be needed before a search_dense() reliably sees it — live-verified in this package's own test suite, which waits ~1-1.5s after seeding data.
  • Upstash does support real hybrid dense+sparse search, but that requires a hybrid-type index and sparse embeddings this package doesn't generate, so it's honestly reported as unsupported.

OpenSearch-specific

  • Requires a real running instance. There is no embedded/local mode, unlike Chroma/LanceDB.
  • The nmslib k-NN engine is rejected on OpenSearch 3.0+. Most tutorials use it; attempting it raises mapper_parsing_exception at index creation — live-verified. This backend uses engine: "lucene" instead.
  • The k-NN score is not raw cosine similarity. With cosinesimil and the lucene engine, the score is (1 + cosine_similarity) / 2, mapping [-1, 1] onto [0, 1] — derived from real numbers (near-identical vectors scored 0.9969, orthogonal vectors 0.5552) and used directly as similarity_score.
  • No bulk-insert path yet. insert_chunk() indexes one document per request — fine for moderate chunk counts, not optimized for very large ingests.
  • Tested locally only. The 16 OpenSearch tests ran against OpenSearch 3.8.0 in Docker on a local Windows machine (Python 3.14, opensearch-py==3.2.0); they skip when OPENSEARCH_TEST_URL is unset and have not been run in CI or on Linux.
  • OpenSearch does support real BM25/hybrid search, but that surface wasn't implemented in this release, so it's honestly reported as unsupported.

Fixed in v0.5.1

  • REAL BUG in v0.5.0, on Python 3.10 only. OpenSearchBackend.insert_document() used datetime.UTC, which only exists on Python 3.11+, so it raised AttributeError on Python 3.10 - despite this package declaring support for 3.10. Reproduced with the published v0.5.0 wheel; fixed in v0.5.1 by switching to datetime.timezone.utc, which works on every supported version and produces the same timestamp. Python 3.11+ was never affected.
  • The deprecated datetime.utcnow() was also removed from the Chroma, LanceDB, Redis and Upstash backends in v0.5.1, with the identical naive-UTC timestamp string preserved - stored data and list_documents() output are unchanged.