diff --git a/qdrant-landing/content/course/beginners/_index.md b/qdrant-landing/content/course/beginners/_index.md new file mode 100644 index 0000000000..3591bf4227 --- /dev/null +++ b/qdrant-landing/content/course/beginners/_index.md @@ -0,0 +1,157 @@ +--- +title: "Beginners Course" +page_title: "Qdrant Beginners Course" +short_description: "Learn the fundamentals of vector search: why keyword search fails, how semantic search works, embeddings, distance metrics, and hybrid systems." +description: "Understand the fundamentals of vector search. Learn why keyword search breaks, how semantic search with vectors solves it, and build your first search system." +content: + sidebarTitle: "Beginners Course" + menuTitle: + text: Course Overview + url: /course/beginners/ + nextButton: Continue to Next Step + nextDay: Complete + title: "Beginners Course" + description: "Understand the fundamentals of vector search. Learn why keyword search breaks, how semantic search with vectors solves it, and build your first search system." +partition: course +isLesson: true +--- + +# Beginners Course + +**Learn the fundamentals of vector search** + +Understand why traditional keyword search fails and how semantic (vector) search solves it. Learn about embeddings, distance metrics, and hybrid search systems. + +
+ +{{< cards-list >}} +- icon: /icons/outline/play-white.svg + title: Multiple modules + content: Focused lessons building from fundamentals to practical applications +- icon: /icons/outline/cloud-check-blue.svg + title: Shareable certificate + content: Earn a digital certificate upon completion +- icon: /icons/outline/time-blue.svg + title: Flexible schedule + content: Learn at your own pace +- icon: /icons/outline/plan.svg + title: Beginner level + content: No prior experience required + +{{< /cards-list >}} + +
+ +## What you'll learn +{{< course-card + title="Skills you'll gain:" + image="/icons/outline/training-white.svg" + type="wide-list">}} + +- Why keyword search breaks and how semantic search solves it +- How embeddings convert text to vectors that capture meaning +- Distance metrics: cosine similarity, dot product, and Euclidean +- Hybrid search: combining dense and sparse retrieval +- Building your first Qdrant collection and queries + +{{< /course-card >}} + +### The Path + +**Module 0**: Setup. Configure your environment and get started with the basics. + +**Module 1**: Let's Understand Search. Understand why keyword search fails and how semantic search solves it. + +**Module 2**: First Principles of Vector Search. Learn what vectors are, how dimensions represent meaning, similarity metrics, and build your first Qdrant collection. + +## How the course works + +{{< cards-list >}} + +- icon: /icons/outline/training-purple.svg + title: Clear lessons + content: Focused modules by the Qdrant team +- icon: /icons/outline/hacker-purple.svg + title: Hands-on learning + content: Practical examples and exercises +- icon: /icons/outline/similarity-blue.svg + title: Progressive learning + content: Build from fundamentals to advanced concepts +- icon: /icons/outline/copy.svg + title: Self-paced + content: Learn at your own speed + {{< /cards-list >}} + +
+ +## Syllabus + +{{< accordion >}} +- title: "Module 0: Setting Up Dependencies" + content: | + - Qdrant Cloud Setup + - Implementing a Basic Vector Search + - Project: Building Your First Vector Search System +
+
+

→ Start Module 0

+ +- title: "Module 1: Let's Understand Search" + content: | + - The Problem: Why Keyword Search Breaks + - How Traditional Search Improved + - Enter Semantic Search + - How It Works: Embeddings + - Comparing Meaning: Distance Metrics + - Why Similarity Alone Is Not Enough + - Modern Search = Hybrid Systems + - References & Further Reading +
+
+

→ Coming soon

+ +- title: "Module 2: First Principles of Vector Search" + content: | + - What is a Vector? + - How Dimensions Represent Meaning + - Similarity Under the Hood + - Your First Qdrant Collection + - Points, Payloads, and Queries +
+
+

→ Coming soon

+ +- title: "Module 3: Sparse vs Dense vs Hybrid Search" + content: | + - The Two Families of Search + - Hybrid Search: Dense + Sparse + Filters + - Setting Up Hybrid Search in Qdrant + - Fusion Strategies + - Beyond Text: Multimodal Search + - Real-World Use Cases +
+
+

→ Coming soon

+{{< /accordion >}} + +## Who it's for + +Anyone new to vector search who wants to understand the fundamentals. No prior experience with Qdrant or vector databases required. + +## Time commitment + +- Duration: Multiple modules +- Self-paced learning +- Flexible schedule + + +{{< course-card + title="Ready to start your vector search journey?" + image="/icons/outline/rocket-white-light.svg" + link="/course/beginners/module-0/">}} +**What you'll get** +- Understand the fundamentals of vector search +- Learn why semantic search outperforms keyword search +- Build your first Qdrant collection +- Foundation for advanced courses +{{< /course-card >}} diff --git a/qdrant-landing/content/course/beginners/module-0/_index.md b/qdrant-landing/content/course/beginners/module-0/_index.md new file mode 100644 index 0000000000..1fd62b8d5a --- /dev/null +++ b/qdrant-landing/content/course/beginners/module-0/_index.md @@ -0,0 +1,21 @@ +--- +title: "Module 0: Setting Up Dependencies" +short_description: "Module 0 of the Beginners course: set up Qdrant Cloud, build a first vector search, and get started with the basics." +description: "Set up Qdrant and build your first vector search app. Learn how to configure Qdrant Cloud, run a basic search, and get started with the fundamentals." +isLesson: true +weight: 10 +--- + +{{< date >}} Module 0 {{< /date >}} + +# Setting Up Dependencies + +Get started with Qdrant by setting up your environment and building your first vector search application. + +## Today's path + +1. Qdrant Cloud Setup +2. Implementing a Basic Vector Search +3. Project: Building Your First Vector Search System + +By the end, you'll have a working Qdrant setup and a complete first search running. diff --git a/qdrant-landing/content/course/beginners/module-0/qdrant-cloud.md b/qdrant-landing/content/course/beginners/module-0/qdrant-cloud.md new file mode 100644 index 0000000000..e005cf3815 --- /dev/null +++ b/qdrant-landing/content/course/beginners/module-0/qdrant-cloud.md @@ -0,0 +1,176 @@ +--- +title: "Qdrant Setup" +short_description: "Spin up a managed Qdrant Cloud cluster, generate API keys, and explore the Web UI for collections, points, and cluster monitoring." +description: Set up your Qdrant Cloud cluster in minutes. Learn to create collections, manage data, access the Web UI, and connect securely from Python. +weight: 2 +isLesson: true +--- + +{{< date >}} Module 0 {{< /date >}} + +# Qdrant Setup + +
+ +
+ +
+ +Spin up production-grade vector search in minutes. Qdrant Cloud gives you a managed endpoint with TLS, automatic backups, high-availability options, and a clean API. + +## Create your cluster + +1. Sign up at [cloud.qdrant.io](https://cloud.qdrant.io/signup) with email, Google, or GitHub. +2. Open **Clusters** → **Create a Free Cluster**. The Free Tier is enough for this course. + +![Create cluster](/docs/gettingstarted/gui-quickstart/create-cluster.png) + +3. Pick a region close to your users or app. +4. When the cluster is ready, copy the API key and store it securely. You can make new keys later from **API Keys** on the cluster page. + +![Get API key](/docs/gettingstarted/gui-quickstart/api-key.png) + + +## Access the Web UI + +1. Click **Cluster UI** in the top-right of the cluster page to open the dashboard. + +![Access dashboard](/docs/gettingstarted/gui-quickstart/access-dashboard.png) + +### What you can do in the Web UI + +Use the Web UI to manage collections, inspect data, and debug search performance. + +#### Main Navigation + +**Console**: Run REST calls in the browser. Test endpoints, inspect responses, and debug queries without writing code. Handy for exploring the full API. + +**Collections**: See and manage all collections. Create collections, upload snapshots, and track status, size, and configuration at a glance. + +**Tutorial**: Follow an interactive walkthrough with sample data. Create a collection, add vectors, and run semantic search with live results. + +![Interactive tutorial](/docs/gettingstarted/gui-quickstart/interactive-tutorial.png) + +**Datasets**: Bulk-load preconfigured public datasets into your cluster. + +#### Inside a Collection + +When you open a collection by clicking it's name, + +![Select collection](/courses/day0/select-collection.png) + +you'll get a detailed view with these tabs: + +![Collection points](/courses/day0/collection-points.png) + +* **Points Tab**: Inspect, search, and manage individual points. Use the search bar to find by ID or filter by payload fields (e.g., `colony: "Mars"`). For each point, you can: + + * See its payload and vector(s). + * Click **Find Similar** to run an ad-hoc similarity search. + * Click **Open Graph** to jump to a graph view of its HNSW connections. + +* **Info Tab**: Get a full overview of collection health, config, and stats. Key fields: + + * `status`: `green` means healthy. + * `points_count`: Number of active points. + * `indexed_vectors_count`: Points currently in the HNSW index. If this lags behind `points_count`, background indexing is still running. + * `config`: JSON view of all parameters, from vector settings to optimizer options. + +* **Cluster Tab**: See how shards are placed across nodes. Use it to monitor health, find hot spots, and verify shard placement in distributed setups. + +* **Search Quality Tab**: Evaluate and benchmark retrieval precision against ground truth. Tune parameters and measure the impact on accuracy. + +* **Snapshots Tab**: Manage backups for this collection. Create a [snapshot](/documentation/snapshots/), restore it later, or migrate it to another cluster. + +* **Visualize Tab**: Explore your vector space with an interactive 2D projection. See clusters, spot outliers, and build intuition about your embeddings. + +* **Graph Tab**: Explore the HNSW graph interactively. Start from any point, follow nearest neighbors, and see how the graph structure powers fast search. + +## Connect from Python + +Store credentials in an `.env` file at the root of your working directory or in colab: + +```env +QDRANT_URL=https://YOUR-CLUSTER.cloud.qdrant.io:6333 +QDRANT_API_KEY=YOUR_API_KEY +``` + +Load the credentials from the environment and create a Qdrant client: + +```python +from qdrant_client import QdrantClient, models +import os + +client = QdrantClient(url=os.getenv("QDRANT_URL"), api_key=os.getenv("QDRANT_API_KEY")) + +# For Colab: +# from google.colab import userdata +# client = QdrantClient(url=userdata.get("QDRANT_URL"), api_key=userdata.get("QDRANT_API_KEY")) + +# Quick health check +collections = client.get_collections() +print(f"Connected to Qdrant Cloud: {len(collections.collections)} collections") +``` + +## Other ways to connect + +You can also send your key in the `Authorization` header: + +```bash +# Using api-key header +curl -X GET https://xyz-example.eu-central.aws.cloud.qdrant.io:6333/collections \ + --header 'api-key: ' + +# Using Authorization header +curl -X GET https://xyz-example.eu-central.aws.cloud.qdrant.io:6333/collections \ + --header 'Authorization: Bearer ' +``` + +## Quick validation + +Check basic connectivity: + +```bash +# Service health +curl -s "$QDRANT_URL/healthz" -H "api-key: $QDRANT_API_KEY" + +# List collections +curl -s "$QDRANT_URL/collections" -H "api-key: $QDRANT_API_KEY" +``` + +## Good practices + +* Keep secrets out of code; use environment variables or a secret manager. +* Restrict access with IP allow-lists or private networking. +* Rotate API keys regularly from the cluster **Access** tab. +* Use HTTPS only; turn on RBAC and strict limits when exposing endpoints to untrusted clients. + +## Common issues + +* **Authentication error**: Recheck the API key and the `api-key` header. +* **Connection error**: Confirm cluster status and region URL; some corporate proxies block outbound TLS. + +## Qdrant Cloud Inference + +Qdrant Cloud also offers **[Cloud Inference](/cloud-inference/)**—managed embedding generation for text and images. Skip running your own embedding models; create vectors in Qdrant Cloud and write them straight into your collections. + +
+ +
+ +Cut steps from your pipeline: send raw text or images to Qdrant, get vectors and search results in one API call. This helps prototypes and production systems alike by ending the separate embedding-infrastructure layer. + +Learn more: [Qdrant Cloud Inference](/documentation/cloud/inference/) + diff --git a/qdrant-landing/content/course/beginners/module-3/_index.md b/qdrant-landing/content/course/beginners/module-3/_index.md new file mode 100644 index 0000000000..b2ed6fb8a3 --- /dev/null +++ b/qdrant-landing/content/course/beginners/module-3/_index.md @@ -0,0 +1,370 @@ +--- +title: "Module 3: Sparse vs Dense vs Hybrid Search" +short_description: "Module 3 of the Beginners course: Understand dense versus sparse retrieval, their strengths, and how a hybrid approach can combine them." +description: "Understand dense versus sparse retrieval, their strengths, and how a hybrid approach can combine them. Learn about fusion strategies and multimodal search." +isLesson: true +weight: 40 +--- + +{{< date >}} Module 3 {{< /date >}} + +# Sparse vs Dense vs Hybrid Search + +Understand dense versus sparse retrieval, their strengths, and how a hybrid approach can combine them. + +## Today's path + +1. Where We Left Off +2. The Two Families of Search +3. Filtering: Works with Any Retrieval Method +4. Hybrid Search: Dense + Sparse +5. Setting Up Hybrid Search in Qdrant +6. Fusion Strategies +7. Beyond Text: Multimodal Search +8. References & Further Reading + +By the end, you'll understand when to use dense, sparse, or hybrid search and how to implement them in Qdrant. + +## 1. Where We Left Off + +In Module 2, you built a complete ingestion and retrieval pipeline: raw text → vector → store → top-K query. Dense-only retrieval is best for semantic and contextual search. It struggles on precise product names and model numbers. + +| Query | iPhone 15 | +|-------|-----------| +| **The user wants exactly this product. No synonyms. No paraphrasing.** | | + +**Dense search returns:** +| iPhone 14 | (0.93) | ← wrong model | +|----------|--------|---------| +| iPhone 15 Pro Max | (0.91) | ← wrong model | +| iPhone 15 | (0.89) | ← correct | + +### The problem + +Dense search understands meaning - but that's exactly wrong here. "iPhone 14", "iPhone 15", and "iPhone 15 Pro Max" are close together in embedding space because they're about the same product line - but a shopper searching for one wants that exact model, not its closest semantic neighbor. IDs, codes, and specific model names need exact matching, not semantic neighborhood. This is the gap sparse search fills. + +## 2. The Two Families of Search + +Every retrieval system is built from one or both of these families. Understanding what each does - and what it cannot do - is the foundation of production search design. + +### Dense Search (Semantic) + +Dense vectors are embeddings: fixed-length lists of floating-point numbers that encode meaning. Two pieces of text with similar meaning produce vectors that are close in high-dimensional space, regardless of whether they share any words. + +```python +# Dense vector: all dimensions have non-zero values +dense_vector = [0.12, -0.87, 0.33, 0.05, -0.42, ...] # 384 dims + +# Semantically close, even without shared words: +encode("car repair") ≈ encode("automobile maintenance") +encode("cheap flights") ≈ encode("affordable airfare") +``` + +### Sparse Search (Keyword-Based) + +Sparse vectors are token-based. Only the dimensions corresponding to tokens that actually appear in the text have non-zero values - everything else is zero. BM25, SPLADE, and miniCOIL are the most common ways to produce them. + +![sparse.png](/courses/beginners/module-3/sparse.png) + +#### How Sparse Vectors Are Encoded + +A dense vector has a small, fixed number of dimensions (e.g. 384), and every single one holds a value. A sparse vector works the opposite way: it has one dimension per token in the vocabulary - often tens of thousands - but a given piece of text only ever activates the handful of tokens it actually contains. Everything else is implicitly zero. + +Storing tens of thousands of mostly-zero numbers per point would be wasteful, so sparse vectors are represented as two parallel arrays instead: the `indices` of the non-zero dimensions, and the `values` at those positions. Nothing is stored for the dimensions that are zero. + +```python +# Sparse vector: most values are zero +# Only the indices of present tokens are stored - not the full vocabulary +sparse_vector = { + "indices": [142, 9325, 44001], # token IDs: 'nike', 'pegasus', '40' + "values": [2.3, 1.2, 0.8], # weight for each token +} + +# Exact match: 'SKU-48291' only matches the document +# that contains those exact characters. +``` + +#### Sparse Models: BM25, SPLADE, and miniCOIL + +Different sparse models decide *which* tokens get weight and *how much*: + +| Model | How it assigns weights | Notes | +|-------|-------------------------|-------| +| BM25 | Statistical - term frequency and inverse document frequency (IDF), no training involved | Classic, fast, fully interpretable. Only scores tokens exactly as written. Qdrant's default sparse model (`Qdrant/bm25`). | +| SPLADE | Neural - a transformer learns to expand a text with related terms and assign them weights, even terms not in the original text | Captures some synonymy while staying sparse. More compute-intensive than BM25. | +| miniCOIL | Neural, contextualized term weighting - keeps BM25's exact-token vocabulary but weights each occurrence using its surrounding context | Adds context-awareness to exact-match retrieval without the cost of full expansion models like SPLADE. | + +Start with BM25 for interpretable, exact-match retrieval. Reach for SPLADE or miniCOIL when you need sparse retrieval to be more forgiving of related wording, at some extra compute cost. + +![comparison.png](/courses/beginners/module-3/comparison.png) + +#### Indexing Sparse Vectors + +Because the vast majority of dimensions are zero, comparing full vectors would waste effort scanning entries that don't matter. Qdrant indexes sparse vectors with a data structure similar to the **inverted index** used by text search engines: for every token, it keeps a posting list of every point where that token has a non-zero weight. + +``` +Token "nike" → posting list: [point_1, point_5, point_42, ...] +Token "pegasus" → posting list: [point_1, point_5, point_88, ...] +``` + +A query only walks the posting lists for tokens it actually contains, skipping every point that shares none of them. Unlike HNSW (Module 2), which is an approximate index, Qdrant's sparse index is exact - no accuracy is traded away for speed. + +### Head-to-Head Comparison + +| | Dense Search (Semantic) | Sparse Search (Keyword) | +|---|---|---| +| **✔ Strengths** | Synonyms - car = automobile
Paraphrasing - "cheap flights" ≈ "affordable airfare"
Multilingual queries across languages
Intent and context understanding | Exact token matches - IDs, codes, SKUs
Rare or domain-specific terms
Interpretable - easy to debug and explain | +| **✖ Weaknesses** | Exact IDs like SKU-48291 can drift
Rare or invented tokens
Precise code / serial number matching | Synonyms - car ≠ automobile
Paraphrasing and rewordings
Cross-language queries | + +### Key insight + +Dense = meaning. Sparse = exact matching. Neither is complete alone. Every real-world query contains both semantic intent (what the user means) and exact constraints (what the user needs precisely). You need both. + +## 3. Filtering: Works with Any Retrieval Method + +Payload filters are not a hybrid-only feature. The same `query_filter` applies whether you're running dense-only, sparse-only, or hybrid retrieval - it's evaluated as a hard constraint during the search itself, not as a separate step afterward. + +### Filtering a Dense Query + +```python +from qdrant_client.models import Filter, FieldCondition, MatchValue + +results = client.query_points( + collection_name="products", + query=dense_query_vector, + using="dense", + query_filter=Filter( + must=[FieldCondition(key="in_stock", match=MatchValue(value=True))] + ), + limit=5, +) +``` + +### Filtering a Sparse Query + +```python +results = client.query_points( + collection_name="products", + query=sparse_query_vector, + using="sparse", + query_filter=Filter( + must=[FieldCondition(key="in_stock", match=MatchValue(value=True))] + ), + limit=5, +) +``` + +### Filtering a Hybrid Query + +The identical `query_filter` parameter also applies when fusing dense and sparse prefetches together - see Section 5 for a full hybrid example with filters attached. + +### Why it matters + +Because filters are applied during the search rather than after it, out-of-scope points never take up a slot in your top-K results - regardless of whether the underlying retrieval is dense, sparse, or hybrid. This keeps results both relevant and valid: in stock, within permissions, within a date range. + +## 4. Hybrid Search: Dense + Sparse + +Hybrid search runs dense and sparse retrieval simultaneously, then fuses the ranked candidate lists into a single result set. The result: semantic understanding with exact-match precision. Filters (Section 3) can be layered on top of any of this. + +### A Concrete Example + +![nike-example.png](/courses/beginners/module-3/nike-example.png) + +### RRF Fusion + +![fusion.png](/courses/beginners/module-3/fusion.png) + +You can learn more about fusion in the [Hybrid Queries documentation](https://qdrant.tech/documentation/search/hybrid-queries/#reciprocal-rank-fusion-rrf). + +## 5. Setting Up Hybrid Search in Qdrant + +Hybrid search in Qdrant uses named vectors - dense and sparse stored together on the same point - and the Universal Query API to prefetch from each, then fuse the results. + +### Step 1 - Create a Hybrid Collection + +Declare both a dense vector config and a sparse vector config on the same collection. Points will carry both. + +```python +from qdrant_client import QdrantClient, models + +client = QdrantClient(":memory:") + +client.create_collection( + collection_name="products", + vectors_config={ + "dense": models.VectorParams( + size=384, + distance=models.Distance.COSINE, + ), + }, + sparse_vectors_config={ + "sparse": models.SparseVectorParams(), + }, +) +``` + +### Step 2 - Insert Points with Both Vectors + +Each point carries a dense embedding and a sparse vector. Pass a models.Document object and specify the model - Qdrant handles embedding on the server side. + +```python +client.upload_points( + collection_name="products", + points=[ + models.PointStruct( + id=1, + vector={ + "dense": models.Document( + text="Nike Pegasus running shoes", + model="sentence-transformers/all-MiniLM-L6-v2", + ), + "sparse": models.Document( + text="Nike Pegasus running shoes", + model="Qdrant/bm25", + ), + }, + payload={"price": 120, "in_stock": True, "size": [9, 10, 11]}, + ) + ], +) +``` + +### Step 3 - Hybrid Query with Fusion + +Prefetch from both the dense and sparse indexes simultaneously, then fuse the two ranked lists with RRF into a single result. + +```python +from qdrant_client.models import Filter, FieldCondition, MatchValue + +results = client.query_points( + collection_name="products", + prefetch=[ + models.Prefetch( + query=models.Document( + text="Nike Pegasus 40 size 10", + model="sentence-transformers/all-MiniLM-L6-v2", + ), + using="dense", + limit=20, + ), + models.Prefetch( + query=models.Document( + text="Nike Pegasus 40 size 10", + model="Qdrant/bm25", + ), + using="sparse", + limit=20, + ), + ], + query=models.FusionQuery(fusion=models.Fusion.RRF), + query_filter=Filter( + must=[FieldCondition(key="in_stock", match=MatchValue(value=True))] + ), + limit=5, +) +``` + +### How it works + +Both prefetch calls run in parallel. Each returns 20 candidates. RRF fusion merges the two ranked lists based on position (not score), then the final limit=5 takes the top results. The filter applies throughout - out-of-stock products never enter the candidate set. + +## 6. Fusion Strategies + +Once both retrievers return their candidate sets, a fusion algorithm merges them into a single ranked list. Qdrant supports two strategies: + +| Strategy | How it works | When to use it | +|----------|--------------|----------------| +| RRF (Reciprocal Rank Fusion) | Combines rankings only - ignores raw score values. Robust, hard to game. | Default for most cases. Safe starting point when score scales differ between dense and sparse. | +| DBSF (Distribution-Based Score Fusion) | Normalizes score distributions before merging. Sensitive to relative score differences. | Better when score gaps meaningfully encode relevance and both retrievers are well-calibrated. | + +You can learn more about fusion in the [Hybrid Queries documentation](https://qdrant.tech/documentation/search/hybrid-queries/#reciprocal-rank-fusion-rrf). + +### Starting point + +Start with RRF. It's the safer default because dense and sparse scores are on different scales - raw score fusion without normalization produces unreliable results. Switch to DBSF only after evaluating on a labeled test set. + +## 7. Beyond Text: Multimodal Search + +The same primitive - embed data, store as a vector, search by similarity - applies to any modality. Qdrant stores whatever vectors your embedding model produces. The retrieval mechanics are identical. + +- **Images** +"red dress" → visually similar products +CLIP, SigLIP embed images and text into the same space + +- **Video** +"factory fire" → matching video scenes +Frames are sampled, embedded, stored as named vectors + +- **Audio** +Hum a melody → matching songs +Audio fingerprints or spectrogram embeddings + +- **Text** +"cheap flights NYC" → semantic docs +Sentence transformers, OpenAI embeddings, etc. + +![multimodal.png](/courses/beginners/module-3/multimodal.png) + +### The Unifying Principle + +**Data → Embedding Model → Vector → Qdrant** + +The modality changes. The system does not. + +### Named Vectors for Multimodal + +When text and images must be searchable together, store them as named vectors on the same point. A query against one named vector retrieves across both. + +```python +# A product point with both text and image embeddings +models.PointStruct( + id=42, + vector={ + "text": text_model.encode("Red Nike running shoe").tolist(), + "image": clip_model.encode_image(product_image).tolist(), + }, + payload={"sku": "NK-RED-10", "price": 120}, +) + +# Query by image: find visually similar products +client.query_points( + collection_name="products", + query=clip_model.encode_image(query_image).tolist(), + using="image", + limit=10, +) +``` + +## 8. References & Further Reading + +- **Sparse Retrieval Demo** - [Demo: Keyword Search with Sparse Vectors - Qdrant](https://qdrant.tech/documentation/tutorials/sparse_search/) + - Live comparison of BM25, SPLADE, and dense embeddings on the same queries. + +- **Hybrid Queries (RRF + DBSF)** - [Hybrid Queries - Qdrant](https://qdrant.tech/documentation/concepts/hybrid_query/) + - Full reference for prefetch, FusionQuery, RRF, and DBSF in the Qdrant API. + +- **Sparse Vectors Reference** - [Vectors - Qdrant](https://qdrant.tech/documentation/concepts/vectors/#sparse-vectors) + - SparseVectorParams, index configuration, and storage options. + +- **Sparse Vector Indexing** - [Indexing - Qdrant](https://qdrant.tech/documentation/manage-data/indexing/#sparse-vector-index) + - How Qdrant's inverted-index-style sparse index works, and when it rebuilds into an immutable index. + +- **Working with miniCOIL** - [FastEmbed: miniCOIL - Qdrant](https://qdrant.tech/documentation/fastembed/fastembed-minicoil/) + - How miniCOIL's contextualized term weighting works, and how to use it via FastEmbed. + +- **Multimodal Search Tutorial** - [Multimodal and Multilingual RAG with LlamaIndex and Qdrant](https://qdrant.tech/documentation/tutorials/multimodal_rag/) + - Text + image search with embeddings and named vectors. + +- **Named Vectors** - [Collections - Qdrant](https://qdrant.tech/documentation/concepts/collections/#multiple-vectors) + - How to configure and query multiple named vectors on the same point. + +## What's Next - Module 4 + +Next, we'll explore: + +- Production patterns: multi-tenancy, agent memory, and RAG pipelines +- Deployment options: Cloud, Hybrid Cloud, Edge, and self-hosted +- Formula queries - when RRF and DBSF aren't enough +- etc.. + +End of Module 3. Continue to Module 4: Customer Use Cases and Production Architecture. diff --git a/qdrant-landing/static/courses/beginners/module-3/comparison.png b/qdrant-landing/static/courses/beginners/module-3/comparison.png new file mode 100644 index 0000000000..669a5c6da1 Binary files /dev/null and b/qdrant-landing/static/courses/beginners/module-3/comparison.png differ diff --git a/qdrant-landing/static/courses/beginners/module-3/fusion.png b/qdrant-landing/static/courses/beginners/module-3/fusion.png new file mode 100644 index 0000000000..583de6676c Binary files /dev/null and b/qdrant-landing/static/courses/beginners/module-3/fusion.png differ diff --git a/qdrant-landing/static/courses/beginners/module-3/multimodal.png b/qdrant-landing/static/courses/beginners/module-3/multimodal.png new file mode 100644 index 0000000000..0feb6f8464 Binary files /dev/null and b/qdrant-landing/static/courses/beginners/module-3/multimodal.png differ diff --git a/qdrant-landing/static/courses/beginners/module-3/nike-example.png b/qdrant-landing/static/courses/beginners/module-3/nike-example.png new file mode 100644 index 0000000000..562f9f6193 Binary files /dev/null and b/qdrant-landing/static/courses/beginners/module-3/nike-example.png differ diff --git a/qdrant-landing/static/courses/beginners/module-3/sparse.png b/qdrant-landing/static/courses/beginners/module-3/sparse.png new file mode 100644 index 0000000000..6749b585da Binary files /dev/null and b/qdrant-landing/static/courses/beginners/module-3/sparse.png differ