Start hereQuick start · 6 you’ll reach for daily
Target versions · paceVersions
milvus ≥ 2.4
pymilvus ≥ 2.4
python ≥ 3.8
MilvusClient is the modern entry point — replaces direct
connections.connect() + ORM Collection(...)
code from 2.x. Milvus Lite (embedded SQLite-backed binary) ships in pymilvus itself
— just pass a file path. Multi-vector hybrid + sparse vectors + RRF / weighted rankers landed in 2.4.
DiskANN and GPU indexes are stable. This sheet pins to Milvus 2.4+.
Install · server · LiteSetup
# Milvus Lite — zero-ops, embedded in Python (file-based)
pip install "pymilvus>=2.4"
python -c "from pymilvus import MilvusClient; \
print(MilvusClient('./milvus.db').list_collections())"
# Docker — standalone for dev (gRPC :19530, REST :9091)
curl -sf https://raw.githubusercontent.com/milvus-io/milvus/master/scripts/standalone_embed.sh | bash
bash standalone_embed.sh start
# Or production-grade compose
wget https://github.com/milvus-io/milvus/releases/latest/download/milvus-standalone-docker-compose.yml -O docker-compose.yml
docker compose up -d
# Helm chart (k8s, distributed mode)
helm repo add milvus https://zilliztech.github.io/milvus-helm/
helm install my-milvus milvus/milvus --set cluster.enabled=true
# JS / Go / Java clients
npm install @zilliz/milvus2-sdk-node
Where things liveCommon imports
| from pymilvus import MilvusClient | Modern unified client. |
| from pymilvus import DataType | Field types — FLOAT_VECTOR, SPARSE_FLOAT_VECTOR, VARCHAR, JSON, etc. |
| from pymilvus import AnnSearchRequest, RRFRanker, WeightedRanker | Hybrid-search building blocks. |
| from pymilvus import Function, FunctionType | Server-side analyzers (BM25 tokenizer, etc.). |
| from pymilvus import connections, Collection | Legacy ORM API. Use only for old code. |
| from pymilvus.model.dense import SentenceTransformerEmbeddingFunction | Bundled embedders — install with pymilvus[model]. |
| from pymilvus.exceptions import MilvusException | Catch-all client error. |
Lite · server · cloudClients
| MilvusClient("./milvus.db") | Milvus Lite — file-based, no server. Dev / tests. |
| MilvusClient(uri="http://localhost:19530", token="root:Milvus") | gRPC over HTTP/2 to a server. Default port. |
| MilvusClient(uri="https://...zilliz.com", token=API_KEY) | Zilliz Cloud (managed Milvus). |
| MilvusClient(..., db_name="prod") | Pick a database (databases are namespaces > collections). |
| client.create_database("prod") / client.drop_database("prod") | Provision databases. |
| client.list_databases() | Inventory. |
| client.use_database("prod") | Switch the active db. |
| client.close() | Release the channel. Always pair with creation in long-running apps. |
Quick · explicit · loadCollections & schema
| create_collection(name, dimension=384, metric_type="COSINE") | Quick-start mode — fixed schema, dynamic fields on, auto-indexed. |
| schema = MilvusClient.create_schema(auto_id=False, enable_dynamic_field=True) | Build an explicit schema for production. |
| schema.add_field("id", DataType.INT64, is_primary=True) | Primary key — INT64 or VARCHAR. |
| schema.add_field("vec", DataType.FLOAT_VECTOR, dim=384) | Dense vector field. |
| DataType.SPARSE_FLOAT_VECTOR / BFLOAT16_VECTOR / BINARY_VECTOR | Sparse / FP16 / binary vectors. |
| schema.add_field("tags", DataType.ARRAY, element_type=DataType.VARCHAR, max_capacity=16) | Array field. |
| schema.add_field("meta", DataType.JSON) | Embedded JSON column. |
| client.create_collection(name, schema=schema, index_params=idx) | Create with explicit schema + indexes. |
| client.load_collection(name) | Required — collections must be loaded into RAM to be searched. |
| client.release_collection(name) | Free RAM when idle. |
| client.describe_collection(name) | Inspect schema + indexes. |
| client.drop_collection(name) | Destructive. |
from pymilvus import MilvusClient, DataType
client = MilvusClient("./milvus.db") # Milvus Lite
# or MilvusClient(uri="http://localhost:19530", token="root:Milvus")
# Quick-start path — fixed schema, dynamic fields enabled
client.create_collection(
collection_name="kb_quick",
dimension=384,
metric_type="COSINE", # COSINE | L2 | IP
)
# Explicit schema — for production, multi-vector, FP16, etc.
schema = MilvusClient.create_schema(auto_id=False, enable_dynamic_field=True)
schema.add_field("id", DataType.INT64, is_primary=True)
schema.add_field("vec", DataType.FLOAT_VECTOR, dim=384)
schema.add_field("sparse", DataType.SPARSE_FLOAT_VECTOR) # BM25 / SPLADE
schema.add_field("title", DataType.VARCHAR, max_length=256)
schema.add_field("tags", DataType.ARRAY, element_type=DataType.VARCHAR,
max_capacity=16, max_length=64)
schema.add_field("year", DataType.INT32)
# Per-vector index params — must be created before loading
idx = client.prepare_index_params()
idx.add_index(field_name="vec", index_type="HNSW",
metric_type="COSINE", params={"M": 16, "efConstruction": 200})
idx.add_index(field_name="sparse", index_type="SPARSE_INVERTED_INDEX",
metric_type="IP")
client.create_collection("kb", schema=schema, index_params=idx)
client.load_collection("kb") # must be loaded into memory to query
HNSW · IVF · DiskANN · GPUIndexes
| idx = client.prepare_index_params() | Start a parameter set — one collection can index many fields. |
| idx.add_index(field_name="vec", index_type="HNSW", params={"M":16, "efConstruction":200}) | HNSW — Preferred dense default. |
| index_type="IVF_FLAT" params={"nlist": 1024} | IVF + flat probes. Cheap memory, decent recall. |
| index_type="IVF_PQ" params={"nlist":1024,"m":16,"nbits":8} | IVF + product quantization. Smaller index, slower build. |
| index_type="DISKANN" | SSD-resident. Use when working set won’t fit RAM. |
| index_type="GPU_CAGRA" / "GPU_IVF_FLAT" | GPU-backed indexes (server build with CUDA). |
| index_type="SPARSE_INVERTED_INDEX" metric_type="IP" | For sparse fields. Required before sparse search. |
| index_type="BIN_FLAT" / "BIN_IVF_FLAT" metric_type="HAMMING" | Binary vectors. |
| metric_type="COSINE" | "L2" | "IP" | Distance. Cosine = IP on L2-normalized vectors. |
| idx.add_index("year", index_type="STL_SORT") | Scalar index for range filters. |
| idx.add_index("title", index_type="INVERTED") | Inverted index for keyword filter / BM25 input. |
| client.create_index(name, index_params=idx) | Apply after create. create_collection can take it directly. |
insert · upsert · deleteInsert & DML
| client.insert(name, data=[{"id": 1, "vec": [...]}]) | Insert rows. Auto-flush is configurable. |
| client.upsert(name, data=[{...}]) | Insert-or-replace by primary key. Preferred for idempotent ingest. |
| client.delete(name, ids=[1, 2, 3]) | Delete by primary key. |
| client.delete(name, filter="year < 2020") | Delete by boolean expression. |
| client.flush(name) | Force buffered inserts to disk. Rarely needed. |
| client.get(name, ids=[1,2], output_fields=["title"]) | Random access by id. |
| batch size ≤ ~10000 rows | Soft cap per RPC. Larger payloads time out. |
| data=[{"vec": [...], "$meta": {"k":"v"}}] | Dynamic field — goes into the meta-JSON when enable_dynamic_field=True. |
ANN · filter · querySearch & query
| client.search(name, data=[vec], limit=5) | k-NN. Returns list of lists (per query vector). |
| data=[v1, v2] | Batched queries — one inner list per input vector. |
| anns_field="vec" | Which vector field to search. Required when multiple exist. |
| search_params={"metric_type":"COSINE","params":{"ef":64}} | Per-index runtime knob. HNSW: ef; IVF: nprobe. |
| filter="source == 'docs' and year >= 2024" | Boolean expression on scalar fields. Pre-filter. |
| output_fields=["title","tags","$meta"] | Fetch extra fields alongside hits. |
| consistency_level="Strong" | "Bounded" | "Eventually" | Read freshness vs latency tradeoff. |
| client.query(name, filter="id in [1,2,3]", output_fields=["text"]) | Filter-only scan — no similarity. |
| limit=100, offset=0 | Pagination. Offset is bounded server-side. |
| group_by_field="doc_id" | Top-k per group — great for dedup across chunks. |
Scalar filter DSLFilter expressions
| "source == 'docs'" | Equality. Strings are single-quoted. |
| "year >= 2024 and year < 2026" | Numeric range with and. |
| "tag in ['rag','llm']" | IN operator on scalar fields. |
| "tag not in ['draft']" | NOT IN. |
| "title like 'rag%'" | Prefix match. Needs an INVERTED index for speed. |
| "json_contains(meta['tags'], 'rag')" | JSON membership. |
| "json_contains_any(meta['tags'], ['a','b'])" | Any-of membership. |
| "ARRAY_CONTAINS(tags, 'rag')" | Array column membership. |
| "meta['author'] == 'ada'" | Path into a JSON column. |
| "exists(meta['rating'])" | Field presence in dynamic JSON. |
Multi-vector · RRF · weightedHybrid & multi-vector search
| AnnSearchRequest(data=[vec], anns_field="vec", param={...}, limit=20) | One sub-request per vector field. |
| RRFRanker(k=60) | Reciprocal-rank fusion. Robust default. |
| WeightedRanker(0.7, 0.3) | Linear blend — weights in sub-request order. |
| client.hybrid_search(name, reqs=[r1, r2], ranker=..., limit=5) | Server-side fusion. |
| DataType.SPARSE_FLOAT_VECTOR | Sparse field for BM25 / SPLADE vectors (dict form). |
| data=[{7: 1.5, 42: 0.8}] | Sparse vector wire form: {index: value}. |
| drop_ratio_search=0.2 (sparse) | Skip the smallest 20% of dimensions at query time — faster. |
| Function(function_type=FunctionType.BM25, input_field_names=["text"], output_field_names=["sparse"]) | Server-side BM25 tokenizer — raw text → sparse vector at write time. |
from pymilvus import MilvusClient, AnnSearchRequest, RRFRanker, WeightedRanker
client = MilvusClient(uri="http://localhost:19530")
client.load_collection("kb")
# Two sub-requests — one dense, one sparse — same collection, different fields
dense_req = AnnSearchRequest(
data=[[0.0]*384],
anns_field="vec",
param={"metric_type": "COSINE", "params": {"ef": 64}},
limit=20,
)
sparse_req = AnnSearchRequest(
data=[{7: 1.5, 42: 0.8}], # sparse vector as {idx: val}
anns_field="sparse",
param={"metric_type": "IP", "params": {"drop_ratio_search": 0.2}},
limit=20,
)
# Fuse — RRF or weighted
res = client.hybrid_search(
collection_name="kb",
reqs=[dense_req, sparse_req],
ranker=RRFRanker(k=60), # or WeightedRanker(0.7, 0.3)
limit=5,
output_fields=["title", "tags"],
)
for hit in res[0]:
print(f"{hit['distance']:.3f} {hit['entity']['title']}")
Partitions · partition keysPartitions & multi-tenancy
| client.create_partition(name, partition_name="2026-Q1") | Static partition. Useful for time-bucketing. |
| client.list_partitions(name) | Inventory. |
| client.insert(..., partition_name="2026-Q1") | Route writes to a specific partition. |
| client.search(..., partition_names=["2026-Q1"]) | Restrict search to partitions. |
| schema.add_field("tenant", DataType.VARCHAR, max_length=64, is_partition_key=True) | Partition key — Milvus auto-shards by hash of this field. |
| num_partitions=64 (at create) | How many hash buckets for the partition key. |
| filter="tenant == 'acme'" | Searches scoped by the partition key field automatically prune. |
| client.drop_partition(name, "2026-Q1") | Cheap bulk delete — drops one partition. |
LangChain · LlamaIndex · HaystackFramework integrations
| from langchain_milvus import Milvus | Modern LangChain wrapper. |
| Milvus.from_documents(docs, embedding, connection_args={"uri":"http://..."}) | Build store from LangChain Documents. |
| store.as_retriever(search_kwargs={"k": 4, "expr": "year >= 2024"}) | Drop into an LCEL chain. |
| from langchain.vectorstores import Milvus | Legacy import — superseded by langchain-milvus. |
| from llama_index.vector_stores.milvus import MilvusVectorStore | LlamaIndex adapter. |
| from haystack_integrations.document_stores.milvus import MilvusDocumentStore | Haystack 2 document store. |
Lite + insert + filtered searchEnd-to-end · Minimal RAG store
Milvus Lite, OpenAI embeddings, scalar filter expression. Swap the URI for a server URL — the rest stays.
# Minimal Milvus RAG store — Milvus Lite, insert, filtered search
from pymilvus import MilvusClient
from openai import OpenAI
client = MilvusClient("./milvus.db")
oai = OpenAI()
NAME, DIM = "kb", 1536
if NAME not in client.list_collections():
client.create_collection(
collection_name=NAME,
dimension=DIM,
metric_type="COSINE",
primary_field_name="id",
id_type="int",
vector_field_name="vector",
auto_id=False,
)
def embed(texts):
return [d.embedding for d in
oai.embeddings.create(model="text-embedding-3-small", input=texts).data]
docs = [
{"id": 1, "text": "Milvus is an ANN vector DB.", "source": "blog"},
{"id": 2, "text": "RAG joins retrieval with gen.", "source": "docs"},
{"id": 3, "text": "DiskANN trades RAM for SSD.", "source": "docs"},
]
vecs = embed([d["text"] for d in docs])
client.insert(NAME, [{**d, "vector": v} for d, v in zip(docs, vecs)])
q = embed(["what is RAG?"])[0]
res = client.search(
collection_name=NAME,
data=[q], limit=2,
filter="source == 'docs'", # boolean expression on scalar fields
output_fields=["text", "source"],
search_params={"metric_type": "COSINE", "params": {"nprobe": 10}},
)
for hit in res[0]:
e = hit["entity"]
print(f"{hit['distance']:.3f} [{e['source']}] {e['text']}")
Best practiceGood to know
uri from a file path to http://... and you’re on the server. Cluster mode is the same SDK with a different deploy.
ef / nprobe per query, not globally.
The right value depends on recall target. Pass them in search_params; benchmark with consistency_level="Strong" until happy, then drop to "Bounded" in prod.
Common trapsWatch out for
load_collection before searching.
A freshly created collection (or one after server restart) is on disk only. search errors with "collection not loaded" until you call load_collection — remember to release when done.
Bounded — reads may lag writes.
Fresh inserts may not appear immediately. Use consistency_level="Strong" in tests; in prod, prefer write-then-flush or accept the lag.