Qdrant

Qdrant is a standalone vector database (Rust). It serves HTTP 6333 (REST / dashboard) and 6334 (gRPC). Use it when you need payload filters, many clients, or hybrid search later. Docs: qdrant.tech/documentation.

Keep Chroma for laptop spikes and pgvector when Postgres is already the system of record.


Start with Docker

You need Docker locally. The official quickstart maps both ports and mounts a volume.

docker pull qdrant/qdrant
docker run -p 6333:6333 -p 6334:6334 \
  -v "$(pwd)/qdrant_storage:/qdrant/storage:z" \
  qdrant/qdrant
docker pull qdrant/qdrant
docker run -p 6333:6333 -p 6334:6334 `
  -v "${PWD}/qdrant_storage:/qdrant/storage" `
  qdrant/qdrant

Open http://localhost:6333/dashboard. Health:

curl http://localhost:6333/readyz
Invoke-RestMethod http://localhost:6333/readyz

If you only publish 6333, Python clients that prefer gRPC cannot reach 6334. Mapping both ports is the boring, reliable choice. Docker basics: Docker tutorial.


pip install qdrant-client
from qdrant_client import QdrantClient
from qdrant_client.models import Distance, PointStruct, VectorParams

client = QdrantClient(url="http://localhost:6333")

client.create_collection(
    collection_name="faq",
    vectors_config=VectorParams(size=1536, distance=Distance.COSINE),
)

vec = [0.0] * 1536  # replace with a real embedding
vec[0] = 0.12
client.upsert(
    collection_name="faq",
    wait=True,
    points=[
        PointStruct(
            id=1,
            vector=vec,
            payload={
                "source": "refund.md",
                "dept": "hr",
                "text": "Refunds must be requested within 7 days of delivery.",
            },
        )
    ],
)

hits = client.query_points(
    collection_name="faq",
    query=vec,
    limit=3,
    with_payload=True,
).points
print([(h.id, h.payload) for h in hits])

upsert inserts or overwrites the same id. Older samples call client.search(...); new code should follow the official quickstart and use query_points. size must match the embedding (OpenAI small = 1536, bge-m3 ≈ 1024).


Payload filters

from qdrant_client.models import FieldCondition, Filter, MatchValue

hits = client.query_points(
    collection_name="faq",
    query=vec,
    query_filter=Filter(
        must=[FieldCondition(key="dept", match=MatchValue(value="hr"))]
    ),
    limit=5,
    with_payload=True,
).points

This is the first layer of “hybrid”: semantic neighbors and department / language / time constraints. A full FAQ + filter walkthrough is Practical Examples example 3.


Hybrid teaser (dense + sparse)

Qdrant can store dense and sparse (BM25-style) vectors in one collection, then fuse ranks (RRF). Get dense + filter solid first:

from qdrant_client.models import Fusion, FusionQuery, Prefetch

# Sketch: the collection must declare named vectors "dense" and "sparse"
# hits = client.query_points(
#     collection_name="faq",
#     prefetch=[
#         Prefetch(query=dense_vec, using="dense", limit=20),
#         Prefetch(query=sparse_vec, using="sparse", limit=20),
#     ],
#     query=FusionQuery(fusion=Fusion.RRF),
# ).points

Clause numbers and proper nouns often prefer sparse; paraphrases prefer dense. Before combining them, compare “dense only” vs “dense + filter” with hit@k in Evaluation.


Next steps

评论