Skip to content

Python client

A dependency-free (stdlib-only) REST client for Rostam, with optional LangChain, LlamaIndex, and Haystack adapters. Requires Python ≥ 3.9.

pip install rostam-client

# with an adapter
pip install "rostam-client[langchain]"   # or [llamaindex], [haystack]
from rostam import RostamClient

c = RostamClient("http://localhost:8080", api_key=None, timeout=30.0)
c.health()  # -> bool

All errors raise RostamError(message, status) carrying the HTTP status. Metadata is plain Python dicts — the client converts to/from the server's tagged value encoding automatically.

Collections

c.create_collection(
    "docs", dim=768,
    metric="cosine",          # "cosine" | "l2" | "dot"
    quant="sq8",              # "" | "sq8" | "bq1" | "pq" | "sq" | "prq"
    index_type="hnsw",        # "" | "hnsw" | "ivf" | "vamana"
    m=0, ef_construction=0, ef_search=0,   # 0 = engine defaults
    persistent=False, rescore_factor=0,
    full_text=True,           # True or {"analyzer": "english", "k1": 1.2, "b": 0.75}
)
c.drop_collection("docs")

Advanced index knobs (sq_bits, prq_layers, pq_nbits, vamana_r/l/alpha, anisotropic_eta, soar, soar_lambda, seed) pass straight through to the collection config.

Writing points

c.upsert("docs", 1, vec, content="document text",
         metadata={"tenant": "acme", "year": 2026},
         ttl_ms=0, sparse=None)              # insert-or-replace
c.insert("docs", 2, vec)                     # create-only: duplicate id raises
c.delete("docs", 2)                          # -> bool (existed)
c.delete_by_filter("docs", {"op": "eq", "field": "tenant", "value": "acme"})  # -> count

Bulk loading (binary wire)

Three methods cover large loads. They ship vectors over the binary bulk wire (Content-Type: application/octet-stream, raw f32 instead of JSON text) and automatically split the load across requests to stay under the server's per-request caps (256 MiB, 262,144 points) — a million vectors in one call is fine:

# Initial load of an empty collection: cheap parallel staging, then one
# multi-core index build. Metadata rides along, so filtered workloads get
# the fast path too (measured ~6x faster to searchable than inline indexing).
c.bulk_stage("docs", ids, vectors, metadatas=metas)   # metas: one dict/None per id
c.bulk_build("docs", workers=0)                       # 0 = all cores; blocks

# Writes into a collection that is already built: each point indexed inline.
c.batch_upsert("docs", ids, vectors, metadatas=metas, upsert=True)  # -> count

Prefer bulk_stage + bulk_build for initial loads; batch_upsert is for incremental writes, or for points that need content, sparse vectors, TTLs, or CAS — none of which the staging wire carries.

Reading & listing

points = c.get_batch("docs", [1, 2, 3], with_vector=True, with_payload=True)
# -> [Point(id, vector, content, metadata)], content lifted out of $content

page = c.scroll("docs", filter=None, limit=100)      # ScrollPage: list-like
while page.next_cursor:
    page = c.scroll("docs", limit=100, cursor=page.next_cursor)
c.search("docs", query_vec, k=10, filter=None)
# -> [SearchResult(id, distance, score)]

c.search_docs("docs", query_vec, k=10)
# -> [Document(id, distance, content, score, metadata)]

c.search_groups("docs", query_vec, k=5, group_by="doc_id", group_size=2)
# -> [Group(key, hits)]

c.hybrid_search("docs", dense=query_vec, k=10,
                sparse={"indices": [3, 17], "values": [0.4, 0.9]},
                method="rrf", alpha=0.0)      # method: "rrf" | "weighted"

c.search_text("docs", "how do i rotate api keys", k=10, global_idf=False)
# BM25 — requires full_text=True at creation

c.hybrid_text("docs", vector=query_vec, text="rotate api keys", k=10,
              method="rrf")                   # "rrf" | "weighted" | "dbsf"

Filters are plain dicts in the server's filter JSON (operators):

f = {"op": "and", "and": [
    {"op": "eq",  "field": "tenant", "value": "acme"},
    {"op": "gte", "field": "year",   "value": 2020},
]}
c.search("docs", query_vec, k=10, filter=f)

The rostam.filters module builds the same trees with less ceremony:

from rostam import filters as f

c.search("docs", query_vec, k=10,
         filter=f.and_(f.eq("tenant", "acme"), f.gte("year", 2020)))

Text-first helpers

The core client is deliberately vector-only. rostam.TextStore wraps a client plus an embedder for a text-in, text-out surface — FunctionEmbedder adapts any callable (e.g. a sentence-transformers model's .encode), and OpenAIEmbedder calls any OpenAI-compatible /embeddings endpoint over the standard library:

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}])
hits = store.search("a question", k=4)

Multi-vector (late interaction)

c.mv_create_collection("docs-colbert", dim=128)
c.mv_add("docs-colbert", doc_id=1, tokens=token_vectors, metadata={"src": "faq"})
c.mv_search("docs-colbert", query_tokens, k=10)   # -> [MultiResult(id, score, metadata)]
c.mv_delete("docs-colbert", 1)

Framework adapters

Installing an extra enables the corresponding integration package for LangChain (langchain-core ≥ 0.2), LlamaIndex (llama-index-core ≥ 0.10), or Haystack (haystack-ai ≥ 2.0) — use Rostam as the vector store behind your existing pipeline.

Return types

Class Fields
SearchResult id, distance, score
Document id, distance, content, score, metadata
Point id, vector, content, metadata
Group key, hits
MultiResult id, score, metadata
ScrollPage list-like of Document + next_cursor