dist/plugins/api-vector-db-qdrant/skills/api-vector-db-qdrant/SKILL.md
Qdrant vector database -- collection management, point operations, payload filtering, named vectors, quantization, recommendations, snapshots
npx skillsauth add agents-inc/skills api-vector-db-qdrantInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Quick Guide: Use
@qdrant/js-client-rest(v1.17.x) for high-performance vector search. Collections define vector dimensions and distance metrics upfront -- mismatches cause silent failures. Usemust/should/must_notfilter clauses with payload conditions (not Pinecone-style$eq/$and). Payload indexes are optional but critical for filter performance at scale -- create them explicitly withcreatePayloadIndex(). Named vectors let you store multiple embeddings per point (e.g., title + content). Quantization (scalar/binary/product) trades accuracy for memory and speed. Thequery()method is the universal search endpoint -- prefer it over the oldersearch()method.
<critical_requirements>
All code must follow project conventions in CLAUDE.md (kebab-case, named exports, import ordering,
import type, named constants)
(You MUST create payload indexes with createPayloadIndex() for any field used in filters -- unindexed fields cause full scans that degrade linearly with collection size)
(You MUST use must/should/must_not filter syntax -- Qdrant does NOT use $eq/$and/$or operators like Pinecone)
(You MUST match vector dimensions exactly between embedding model output and collection config -- dimension mismatches cause silent upsert failures or corrupt search results)
(You MUST set wait: true on writes when subsequent reads depend on the data -- Qdrant writes are asynchronous by default and may not be immediately visible)
</critical_requirements>
Additional resources:
Auto-detection: Qdrant, QdrantClient, @qdrant/js-client-rest, createCollection, upsert, query, scroll, recommend, setPayload, createPayloadIndex, must, should, must_not, payload, named vectors, quantization, vector database, similarity search, semantic search, RAG retrieval, embedding search
When to use:
Key patterns covered:
When NOT to use:
Qdrant is a high-performance open-source vector database built in Rust, designed for filtered similarity search at scale. The core principle: store vectors with rich payloads, search by similarity, filter by payload conditions.
Core principles:
createPayloadIndex(). Without indexes, filters cause full collection scans.always_ram: true to keep quantized vectors in memory for speed.wait: true when immediate consistency matters (e.g., read-after-write flows).Create a QdrantClient connected to a local instance or Qdrant Cloud. See examples/core.md for full examples.
// Good Example
import { QdrantClient } from "@qdrant/js-client-rest";
function createQdrantClient(): QdrantClient {
const url = process.env.QDRANT_URL;
const apiKey = process.env.QDRANT_API_KEY;
if (!url) {
throw new Error("QDRANT_URL environment variable is required");
}
return new QdrantClient({ url, apiKey });
}
export { createQdrantClient };
Why good: URL and API key from environment, validation before construction, named export
// Bad Example
import { QdrantClient } from "@qdrant/js-client-rest";
const client = new QdrantClient({
host: "my-cluster.cloud.qdrant.io",
apiKey: "sk-abc123...",
});
// Hardcoded credentials leak in version control
Why bad: Hardcoded API key, host without HTTPS (use url with full protocol for cloud)
Define vector dimensions and distance metric. Dimension must exactly match your embedding model output. See examples/core.md.
// Good Example
const EMBEDDING_DIMENSION = 1536;
await client.createCollection("documents", {
vectors: {
size: EMBEDDING_DIMENSION,
distance: "Cosine",
},
});
export { EMBEDDING_DIMENSION };
Why good: Named constant for dimension, explicit distance metric, clean config
// Bad Example
await client.createCollection("documents", {
vectors: { size: 768, distance: "Cosine" },
// Dimension mismatch if using a 1536-dim model -- upserts may silently fail or produce garbage search results
});
Why bad: Hardcoded dimension that may not match embedding model, no named constant
Upsert vectors with payload (Qdrant's term for metadata). See examples/core.md.
// Good Example
interface DocumentPayload {
title: string;
category: string;
createdAt: number;
tags: string[];
}
await client.upsert("documents", {
wait: true,
points: [
{
id: "doc-1",
vector: embedding,
payload: {
title: "Guide",
category: "tutorial",
createdAt: 1710000000,
tags: ["ai", "search"],
},
},
],
});
Why good: Typed payload interface, wait: true for immediate consistency, structured payload
Use must/should/must_not filter clauses -- NOT Pinecone-style $eq/$and. See examples/filtering.md.
// Good Example
const TOP_K = 10;
const results = await client.query("documents", {
query: queryEmbedding,
filter: {
must: [
{ key: "category", match: { value: "tutorial" } },
{ key: "createdAt", range: { gte: 1700000000 } },
],
},
with_payload: true,
limit: TOP_K,
});
for (const point of results.points) {
console.log(point.id, point.score, point.payload);
}
Why good: Named constant for limit, Qdrant filter syntax (must + match/range), with_payload included
// Bad Example -- Pinecone syntax does NOT work in Qdrant
const results = await client.query("documents", {
query: embedding,
filter: {
$and: [{ category: { $eq: "tutorial" } }],
},
limit: 100,
});
Why bad: Pinecone-style $and/$eq operators are invalid in Qdrant, magic number for limit
Store multiple embeddings per point. See examples/named-vectors-quantization.md.
// Good Example
const TITLE_DIM = 384;
const CONTENT_DIM = 1536;
await client.createCollection("articles", {
vectors: {
title: { size: TITLE_DIM, distance: "Cosine" },
content: { size: CONTENT_DIM, distance: "Cosine" },
},
});
// Upsert with named vectors
await client.upsert("articles", {
wait: true,
points: [
{
id: "article-1",
vector: { title: titleEmbedding, content: contentEmbedding },
payload: { title: "Intro to Vectors" },
},
],
});
// Search by specific named vector
const results = await client.query("articles", {
query: queryEmbedding,
using: "content",
limit: TOP_K,
});
Why good: Different dimensions per named vector, using specifies which vector to search, avoids duplicating payloads across collections
Find similar points using positive/negative examples. See examples/recommendations-batch.md.
// Good Example
const results = await client.query("documents", {
query: {
recommend: {
positive: [1, 42],
negative: [7],
strategy: "best_score",
},
},
limit: TOP_K,
with_payload: true,
});
Why good: Uses point IDs as positive/negative examples, best_score strategy handles negatives better than default average_vector
<decision_framework>
Which distance metric should I use?
|-- Using normalized embeddings (OpenAI, Cohere)? -> Cosine (most common, safe default)
|-- Pre-normalized embeddings and need speed? -> Dot (faster, same results as Cosine for unit vectors)
|-- Raw feature vectors where magnitude matters? -> Euclid (L2 distance)
|-- City-block distance needed? -> Manhattan
'-- Unsure? -> Cosine (works with any embedding model)
How many embeddings per point?
|-- One embedding model? -> Single vector (simpler config)
|-- Multiple embedding models (title + content)? -> Named vectors
|-- Same model, different text segments? -> Named vectors
|-- Multi-modal (text + image)? -> Named vectors with different dimensions
'-- Want to avoid duplicating payloads across collections? -> Named vectors
How should I optimize memory?
|-- Good default, balanced accuracy/speed? -> Scalar (int8, 4x compression)
|-- Maximum speed, can tolerate accuracy loss? -> Binary (32x compression)
| '-- Best with high-dimensional models (>= 1024 dims)
|-- Maximum compression, speed not critical? -> Product (up to 64x compression)
| '-- Slowest quantization, most accuracy loss
'-- No memory pressure? -> Skip quantization (full float32 precision)
Should I create a payload index?
|-- Field used in filter conditions? -> YES, always index
|-- Field used in order_by for scroll? -> YES, index for sort performance
|-- Field only read after search (display only)? -> NO, skip index
|-- High-cardinality field (UUIDs, timestamps)? -> YES, but evaluate index type
'-- Low-cardinality field (enum-like)? -> YES, keyword index is very efficient
</decision_framework>
<red_flags>
High Priority Issues:
$eq, $and, $or) -- Qdrant uses must/should/must_not with match/range conditionswait: true when read-after-write consistency is needed -- writes are async by defaultMedium Priority Issues:
search() method instead of query() -- query() is the universal endpoint with prefetch and fusion supportwith_payload: true in queries -- payload is NOT included by defaultoffset for deep pagination in scroll -- performance degrades; use offset as cursor (point ID), not page numberCommon Mistakes:
filter at the wrong nesting level -- filter goes at the top level of the query args, not nested inside another objectid: 0 as a point ID -- Qdrant requires positive integers or UUID strings; 0 is invalidsetPayload (merge) with overwritePayload (replace) -- setPayload merges fields, overwritePayload replaces the entire payloaddeletePayload with field names but no point selector -- you must specify which points to update via points array or filterGotchas & Edge Cases:
scroll() with order_by requires a payload index on the sort field -- without it, the request failscount() with exact: true is slow on large collections -- use exact: false (default) for approximate countsquery() with prefetch enables multi-stage retrieval (retrieve 1000, then re-rank to top 10) -- but requires understanding the prefetch pipelineusing parameter -- omitting it searches the default (unnamed) vector, which may not existdeletePayload removes specific keys, clearPayload removes ALL keys -- they are different operations</red_flags>
<critical_reminders>
All code must follow project conventions in CLAUDE.md (kebab-case, named exports, import ordering,
import type, named constants)
(You MUST create payload indexes with createPayloadIndex() for any field used in filters -- unindexed fields cause full scans that degrade linearly with collection size)
(You MUST use must/should/must_not filter syntax -- Qdrant does NOT use $eq/$and/$or operators like Pinecone)
(You MUST match vector dimensions exactly between embedding model output and collection config -- dimension mismatches cause silent upsert failures or corrupt search results)
(You MUST set wait: true on writes when subsequent reads depend on the data -- Qdrant writes are asynchronous by default and may not be immediately visible)
Failure to follow these rules will cause empty search results, degraded filter performance, data consistency issues, and hard-to-debug dimension mismatch errors.
</critical_reminders>
development
Material Design component library for Vue 3
development
VitePress 1.x — Vue-powered static site generator for documentation sites, built on Vite
tools
Docusaurus 3.x documentation framework — site configuration, docs/blog plugins, sidebars, versioning, MDX, swizzling, and deployment
development
TanStack Form patterns - useForm, form.Field, validators, arrays, linked fields, createFormHook, type safety