Claude Code for Qdrant Vector DB — Guide

Written by Michael Lip · Solo founder of Zovo · $400K+ on Upwork · 100% JSS Join 50+ builders · More at zovo.one

The Setup

You are building vector search with Qdrant, a high-performance vector database written in Rust. Qdrant provides similarity search with filtering, payload storage, and multi-vector support for RAG applications, recommendation systems, and semantic search. Claude Code can implement vector search, but it generates Pinecone or simple FAISS code instead of Qdrant’s rich API.

What Claude Code Gets Wrong By Default

  1. Uses Pinecone’s API. Claude writes pinecone.Index('my-index').upsert(vectors). Qdrant uses client.upsert(collection_name, points) with a different data model — points have IDs, vectors, and payloads.

  2. Creates in-memory FAISS indexes. Claude builds vector search with faiss.IndexFlatL2. FAISS lacks persistence, filtering, and payload storage. Qdrant provides persistent storage, metadata filtering, and a full REST/gRPC API.

  3. Ignores payload filtering. Claude retrieves all similar vectors then filters in Python. Qdrant supports filter conditions on payloads during search — search(query_filter=Filter(...)) combines vector similarity with metadata filtering in a single query.

  4. Uses flat vector storage. Claude stores one vector per document. Qdrant supports named vectors (multiple vector spaces per point), sparse vectors for hybrid search, and quantization for memory efficiency.

The CLAUDE.md Configuration

# Qdrant Vector Search Project

## Database
- Engine: Qdrant (Rust vector database)
- API: REST and gRPC
- Client: qdrant-client (Python/JS/Rust)
- Storage: persistent with snapshots

## Qdrant Rules
- Collection: create with vector size and distance metric
- Points: id + vector + payload (metadata)
- Search: query vector + optional filter + limit
- Filter: Match, Range, Geo on payload fields
- Named vectors: multiple vector spaces per collection
- Quantization: scalar/product for memory savings
- Batch: upsert with batches of 100-500 points

## Conventions
- Create collection before upserting points
- Use cosine distance for normalized embeddings
- Store metadata in payloads for filtering
- Batch upserts for large datasets
- Use scroll API for iterating all points
- Create payload indexes for frequently filtered fields
- Snapshots for backup: client.create_snapshot()

Workflow Example

You want to build a semantic search API for a document collection. Prompt Claude Code:

“Create a Qdrant-powered semantic search API. Set up a collection for document embeddings with 1536 dimensions (OpenAI embeddings), upsert documents with title, category, and date payloads, and implement search with optional category filtering and date range. Use the Python client.”

Claude Code should create a collection with vectors_config=VectorParams(size=1536, distance=Distance.COSINE), a function to upsert documents as points with payloads, create payload indexes on category and date, and a search function that accepts a query vector with optional Filter combining FieldCondition for category match and date range.

Common Pitfalls

  1. Missing payload indexes for filtered search. Claude filters on payload fields without creating indexes. Without indexes, Qdrant performs full scan filtering which is slow on large collections. Create indexes with client.create_payload_index() on frequently filtered fields.

  2. Upserting one point at a time. Claude upserts documents in a loop, one API call per document. Batch upserts (100-500 points per call) are dramatically faster — accumulate points and upsert in batches.

  3. Wrong distance metric for embeddings. Claude uses Euclidean distance with normalized embeddings. OpenAI and most embeddings are normalized — use Distance.COSINE or Distance.DOT for correct similarity ranking.