rostam-client
A dependency-free Python client and LangChain adapter for the Rostam vector store.
The core client uses only the Python standard library — no requests, no
gRPC, nothing to pull in. The optional LangChain adapter requires
langchain-core.
Install
pip install rostam-client # core client only (zero dependencies)
pip install rostam-client[langchain] # + the LangChain VectorStore adapter
Run a server
The client talks to a Rostam HTTP server. Build and run one from this repo:
go build -o rostam-server ./cmd/rostam-server
./rostam-server -http 127.0.0.1:8080 -data ./data # REST/JSON
./rostam-server -http 127.0.0.1:8080 -grpc 127.0.0.1:9090 -tcp 127.0.0.1:7000
./rostam-server -http :8080 -api-key "$KEY" # reachable, with auth
Note the loopback addresses. With no authenticator configured the server
refuses to bind a reachable address rather than serve an open datastore to
the network, so a bare :8080 will not start. To listen beyond loopback, give
it auth (-api-key or -keys-file) as in the third line, or pass -insecure
to run open deliberately.
A single server can expose REST, gRPC, and the binary TCP protocol at once over
one shared store — a write on any transport is visible on the others. Set a
transport's flag to "" to disable it.
Quickstart
from rostam import RostamClient
from rostam import filters as f
c = RostamClient("http://localhost:8080", api_key="optional-bearer-token")
c.create_collection("docs", dim=384, metric="cosine") # metric: cosine|l2|dot
# Upsert points. Metadata is plain Python — the client encodes it to Rostam's
# tagged wire form for you (and decodes it back on the way out).
c.upsert("docs", 1, embedding, content="the chunk text", metadata={"doc_id": 7, "lang": "en"})
# k-NN with content + metadata, filtered.
hits = c.search_docs("docs", query_embedding, k=5, filter=f.eq("doc_id", 7))
for d in hits:
print(d.id, d.distance, d.content, d.metadata)
# Group-by-document: the top-k distinct documents, best chunk(s) each.
for g in c.search_groups("docs", query_embedding, k=5, group_by="doc_id", group_size=2):
print(g.key, [h.content for h in g.hits])
# Compound filters.
hits = c.search_docs("docs", query_embedding, k=5,
filter=f.and_(f.gte("price", 10.0), f.eq("in_stock", True)))
c.delete("docs", 1)
c.delete_by_filter("docs", f.eq("doc_id", 7)) # purge a whole document
The client also exposes insert (rejects duplicate ids), hybrid_search
(dense + sparse fusion), drop_collection, and health.
Embeddings (work in text, not vectors)
The core client takes vectors. TextStore adds the text-first ergonomics —
embedding happens client-side, so no model dependency touches Rostam's engine.
from rostam import RostamClient, TextStore, OpenAIEmbedder
store = TextStore(RostamClient("http://localhost:8080"), "docs", OpenAIEmbedder())
store.create_collection() # dim inferred from the embedder
store.add(["first chunk", "second chunk"], metadatas=[{"doc_id": 1}, {"doc_id": 1}])
docs = store.search("a question", k=4) # embeds the query for you
groups = store.search_groups("a question", k=4, group_by="doc_id")
Embedder options:
OpenAIEmbedder— calls any OpenAI-compatible/embeddingsendpoint using only the standard library (noopenaipackage). Works with OpenAI, Azure OpenAI, and local servers (Ollama, LM Studio, text-embeddings-inference) viabase_url. ReadsOPENAI_API_KEYby default.FunctionEmbedder— wraps any callable, e.g. a local model:from sentence_transformers import SentenceTransformer m = SentenceTransformer("all-MiniLM-L6-v2") embedder = FunctionEmbedder(lambda ts: m.encode(ts).tolist())
Embedders implement the same interface as LangChain's Embeddings, so the same
object feeds both TextStore and RostamVectorStore.
Multi-vector / late interaction (ColBERT MaxSim)
For late-interaction retrieval, a document is represented by many token vectors
and scored by MaxSim (Σ_q max_d cos(q,d)) rather than a single pooled vector.
Multi-vector collections are in-memory.
# quant ("sq8"/"bq1") quantizes the first-stage graph; persistent=True keeps the
# float32 token vectors off-heap in an mmap file and survives restart.
c.mv_create_collection("docs", dim=128, quant="sq8", persistent=True)
c.mv_add("docs", 1, doc_token_vectors, metadata={"doc_id": 1}) # token matrix
c.mv_add("docs", 2, other_token_vectors)
hits = c.mv_search("docs", query_token_vectors, k=5) # MaxSim ranking
for h in hits:
print(h.id, h.score, h.metadata)
c.mv_delete("docs", 1)
You supply token vectors yourself (e.g. from a ColBERT/late-interaction model);
the client handles the wire encoding and decodes results (including native
metadata). Persistent collections are flushed server-side (embedded
CollectionStore.FlushMultiVector).
LangChain
RostamVectorStore implements the standard LangChain VectorStore interface, so
it drops into existing retrieval chains. You bring the embeddings; Rostam stores
the vector, the chunk text, and metadata.
from langchain_openai import OpenAIEmbeddings
from rostam import RostamClient
from rostam.langchain import RostamVectorStore
client = RostamClient("http://localhost:8080")
client.create_collection("docs", dim=1536, metric="cosine")
store = RostamVectorStore.from_texts(
texts=["first chunk", "second chunk"],
embedding=OpenAIEmbeddings(),
metadatas=[{"doc_id": 1}, {"doc_id": 1}],
client=client,
collection="docs",
)
docs = store.similarity_search("a question", k=4, filter={"doc_id": 1})
docs_scored = store.similarity_search_with_score("a question", k=4)
# Rostam-specific extension: retrieve the top-k distinct documents.
groups = store.search_grouped("a question", k=4, group_by="doc_id", group_size=2)
filter accepts either a native Rostam filter (rostam.filters) or a simple
{field: value} map (translated to an AND of equalities). Relevance scores map
Rostam's distance to a 0..1 range.
Hybrid retrieval
Fuse dense KNN with BM25 full-text search by enabling full_text on the store
(the collection must have the full-text index enabled):
store = RostamVectorStore(
client, "docs", embedding, full_text=True, auto_create=True
)
# Dense + server-side BM25 over the raw query string (default):
docs = store.hybrid_search("apple pie", k=4)
# Dense + SPLADE-style sparse (pass a callable that returns a sparse vector):
store = RostamVectorStore(
client, "docs", embedding, full_text=True, sparse_embedding=my_splade_fn
)
docs = store.hybrid_search("apple pie", k=4)
hybrid_search signature: hybrid_search(query, k=4, *, filter=None, method="rrf", alpha=0.0).
method and alpha are forwarded to Rostam's fusion endpoint unchanged.
Maximal Marginal Relevance (MMR)
Retrieve diverse results by trading off relevance against redundancy:
docs = store.max_marginal_relevance_search(
"apple pie", k=4, fetch_k=20, lambda_mult=0.5
)
fetch_k candidates are fetched first; MMR re-ranks them to the final k.
lambda_mult=1.0 is pure relevance; 0.0 is pure diversity.
The async variant is await store.amax_marginal_relevance_search(...).
Fetch by id
Retrieve documents by their original string ids (missing ids are silently omitted):
docs = store.get_by_ids(["id-1", "id-2"])
# Async:
docs = await store.aget_by_ids(["id-1", "id-2"])
Async methods
Every retrieval and write method has an a-prefixed async counterpart. They
offload to a thread pool over the synchronous client — no extra dependency is
required:
docs = await store.asimilarity_search("a question", k=4)
docs_scored = await store.asimilarity_search_with_score("a question", k=4)
ids = await store.aadd_texts(["chunk one", "chunk two"])
ok = await store.adelete(["id-1"])
Auto-create
By default (auto_create=True) the collection is created on the first write.
Dimensionality is inferred from the first batch of embeddings. If the store is
configured with full_text=True the collection is created with the full-text
index enabled (required for hybrid search):
# Collection created automatically on first add_texts / from_texts call:
store = RostamVectorStore(client, "docs", embedding, auto_create=True, full_text=True)
store.add_texts(["chunk one"]) # collection created here
# Manage the collection yourself:
client.create_collection("docs", dim=1536, metric="cosine")
store = RostamVectorStore(client, "docs", embedding, auto_create=False)
from_texts forwards auto_create, metric, and full_text to the
constructor, so the class method works the same way.
LlamaIndex
rostam.llamaindex.RostamVectorStore implements the LlamaIndex VectorStore
interface (pip install rostam-client[llamaindex]).
from rostam import RostamClient
from rostam.llamaindex import RostamVectorStore
from llama_index.core import VectorStoreIndex, StorageContext
client = RostamClient("http://localhost:8080")
client.create_collection("docs", dim=1536, metric="cosine")
store = RostamVectorStore(client=client, collection="docs")
index = VectorStoreIndex.from_documents(
documents, storage_context=StorageContext.from_defaults(vector_store=store)
)
results = index.as_retriever().retrieve("a question")
Nodes are serialized with LlamaIndex's own metadata utils; delete(ref_doc_id)
purges every node of a document via a metadata filter; metadata filters on the
query translate to Rostam filters.
Hybrid mode
Pass mode=VectorStoreQueryMode.HYBRID and set query_str to enable hybrid
retrieval. The collection must have the full-text index enabled (full_text=True).
Note that query_embedding is always required — the hybrid path uses the dense
vector unconditionally and only fuses it with BM25/sparse when query_str is also set:
from llama_index.core.vector_stores.types import VectorStoreQuery, VectorStoreQueryMode
store = RostamVectorStore(client=client, collection="docs", full_text=True)
q = VectorStoreQuery(
query_embedding=embedding,
query_str="apple pie", # required for hybrid
mode=VectorStoreQueryMode.HYBRID,
similarity_top_k=4,
)
result = store.query(q)
With a sparse_embedding callable, dense + sparse (SPLADE-style) fusion is used
instead of dense + BM25:
store = RostamVectorStore(
client=client, collection="docs", full_text=True, sparse_embedding=my_splade_fn
)
Hybrid results are ranked by the server's fusion score; similarities in the
returned VectorStoreQueryResult are rank-based (1/(1+rank)).
Async methods
async_add, aquery, and adelete are available. They offload to a thread
pool over the sync client — no extra dependency is required:
ids = await store.async_add(nodes)
result = await store.aquery(query)
await store.adelete(ref_doc_id)
Auto-create
By default (auto_create=True) the collection is created on the first add
call. Dimensionality is inferred from the first node's embedding. If the store
is configured with full_text=True the collection is created with the full-text
index enabled (required for hybrid mode):
# Collection created automatically on first add:
store = RostamVectorStore(client=client, collection="docs", full_text=True)
index = VectorStoreIndex.from_documents(
documents, storage_context=StorageContext.from_defaults(vector_store=store)
)
# Manage the collection yourself:
client.create_collection("docs", dim=1536, metric="cosine")
store = RostamVectorStore(client=client, collection="docs", auto_create=False)
Haystack
rostam.haystack provides a RostamDocumentStore (Haystack 2.x DocumentStore)
and a RostamEmbeddingRetriever component (pip install rostam-client[haystack]).
from haystack import Document
from rostam import RostamClient
from rostam.haystack import RostamDocumentStore, RostamEmbeddingRetriever
RostamClient("http://localhost:8080").create_collection("docs", dim=384, metric="cosine")
store = RostamDocumentStore(url="http://localhost:8080", collection="docs")
store.write_documents([Document(content="hello", embedding=[...], meta={"src": "a"})])
retriever = RostamEmbeddingRetriever(document_store=store, top_k=5)
docs = retriever.run(query_embedding=[...])["documents"]
count_documents / filter_documents are served by Rostam's scroll listing.
Documents must carry embeddings; writes use overwrite semantics.
Notes
- IDs. Rostam point ids are
uint64. The client takes integers directly. The LangChain adapter accepts string ids: a purely-numeric string is used verbatim, anything else is hashed (BLAKE2b) to a stable 64-bit id, so repeated upserts/deletes of the same external id address the same point. - Metadata kinds. Supported value types:
int,float,str,bool, and lists ofint/float/str.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file rostam_client-0.1.1.tar.gz.
File metadata
- Download URL: rostam_client-0.1.1.tar.gz
- Upload date:
- Size: 44.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
6308cb925327166a4dffa321c2fd0c931d2dcf8b2f8856f5249c34b16f888e45
|
|
| MD5 |
ea5e6e131a91d18f977993ecf4fc723a
|
|
| BLAKE2b-256 |
10964bc4e80d8972a915c3f85b38ac7d94f55768cb75294c3ab54d9f0b288aef
|
Provenance
The following attestation bundles were made for rostam_client-0.1.1.tar.gz:
Publisher:
publish-python.yml on rostamlabs/rostam
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
rostam_client-0.1.1.tar.gz -
Subject digest:
6308cb925327166a4dffa321c2fd0c931d2dcf8b2f8856f5249c34b16f888e45 - Sigstore transparency entry: 2419667669
- Sigstore integration time:
-
Permalink:
rostamlabs/rostam@e59e913633c05c9fa516f8e00413ca1f77f3400e -
Branch / Tag:
refs/tags/py-v0.1.1 - Owner: https://github.com/rostamlabs
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish-python.yml@e59e913633c05c9fa516f8e00413ca1f77f3400e -
Trigger Event:
push
-
Statement type:
File details
Details for the file rostam_client-0.1.1-py3-none-any.whl.
File metadata
- Download URL: rostam_client-0.1.1-py3-none-any.whl
- Upload date:
- Size: 30.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
91ca290efd718aba5dc81579afcb1625562b4afd8c05938abe6629aee870ddf4
|
|
| MD5 |
45e27ca1a98a099e0ff676d67f6be499
|
|
| BLAKE2b-256 |
315d20db9c072c7aba9a125af04999291e4d7da0392bab3d9b8bda20ba0a0ab1
|
Provenance
The following attestation bundles were made for rostam_client-0.1.1-py3-none-any.whl:
Publisher:
publish-python.yml on rostamlabs/rostam
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
rostam_client-0.1.1-py3-none-any.whl -
Subject digest:
91ca290efd718aba5dc81579afcb1625562b4afd8c05938abe6629aee870ddf4 - Sigstore transparency entry: 2419667700
- Sigstore integration time:
-
Permalink:
rostamlabs/rostam@e59e913633c05c9fa516f8e00413ca1f77f3400e -
Branch / Tag:
refs/tags/py-v0.1.1 - Owner: https://github.com/rostamlabs
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish-python.yml@e59e913633c05c9fa516f8e00413ca1f77f3400e -
Trigger Event:
push
-
Statement type: