$npx -y skills add AlexAI-MCP/hermes-CCC --skill qdrantHigh-performance vector search engine for production RAG — Rust-powered, horizontal scaling, hybrid dense+sparse search, metadata filtering.
| 1 | # Qdrant — Production Vector Search Engine |
| 2 | |
| 3 | High-performance, Rust-powered vector database for production RAG systems. Best for self-hosted deployments needing speed and horizontal scale. |
| 4 | |
| 5 | ## When to Use Qdrant vs Alternatives |
| 6 | |
| 7 | - **Qdrant**: Production self-hosted, need speed + filtering + scale |
| 8 | - **Chroma**: Local dev, simple RAG prototypes |
| 9 | - **Pinecone**: Managed cloud, don't want to self-host |
| 10 | - **FAISS**: Pure in-memory, research, maximum speed |
| 11 | |
| 12 | --- |
| 13 | |
| 14 | ## Setup |
| 15 | |
| 16 | ```bash |
| 17 | pip install qdrant-client sentence-transformers |
| 18 | |
| 19 | # Run Qdrant server |
| 20 | docker run -d -p 6333:6333 -p 6334:6334 \ |
| 21 | -v $(pwd)/qdrant_storage:/qdrant/storage \ |
| 22 | qdrant/qdrant |
| 23 | ``` |
| 24 | |
| 25 | --- |
| 26 | |
| 27 | ## Connect |
| 28 | |
| 29 | ```python |
| 30 | from qdrant_client import QdrantClient |
| 31 | from qdrant_client.models import Distance, VectorParams |
| 32 | |
| 33 | # Local Docker |
| 34 | client = QdrantClient(host="localhost", port=6333) |
| 35 | |
| 36 | # Cloud |
| 37 | client = QdrantClient( |
| 38 | url="https://your-cluster.aws.cloud.qdrant.io", |
| 39 | api_key="your-api-key" |
| 40 | ) |
| 41 | |
| 42 | # In-memory (testing) |
| 43 | client = QdrantClient(":memory:") |
| 44 | ``` |
| 45 | |
| 46 | --- |
| 47 | |
| 48 | ## Create Collection |
| 49 | |
| 50 | ```python |
| 51 | client.create_collection( |
| 52 | collection_name="my_docs", |
| 53 | vectors_config=VectorParams( |
| 54 | size=384, # match embedding model dimension |
| 55 | distance=Distance.COSINE, # COSINE | EUCLID | DOT |
| 56 | ), |
| 57 | ) |
| 58 | ``` |
| 59 | |
| 60 | --- |
| 61 | |
| 62 | ## Upsert Points |
| 63 | |
| 64 | ```python |
| 65 | from qdrant_client.models import PointStruct |
| 66 | from sentence_transformers import SentenceTransformer |
| 67 | import uuid |
| 68 | |
| 69 | model = SentenceTransformer("all-MiniLM-L6-v2") |
| 70 | |
| 71 | documents = [ |
| 72 | {"text": "Python async programming", "source": "docs", "year": 2024}, |
| 73 | {"text": "Machine learning with PyTorch", "source": "tutorial", "year": 2023}, |
| 74 | ] |
| 75 | |
| 76 | embeddings = model.encode([d["text"] for d in documents]) |
| 77 | |
| 78 | points = [ |
| 79 | PointStruct( |
| 80 | id=str(uuid.uuid4()), |
| 81 | vector=emb.tolist(), |
| 82 | payload=doc, |
| 83 | ) |
| 84 | for doc, emb in zip(documents, embeddings) |
| 85 | ] |
| 86 | |
| 87 | client.upsert(collection_name="my_docs", points=points) |
| 88 | ``` |
| 89 | |
| 90 | --- |
| 91 | |
| 92 | ## Search |
| 93 | |
| 94 | ```python |
| 95 | from qdrant_client.models import Filter, FieldCondition, MatchValue |
| 96 | |
| 97 | query = "how to write async code?" |
| 98 | q_vec = model.encode([query])[0].tolist() |
| 99 | |
| 100 | # Basic search |
| 101 | results = client.search( |
| 102 | collection_name="my_docs", |
| 103 | query_vector=q_vec, |
| 104 | limit=5, |
| 105 | ) |
| 106 | |
| 107 | # With metadata filter |
| 108 | results = client.search( |
| 109 | collection_name="my_docs", |
| 110 | query_vector=q_vec, |
| 111 | query_filter=Filter( |
| 112 | must=[FieldCondition(key="source", match=MatchValue(value="docs"))] |
| 113 | ), |
| 114 | limit=5, |
| 115 | with_payload=True, |
| 116 | ) |
| 117 | |
| 118 | for r in results: |
| 119 | print(f"[{r.score:.3f}] {r.payload['text']}") |
| 120 | ``` |
| 121 | |
| 122 | --- |
| 123 | |
| 124 | ## Hybrid Search (Dense + Sparse) |
| 125 | |
| 126 | ```python |
| 127 | from qdrant_client.models import SparseVector, NamedSparseVector, NamedVector |
| 128 | |
| 129 | # Setup collection with both dense and sparse |
| 130 | client.create_collection( |
| 131 | collection_name="hybrid", |
| 132 | vectors_config={ |
| 133 | "dense": VectorParams(size=384, distance=Distance.COSINE), |
| 134 | }, |
| 135 | sparse_vectors_config={ |
| 136 | "sparse": SparseVectorParams(), |
| 137 | }, |
| 138 | ) |
| 139 | |
| 140 | # Search with RRF fusion |
| 141 | from qdrant_client.models import Prefetch, FusionQuery, Fusion |
| 142 | |
| 143 | results = client.query_points( |
| 144 | collection_name="hybrid", |
| 145 | prefetch=[ |
| 146 | Prefetch(query=dense_vec, using="dense", limit=20), |
| 147 | Prefetch(query=SparseVector(indices=[1,5,3], values=[0.1, 0.8, 0.5]), |
| 148 | using="sparse", limit=20), |
| 149 | ], |
| 150 | query=FusionQuery(fusion=Fusion.RRF), |
| 151 | limit=5, |
| 152 | ) |
| 153 | ``` |
| 154 | |
| 155 | --- |
| 156 | |
| 157 | ## Filtering Operations |
| 158 | |
| 159 | ```python |
| 160 | from qdrant_client.models import ( |
| 161 | Filter, FieldCondition, MatchValue, MatchAny, |
| 162 | Range, HasIdCondition |
| 163 | ) |
| 164 | |
| 165 | # Match value |
| 166 | Filter(must=[FieldCondition(key="source", match=MatchValue(value="docs"))]) |
| 167 | |
| 168 | # Match any of |
| 169 | Filter(must=[FieldCondition(key="category", match=MatchAny(any=["tech", "science"]))]) |
| 170 | |
| 171 | # Range filter |
| 172 | Filter(must=[FieldCondition(key="year", range=Range(gte=2023, lte=2025))]) |
| 173 | |
| 174 | # Combine |
| 175 | Filter( |
| 176 | must=[FieldCondition(key="source", match=MatchValue(value="docs"))], |
| 177 | should=[FieldCondition(key="year", range=Range(gte=2024))], |
| 178 | must_not=[FieldCondition(key="archived", match=MatchValue(value=True))], |
| 179 | ) |
| 180 | ``` |
| 181 | |
| 182 | --- |
| 183 | |
| 184 | ## Delete / Update |
| 185 | |
| 186 | ```python |
| 187 | # Delete by IDs |
| 188 | client.delete(collection_name="my_docs", points_selector=["id1", "id2"]) |
| 189 | |
| 190 | # Delete by filter |
| 191 | from qdrant_client.models import FilterSelector |
| 192 | client.delete( |
| 193 | collection_name="my_docs", |
| 194 | points_selector=FilterSelector( |
| 195 | filter=Filter(must=[FieldCondition(key="source", match=MatchValue(value="old"))]) |
| 196 | ) |
| 197 | ) |
| 198 | |
| 199 | # Collection info |
| 200 | info = client.get_collection("my_docs") |
| 201 | print(f"Vectors: {info.points_count}") |
| 202 | ``` |
| 203 | |
| 204 | --- |
| 205 | |
| 206 | ## Batch Upsert (Large Datasets) |
| 207 | |
| 208 | ```python |
| 209 | BATCH_SIZE = 100 |