docs/src/content/docs/connectors/qdrant.mdx
The qdrant connector provides utilities for writing points to Qdrant vector databases, with support for single vectors, named dense vectors, sparse vectors, and multi-vector configurations.
from cocoindex.connectors import qdrant
:::note[Dependencies] This connector requires additional dependencies. Install with:
pip install cocoindex[qdrant]
:::
create_client() creates a Qdrant client connection with optional gRPC support.
def create_client(
url: str,
*,
prefer_grpc: bool = True,
**kwargs: Any,
) -> QdrantClient
Parameters:
url — Qdrant server URL (e.g., "http://localhost:6333").prefer_grpc — Whether to prefer gRPC over HTTP (default: True).**kwargs — Additional arguments passed directly to QdrantClient.Returns: A Qdrant client instance.
Example:
client = qdrant.create_client("http://localhost:6333")
The qdrant connector provides target state APIs for writing points to collections. CocoIndex tracks what points should exist and automatically handles upserts and deletions.
Create a ContextKey[QdrantClient] to identify your Qdrant client, then provide it in your lifespan:
:::note The key name is load-bearing across runs — it's the stable identity CocoIndex uses to track managed collections. See ContextKey as stable identity before renaming. :::
from qdrant_client import QdrantClient
import cocoindex as coco
QDRANT_DB = coco.ContextKey[QdrantClient]("my_vectors")
@coco.lifespan
async def coco_lifespan(builder: coco.EnvironmentBuilder) -> AsyncIterator[None]:
client = qdrant.create_client(QDRANT_URL)
builder.provide(QDRANT_DB, client)
yield
Declares a collection as a target state. Returns a CollectionTarget for declaring points.
def declare_collection_target(
db: ContextKey[QdrantClient],
collection_name: str,
schema: CollectionSchema,
*,
managed_by: Literal["system", "user"] = "system",
) -> CollectionTarget[coco.PendingS]
Parameters:
db — A ContextKey[QdrantClient] identifying the Qdrant client to use.collection_name — Name of the collection.schema — Schema definition specifying vector configurations (see Collection Schema).managed_by — Whether CocoIndex manages the collection lifecycle ("system") or assumes it exists ("user").Returns: A pending CollectionTarget. Use the convenience wrapper await qdrant.mount_collection_target(QDRANT_DB, collection_name, schema) to resolve.
Once a CollectionTarget is resolved, declare points to be upserted using qdrant.PointStruct, which is an alias of qdrant_client.http.models.PointStruct:
def CollectionTarget.declare_point(
self,
point: qdrant.PointStruct,
) -> None
Parameters:
point — A qdrant.PointStruct (alias of qdrant_client.http.models.PointStruct) containing:
id — Point ID (str, int, or UUID)vector — Vector data (single vector or dict of named vectors)payload — Optional metadata as a JSON-serializable dictDefine vector configurations for a collection using CollectionSchema. Unlike row-oriented databases, Qdrant uses a point-oriented model where each point has schemaless payload and one or more vectors with predefined dimensions.
class CollectionSchema:
@classmethod
async def create(
cls,
vectors: QdrantVectorDef
| dict[str, QdrantVectorDef | QdrantSparseVectorDef]
| None = None,
) -> CollectionSchema
Parameters:
vectors — Either:
QdrantVectorDef for an unnamed dense vectorQdrantVectorDef (dense) or QdrantSparseVectorDef (sparse)Dense and sparse vectors share one namespace in Qdrant (the server rejects duplicate names across the two kinds), so both live in the same dict — a name collision is impossible to express. Sparse vectors are always named.
Specifies vector configuration including dimension, distance metric, and multi-vector settings:
class QdrantVectorDef(NamedTuple):
schema: VectorSchemaProvider | MultiVectorSchemaProvider
distance: Literal["cosine", "dot", "euclid"] = "cosine"
multivector_comparator: Literal["max_sim"] = "max_sim"
Parameters:
schema — A VectorSchemaProvider or MultiVectorSchemaProvider that defines vector dimensionsdistance — Distance metric for similarity search (default: "cosine")multivector_comparator — Comparator for multi-vector fields (only applies to MultiVectorSchemaProvider)Specifies a named sparse vector configuration:
class QdrantSparseVectorDef(NamedTuple):
modifier: Literal["idf"] | None = None
Sparse vectors use Qdrant's native sparse-vector storage and dot-product scoring. The optional modifier="idf" enables Qdrant's IDF modifier for sparse vectors.
For collections with a single unnamed vector:
from cocoindex.ops.sentence_transformers import SentenceTransformerEmbedder
embedder = SentenceTransformerEmbedder("sentence-transformers/all-MiniLM-L6-v2")
schema = await qdrant.CollectionSchema.create(
vectors=qdrant.QdrantVectorDef(schema=embedder)
)
Points use the vector directly:
point = qdrant.PointStruct(
id=123, # unsigned int or UUID — see "Point IDs" below
vector=embedding.tolist(), # Single vector
payload={"text": "...", "metadata": {...}},
)
For collections with multiple named vectors:
from cocoindex.resources.schema import VectorSchema
import numpy as np
schema = await qdrant.CollectionSchema.create(
vectors={
"text_embedding": qdrant.QdrantVectorDef(
schema=VectorSchema(dtype=np.float32, size=384),
distance="cosine",
),
"image_embedding": qdrant.QdrantVectorDef(
schema=VectorSchema(dtype=np.float32, size=512),
distance="dot",
),
}
)
Points use a dict of vectors:
point = qdrant.PointStruct(
id=123, # unsigned int or UUID — see "Point IDs" below
vector={
"text_embedding": text_vec.tolist(),
"image_embedding": image_vec.tolist(),
},
payload={"text": "...", "metadata": {...}},
)
For hybrid retrieval, declare a named dense vector and a named sparse vector in the same collection:
from qdrant_client.http import models as qdrant_models
from cocoindex.resources.schema import VectorSchema
import numpy as np
schema = await qdrant.CollectionSchema.create(
vectors={
"dense": qdrant.QdrantVectorDef(
schema=VectorSchema(dtype=np.float32, size=384),
distance="cosine",
),
"sparse": qdrant.QdrantSparseVectorDef(modifier="idf"),
},
)
Points put both vector types in PointStruct.vector. The sparse vector is a native Qdrant SparseVector, not payload:
point = qdrant.PointStruct(
id=123,
vector={
"dense": dense_embedding.tolist(),
"sparse": qdrant_models.SparseVector(
indices=sparse_indices,
values=sparse_values,
),
},
payload={"text": text},
)
target.declare_point(point)
The schema field of QdrantVectorDef accepts a VectorSchemaProvider, a ContextKey, or an explicit VectorSchema to specify the vector dimension and dtype. See Vector Schema for details.
For multi-vector configurations (multiple vectors per point stored together):
from cocoindex.resources.schema import MultiVectorSchema, VectorSchema
import numpy as np
schema = await qdrant.CollectionSchema.create(
vectors=qdrant.QdrantVectorDef(
schema=MultiVectorSchema(
vector_schema=VectorSchema(dtype=np.float32, size=384)
),
multivector_comparator="max_sim",
)
)
The distance parameter in QdrantVectorDef specifies the similarity metric:
"cosine" — Cosine similarity (default, normalized dot product)"dot" — Dot product similarity"euclid" — Euclidean distance (L2)from qdrant_client import QdrantClient
import cocoindex as coco
from cocoindex.connectors import qdrant
from cocoindex.ops.sentence_transformers import SentenceTransformerEmbedder
from typing import AsyncIterator
QDRANT_URL = "http://localhost:6333"
QDRANT_DB = coco.ContextKey[QdrantClient]("main_vectors")
embedder = SentenceTransformerEmbedder("sentence-transformers/all-MiniLM-L6-v2")
@coco.lifespan
async def coco_lifespan(builder: coco.EnvironmentBuilder) -> AsyncIterator[None]:
client = qdrant.create_client(QDRANT_URL)
builder.provide(QDRANT_DB, client)
yield
@coco.fn
async def process_document(
doc_id: str,
text: str,
target: qdrant.CollectionTarget,
) -> None:
embedding = await embedder.embed(text)
point = qdrant.PointStruct(
id=doc_id,
vector=embedding.tolist(),
payload={"text": text},
)
target.declare_point(point)
@coco.fn
async def app_main() -> None:
# Declare collection target state
collection = await qdrant.mount_collection_target(
QDRANT_DB,
"documents",
await qdrant.CollectionSchema.create(
vectors=qdrant.QdrantVectorDef(schema=embedder)
),
)
# Declare points
for doc_id, text in documents:
await coco.mount(
coco.component_subpath("doc", doc_id),
process_document,
doc_id,
text,
collection,
)
from cocoindex.resources.schema import VectorSchema
import numpy as np
@coco.fn
async def app_main() -> None:
collection = await qdrant.mount_collection_target(
QDRANT_DB,
"multimodal_docs",
await qdrant.CollectionSchema.create(
vectors={
"text": qdrant.QdrantVectorDef(
schema=text_embedder,
distance="cosine",
),
"image": qdrant.QdrantVectorDef(
schema=VectorSchema(dtype=np.float32, size=512),
distance="dot",
),
}
),
)
# Declare points with named vectors
for doc in documents:
point = qdrant.PointStruct(
id=doc.id,
vector={
"text": doc.text_embedding.tolist(),
"image": doc.image_embedding.tolist(),
},
payload={"title": doc.title, "url": doc.url},
)
collection.declare_point(point)
from qdrant_client.http import models as qdrant_models
from cocoindex.resources.schema import VectorSchema
import numpy as np
@coco.fn
async def app_main() -> None:
collection = await qdrant.mount_collection_target(
QDRANT_DB,
"hybrid_docs",
await qdrant.CollectionSchema.create(
vectors={
"dense": qdrant.QdrantVectorDef(
schema=VectorSchema(dtype=np.float32, size=384),
distance="cosine",
),
"sparse": qdrant.QdrantSparseVectorDef(modifier="idf"),
},
),
)
for chunk in chunks:
point = qdrant.PointStruct(
id=chunk.id,
vector={
"dense": chunk.dense_embedding.tolist(),
"sparse": qdrant_models.SparseVector(
indices=chunk.sparse_indices,
values=chunk.sparse_values,
),
},
payload={"text": chunk.text},
)
collection.declare_point(point)
Qdrant only accepts two point ID types — this is enforced by the Qdrant server itself (see Qdrant's Point IDs documentation):
int — unsigned 64-bit integers (0 <= id < 2**64)str — UUID strings, in any standard textual form (hyphenated, 32-character hex, or URN)uuid.UUID instances are also accepted and converted to strings. declare_point validates the ID eagerly and raises ValueError for anything else, rather than letting the write fail later at the Qdrant server.
To derive a stable point ID from an arbitrary string key (e.g. a document path or chunk key), use a deterministic UUID:
import uuid
point_id = str(uuid.uuid5(uuid.NAMESPACE_URL, f"doc/{chunk_key}"))
Point payloads are schemaless JSON objects. Any JSON-serializable Python data structure can be used:
payload = {
"text": "Document content",
"metadata": {
"author": "Alice",
"tags": ["machine-learning", "nlp"],
"published": "2024-01-15",
},
"stats": {
"views": 1500,
"likes": 42,
},
}
The connector focuses on writing points to Qdrant. For vector search, use the Qdrant client directly:
from qdrant_client.http import models as qdrant_models
# Get the registered client
client = qdrant.create_client("http://localhost:6333")
# Perform search
results = client.query_points(
collection_name="documents",
query=query_embedding.tolist(),
limit=10,
with_payload=True,
)
for point in results.points:
print(f"Score: {point.score}, ID: {point.id}")
print(f"Payload: {point.payload}")
For named vectors:
results = client.query_points(
collection_name="documents",
query=query_embedding.tolist(),
using="text", # Search using the "text" vector
limit=10,
)
For hybrid dense + sparse retrieval, use Qdrant's query API with prefetches and reciprocal rank fusion:
results = client.query_points(
collection_name="hybrid_docs",
prefetch=[
qdrant_models.Prefetch(
query=query_dense_embedding.tolist(),
using="dense",
limit=100,
),
qdrant_models.Prefetch(
query=qdrant_models.SparseVector(
indices=query_sparse_indices,
values=query_sparse_values,
),
using="sparse",
limit=100,
),
],
query=qdrant_models.FusionQuery(fusion=qdrant_models.Fusion.RRF),
limit=10,
with_payload=True,
)