Skip to main content

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 /embeddings endpoint using only the standard library (no openai package). Works with OpenAI, Azure OpenAI, and local servers (Ollama, LM Studio, text-embeddings-inference) via base_url. Reads OPENAI_API_KEY by 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 of int/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

rostam_client-0.1.1.tar.gz (44.9 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

rostam_client-0.1.1-py3-none-any.whl (30.2 kB view details)

Uploaded Python 3

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

Hashes for rostam_client-0.1.1.tar.gz
Algorithm Hash digest
SHA256 6308cb925327166a4dffa321c2fd0c931d2dcf8b2f8856f5249c34b16f888e45
MD5 ea5e6e131a91d18f977993ecf4fc723a
BLAKE2b-256 10964bc4e80d8972a915c3f85b38ac7d94f55768cb75294c3ab54d9f0b288aef

See more details on using hashes here.

Provenance

The following attestation bundles were made for rostam_client-0.1.1.tar.gz:

Publisher: publish-python.yml on rostamlabs/rostam

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

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

Hashes for rostam_client-0.1.1-py3-none-any.whl
Algorithm Hash digest
SHA256 91ca290efd718aba5dc81579afcb1625562b4afd8c05938abe6629aee870ddf4
MD5 45e27ca1a98a099e0ff676d67f6be499
BLAKE2b-256 315d20db9c072c7aba9a125af04999291e4d7da0392bab3d9b8bda20ba0a0ab1

See more details on using hashes here.

Provenance

The following attestation bundles were made for rostam_client-0.1.1-py3-none-any.whl:

Publisher: publish-python.yml on rostamlabs/rostam

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page