mirror of
https://github.com/qdrant/landing_page.git
synced 2026-09-29 07:58:31 +02:00
v1
This commit is contained in:
@@ -32,6 +32,8 @@ jobs:
|
|||||||
curl -sf http://localhost:1314/ >/dev/null && break
|
curl -sf http://localhost:1314/ >/dev/null && break
|
||||||
sleep 1
|
sleep 1
|
||||||
done
|
done
|
||||||
|
- name: Learn Navigation Check
|
||||||
|
run: python3 automation/check-learn.py --public qdrant-landing/public
|
||||||
- name: Internal Links Check
|
- name: Internal Links Check
|
||||||
id: lychee
|
id: lychee
|
||||||
uses: lycheeverse/lychee-action@e7477775783ea5526144ba13e8db5eec57747ce8 # v2.9.0
|
uses: lycheeverse/lychee-action@e7477775783ea5526144ba13e8db5eec57747ce8 # v2.9.0
|
||||||
|
|||||||
@@ -0,0 +1,140 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Check a Hugo build of Learn: python3 automation/check-learn.py --public PATH."""
|
||||||
|
import argparse
|
||||||
|
import hashlib
|
||||||
|
from html.parser import HTMLParser
|
||||||
|
import json
|
||||||
|
from pathlib import Path
|
||||||
|
import re
|
||||||
|
from urllib.parse import urlsplit, unquote
|
||||||
|
|
||||||
|
class Page(HTMLParser):
|
||||||
|
def __init__(self, text):
|
||||||
|
super().__init__()
|
||||||
|
self.links, self.ids, self.guides = set(), set(), set()
|
||||||
|
self.examples, self.neighbors, self.redirect = 0, {}, None
|
||||||
|
self.feed(text)
|
||||||
|
|
||||||
|
def handle_starttag(self, tag, attrs):
|
||||||
|
attr = dict(attrs)
|
||||||
|
if attr.get('id'):
|
||||||
|
self.ids.add(attr['id'])
|
||||||
|
if 'data-example' in attr:
|
||||||
|
self.examples += 1
|
||||||
|
if tag == 'a' and attr.get('href'):
|
||||||
|
href = attr['href']
|
||||||
|
self.links.add(href)
|
||||||
|
if 'data-guide-link' in attr:
|
||||||
|
self.guides.add(urlsplit(href).path)
|
||||||
|
if attr.get('rel') in ('prev', 'next'):
|
||||||
|
self.neighbors[attr['rel']] = urlsplit(href).path
|
||||||
|
if tag == 'meta' and attr.get('http-equiv', '').lower() == 'refresh':
|
||||||
|
self.redirect = urlsplit(attr.get('content', '').split('url=', 1)[-1]).path
|
||||||
|
|
||||||
|
parser = argparse.ArgumentParser(description=__doc__)
|
||||||
|
parser.add_argument('--public', type=Path, required=True)
|
||||||
|
args = parser.parse_args()
|
||||||
|
root = Path(__file__).resolve().parents[1]
|
||||||
|
manifest = json.loads((root / 'contributing/guide-sources.json').read_text())
|
||||||
|
errors, cache = [], {}
|
||||||
|
|
||||||
|
def page(route):
|
||||||
|
route = urlsplit(route).path
|
||||||
|
if route not in cache:
|
||||||
|
path = args.public / route.strip('/') / 'index.html'
|
||||||
|
if not path.exists():
|
||||||
|
errors.append(f'Missing page: {route}')
|
||||||
|
cache[route] = Page(path.read_text() if path.exists() else '')
|
||||||
|
return cache[route]
|
||||||
|
|
||||||
|
# Compare preserved guide text with the original source hash, allowing routing and presentation changes.
|
||||||
|
for entry in manifest['guides']:
|
||||||
|
source = root / entry['guide']
|
||||||
|
body = source.read_text().split('---', 2)[2]
|
||||||
|
original_title = '# ' + entry['title']
|
||||||
|
# Source documentation already contains its H1; migrated articles receive one for the docs layout.
|
||||||
|
if entry['added_title']:
|
||||||
|
body = body.replace(original_title, '', 1)
|
||||||
|
body = re.sub(r'{{< /?read-more >}}', '', body)
|
||||||
|
for other in manifest['guides']:
|
||||||
|
old = '/' + other['source'].split('content/', 1)[1].removesuffix('.md') + '/'
|
||||||
|
body = body.replace(old, other['url'])
|
||||||
|
digest = hashlib.sha256(re.sub(r'\s+', ' ', body).strip().encode()).hexdigest()
|
||||||
|
if digest != entry['body_sha256']:
|
||||||
|
errors.append(f'Guide source text changed: {source}')
|
||||||
|
if (root / entry['source']).exists():
|
||||||
|
errors.append(f'Duplicate source: {entry["source"]}')
|
||||||
|
if page(entry['url']).redirect:
|
||||||
|
errors.append(f'Guide must render its content: {entry["url"]}')
|
||||||
|
if '/articles/' in entry['source']:
|
||||||
|
old = '/articles/' + Path(entry['source']).stem + '/'
|
||||||
|
if page(old).redirect != entry['url']:
|
||||||
|
errors.append(f'Article redirect missing: {old}')
|
||||||
|
|
||||||
|
routes = {entry['url'] for entry in manifest['guides']}
|
||||||
|
for section, count in [('search-quality', 2), ('search-tuning', 8), ('production-patterns', 3)]:
|
||||||
|
route = '/documentation/' + section + '/'
|
||||||
|
links = {urlsplit(link).path for link in page(route).links}
|
||||||
|
expected = {e['url'] for e in manifest['guides'] if Path(e['guide']).parent.name == section}
|
||||||
|
if len(expected) != count or not expected <= links or page(route).guides != routes:
|
||||||
|
errors.append(f'Guide category has incorrect membership: {section}')
|
||||||
|
for landing in ['/learn/', '/documentation/guides/']:
|
||||||
|
if route not in {urlsplit(link).path for link in page(landing).links}:
|
||||||
|
errors.append(f'{landing} omits {route}')
|
||||||
|
|
||||||
|
series = [e['url'] for e in manifest['guides'] if '/search-tuning/' in e['url']]
|
||||||
|
for index, route in enumerate(series):
|
||||||
|
expected = {}
|
||||||
|
if index:
|
||||||
|
expected['prev'] = series[index - 1]
|
||||||
|
if index + 1 < len(series):
|
||||||
|
expected['next'] = series[index + 1]
|
||||||
|
if page(route).neighbors != expected:
|
||||||
|
errors.append(f'Series order incorrect: {route}')
|
||||||
|
for link in page(route).links:
|
||||||
|
target = urlsplit(link)
|
||||||
|
if target.path in series and target.fragment and unquote(target.fragment) not in page(target.path).ids:
|
||||||
|
errors.append(f'Broken series anchor: {link}')
|
||||||
|
|
||||||
|
entries = re.findall(r'^- page: (\S+)', (root / 'qdrant-landing/data/examples.yaml').read_text(), re.M)
|
||||||
|
catalog = page('/learn/examples/')
|
||||||
|
if catalog.examples != len(entries) or len(set(entries)) != len(entries):
|
||||||
|
errors.append('Catalog must show each registered tutorial exactly once')
|
||||||
|
if not {'example-query', 'example-goal', 'example-stack'} <= catalog.ids:
|
||||||
|
errors.append('Catalog filters are missing')
|
||||||
|
for route in entries:
|
||||||
|
route = route.lower()
|
||||||
|
if page(route).redirect:
|
||||||
|
errors.append(f'Tutorial source was replaced: {route}')
|
||||||
|
if route not in {urlsplit(link).path for link in catalog.links}:
|
||||||
|
errors.append(f'Tutorial missing from catalog: {route}')
|
||||||
|
if (args.public / 'learn/examples/index.md').read_text().count('[Open Example]') != len(entries):
|
||||||
|
errors.append('Markdown catalog differs from HTML catalog')
|
||||||
|
|
||||||
|
# Cascade rules suppress archive bodies in HTML, Markdown, and discovery indexes.
|
||||||
|
index = (root / 'qdrant-landing/content/articles/_index.md').read_text()
|
||||||
|
retired = re.search(r'path: /articles/\{([^}]+)\}', index)[1].split(',')
|
||||||
|
discovery = '\n'.join((args.public / name).read_text() for name in ['sitemap.xml', 'llms.txt', 'articles/index.md'])
|
||||||
|
for slug in retired:
|
||||||
|
source = root / 'qdrant-landing/content/articles' / (slug + '.md')
|
||||||
|
if not source.exists():
|
||||||
|
source = root / 'qdrant-landing/content/articles' / slug / '_index.md'
|
||||||
|
fm = source.read_text().split('---', 2)[1]
|
||||||
|
explicit = re.search(r'^slug:\s*(\S+)', fm, re.M)
|
||||||
|
route = '/articles/' + re.sub(r'[^\w-]', '', explicit[1].strip('"\'').lower() if explicit else slug) + '/'
|
||||||
|
path = args.public / route.strip('/')
|
||||||
|
if re.search(r'^draft: true\s*$', fm, re.M):
|
||||||
|
if (path / 'index.html').exists():
|
||||||
|
errors.append(f'Archived draft published: {route}')
|
||||||
|
continue
|
||||||
|
if page(route).redirect != '/articles/' or not (path / 'index.md').exists() or (path / 'index.md').read_text().strip() != '# Articles\n\nBrowse current Qdrant articles in [Articles](/articles/index.md).':
|
||||||
|
errors.append(f'Archive body exposed: {route}')
|
||||||
|
if route in discovery:
|
||||||
|
errors.append(f'Archive listed in discovery: {route}')
|
||||||
|
for slug in ['search-quality', 'embedding-research', 'qdrant-internals', 'production-ops']:
|
||||||
|
category = page('/articles/' + slug + '/')
|
||||||
|
if category.redirect:
|
||||||
|
errors.append(f'Active article category redirected: {slug}')
|
||||||
|
if errors:
|
||||||
|
raise SystemExit('\n'.join(errors))
|
||||||
|
print(f'PASS: {len(routes)} preserved guides; {len(series)} ordered series parts; {len(entries)} original tutorials; archive bodies excluded.')
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
# Maintain Learn
|
||||||
|
|
||||||
|
Learn has four resources: Guides, Tutorials & Examples, Courses, and Articles. Keep each piece in one source file and use the collections to make it discoverable.
|
||||||
|
|
||||||
|
## Tutorials & Examples
|
||||||
|
|
||||||
|
`qdrant-landing/data/examples.yaml` is the catalog. Each entry identifies an existing tutorial page, its goal, and its stack. Optional keywords improve search. Selected notebook and repository links appear in `resources`.
|
||||||
|
|
||||||
|
Hugo reads each title and description from the source tutorial. The catalog links to that page; it does not move or copy the tutorial. Both the HTML and Markdown catalog use the same entries. The browser filters the rendered cards without a separate search service.
|
||||||
|
|
||||||
|
Adding a tutorial requires one catalog entry. Use existing goal and stack labels where they fit. The build fails if the source page is missing or a selected resource URL no longer appears in the source tutorial.
|
||||||
|
|
||||||
|
## Guides
|
||||||
|
|
||||||
|
The three guide sections use `learning_kind: guides` and `partition: learn`. Topic cards and sidebar entries derive from their contents. Public URLs can remain stable through `url`, while `aliases` preserve former article URLs after a move.
|
||||||
|
|
||||||
|
The tuning series uses `guide_series: true` on its six pages. Their weights determine the order, numbered cards, and previous/next links. Standalone design guides remain outside that sequence.
|
||||||
|
|
||||||
|
`contributing/guide-sources.json` records the existing source for each migrated guide. Its hashes protect the preserved source text while allowing the routing and presentation changes recorded there. Review technical revisions separately from navigation changes.
|
||||||
|
|
||||||
|
## Articles
|
||||||
|
|
||||||
|
An article's `category` remains its normal topic field. The Articles index contains the few compatibility mappings needed for the four public topics. Authors can use Search Quality, Embedding Research, Qdrant Internals, or Production Ops directly for new articles.
|
||||||
|
|
||||||
|
The index also contains the archive cascade. It makes the listed pages redirect, excludes them from discovery, and suppresses their bodies in HTML and Markdown. Their original source files remain untouched. Apply retirement rules there instead of editing each archived article.
|
||||||
|
|
||||||
|
## Verify Changes
|
||||||
|
|
||||||
|
Build Hugo, then run `python3 automation/check-learn.py --public qdrant-landing/public`. The check covers preserved guide text, category membership, series navigation, catalog links, redirects, and archive exclusions. The existing redirect audit remains part of CI.
|
||||||
|
|
||||||
|
Check search, filters, Clear Filters, and a narrow-screen layout in the preview before submitting navigation changes.
|
||||||
@@ -0,0 +1,109 @@
|
|||||||
|
{
|
||||||
|
"baseline": "0490e1e38cde035eacece7ca185e5ff44bbca32e",
|
||||||
|
"guides": [
|
||||||
|
{
|
||||||
|
"source": "qdrant-landing/content/documentation/improve-search/retrieval-relevance.md",
|
||||||
|
"guide": "qdrant-landing/content/documentation/search-quality/retrieval-relevance.md",
|
||||||
|
"url": "/documentation/improve-search/retrieval-relevance/",
|
||||||
|
"title": "Measuring Retrieval Relevance",
|
||||||
|
"body_sha256": "4809cf786abb14b818800e002d2b505bb8ce80fad66df477f1ec513fd02b2926",
|
||||||
|
"added_title": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "qdrant-landing/content/documentation/improve-search/pipeline-output-quality.md",
|
||||||
|
"guide": "qdrant-landing/content/documentation/search-quality/pipeline-output-quality.md",
|
||||||
|
"url": "/documentation/improve-search/pipeline-output-quality/",
|
||||||
|
"title": "Evaluating Pipeline Output Quality",
|
||||||
|
"body_sha256": "2673b05d442d7adbf6ffc98c3cebd2658fd41a533b6776f98f74c7a0d399defa",
|
||||||
|
"added_title": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "qdrant-landing/content/documentation/improve-search/query-decomposition.md",
|
||||||
|
"guide": "qdrant-landing/content/documentation/search-tuning/query-decomposition.md",
|
||||||
|
"url": "/documentation/improve-search/query-decomposition/",
|
||||||
|
"title": "Query Decomposition for Multi-Hop Questions",
|
||||||
|
"body_sha256": "2d7a8721d8d796706d4d7c4eea9a582fa890dcf8c673ad3f757fd9126b859178",
|
||||||
|
"added_title": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "qdrant-landing/content/articles/how-to-choose-an-embedding-model.md",
|
||||||
|
"guide": "qdrant-landing/content/documentation/search-tuning/choose-embedding-model.md",
|
||||||
|
"url": "/documentation/search-quality/choose-embedding-model/",
|
||||||
|
"title": "How to Choose an Embedding Model: Evaluation & Tradeoffs",
|
||||||
|
"body_sha256": "45db72823411381133ce3f6ef2ec103c0d6068315a7c6de27baf2ce37488506d",
|
||||||
|
"added_title": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "qdrant-landing/content/articles/multitenancy.md",
|
||||||
|
"guide": "qdrant-landing/content/documentation/production-patterns/multitenant-search.md",
|
||||||
|
"url": "/documentation/production-patterns/multitenant-search/",
|
||||||
|
"title": "How to Implement Multitenancy and Custom Sharding in Qdrant",
|
||||||
|
"body_sha256": "47b7150fdb34474e743884ea4977a0bd1889dd1edf184e450056bf277b75e6df",
|
||||||
|
"added_title": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "qdrant-landing/content/articles/bulk-uploads-in-qdrant.md",
|
||||||
|
"guide": "qdrant-landing/content/documentation/production-patterns/bulk-data-import.md",
|
||||||
|
"url": "/documentation/production-patterns/bulk-data-import/",
|
||||||
|
"title": "Bulk Uploading Data to Qdrant",
|
||||||
|
"body_sha256": "11c1166ab98a23f5cac181e485bd55c5178ef7a1376e4cdee7333e4d1ba72de6",
|
||||||
|
"added_title": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "qdrant-landing/content/articles/memory-tiers-in-qdrant-what-to-use-and-when.md",
|
||||||
|
"guide": "qdrant-landing/content/documentation/production-patterns/memory-tiers.md",
|
||||||
|
"url": "/documentation/production-patterns/memory-tiers/",
|
||||||
|
"title": "Memory Tiers in Qdrant: What to Use and When",
|
||||||
|
"body_sha256": "e28a3be091a91e2675033086b05e03788bf944ea9894a8159c5bcadb9c9f52b5",
|
||||||
|
"added_title": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "qdrant-landing/content/articles/hybrid-search.md",
|
||||||
|
"guide": "qdrant-landing/content/documentation/search-tuning/hybrid-search.md",
|
||||||
|
"url": "/documentation/search-tuning/hybrid-search/",
|
||||||
|
"title": "Hybrid Search in Qdrant",
|
||||||
|
"body_sha256": "de19a6ef0520904a1e7b9b63f4ba663b82b110683730a1b015d72543da71cb3b",
|
||||||
|
"added_title": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "qdrant-landing/content/articles/before-tuning-a-qdrant-collection.md",
|
||||||
|
"guide": "qdrant-landing/content/documentation/search-tuning/before-tuning-a-qdrant-collection.md",
|
||||||
|
"url": "/documentation/search-tuning/before-tuning-a-qdrant-collection/",
|
||||||
|
"title": "What to Check Before Tuning a Qdrant Collection",
|
||||||
|
"body_sha256": "16b896e07cea553faced53467a2d1d1530a6b17ae716546a3b4db9f8121f0d82",
|
||||||
|
"added_title": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "qdrant-landing/content/articles/candidate-depth.md",
|
||||||
|
"guide": "qdrant-landing/content/documentation/search-tuning/candidate-depth.md",
|
||||||
|
"url": "/documentation/search-tuning/candidate-depth/",
|
||||||
|
"title": "Candidate Depth: How Much Retrieval Is Enough?",
|
||||||
|
"body_sha256": "4a62dc32ef582437e87cbb5ed4f6839d99173dada631a3aeca82515a8566a4e0",
|
||||||
|
"added_title": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "qdrant-landing/content/articles/how-to-tune-hybrid-search.md",
|
||||||
|
"guide": "qdrant-landing/content/documentation/search-tuning/how-to-tune-hybrid-search.md",
|
||||||
|
"url": "/documentation/search-tuning/how-to-tune-hybrid-search/",
|
||||||
|
"title": "How to Tune Hybrid Search in Qdrant",
|
||||||
|
"body_sha256": "ac5ff44ad1e7ec60ae96f27e9511e782eb461fb7ca532b1f01271b6200f434f3",
|
||||||
|
"added_title": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "qdrant-landing/content/articles/when-a-reranker-is-worth-it.md",
|
||||||
|
"guide": "qdrant-landing/content/documentation/search-tuning/when-a-reranker-is-worth-it.md",
|
||||||
|
"url": "/documentation/search-tuning/when-a-reranker-is-worth-it/",
|
||||||
|
"title": "When Is a Reranker Worth It?",
|
||||||
|
"body_sha256": "08b380373ea6efb9350a4ba49a59cf05d6f5a8c13e3d6bb4adc41a47eb7878c8",
|
||||||
|
"added_title": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "qdrant-landing/content/articles/when-your-collection-outgrows-ram.md",
|
||||||
|
"guide": "qdrant-landing/content/documentation/search-tuning/when-your-collection-outgrows-ram.md",
|
||||||
|
"url": "/documentation/search-tuning/when-your-collection-outgrows-ram/",
|
||||||
|
"title": "When Your Collection Outgrows RAM",
|
||||||
|
"body_sha256": "ebca984ca16a8a307e35af82318bf2eb0649cdb606bf96a104dd0bf6f83d8253",
|
||||||
|
"added_title": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -136,8 +136,14 @@ disableKinds = ["taxonomy", "term"]
|
|||||||
id = 'G-NZYW2651NE'
|
id = 'G-NZYW2651NE'
|
||||||
|
|
||||||
[server]
|
[server]
|
||||||
[server.headers]
|
# Local previews must show the same navigation after content moves.
|
||||||
|
[[server.headers]]
|
||||||
for = '/get_anonymous_id/**'
|
for = '/get_anonymous_id/**'
|
||||||
[[server.headers.values]]
|
[server.headers.values]
|
||||||
|
Cache-Control = 'no-store'
|
||||||
Content-Security-Policy = 'frame-ancestors https://localhost:3000'
|
Content-Security-Policy = 'frame-ancestors https://localhost:3000'
|
||||||
X-Frame-Options = 'ALLOW-FROM https://localhost:3000'
|
X-Frame-Options = 'ALLOW-FROM https://localhost:3000'
|
||||||
|
[[server.headers]]
|
||||||
|
for = '/**'
|
||||||
|
[server.headers.values]
|
||||||
|
Cache-Control = 'no-store'
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
---
|
---
|
||||||
title: Qdrant Articles
|
title: Qdrant Articles
|
||||||
page_title: Articles about Vector Search
|
page_title: Articles about Vector Search
|
||||||
short_description: "Long-form articles on vector search, RAG, quantization, hybrid retrieval, and Qdrant internals from the engineering team."
|
short_description: Long-form articles on vector search, RAG, quantization, hybrid retrieval, and Qdrant internals from the engineering team.
|
||||||
description: Articles about vector search and similarity larning related topics. Latest updates on Qdrant vector search engine.
|
description: Articles about vector search and similarity larning related topics. Latest updates on Qdrant vector search engine.
|
||||||
section_title: Check out our latest publications
|
section_title: Check out our latest publications
|
||||||
subtitle: Check out our latest publications
|
subtitle: Check out our latest publications
|
||||||
@@ -10,4 +10,20 @@ partition: learn
|
|||||||
learnButton: Learn More
|
learnButton: Learn More
|
||||||
isMainPage: true
|
isMainPage: true
|
||||||
toc_start_level: 2
|
toc_start_level: 2
|
||||||
|
cascade:
|
||||||
|
- _target:
|
||||||
|
path: /articles/{agentic-builders-guide,agentic-rag,batch-vector-search-with-qdrant,binary-quantization,binary-quantization-openai,cars-recognition,core-concepts,data-exploration,data-privacy,dataset-quality,dedicated-service,demos-and-tutorials,detecting-coffee-anomalies,discovery-search,distance-based-exploration,embedding-recycler,faq-question-answering,fastembed,food-discovery-demo,indexing-optimization,langchain-integration,mastering-search,memory-consumption,metric-learning-tips,modern-sparse-neural-retrieval,neural-search-tutorial,product-quantization,qa-with-cohere-and-qdrant,rag-and-agents,rag-is-dead,rapid-rag-optimization-with-qdrant-and-quotient,search-as-you-type,search-feedback-loop,semantic-cache-ai-data-retrieval,serverless,storing-multiple-vectors-per-object-in-qdrant,triplet-loss,vector-search-filtering,vector-search-production,vector-search-resource-optimization,vector-similarity-beyond-search,what-are-embeddings,what-is-a-vector-database,what-is-quantization,what-is-rag-in-ai}
|
||||||
|
layout: redirect
|
||||||
|
redirect_to: /articles/
|
||||||
|
hideFromList: true
|
||||||
|
sitemapExclude: true
|
||||||
|
build:
|
||||||
|
list: never
|
||||||
|
render: always
|
||||||
|
publishResources: false
|
||||||
|
category_aliases:
|
||||||
|
mastering-search: embedding-research
|
||||||
|
category_overrides:
|
||||||
|
/articles/dedicated-vector-search/: qdrant-internals
|
||||||
|
/articles/sparse-vectors/: embedding-research
|
||||||
---
|
---
|
||||||
|
|||||||
@@ -74,5 +74,4 @@ build:
|
|||||||
|
|
||||||
- [Essential Examples](/documentation/tutorials-build-essentials/index.md) — Hands-on tutorials for agentic RAG, multimodal search, data ingestion, and automation integrations.
|
- [Essential Examples](/documentation/tutorials-build-essentials/index.md) — Hands-on tutorials for agentic RAG, multimodal search, data ingestion, and automation integrations.
|
||||||
- [Build Prototypes](/documentation/examples/index.md) — End-to-end code samples for RAG pipelines, hybrid search, multitenancy, recommendations, and multimodal search.
|
- [Build Prototypes](/documentation/examples/index.md) — End-to-end code samples for RAG pipelines, hybrid search, multitenancy, recommendations, and multimodal search.
|
||||||
- [Improve Search](/documentation/improve-search/index.md) — Techniques for improving retrieval relevance and pipeline output quality.
|
|
||||||
- [Practice Datasets](/documentation/datasets/index.md) — Ready-made Qdrant snapshots of public datasets you can import and explore without the embedding step.
|
- [Practice Datasets](/documentation/datasets/index.md) — Ready-made Qdrant snapshots of public datasets you can import and explore without the embedding step.
|
||||||
|
|||||||
@@ -0,0 +1,55 @@
|
|||||||
|
---
|
||||||
|
title: Guides
|
||||||
|
short_description: Find practical guidance on search evaluation, embedding model selection, multitenancy, bulk uploads, and memory placement.
|
||||||
|
description: Explore practical Qdrant guides for evaluating search quality, choosing embedding models, and planning multitenancy, bulk uploads, and memory use.
|
||||||
|
partition: learn
|
||||||
|
learning_kind: guides
|
||||||
|
breadcrumb: false
|
||||||
|
hideTOC: true
|
||||||
|
expandSidebar: true
|
||||||
|
slug: guides
|
||||||
|
hideInSidebar: true
|
||||||
|
build:
|
||||||
|
render: always
|
||||||
|
content:
|
||||||
|
- partial: documentation/banners/banner-a
|
||||||
|
title: Build Better Search
|
||||||
|
description: Practical guidance for evaluating and tuning search, choosing models, and planning how your application grows.
|
||||||
|
linkDescription: Start with the guide that matches your next decision.
|
||||||
|
cloudButton:
|
||||||
|
text: Explore Search Evaluation
|
||||||
|
url: /documentation/search-quality/
|
||||||
|
localButton:
|
||||||
|
text: Explore Production & Performance
|
||||||
|
url: /documentation/production-patterns/
|
||||||
|
- partial: documentation/guides/topics
|
||||||
|
- partial: documentation/sections/cards-section
|
||||||
|
title: Start with a Practical Guide
|
||||||
|
description: Work through a decision you can apply to your own search system.
|
||||||
|
cardsPartial: documentation/cards/docs-cards
|
||||||
|
cards:
|
||||||
|
- title: 'How to Choose an Embedding Model: Evaluation & Tradeoffs'
|
||||||
|
description: Compare relevance, language support, and serving cost before you rebuild document vectors.
|
||||||
|
icon:
|
||||||
|
src: /icons/outline/vectors-blue.svg
|
||||||
|
alt: ''
|
||||||
|
link:
|
||||||
|
text: Compare Models
|
||||||
|
url: /documentation/search-quality/choose-embedding-model/
|
||||||
|
- title: How to Implement Multitenancy and Custom Sharding in Qdrant
|
||||||
|
description: Choose shared collections, tenant filters, and shard placement as customer workloads grow.
|
||||||
|
icon:
|
||||||
|
src: /icons/outline/cloud-cog-teal.svg
|
||||||
|
alt: ''
|
||||||
|
link:
|
||||||
|
text: Plan Tenant Growth
|
||||||
|
url: /documentation/production-patterns/multitenant-search/
|
||||||
|
- title: Bulk Uploading Data to Qdrant
|
||||||
|
description: Plan batching, parallel uploads, sharding, and indexing for large datasets.
|
||||||
|
icon:
|
||||||
|
src: /icons/outline/refresh-cw-purple.svg
|
||||||
|
alt: ''
|
||||||
|
link:
|
||||||
|
text: Plan Your Import
|
||||||
|
url: /documentation/production-patterns/bulk-data-import/
|
||||||
|
---
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
---
|
|
||||||
title: Improve Search
|
|
||||||
weight: 1450
|
|
||||||
partition: ecosystem
|
|
||||||
---
|
|
||||||
|
|
||||||
# Improve Search
|
|
||||||
|
|
||||||
*Embedding choice, chunking strategies, and retrieval evaluation using Python ecosystem tools.*
|
|
||||||
|
|
||||||
| Tutorial | Objective | Stack | Time | Level |
|
|
||||||
| :--- | :--- | :--- | :--- | :--- |
|
|
||||||
| [Measuring Retrieval Relevance](/documentation/improve-search/retrieval-relevance/) | Build a labeled golden set and score retrieval relevance with ranx. | <span class="pill">Python</span> | 40m | <span class="text-yellow">Intermediate</span> |
|
|
||||||
| [Evaluating Pipeline Output Quality](/documentation/improve-search/pipeline-output-quality/) | Score a RAG pipeline with Ragas and isolate retrieval vs generation failures. | <span class="pill">Python</span> | 45m | <span class="text-yellow">Intermediate</span> |
|
|
||||||
| [Query Decomposition for Multi-Hop Questions](/documentation/improve-search/query-decomposition/) | Answer multi-hop questions by retrieving in steps, with an LLM asking the next sub-question. | <span class="pill">Python</span> | 15m | <span class="text-yellow">Intermediate</span> |
|
|
||||||
@@ -7,7 +7,6 @@ aliases:
|
|||||||
- overview
|
- overview
|
||||||
- orientation
|
- orientation
|
||||||
- concepts
|
- concepts
|
||||||
- guides
|
|
||||||
partition: develop
|
partition: develop
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,34 @@
|
|||||||
|
---
|
||||||
|
title: Production & Performance
|
||||||
|
short_description: Plan multitenancy, bulk uploads, and memory placement as your Qdrant application and vector collection grow.
|
||||||
|
description: Plan multitenancy, bulk uploads, and memory placement as your Qdrant application and vector collection grow.
|
||||||
|
partition: learn
|
||||||
|
learning_kind: guides
|
||||||
|
weight: 150
|
||||||
|
hideTOC: true
|
||||||
|
breadcrumb: false
|
||||||
|
guide_icon: /icons/outline/cloud-cog-teal.svg
|
||||||
|
related:
|
||||||
|
- /documentation/manage-data/multitenancy/
|
||||||
|
- /documentation/manage-data/bulk-upload/
|
||||||
|
- /documentation/ops-optimization/read-write-contention/
|
||||||
|
- /documentation/capacity-planning/
|
||||||
|
content:
|
||||||
|
- partial: documentation/banners/banner-a
|
||||||
|
title: Production & Performance
|
||||||
|
description: Plan multitenancy, bulk uploads, and memory placement as your Qdrant application and vector collection grow.
|
||||||
|
linkDescription: Choose the pattern that matches your workload and its constraints.
|
||||||
|
cloudButton:
|
||||||
|
text: Serve Many Tenants
|
||||||
|
url: /documentation/production-patterns/multitenant-search/
|
||||||
|
localButton:
|
||||||
|
text: Plan a Data Import
|
||||||
|
url: /documentation/production-patterns/bulk-data-import/
|
||||||
|
- partial: documentation/guides/guide-cards
|
||||||
|
section: /documentation/production-patterns/
|
||||||
|
worked_examples:
|
||||||
|
- /documentation/tutorials-search-engineering/index-dynamic-payloads/
|
||||||
|
- /documentation/tutorials-search-engineering/branch-aware-search/
|
||||||
|
- /documentation/tutorials-operations/embedding-model-migration/
|
||||||
|
---
|
||||||
|
|
||||||
+16
-10
@@ -1,23 +1,29 @@
|
|||||||
---
|
---
|
||||||
title: "Bulk Uploading Data to Qdrant"
|
title: Bulk Uploading Data to Qdrant
|
||||||
short_description: "Plan bulk uploads in Qdrant at scale: batching, parallelization, sharding, payload indexes, quantization, and on-disk storage."
|
short_description: 'Plan bulk uploads in Qdrant at scale: batching, parallelization, sharding, payload indexes, quantization, and on-disk storage.'
|
||||||
description: "Plan bulk uploads in Qdrant: batching, parallelization, sharding, payload indexes, quantization, and on-disk storage."
|
description: 'Plan bulk uploads in Qdrant: batching, parallelization, sharding, payload indexes, quantization, and on-disk storage.'
|
||||||
preview_dir: /articles_data/bulk-uploads-in-qdrant/preview
|
preview_dir: /articles_data/bulk-uploads-in-qdrant/preview
|
||||||
social_preview_image: /articles_data/bulk-uploads-in-qdrant/preview/social_preview.jpg
|
social_preview_image: /articles_data/bulk-uploads-in-qdrant/preview/social_preview.jpg
|
||||||
weight: 35
|
weight: 35
|
||||||
author: John Kupchanko
|
author: John Kupchanko
|
||||||
author_link: https://github.com/jkupchanko
|
author_link: https://github.com/jkupchanko
|
||||||
keywords:
|
keywords:
|
||||||
- bulk upload
|
- bulk upload
|
||||||
- vector database
|
- vector database
|
||||||
- batching
|
- batching
|
||||||
- quantization
|
- quantization
|
||||||
- sharding
|
- sharding
|
||||||
category: production-ops
|
date: 2026-07-14 00:00:00+00:00
|
||||||
date: 2026-07-14T00:00:00.000Z
|
|
||||||
draft: false
|
draft: false
|
||||||
|
partition: learn
|
||||||
|
learning_kind: guides
|
||||||
|
url: /documentation/production-patterns/bulk-data-import/
|
||||||
|
aliases:
|
||||||
|
- /articles/bulk-uploads-in-qdrant/
|
||||||
---
|
---
|
||||||
|
|
||||||
|
# Bulk Uploading Data to Qdrant
|
||||||
|
|
||||||
## Why Bulk Uploading Matters
|
## Why Bulk Uploading Matters
|
||||||
|
|
||||||
When you start using Qdrant at scale, one of the first challenges you may run into is uploading large amounts of data efficiently. Small uploads are usually straightforward, but bulk ingestion introduces a different set of concerns. As millions of vectors, payloads, and indexes are written into a collection, the system has to manage memory usage, disk writes, background optimization, and search availability at the same time.
|
When you start using Qdrant at scale, one of the first challenges you may run into is uploading large amounts of data efficiently. Small uploads are usually straightforward, but bulk ingestion introduces a different set of concerns. As millions of vectors, payloads, and indexes are written into a collection, the system has to manage memory usage, disk writes, background optimization, and search availability at the same time.
|
||||||
+20
-10
@@ -1,23 +1,29 @@
|
|||||||
---
|
---
|
||||||
title: "Memory Tiers in Qdrant: What to Use and When"
|
title: 'Memory Tiers in Qdrant: What to Use and When'
|
||||||
short_description: "A guide to choosing a Qdrant memory tier layout as your collection grows."
|
short_description: A guide to choosing a Qdrant memory tier layout as your collection grows.
|
||||||
description: "Which Qdrant memory tier layout to use and when, and why, backed by benchmarks."
|
description: Which Qdrant memory tier layout to use and when, and why, backed by benchmarks.
|
||||||
social_preview_image: /articles_data/memory-tiers-in-qdrant-what-to-use-and-when/preview/social_preview.jpg
|
social_preview_image: /articles_data/memory-tiers-in-qdrant-what-to-use-and-when/preview/social_preview.jpg
|
||||||
preview_dir: /articles_data/memory-tiers-in-qdrant-what-to-use-and-when/preview
|
preview_dir: /articles_data/memory-tiers-in-qdrant-what-to-use-and-when/preview
|
||||||
author: Clelia Bertelli
|
author: Clelia Bertelli
|
||||||
author_link: https://qdrant.tech
|
author_link: https://qdrant.tech
|
||||||
date: 2026-08-28T10:00:00+02:00
|
date: 2026-08-28 10:00:00+02:00
|
||||||
draft: false
|
draft: false
|
||||||
keywords:
|
keywords:
|
||||||
- memory tiers
|
- memory tiers
|
||||||
- caching
|
- caching
|
||||||
- disk
|
- disk
|
||||||
- scaling
|
- scaling
|
||||||
- benchmark
|
- benchmark
|
||||||
category: production-ops
|
|
||||||
weight: 8
|
weight: 8
|
||||||
|
partition: learn
|
||||||
|
learning_kind: guides
|
||||||
|
url: /documentation/production-patterns/memory-tiers/
|
||||||
|
aliases:
|
||||||
|
- /articles/memory-tiers-in-qdrant-what-to-use-and-when/
|
||||||
---
|
---
|
||||||
|
|
||||||
|
# Memory Tiers in Qdrant: What to Use and When
|
||||||
|
|
||||||
A growing vector collection eventually outgrows the RAM it started with: Qdrant handles that by letting you assign dense vectors, the HNSW graph, quantized vectors, payloads, and payload indexes each to whichever memory tier that structure supports, instead of forcing one RAM-versus-disk trade-off onto the whole collection.
|
A growing vector collection eventually outgrows the RAM it started with: Qdrant handles that by letting you assign dense vectors, the HNSW graph, quantized vectors, payloads, and payload indexes each to whichever memory tier that structure supports, instead of forcing one RAM-versus-disk trade-off onto the whole collection.
|
||||||
|
|
||||||
This article will give you practical guidance over which combination of tiers and quantization to reach for at each stage of a collection's growth, and the reasons behind the choice.
|
This article will give you practical guidance over which combination of tiers and quantization to reach for at each stage of a collection's growth, and the reasons behind the choice.
|
||||||
@@ -122,6 +128,10 @@ A story built only on point count, where more data always means a worse tail, do
|
|||||||
|
|
||||||
## Adjacent Work
|
## Adjacent Work
|
||||||
|
|
||||||
|
{{< read-more >}}
|
||||||
|
|
||||||
- [Memory tiers documentation](/documentation/ops-configuration/memory-tiers/): the full set of tier and quantization options per structure.
|
- [Memory tiers documentation](/documentation/ops-configuration/memory-tiers/): the full set of tier and quantization options per structure.
|
||||||
- [Storage documentation](/documentation/manage-data/storage/): how collections, segments, and storage structures fit together on disk.
|
- [Storage documentation](/documentation/manage-data/storage/): how collections, segments, and storage structures fit together on disk.
|
||||||
- [qdrant-labs/memory-tiers-explained](https://github.com/qdrant-labs/memory-tiers-explained): the benchmark code and raw results behind the guidance in this piece.
|
- [qdrant-labs/memory-tiers-explained](https://github.com/qdrant-labs/memory-tiers-explained): the benchmark code and raw results behind the guidance in this piece.
|
||||||
|
|
||||||
|
{{< /read-more >}}
|
||||||
+13
-9
@@ -1,20 +1,24 @@
|
|||||||
---
|
---
|
||||||
title: "How to Implement Multitenancy and Custom Sharding in Qdrant"
|
title: How to Implement Multitenancy and Custom Sharding in Qdrant
|
||||||
short_description: "Explore how Qdrant's multitenancy and custom sharding streamline machine-learning operations, enhancing scalability and data security."
|
short_description: Explore how Qdrant's multitenancy and custom sharding streamline machine-learning operations, enhancing scalability and data security.
|
||||||
description: "Discover how multitenancy and custom sharding in Qdrant can streamline your machine-learning operations. Learn how to scale efficiently and manage data securely."
|
description: Discover how multitenancy and custom sharding in Qdrant can streamline your machine-learning operations. Learn how to scale efficiently and manage data securely.
|
||||||
social_preview_image: /articles_data/multitenancy/preview/social_preview.jpg
|
social_preview_image: /articles_data/multitenancy/preview/social_preview.jpg
|
||||||
preview_dir: /articles_data/multitenancy/preview
|
preview_dir: /articles_data/multitenancy/preview
|
||||||
small_preview_image: /articles_data/multitenancy/icon.svg
|
small_preview_image: /articles_data/multitenancy/icon.svg
|
||||||
weight: 60
|
weight: 60
|
||||||
author: David Myriel
|
author: David Myriel
|
||||||
date: 2024-02-06T13:21:00.000Z
|
date: 2024-02-06 13:21:00+00:00
|
||||||
draft: false
|
draft: false
|
||||||
keywords:
|
keywords:
|
||||||
- multitenancy
|
- multitenancy
|
||||||
- custom sharding
|
- custom sharding
|
||||||
- multiple partitions
|
- multiple partitions
|
||||||
- vector database
|
- vector database
|
||||||
category: production-ops
|
partition: learn
|
||||||
|
learning_kind: guides
|
||||||
|
url: /documentation/production-patterns/multitenant-search/
|
||||||
|
aliases:
|
||||||
|
- /articles/multitenancy/
|
||||||
---
|
---
|
||||||
|
|
||||||
# Scaling Your Machine Learning Setup: The Power of Multitenancy and Custom Sharding in Qdrant
|
# Scaling Your Machine Learning Setup: The Power of Multitenancy and Custom Sharding in Qdrant
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
---
|
||||||
|
title: Search Evaluation
|
||||||
|
short_description: Measure retrieval relevance and pipeline output quality to establish a baseline and decide whether a search change helps.
|
||||||
|
description: Evaluate Qdrant retrieval relevance and pipeline output quality with labeled queries, repeatable measurements, and checks for meaningful improvements.
|
||||||
|
partition: learn
|
||||||
|
learning_kind: guides
|
||||||
|
weight: 100
|
||||||
|
hideTOC: true
|
||||||
|
breadcrumb: false
|
||||||
|
guide_icon: /icons/outline/search-blue.svg
|
||||||
|
related:
|
||||||
|
- /documentation/search/
|
||||||
|
- /articles/search-quality/
|
||||||
|
content:
|
||||||
|
- partial: documentation/banners/banner-a
|
||||||
|
title: Search Evaluation
|
||||||
|
description: Build an evaluation baseline and measure whether your search system produces useful results.
|
||||||
|
linkDescription: Choose the evaluation method that matches the result you need to judge.
|
||||||
|
cloudButton:
|
||||||
|
text: Measure Retrieval Relevance
|
||||||
|
url: /documentation/improve-search/retrieval-relevance/
|
||||||
|
localButton:
|
||||||
|
text: Evaluate Pipeline Output
|
||||||
|
url: /documentation/improve-search/pipeline-output-quality/
|
||||||
|
- partial: documentation/guides/guide-cards
|
||||||
|
section: /documentation/search-quality/
|
||||||
|
aliases:
|
||||||
|
- /documentation/improve-search/
|
||||||
|
worked_examples:
|
||||||
|
- /documentation/tutorials-search-engineering/ann-recall/
|
||||||
|
---
|
||||||
|
|
||||||
+6
-2
@@ -2,8 +2,12 @@
|
|||||||
title: Evaluating Pipeline Output Quality
|
title: Evaluating Pipeline Output Quality
|
||||||
weight: 7
|
weight: 7
|
||||||
aliases:
|
aliases:
|
||||||
- /documentation/tutorials/retrieval-quality-pipeline-output/
|
- /documentation/tutorials/retrieval-quality-pipeline-output/
|
||||||
partition: ecosystem
|
partition: learn
|
||||||
|
learning_kind: guides
|
||||||
|
url: /documentation/improve-search/pipeline-output-quality/
|
||||||
|
short_description: Separate retrieval failures from generation failures and evaluate whether your full pipeline produces supported, useful answers.
|
||||||
|
description: Evaluate retrieval and generation separately to identify why a Qdrant search pipeline returns an unsupported answer or misses the information users need.
|
||||||
---
|
---
|
||||||
|
|
||||||
# Evaluating Pipeline Output Quality
|
# Evaluating Pipeline Output Quality
|
||||||
+6
-2
@@ -2,8 +2,12 @@
|
|||||||
title: Measuring Retrieval Relevance
|
title: Measuring Retrieval Relevance
|
||||||
weight: 6
|
weight: 6
|
||||||
aliases:
|
aliases:
|
||||||
- /documentation/tutorials/retrieval-quality-golden-set/
|
- /documentation/tutorials/retrieval-quality-golden-set/
|
||||||
partition: ecosystem
|
partition: learn
|
||||||
|
learning_kind: guides
|
||||||
|
url: /documentation/improve-search/retrieval-relevance/
|
||||||
|
short_description: Build a labeled query set and measure whether retrieved documents answer users' questions, with query-level relevance metrics.
|
||||||
|
description: Measure Qdrant retrieval relevance with labeled queries, document IDs, and ranking metrics to compare search configurations on your own data.
|
||||||
---
|
---
|
||||||
|
|
||||||
# Measuring Retrieval Relevance
|
# Measuring Retrieval Relevance
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
---
|
||||||
|
title: Search Design & Tuning
|
||||||
|
short_description: Choose embedding models and retrieval strategies, then tune candidate depth, fusion, reranking, and memory against your search goals.
|
||||||
|
description: 'Design and tune Qdrant search: choose embeddings and retrieval strategies, then evaluate changes to candidate depth, fusion, reranking, and memory.'
|
||||||
|
partition: learn
|
||||||
|
learning_kind: guides
|
||||||
|
weight: 125
|
||||||
|
hideTOC: true
|
||||||
|
breadcrumb: false
|
||||||
|
guide_icon: /icons/outline/speedometer-blue.svg
|
||||||
|
content:
|
||||||
|
- partial: documentation/banners/banner-a
|
||||||
|
title: Search Design & Tuning
|
||||||
|
description: Choose a search approach, then use evaluation results to decide what to change.
|
||||||
|
linkDescription: Start with a design decision or follow the complete retrieval tuning series.
|
||||||
|
cloudButton:
|
||||||
|
text: Choose an Embedding Model
|
||||||
|
url: /documentation/search-quality/choose-embedding-model/
|
||||||
|
localButton:
|
||||||
|
text: Start the Tuning Series
|
||||||
|
url: /documentation/search-tuning/hybrid-search/
|
||||||
|
- partial: documentation/guides/guide-cards
|
||||||
|
section: /documentation/search-tuning/
|
||||||
|
guide_series_title: Tune Your Retrieval Pipeline
|
||||||
|
---
|
||||||
+24
-20
@@ -1,21 +1,25 @@
|
|||||||
---
|
---
|
||||||
title: "What to Check Before Tuning a Qdrant Collection"
|
title: What to Check Before Tuning a Qdrant Collection
|
||||||
short_description: "Seven collection settings that degrade retrieval without an error, the order to try changes in, and how many labeled queries a gain needs."
|
short_description: Seven collection settings that degrade retrieval without an error, the order to try changes in, and how many labeled queries a gain needs.
|
||||||
description: "Audit a Qdrant collection: find the settings that degrade retrieval silently, choose the cheapest next change, and size a labeled query set."
|
description: 'Audit a Qdrant collection: find the settings that degrade retrieval silently, choose the cheapest next change, and size a labeled query set.'
|
||||||
preview_dir: /articles_data/before-tuning-a-qdrant-collection/preview
|
preview_dir: /articles_data/before-tuning-a-qdrant-collection/preview
|
||||||
social_preview_image: /articles_data/before-tuning-a-qdrant-collection/preview/social_preview.jpg
|
social_preview_image: /articles_data/before-tuning-a-qdrant-collection/preview/social_preview.jpg
|
||||||
weight: -214
|
weight: 120
|
||||||
author: Dylan Couzon
|
author: Dylan Couzon
|
||||||
author_link: https://www.linkedin.com/in/dcouzon/
|
author_link: https://www.linkedin.com/in/dcouzon/
|
||||||
date: 2026-08-20T00:00:00+03:00
|
date: 2026-08-20 00:00:00+03:00
|
||||||
draft: false
|
draft: false
|
||||||
keywords:
|
keywords:
|
||||||
- retrieval tuning
|
- retrieval tuning
|
||||||
- search relevance
|
- search relevance
|
||||||
- nDCG
|
- nDCG
|
||||||
- labeled query set
|
- labeled query set
|
||||||
- Qdrant collection audit
|
- Qdrant collection audit
|
||||||
category: search-quality
|
partition: learn
|
||||||
|
learning_kind: guides
|
||||||
|
aliases:
|
||||||
|
- /articles/before-tuning-a-qdrant-collection/
|
||||||
|
guide_series: true
|
||||||
---
|
---
|
||||||
|
|
||||||
Before you change a setting, decide what better retrieval means for your workload. The right document at rank one, more candidates for a reranker, lower latency, and a smaller memory footprint each favor different settings, so pick your goal first. If your labeled queries can't detect the improvement you're chasing, you won't be able to tell whether a change helped.
|
Before you change a setting, decide what better retrieval means for your workload. The right document at rank one, more candidates for a reranker, lower latency, and a smaller memory footprint each favor different settings, so pick your goal first. If your labeled queries can't detect the improvement you're chasing, you won't be able to tell whether a change helped.
|
||||||
@@ -30,7 +34,7 @@ Every query first retrieves candidates, then ranks them. In dense-only search, o
|
|||||||
|
|
||||||
_The hybrid pipeline and the settings each stage owns. Dense-only search uses the dense prefetch path on its own, so `limit` and `hnsw_ef` are its only settings here._
|
_The hybrid pipeline and the settings each stage owns. Dense-only search uses the dense prefetch path on its own, so `limit` and `hnsw_ef` are its only settings here._
|
||||||
|
|
||||||
If you run dense-only search and exact keywords are missing from results, hybrid search is the first change to test. [Tuning hybrid search](/articles/how-to-tune-hybrid-search/) covers the request shape, what the second prefetch costs, and how to check that fusion beats either prefetch on your labels.
|
If you run dense-only search and exact keywords are missing from results, hybrid search is the first change to test. [Tuning hybrid search](/documentation/search-tuning/how-to-tune-hybrid-search/) covers the request shape, what the second prefetch costs, and how to check that fusion beats either prefetch on your labels.
|
||||||
|
|
||||||
Before you tune:
|
Before you tune:
|
||||||
|
|
||||||
@@ -44,12 +48,12 @@ Start with the failure mode, not the config reference. The table maps each sympt
|
|||||||
| What You See | First Check | Read Next |
|
| What You See | First Check | Read Next |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| You cannot separate a gain from noise | Build labeled queries, choose a metric, and calculate an interval | This article |
|
| You cannot separate a gain from noise | Build labeled queries, choose a metric, and calculate an interval | This article |
|
||||||
| Relevant documents do not appear | Measure whether candidate depth is limiting recall | [Candidate Depth: How Much Retrieval Is Enough?](/articles/candidate-depth/) |
|
| Relevant documents do not appear | Measure whether candidate depth is limiting recall | [Candidate Depth: How Much Retrieval Is Enough?](/documentation/search-tuning/candidate-depth/) |
|
||||||
| Keywords, identifiers, SKUs, or error codes do not match | Add a sparse prefetch and measure fusion against each prefetch alone | [How to Tune Hybrid Search in Qdrant](/articles/how-to-tune-hybrid-search/) |
|
| Keywords, identifiers, SKUs, or error codes do not match | Add a sparse prefetch and measure fusion against each prefetch alone | [How to Tune Hybrid Search in Qdrant](/documentation/search-tuning/how-to-tune-hybrid-search/) |
|
||||||
| Relevant documents are present but misordered | For hybrid search, tune fusion. If the candidate list needs another ranking stage, test a reranker | [How to Tune Hybrid Search in Qdrant](/articles/how-to-tune-hybrid-search/), [When Is a Reranker Worth It?](/articles/when-a-reranker-is-worth-it/) |
|
| Relevant documents are present but misordered | For hybrid search, tune fusion. If the candidate list needs another ranking stage, test a reranker | [How to Tune Hybrid Search in Qdrant](/documentation/search-tuning/how-to-tune-hybrid-search/), [When Is a Reranker Worth It?](/documentation/search-tuning/when-a-reranker-is-worth-it/) |
|
||||||
| Results repeat near-duplicates | Test maximal marginal relevance. If chunks from one document fill the page, use grouping | [When Is a Reranker Worth It?](/articles/when-a-reranker-is-worth-it/) |
|
| Results repeat near-duplicates | Test maximal marginal relevance. If chunks from one document fill the page, use grouping | [When Is a Reranker Worth It?](/documentation/search-tuning/when-a-reranker-is-worth-it/) |
|
||||||
| Search misses its p95 target | Measure the cost of candidate depth before adding another retrieval stage | [Candidate Depth: How Much Retrieval Is Enough?](/articles/candidate-depth/) |
|
| Search misses its p95 target | Measure the cost of candidate depth before adding another retrieval stage | [Candidate Depth: How Much Retrieval Is Enough?](/documentation/search-tuning/candidate-depth/) |
|
||||||
| The collection no longer fits in RAM | Test memory placement and rescoring | [When Your Collection Outgrows RAM](/articles/when-your-collection-outgrows-ram/) |
|
| The collection no longer fits in RAM | Test memory placement and rescoring | [When Your Collection Outgrows RAM](/documentation/search-tuning/when-your-collection-outgrows-ram/) |
|
||||||
|
|
||||||
## How to Read These Measurements
|
## How to Read These Measurements
|
||||||
|
|
||||||
@@ -213,7 +217,7 @@ The more labeled queries you evaluate, the more precise the measured gain. Acros
|
|||||||
|
|
||||||
The label count you need depends primarily on effect size and query-to-query variation, not collection size alone.
|
The label count you need depends primarily on effect size and query-to-query variation, not collection size alone.
|
||||||
|
|
||||||
In our measurements, [fusion settings](/articles/how-to-tune-hybrid-search/) moved `nDCG@10` by 0.012 to 0.038, gains from tuning an already-working collection rather than rebuilding the retrieval pipeline.
|
In our measurements, [fusion settings](/documentation/search-tuning/how-to-tune-hybrid-search/) moved `nDCG@10` by 0.012 to 0.038, gains from tuning an already-working collection rather than rebuilding the retrieval pipeline.
|
||||||
|
|
||||||
Fifty labeled queries were enough for the larger gains: the 0.038 gain had an interval excluding zero in 93% of draws, while gains under 0.02 cleared that bar in 7% to 38%. Treat small movement as unresolved until you have the labels to measure it.
|
Fifty labeled queries were enough for the larger gains: the 0.038 gain had an interval excluding zero in 93% of draws, while gains under 0.02 cleared that bar in 7% to 38%. Treat small movement as unresolved until you have the labels to measure it.
|
||||||
|
|
||||||
@@ -227,4 +231,4 @@ If you compare separately rebuilt indexes, check top-10 agreement across two bui
|
|||||||
|
|
||||||
## Start with One Change
|
## Start with One Change
|
||||||
|
|
||||||
Record the current relevance metric and p95 latency for a representative query set. Choose one low-cost change from the symptom table, validate it on fresh queries, and keep it only if the gain survives. Once you have that baseline, [Candidate Depth: How Much Retrieval Is Enough?](/articles/candidate-depth/) shows how to test whether retrieval depth is the constraint.
|
Record the current relevance metric and p95 latency for a representative query set. Choose one low-cost change from the symptom table, validate it on fresh queries, and keep it only if the gain survives. Once you have that baseline, [Candidate Depth: How Much Retrieval Is Enough?](/documentation/search-tuning/candidate-depth/) shows how to test whether retrieval depth is the constraint.
|
||||||
+22
-18
@@ -1,24 +1,28 @@
|
|||||||
---
|
---
|
||||||
title: "Candidate Depth: How Much Retrieval Is Enough?"
|
title: 'Candidate Depth: How Much Retrieval Is Enough?'
|
||||||
short_description: "Raising candidate depth raises the best score a later ranking stage could reach, but default fusion barely used that extra room."
|
short_description: Raising candidate depth raises the best score a later ranking stage could reach, but default fusion barely used that extra room.
|
||||||
description: "Set candidate depth and hnsw_ef in Qdrant, measure the gap between your ranking and a perfect one, and balance the trade-offs."
|
description: Set candidate depth and hnsw_ef in Qdrant, measure the gap between your ranking and a perfect one, and balance the trade-offs.
|
||||||
preview_dir: /articles_data/candidate-depth/preview
|
preview_dir: /articles_data/candidate-depth/preview
|
||||||
social_preview_image: /articles_data/candidate-depth/preview/social_preview.jpg
|
social_preview_image: /articles_data/candidate-depth/preview/social_preview.jpg
|
||||||
weight: -213
|
weight: 130
|
||||||
author: Dylan Couzon
|
author: Dylan Couzon
|
||||||
author_link: https://www.linkedin.com/in/dcouzon/
|
author_link: https://www.linkedin.com/in/dcouzon/
|
||||||
date: 2026-08-21T00:00:00+03:00
|
date: 2026-08-21 00:00:00+03:00
|
||||||
draft: false
|
draft: false
|
||||||
keywords:
|
keywords:
|
||||||
- candidate depth
|
- candidate depth
|
||||||
- hnsw_ef
|
- hnsw_ef
|
||||||
- scalar quantization
|
- scalar quantization
|
||||||
- memory tiers
|
- memory tiers
|
||||||
- HNSW tuning
|
- HNSW tuning
|
||||||
category: search-quality
|
partition: learn
|
||||||
|
learning_kind: guides
|
||||||
|
aliases:
|
||||||
|
- /articles/candidate-depth/
|
||||||
|
guide_series: true
|
||||||
---
|
---
|
||||||
|
|
||||||
Before you tune candidate depth, use the [pre-tuning checks](/articles/before-tuning-a-qdrant-collection/) to verify index state and set a labeled baseline. Everything below measures against that baseline.
|
Before you tune candidate depth, use the [pre-tuning checks](/documentation/search-tuning/before-tuning-a-qdrant-collection/) to verify index state and set a labeled baseline. Everything below measures against that baseline.
|
||||||
|
|
||||||
Candidate depth is the number of candidates a retrieval stage passes to a later ranking stage. It matters only when a later stage can use the extra candidates. In hybrid search, every `prefetch` carries its own `limit`, and a [multi-stage query](/documentation/search/hybrid-queries/#multi-stage-queries) that nests one prefetch inside another sets a depth at each level. In dense-only or sparse-only search, it is the number of candidates you pass to a reranker or other downstream stage.
|
Candidate depth is the number of candidates a retrieval stage passes to a later ranking stage. It matters only when a later stage can use the extra candidates. In hybrid search, every `prefetch` carries its own `limit`, and a [multi-stage query](/documentation/search/hybrid-queries/#multi-stage-queries) that nests one prefetch inside another sets a depth at each level. In dense-only or sparse-only search, it is the number of candidates you pass to a reranker or other downstream stage.
|
||||||
|
|
||||||
@@ -34,7 +38,7 @@ Candidate depth is the number of candidates a retrieval stage passes to a later
|
|||||||
|
|
||||||
## More Candidates Can Raise the Best Possible Score
|
## More Candidates Can Raise the Best Possible Score
|
||||||
|
|
||||||
Start by measuring the gap between the candidates you retrieved and the order your pipeline returns them in. Use your [labeled query set](/articles/before-tuning-a-qdrant-collection/#make-sure-your-labels-can-detect-a-gain) to score the candidate set as if it were ordered perfectly. That is the best possible score any later ranking of those candidates could reach. Compare it with the current score from the same queries. In these hybrid measurements, the current score is fusion's `nDCG@10` over the same candidates. `nDCG@10` grades the top 10 results and gives more credit to relevant documents near the top.
|
Start by measuring the gap between the candidates you retrieved and the order your pipeline returns them in. Use your [labeled query set](/documentation/search-tuning/before-tuning-a-qdrant-collection/#make-sure-your-labels-can-detect-a-gain) to score the candidate set as if it were ordered perfectly. That is the best possible score any later ranking of those candidates could reach. Compare it with the current score from the same queries. In these hybrid measurements, the current score is fusion's `nDCG@10` over the same candidates. `nDCG@10` grades the top 10 results and gives more credit to relevant documents near the top.
|
||||||
|
|
||||||
Suppose a query retrieves three relevant documents, and fusion ranks them 4, 30, and 180. The current score sees only the one at rank 4, since the other two sit outside the top 10 it grades. The best possible score reorders those same candidates and puts all three at the top. No later ranking stage could do better with the candidates that were retrieved.
|
Suppose a query retrieves three relevant documents, and fusion ranks them 4, 30, and 180. The current score sees only the one at rank 4, since the other two sit outside the top 10 it grades. The best possible score reorders those same candidates and puts all three at the top. No later ranking stage could do better with the candidates that were retrieved.
|
||||||
|
|
||||||
@@ -56,9 +60,9 @@ _The full sweep behind the table. The best possible score climbs at every depth
|
|||||||
|
|
||||||
The best possible score change rises with corpus size across these five, from 5,183 documents on SciFact to 100,000 on DBPedia-entity, while the current score change stays flat. Size and domain move together here, so re-measure the gap as your own collection grows.
|
The best possible score change rises with corpus size across these five, from 5,183 documents on SciFact to 100,000 on DBPedia-entity, while the current score change stays flat. Size and domain move together here, so re-measure the gap as your own collection grows.
|
||||||
|
|
||||||
With Qdrant's default [RRF](/documentation/search/hybrid-queries/#reciprocal-rank-fusion-rrf), the top ranks in each `prefetch` contribute far more to the fused score than the tail. Raising `limit` can add candidates without changing the top 10, or replace a more relevant result. The fused score is not always higher at greater depth: CodeSearchNet peaks at `limit=200` and is lower at 500, and DBPedia-entity peaks at 50. Other fusion methods can rank those candidates differently. [Fusion tuning](/articles/how-to-tune-hybrid-search/) shows how to test them on your labels.
|
With Qdrant's default [RRF](/documentation/search/hybrid-queries/#reciprocal-rank-fusion-rrf), the top ranks in each `prefetch` contribute far more to the fused score than the tail. Raising `limit` can add candidates without changing the top 10, or replace a more relevant result. The fused score is not always higher at greater depth: CodeSearchNet peaks at `limit=200` and is lower at 500, and DBPedia-entity peaks at 50. Other fusion methods can rank those candidates differently. [Fusion tuning](/documentation/search-tuning/how-to-tune-hybrid-search/) shows how to test them on your labels.
|
||||||
|
|
||||||
Start `limit` around 100 to 200, then test larger values on your own labels. A [reranker](/articles/when-a-reranker-is-worth-it/) can use the added candidates, and a [Formula Query](/documentation/search/hybrid-queries/#custom-scoring-with-a-formula-query) can rescore those same candidates from payload fields.
|
Start `limit` around 100 to 200, then test larger values on your own labels. A [reranker](/documentation/search-tuning/when-a-reranker-is-worth-it/) can use the added candidates, and a [Formula Query](/documentation/search/hybrid-queries/#custom-scoring-with-a-formula-query) can rescore those same candidates from payload fields.
|
||||||
|
|
||||||
Raising `limit` adds retrieval work. If a reranker follows, it also increases the number of candidates the reranker scores. In our single-shard tests, raising `limit` from 10 to 500 increased median latency by 37% to 43%. These results establish the direction, not a portable ratio. Measure the change under your own p95 budget, concurrency, and shard fan-out.
|
Raising `limit` adds retrieval work. If a reranker follows, it also increases the number of candidates the reranker scores. In our single-shard tests, raising `limit` from 10 to 500 increased median latency by 37% to 43%. These results establish the direction, not a portable ratio. Measure the change under your own p95 budget, concurrency, and shard fan-out.
|
||||||
|
|
||||||
@@ -122,7 +126,7 @@ for ef in (16, 64, 128, 256, 512):
|
|||||||
|
|
||||||
On these five datasets, raising `hnsw_ef` through 16, 64, 128, and 512 at depth 200 moved fused `nDCG@10` by at most 0.0022. Relevant-document recall in the candidate union moved by at most 0.0040. Median latency rose between 4% and 49% across the five hybrid requests at prefetch `limit=200`. When the graph is already saturated, the wider search budget is close to pure cost.
|
On these five datasets, raising `hnsw_ef` through 16, 64, 128, and 512 at depth 200 moved fused `nDCG@10` by at most 0.0022. Relevant-document recall in the candidate union moved by at most 0.0040. Median latency rose between 4% and 49% across the five hybrid requests at prefetch `limit=200`. When the graph is already saturated, the wider search budget is close to pure cost.
|
||||||
|
|
||||||
On your collection, choose the lowest `hnsw_ef` that reaches your recall target inside your latency budget. When recall is flat from the first value, keep `hnsw_ef` where it is and confirm that Qdrant has built an HNSW graph. Qdrant builds that graph after a segment passes the default `indexing_threshold`, and smaller segments use exhaustive search where `hnsw_ef` has no effect. The [pre-tuning checks](/articles/before-tuning-a-qdrant-collection/) show how to confirm the graph exists.
|
On your collection, choose the lowest `hnsw_ef` that reaches your recall target inside your latency budget. When recall is flat from the first value, keep `hnsw_ef` where it is and confirm that Qdrant has built an HNSW graph. Qdrant builds that graph after a segment passes the default `indexing_threshold`, and smaller segments use exhaustive search where `hnsw_ef` has no effect. The [pre-tuning checks](/documentation/search-tuning/before-tuning-a-qdrant-collection/) show how to confirm the graph exists.
|
||||||
|
|
||||||
Saturation is a property of your own graph. These collections held at most 100,000 documents, built in one batch, unfiltered and unquantized. We ran the same check on the full 4,635,922-document DBPedia-entity collection, and it returned 0.957 of the exact top 10: about 4% of the true nearest neighbors never came back.
|
Saturation is a property of your own graph. These collections held at most 100,000 documents, built in one batch, unfiltered and unquantized. We ran the same check on the full 4,635,922-document DBPedia-entity collection, and it returned 0.957 of the exact top 10: about 4% of the true nearest neighbors never came back.
|
||||||
|
|
||||||
@@ -146,10 +150,10 @@ This measurement covers int8 scalar quantization on one shard at 5,000 and 100,0
|
|||||||
|
|
||||||
Compare quantization with cutting `limit`. Dropping depth from 500 to 10 removed 27% to 30% of median latency in our runs and left the footprint where it was. Int8 quantization stored the vectors at one-quarter the size and moved fused `nDCG@10` by at most 0.0001 in either direction. Of the two, quantization is the one that shrinks what the vectors need in RAM.
|
Compare quantization with cutting `limit`. Dropping depth from 500 to 10 removed 27% to 30% of median latency in our runs and left the footprint where it was. Int8 quantization stored the vectors at one-quarter the size and moved fused `nDCG@10` by at most 0.0001 in either direction. Of the two, quantization is the one that shrinks what the vectors need in RAM.
|
||||||
|
|
||||||
Once the collection outgrows RAM, the question stops being how many candidates to fetch and becomes which structures stay resident. [Memory placement and rescoring](/articles/when-your-collection-outgrows-ram/) measures that boundary on 4.6 million vectors and explains the placement rules.
|
Once the collection outgrows RAM, the question stops being how many candidates to fetch and becomes which structures stay resident. [Memory placement and rescoring](/documentation/search-tuning/when-your-collection-outgrows-ram/) measures that boundary on 4.6 million vectors and explains the placement rules.
|
||||||
|
|
||||||
## What to Tune Next
|
## What to Tune Next
|
||||||
|
|
||||||
The gap between the best possible score and the current score tells you whether the next experiment should focus on ranking or retrieval. A large gap means relevant candidates are present but not ranked highly enough. In hybrid search, test fusion settings; in any pipeline with a downstream stage, test whether a reranker can recover the gap. A small gap means ranking is already close to the best the candidate set allows, so improve the candidates instead.
|
The gap between the best possible score and the current score tells you whether the next experiment should focus on ranking or retrieval. A large gap means relevant candidates are present but not ranked highly enough. In hybrid search, test fusion settings; in any pipeline with a downstream stage, test whether a reranker can recover the gap. A small gap means ranking is already close to the best the candidate set allows, so improve the candidates instead.
|
||||||
|
|
||||||
Next, if you use hybrid search, [tune fusion over the candidates you already retrieve](/articles/how-to-tune-hybrid-search/).
|
Next, if you use hybrid search, [tune fusion over the candidates you already retrieve](/documentation/search-tuning/how-to-tune-hybrid-search/).
|
||||||
+11
-5
@@ -1,17 +1,23 @@
|
|||||||
---
|
---
|
||||||
title: "How to Choose an Embedding Model: Evaluation & Tradeoffs"
|
title: 'How to Choose an Embedding Model: Evaluation & Tradeoffs'
|
||||||
short_description: "There is no one-size-fits-all solution when it comes to embedding models. Learn how to choose the right one for your use case."
|
short_description: There is no one-size-fits-all solution when it comes to embedding models. Learn how to choose the right one for your use case.
|
||||||
description: "Building proper search requires selecting the right embedding model for your specific use case. This guide helps you navigate the selection process based on performance, cost, and other practical considerations."
|
description: Building proper search requires selecting the right embedding model for your specific use case. This guide helps you navigate the selection process based on performance, cost, and other practical considerations.
|
||||||
preview_dir: /articles_data/how-to-choose-an-embedding-model/preview
|
preview_dir: /articles_data/how-to-choose-an-embedding-model/preview
|
||||||
social_preview_image: /articles_data/how-to-choose-an-embedding-model/preview/social_preview.jpg
|
social_preview_image: /articles_data/how-to-choose-an-embedding-model/preview/social_preview.jpg
|
||||||
author: Kacper Łukawski
|
author: Kacper Łukawski
|
||||||
author_link: https://www.kacperlukawski.com
|
author_link: https://www.kacperlukawski.com
|
||||||
date: 2025-07-15T00:00:00.000Z
|
date: 2025-07-15 00:00:00+00:00
|
||||||
category: core-concepts
|
|
||||||
draft: false
|
draft: false
|
||||||
weight: 10
|
weight: 10
|
||||||
|
partition: learn
|
||||||
|
learning_kind: guides
|
||||||
|
url: /documentation/search-quality/choose-embedding-model/
|
||||||
|
aliases:
|
||||||
|
- /articles/how-to-choose-an-embedding-model/
|
||||||
---
|
---
|
||||||
|
|
||||||
|
# How to Choose an Embedding Model: Evaluation & Tradeoffs
|
||||||
|
|
||||||
No matter if you are just beginning your journey in the world of vector search, or you are a seasoned practitioner, you
|
No matter if you are just beginning your journey in the world of vector search, or you are a seasoned practitioner, you
|
||||||
have probably wondered how to choose the right embedding model to achieve the best search quality. There are some
|
have probably wondered how to choose the right embedding model to achieve the best search quality. There are some
|
||||||
public benchmarks, such as [MTEB](https://huggingface.co/spaces/mteb/leaderboard), that can help you narrow down the
|
public benchmarks, such as [MTEB](https://huggingface.co/spaces/mteb/leaderboard), that can help you narrow down the
|
||||||
+23
-17
@@ -1,24 +1,30 @@
|
|||||||
---
|
---
|
||||||
title: "How to Tune Hybrid Search in Qdrant"
|
title: How to Tune Hybrid Search in Qdrant
|
||||||
short_description: "Tune hybrid search with RRF or DBSF, choose k from relevance labels, and learn why weights are pairs instead of ratios."
|
short_description: Tune hybrid search with RRF or DBSF, choose k from relevance labels, and learn why weights are pairs instead of ratios.
|
||||||
description: "Tune hybrid search fusion in Qdrant: choose between RRF and DBSF, set the constant k from your relevance labels, and get weights right."
|
description: 'Tune hybrid search fusion in Qdrant: choose between RRF and DBSF, set the constant k from your relevance labels, and get weights right.'
|
||||||
preview_dir: /articles_data/how-to-tune-hybrid-search/preview
|
preview_dir: /articles_data/how-to-tune-hybrid-search/preview
|
||||||
social_preview_image: /articles_data/how-to-tune-hybrid-search/preview/social_preview.jpg
|
social_preview_image: /articles_data/how-to-tune-hybrid-search/preview/social_preview.jpg
|
||||||
weight: -211
|
weight: 140
|
||||||
author: Dylan Couzon
|
author: Dylan Couzon
|
||||||
author_link: https://www.linkedin.com/in/dcouzon/
|
author_link: https://www.linkedin.com/in/dcouzon/
|
||||||
date: 2026-08-22T00:00:00+03:00
|
date: 2026-08-22 00:00:00+03:00
|
||||||
draft: false
|
draft: false
|
||||||
keywords:
|
keywords:
|
||||||
- hybrid search tuning
|
- hybrid search tuning
|
||||||
- reciprocal rank fusion
|
- reciprocal rank fusion
|
||||||
- RRF k parameter
|
- RRF k parameter
|
||||||
- fusion weights
|
- fusion weights
|
||||||
- DBSF
|
- DBSF
|
||||||
category: search-quality
|
partition: learn
|
||||||
|
learning_kind: guides
|
||||||
|
aliases:
|
||||||
|
- /articles/how-to-tune-hybrid-search/
|
||||||
|
guide_series: true
|
||||||
---
|
---
|
||||||
|
|
||||||
Before you tune fusion, use the [pre-tuning checks](/articles/before-tuning-a-qdrant-collection/) to verify index state and set a labeled baseline.
|
# How to Tune Hybrid Search in Qdrant
|
||||||
|
|
||||||
|
Before you tune fusion, use the [pre-tuning checks](/documentation/search-tuning/before-tuning-a-qdrant-collection/) to verify index state and set a labeled baseline.
|
||||||
|
|
||||||
Hybrid search retrieves dense and sparse candidate lists, then fuses them into one ranking. The dense prefetch finds similar meaning; the sparse prefetch finds matching keywords. Fusion reorders the candidates the prefetches return, so a document missing from both lists cannot appear in the result.
|
Hybrid search retrieves dense and sparse candidate lists, then fuses them into one ranking. The dense prefetch finds similar meaning; the sparse prefetch finds matching keywords. Fusion reorders the candidates the prefetches return, so a document missing from both lists cannot appear in the result.
|
||||||
|
|
||||||
@@ -29,7 +35,7 @@ Before tuning, compare dense retrieval, sparse retrieval, and default [Reciproca
|
|||||||
Qdrant defaults to `k=2`. The original RRF paper uses 60, which maps to `k=61` in Qdrant's formula. That gap is what most of this article is about.
|
Qdrant defaults to `k=2`. The original RRF paper uses 60, which maps to `k=61` in Qdrant's formula. That gap is what most of this article is about.
|
||||||
|
|
||||||
<aside role="status">
|
<aside role="status">
|
||||||
<strong>Note:</strong> The measurements in this article use five public datasets chosen to vary in corpus size, document and query shape, and relevance task, so read these fusion deltas as directional. They range from 5,183 to 100,000 documents. Each collection was built in one batch on one shard, unquantized and unfiltered, with <code>all-MiniLM-L6-v2</code> for dense retrieval, Qdrant's core BM25 for sparse retrieval, and 200 candidates from each prefetch. A graph shaped by continuous upserts and optimizer merges can return a different ranking. Latency medians come from one Qdrant container on an idle laptop, one request at a time, so re-measure under your own p95 budget, concurrency, and shard fan-out. <a href="/articles/before-tuning-a-qdrant-collection/">Building a labeled set</a> explains how the winning configuration was rechecked on held-out queries.
|
<strong>Note:</strong> The measurements in this article use five public datasets chosen to vary in corpus size, document and query shape, and relevance task, so read these fusion deltas as directional. They range from 5,183 to 100,000 documents. Each collection was built in one batch on one shard, unquantized and unfiltered, with <code>all-MiniLM-L6-v2</code> for dense retrieval, Qdrant's core BM25 for sparse retrieval, and 200 candidates from each prefetch. A graph shaped by continuous upserts and optimizer merges can return a different ranking. Latency medians come from one Qdrant container on an idle laptop, one request at a time, so re-measure under your own p95 budget, concurrency, and shard fan-out. <a href="/documentation/search-tuning/before-tuning-a-qdrant-collection/">Building a labeled set</a> explains how the winning configuration was rechecked on held-out queries.
|
||||||
</aside>
|
</aside>
|
||||||
|
|
||||||
`Over the Better One` is default RRF's `nDCG@10` minus the better individual prefetch. `Second Prefetch Cost` is the median latency the second prefetch adds over the dense prefetch alone.
|
`Over the Better One` is default RRF's `nDCG@10` minus the better individual prefetch. `Second Prefetch Cost` is the median latency the second prefetch adds over the dense prefetch alone.
|
||||||
@@ -58,7 +64,7 @@ RRF ignores score scale, so a cosine similarity and a BM25 score combine without
|
|||||||
|
|
||||||
## Compare RRF and DBSF on Your Labels
|
## Compare RRF and DBSF on Your Labels
|
||||||
|
|
||||||
Use your [labeled query set](/articles/before-tuning-a-qdrant-collection/#make-sure-your-labels-can-detect-a-gain) to compare RRF and DBSF over the same prefetches. Run RRF at `k=2` and equal weights, then run DBSF.
|
Use your [labeled query set](/documentation/search-tuning/before-tuning-a-qdrant-collection/#make-sure-your-labels-can-detect-a-gain) to compare RRF and DBSF over the same prefetches. Run RRF at `k=2` and equal weights, then run DBSF.
|
||||||
|
|
||||||
Both queries read the same two candidate lists, so connect once and build the prefetches once. The prefetches must use the models the collection was indexed with.
|
Both queries read the same two candidate lists, so connect once and build the prefetches once. The prefetches must use the models the collection was indexed with.
|
||||||
|
|
||||||
@@ -99,7 +105,7 @@ dbsf_response = client.query_points(
|
|||||||
```
|
```
|
||||||
|
|
||||||
<aside role="status">
|
<aside role="status">
|
||||||
In a multi-shard collection, each shard applies its own prefetch <code>limit</code>. With root-level fusion, Qdrant combines those candidates across shards. A larger limit can expose more candidates to fusion, but it also adds retrieval work and candidates for a downstream reranker. Fusion nested inside a prefetch runs per shard, and DBSF rescales against the score distribution of each shard's own candidates. The <a href="/articles/candidate-depth/">candidate depth guide</a> explains how to set the limit.
|
In a multi-shard collection, each shard applies its own prefetch <code>limit</code>. With root-level fusion, Qdrant combines those candidates across shards. A larger limit can expose more candidates to fusion, but it also adds retrieval work and candidates for a downstream reranker. Fusion nested inside a prefetch runs per shard, and DBSF rescales against the score distribution of each shard's own candidates. The <a href="/documentation/search-tuning/candidate-depth/">candidate depth guide</a> explains how to set the limit.
|
||||||
</aside>
|
</aside>
|
||||||
|
|
||||||
On three of these five datasets, DBSF scored higher than default RRF by a margin whose 95% interval excludes zero. SciFact's 0.0148 gain and ArguAna's 0.0045 loss both cross zero, so those two datasets are inconclusive.
|
On three of these five datasets, DBSF scored higher than default RRF by a margin whose 95% interval excludes zero. SciFact's 0.0148 gain and ArguAna's 0.0045 loss both cross zero, so those two datasets are inconclusive.
|
||||||
@@ -162,7 +168,7 @@ A weight of 0.0 keeps every document from that prefetch and scores each one 0.0.
|
|||||||
|
|
||||||
## Confirm the Selected Configuration on Held-Out Queries
|
## Confirm the Selected Configuration on Held-Out Queries
|
||||||
|
|
||||||
A configuration can score best on the queries used to select it and still fail on held-out queries. Run both checks from [the pre-tuning article](/articles/before-tuning-a-qdrant-collection/): a bootstrap interval on per-query gain, and a split between selection and held-out queries. Ship a configuration when its interval excludes zero and its selected gain holds on the held-out half.
|
A configuration can score best on the queries used to select it and still fail on held-out queries. Run both checks from [the pre-tuning article](/documentation/search-tuning/before-tuning-a-qdrant-collection/): a bootstrap interval on per-query gain, and a split between selection and held-out queries. Ship a configuration when its interval excludes zero and its selected gain holds on the held-out half.
|
||||||
|
|
||||||
On SciFact's 300 queries, nothing we tried had a 95% interval that excluded zero, including DBSF's 0.0148 gain. Across 200 random splits, a selected fusion configuration kept 67% to 95% of its gain on held-out queries. Keeping the default is a real answer, and it was the right one on one of our five datasets.
|
On SciFact's 300 queries, nothing we tried had a 95% interval that excluded zero, including DBSF's 0.0148 gain. Across 200 random splits, a selected fusion configuration kept 67% to 95% of its gain on held-out queries. Keeping the default is a real answer, and it was the right one on one of our five datasets.
|
||||||
|
|
||||||
@@ -176,4 +182,4 @@ Each step is cheap enough to run in a single session.
|
|||||||
4. Sweep a few weight pairs at that `k`.
|
4. Sweep a few weight pairs at that `k`.
|
||||||
5. Validate the winner on held-out queries before shipping.
|
5. Validate the winner on held-out queries before shipping.
|
||||||
|
|
||||||
Next, if a downstream model could improve the ranking of your retrieved candidates, [test whether a reranker is worth its cost](/articles/when-a-reranker-is-worth-it/).
|
Next, if a downstream model could improve the ranking of your retrieved candidates, [test whether a reranker is worth its cost](/documentation/search-tuning/when-a-reranker-is-worth-it/).
|
||||||
+22
-16
@@ -1,23 +1,29 @@
|
|||||||
---
|
---
|
||||||
title: "Hybrid Search in Qdrant"
|
title: Hybrid Search in Qdrant
|
||||||
short_description: "Run dense and sparse retrieval together: the queries each one gets wrong, what the second index costs, and how to tell if it helped."
|
short_description: 'Run dense and sparse retrieval together: the queries each one gets wrong, what the second index costs, and how to tell if it helped.'
|
||||||
description: "Decide whether to add hybrid search in Qdrant: the queries dense and sparse retrieval each get wrong, and how to measure the gain."
|
description: 'Decide whether to add hybrid search in Qdrant: the queries dense and sparse retrieval each get wrong, and how to measure the gain.'
|
||||||
preview_dir: /articles_data/hybrid-search/preview
|
preview_dir: /articles_data/hybrid-search/preview
|
||||||
social_preview_image: /articles_data/hybrid-search/preview/social_preview.jpg
|
social_preview_image: /articles_data/hybrid-search/preview/social_preview.jpg
|
||||||
weight: -215
|
weight: 110
|
||||||
author: Dylan Couzon
|
author: Dylan Couzon
|
||||||
author_link: https://www.linkedin.com/in/dcouzon/
|
author_link: https://www.linkedin.com/in/dcouzon/
|
||||||
date: 2026-08-24T09:00:00+03:00
|
date: 2026-08-24 09:00:00+03:00
|
||||||
draft: false
|
draft: false
|
||||||
keywords:
|
keywords:
|
||||||
- hybrid search
|
- hybrid search
|
||||||
- sparse vectors
|
- sparse vectors
|
||||||
- BM25
|
- BM25
|
||||||
- reciprocal rank fusion
|
- reciprocal rank fusion
|
||||||
- search relevance
|
- search relevance
|
||||||
category: search-quality
|
partition: learn
|
||||||
|
learning_kind: guides
|
||||||
|
aliases:
|
||||||
|
- /articles/hybrid-search/
|
||||||
|
guide_series: true
|
||||||
---
|
---
|
||||||
|
|
||||||
|
# Hybrid Search in Qdrant
|
||||||
|
|
||||||
A search result can look plausible and still be wrong. Dense retrieval can return a document on the right topic but miss an exact identifier copied into the query. Sparse retrieval can miss a relevant document when the query describes it with terms the corpus doesn't use. Either way, your logs record a successful query.
|
A search result can look plausible and still be wrong. Dense retrieval can return a document on the right topic but miss an exact identifier copied into the query. Sparse retrieval can miss a relevant document when the query describes it with terms the corpus doesn't use. Either way, your logs record a successful query.
|
||||||
|
|
||||||
Hybrid search runs dense and sparse retrieval over the same query, then merges their result lists. Dense retrieval adds semantic similarity, so paraphrases can rank together. Sparse retrieval adds weighted term matching for exact words and identifiers.
|
Hybrid search runs dense and sparse retrieval over the same query, then merges their result lists. Dense retrieval adds semantic similarity, so paraphrases can rank together. Sparse retrieval adds weighted term matching for exact words and identifiers.
|
||||||
@@ -65,7 +71,7 @@ Formula Queries serve a different purpose: they rescore retrieved candidates wit
|
|||||||
|
|
||||||
Fusion only reorders. It works on the union of what the two prefetches returned, so a document neither one found cannot appear anywhere in the results.
|
Fusion only reorders. It works on the union of what the two prefetches returned, so a document neither one found cannot appear anywhere in the results.
|
||||||
|
|
||||||
If a relevant document falls below a prefetch cutoff, increasing one or both prefetch limits can expose it to fusion. A larger limit adds retrieval work, and it does not help if the retrievers still miss the document at greater depth. [Candidate depth](/articles/candidate-depth/) explains how to test the limits, and the [hybrid query documentation](/documentation/search/hybrid-queries/) covers how prefetches feed fusion.
|
If a relevant document falls below a prefetch cutoff, increasing one or both prefetch limits can expose it to fusion. A larger limit adds retrieval work, and it does not help if the retrievers still miss the document at greater depth. [Candidate depth](/documentation/search-tuning/candidate-depth/) explains how to test the limits, and the [hybrid query documentation](/documentation/search/hybrid-queries/) covers how prefetches feed fusion.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
@@ -142,9 +148,9 @@ Run the same labeled queries with dense retrieval, sparse retrieval, and fusion.
|
|||||||
|
|
||||||
First, check whether fusion beats both retrievers. Review the queries where their rankings differ, then see whether the wins and losses cluster around important query types in your workload.
|
First, check whether fusion beats both retrievers. Review the queries where their rankings differ, then see whether the wins and losses cluster around important query types in your workload.
|
||||||
|
|
||||||
If one retriever finds relevant results that fusion ranks too low, tune the fusion method or weights. [How to Tune Hybrid Search](/articles/how-to-tune-hybrid-search/) covers those settings. If both retrievers miss a result, fusion has no candidate to promote.
|
If one retriever finds relevant results that fusion ranks too low, tune the fusion method or weights. [How to Tune Hybrid Search](/documentation/search-tuning/how-to-tune-hybrid-search/) covers those settings. If both retrievers miss a result, fusion has no candidate to promote.
|
||||||
|
|
||||||
Recheck the winning setup on held-out queries. [Building a labeled set](/articles/before-tuning-a-qdrant-collection/) covers query selection and held-out evaluation.
|
Recheck the winning setup on held-out queries. [Building a labeled set](/documentation/search-tuning/before-tuning-a-qdrant-collection/) covers query selection and held-out evaluation.
|
||||||
|
|
||||||
Dense retrieval may already cover much of a natural-language-only workload, but query shape alone cannot tell you whether hybrid search will help.
|
Dense retrieval may already cover much of a natural-language-only workload, but query shape alone cannot tell you whether hybrid search will help.
|
||||||
|
|
||||||
@@ -152,5 +158,5 @@ Keep the sparse retriever when the relevance gain justifies its measured indexin
|
|||||||
|
|
||||||
## What to Test Next
|
## What to Test Next
|
||||||
|
|
||||||
- **Add more stages.** [Multi-stage queries](/documentation/search/hybrid-queries/#multi-stage-queries) retrieve with a cheap representation and rescore with an expensive one. Cross-encoder reranking puts the query and chunk into a model together. [When Is a Reranker Worth It?](/articles/when-a-reranker-is-worth-it/) compares that approach with a tuned first stage.
|
- **Add more stages.** [Multi-stage queries](/documentation/search/hybrid-queries/#multi-stage-queries) retrieve with a cheap representation and rescore with an expensive one. Cross-encoder reranking puts the query and chunk into a model together. [When Is a Reranker Worth It?](/documentation/search-tuning/when-a-reranker-is-worth-it/) compares that approach with a tuned first stage.
|
||||||
- **Tune what you have.** The fusion method, the RRF constant, and the per-retriever weights can all move relevance without adding a stage. [How to Tune Hybrid Search](/articles/how-to-tune-hybrid-search/) measures each one across the same five datasets.
|
- **Tune what you have.** The fusion method, the RRF constant, and the per-retriever weights can all move relevance without adding a stage. [How to Tune Hybrid Search](/documentation/search-tuning/how-to-tune-hybrid-search/) measures each one across the same five datasets.
|
||||||
+6
-4
@@ -1,9 +1,11 @@
|
|||||||
---
|
---
|
||||||
title: Query Decomposition for Multi-Hop Questions
|
title: Query Decomposition for Multi-Hop Questions
|
||||||
short_description: "Answer multi-hop questions by retrieving in steps: an LLM asks each follow-up sub-question, then fuse the per-hop results with RRF."
|
short_description: 'Answer multi-hop questions by retrieving in steps: an LLM asks each follow-up sub-question, then fuse the per-hop results with RRF.'
|
||||||
description: "Answer multi-hop questions in Qdrant: decompose the query into retrieval steps, let an LLM ask each follow-up, and fuse results with RRF."
|
description: 'Answer multi-hop questions in Qdrant: decompose the query into retrieval steps, let an LLM ask each follow-up, and fuse results with RRF.'
|
||||||
weight: 8
|
weight: 20
|
||||||
partition: ecosystem
|
partition: learn
|
||||||
|
learning_kind: guides
|
||||||
|
url: /documentation/improve-search/query-decomposition/
|
||||||
---
|
---
|
||||||
|
|
||||||
# Query Decomposition for Multi-Hop Questions
|
# Query Decomposition for Multi-Hop Questions
|
||||||
+25
-21
@@ -1,40 +1,44 @@
|
|||||||
---
|
---
|
||||||
title: "When Is a Reranker Worth It?"
|
title: When Is a Reranker Worth It?
|
||||||
short_description: "Rerank 10 candidates, compare with your tuned first stage on held-out queries, and raise the count only after the win holds."
|
short_description: Rerank 10 candidates, compare with your tuned first stage on held-out queries, and raise the count only after the win holds.
|
||||||
description: "Test whether a cross-encoder reranker beats your tuned first stage in Qdrant, then choose the model and candidate count from measured results."
|
description: Test whether a cross-encoder reranker beats your tuned first stage in Qdrant, then choose the model and candidate count from measured results.
|
||||||
preview_dir: /articles_data/when-a-reranker-is-worth-it/preview
|
preview_dir: /articles_data/when-a-reranker-is-worth-it/preview
|
||||||
social_preview_image: /articles_data/when-a-reranker-is-worth-it/preview/social_preview.jpg
|
social_preview_image: /articles_data/when-a-reranker-is-worth-it/preview/social_preview.jpg
|
||||||
weight: -210
|
weight: 150
|
||||||
author: Dylan Couzon
|
author: Dylan Couzon
|
||||||
author_link: https://www.linkedin.com/in/dcouzon/
|
author_link: https://www.linkedin.com/in/dcouzon/
|
||||||
date: 2026-08-23T00:00:00+03:00
|
date: 2026-08-23 00:00:00+03:00
|
||||||
draft: false
|
draft: false
|
||||||
keywords:
|
keywords:
|
||||||
- cross-encoder reranker
|
- cross-encoder reranker
|
||||||
- reranking
|
- reranking
|
||||||
- MMR
|
- MMR
|
||||||
- search relevance
|
- search relevance
|
||||||
- FastEmbed
|
- FastEmbed
|
||||||
category: search-quality
|
partition: learn
|
||||||
|
learning_kind: guides
|
||||||
|
aliases:
|
||||||
|
- /articles/when-a-reranker-is-worth-it/
|
||||||
|
guide_series: true
|
||||||
---
|
---
|
||||||
|
|
||||||
Before you tune a reranker, use the [pre-tuning checks](/articles/before-tuning-a-qdrant-collection/) to verify index state and set a labeled baseline.
|
Before you tune a reranker, use the [pre-tuning checks](/documentation/search-tuning/before-tuning-a-qdrant-collection/) to verify index state and set a labeled baseline.
|
||||||
|
|
||||||
Your candidate list can already contain documents your ranking never shows. Score those candidates as if they were perfectly ordered, then compare that with the score your pipeline returns today. The gap between the two is everything a better ranking stage could recover, so measure it before you reach for a model. Use `nDCG@10`, which grades the top 10 results and gives more credit to relevant documents near the top.
|
Your candidate list can already contain documents your ranking never shows. Score those candidates as if they were perfectly ordered, then compare that with the score your pipeline returns today. The gap between the two is everything a better ranking stage could recover, so measure it before you reach for a model. Use `nDCG@10`, which grades the top 10 results and gives more credit to relevant documents near the top.
|
||||||
|
|
||||||
A wide gap means a better order is worth chasing. The deepest count measured here was 200 candidates. At that count, the `nDCG@10` gap ran from 0.247 to 0.487 across the five datasets, and [candidate depth](/articles/candidate-depth/) shows how to measure it on your own collection.
|
A wide gap means a better order is worth chasing. The deepest count measured here was 200 candidates. At that count, the `nDCG@10` gap ran from 0.247 to 0.487 across the five datasets, and [candidate depth](/documentation/search-tuning/candidate-depth/) shows how to measure it on your own collection.
|
||||||
|
|
||||||
Reranking covers several model families. This article measures cross-encoders, which read the query and candidate together as a single sequence. A classification head returns one relevance score for the pair. Joint reading lets the model capture token interactions that separately encoded query and document vectors miss.
|
Reranking covers several model families. This article measures cross-encoders, which read the query and candidate together as a single sequence. A classification head returns one relevance score for the pair. Joint reading lets the model capture token interactions that separately encoded query and document vectors miss.
|
||||||
|
|
||||||
Every candidate takes a forward pass at query time, which rules a cross-encoder out as a first stage and keeps it in the reranking slot. Late interaction models rerank from stored vectors instead, and the last section covers where they fit.
|
Every candidate takes a forward pass at query time, which rules a cross-encoder out as a first stage and keeps it in the reranking slot. Late interaction models rerank from stored vectors instead, and the last section covers where they fit.
|
||||||
|
|
||||||
<aside role="status">
|
<aside role="status">
|
||||||
<strong>Note:</strong> The measurements in this article use five public datasets chosen to vary in corpus size, document and query shape, and relevance task, so read these reranker deltas as directional. They range from 5,183 to 100,000 documents, and each collection was built in one batch on one shard, unquantized and unfiltered, with <code>all-MiniLM-L6-v2</code> for dense retrieval and Qdrant's core BM25 for sparse retrieval. Four cross-encoders reranked the fused candidates at counts from 10 through 200, scored on 200 queries per dataset. <a href="/articles/before-tuning-a-qdrant-collection/#check-the-winner-on-fresh-queries">Held-out validation</a> explains the split, and <a href="/articles/before-tuning-a-qdrant-collection/#make-sure-your-labels-can-detect-a-gain">building a labeled set</a> explains the labels.
|
<strong>Note:</strong> The measurements in this article use five public datasets chosen to vary in corpus size, document and query shape, and relevance task, so read these reranker deltas as directional. They range from 5,183 to 100,000 documents, and each collection was built in one batch on one shard, unquantized and unfiltered, with <code>all-MiniLM-L6-v2</code> for dense retrieval and Qdrant's core BM25 for sparse retrieval. Four cross-encoders reranked the fused candidates at counts from 10 through 200, scored on 200 queries per dataset. <a href="/documentation/search-tuning/before-tuning-a-qdrant-collection/#check-the-winner-on-fresh-queries">Held-out validation</a> explains the split, and <a href="/documentation/search-tuning/before-tuning-a-qdrant-collection/#make-sure-your-labels-can-detect-a-gain">building a labeled set</a> explains the labels.
|
||||||
</aside>
|
</aside>
|
||||||
|
|
||||||
## Test a Reranker in Three Steps
|
## Test a Reranker in Three Steps
|
||||||
|
|
||||||
1. Establish the baseline the reranker has to beat: [tuned fusion](/articles/how-to-tune-hybrid-search/) if you run hybrid search, your current ranking if you run dense-only or sparse-only. Confirm the documents your labels mark relevant reach the candidate list. A reranker only reorders what it receives; missing documents are a [candidate depth](/articles/candidate-depth/) or retrieval problem.
|
1. Establish the baseline the reranker has to beat: [tuned fusion](/documentation/search-tuning/how-to-tune-hybrid-search/) if you run hybrid search, your current ranking if you run dense-only or sparse-only. Confirm the documents your labels mark relevant reach the candidate list. A reranker only reorders what it receives; missing documents are a [candidate depth](/documentation/search-tuning/candidate-depth/) or retrieval problem.
|
||||||
2. Rerank 10 candidates with the model you would actually serve, since model choice moved our results more than any other setting. Read its model card first for languages, domains, and context window. [Reranking with FastEmbed](/documentation/fastembed/fastembed-rerankers/) shows the cross-encoder workflow and the available models. Compare the result with the first-stage baseline on held-out labeled queries.
|
2. Rerank 10 candidates with the model you would actually serve, since model choice moved our results more than any other setting. Read its model card first for languages, domains, and context window. [Reranking with FastEmbed](/documentation/fastembed/fastembed-rerankers/) shows the cross-encoder workflow and the available models. Compare the result with the first-stage baseline on held-out labeled queries.
|
||||||
3. Raise the candidate count only if the reranker wins. Measure throughput on your document lengths before making it part of the serving path.
|
3. Raise the candidate count only if the reranker wins. Measure throughput on your document lengths before making it part of the serving path.
|
||||||
|
|
||||||
@@ -79,7 +83,7 @@ order = sorted(range(len(fused)), key=lambda i: scores[i], reverse=True)
|
|||||||
reranked = [fused[i] for i in order]
|
reranked = [fused[i] for i in order]
|
||||||
```
|
```
|
||||||
|
|
||||||
You now have two orderings of the same 10 candidates. Score both with the `nDCG@10` function from the [pre-tuning article](/articles/before-tuning-a-qdrant-collection/#make-sure-your-labels-can-detect-a-gain).
|
You now have two orderings of the same 10 candidates. Score both with the `nDCG@10` function from the [pre-tuning article](/documentation/search-tuning/before-tuning-a-qdrant-collection/#make-sure-your-labels-can-detect-a-gain).
|
||||||
|
|
||||||
```python
|
```python
|
||||||
# relevance holds this query's labels, keyed by doc_id.
|
# relevance holds this query's labels, keyed by doc_id.
|
||||||
@@ -93,7 +97,7 @@ Run that over your labeled queries, average the per-query difference, then check
|
|||||||
|
|
||||||
Compare the reranker against the strongest first stage you can build. Qdrant's default reciprocal rank fusion (RRF) is already a solid baseline, and fusion tuned on your own labels is stronger. A reranker measured against the default can look like a win that tuning would have delivered for far less work at query time.
|
Compare the reranker against the strongest first stage you can build. Qdrant's default reciprocal rank fusion (RRF) is already a solid baseline, and fusion tuned on your own labels is stronger. A reranker measured against the default can look like a win that tuning would have delivered for far less work at query time.
|
||||||
|
|
||||||
So [tune fusion](/articles/how-to-tune-hybrid-search/) first, then make that tuned ranking the number the reranker has to beat on held-out labeled queries.
|
So [tune fusion](/documentation/search-tuning/how-to-tune-hybrid-search/) first, then make that tuned ranking the number the reranker has to beat on held-out labeled queries.
|
||||||
|
|
||||||
Each row in the following table reports the best of four cross-encoders on that dataset. The deltas show the `nDCG@10` change over default RRF and over fusion tuned on the same candidates. `MiniLM-L-6`, `MiniLM-L-12`, and `bge-reranker-base` truncate each pair at 512 tokens. `jina-reranker-v2` reads up to 1024 and was trained on a broader mix, including code.
|
Each row in the following table reports the best of four cross-encoders on that dataset. The deltas show the `nDCG@10` change over default RRF and over fusion tuned on the same candidates. `MiniLM-L-6`, `MiniLM-L-12`, and `bge-reranker-base` truncate each pair at 512 tokens. `jina-reranker-v2` reads up to 1024 and was trained on a broader mix, including code.
|
||||||
|
|
||||||
@@ -107,7 +111,7 @@ The held-out column is the one that decides. It holds the share of 200 split-hal
|
|||||||
| CodeSearchNet | `jina-reranker-v2` @ 200 | +0.169 | +0.135 | yes, 100% |
|
| CodeSearchNet | `jina-reranker-v2` @ 200 | +0.169 | +0.135 | yes, 100% |
|
||||||
| DBPedia-entity | `jina-reranker-v2` @ 200 | +0.137 | +0.115 | yes, 100% |
|
| DBPedia-entity | `jina-reranker-v2` @ 200 | +0.137 | +0.115 | yes, 100% |
|
||||||
|
|
||||||
Ship a reranker gain only when it survives held-out validation. The two confirmed wins here held in 100% of the split-half draws, while the three unconfirmed results held in under half of them. When a positive result fails the split, add queries or keep fusion, and use [the label-count table in the pre-tuning article](/articles/before-tuning-a-qdrant-collection/#make-sure-your-labels-can-detect-a-gain) to size that confirmation.
|
Ship a reranker gain only when it survives held-out validation. The two confirmed wins here held in 100% of the split-half draws, while the three unconfirmed results held in under half of them. When a positive result fails the split, add queries or keep fusion, and use [the label-count table in the pre-tuning article](/documentation/search-tuning/before-tuning-a-qdrant-collection/#make-sure-your-labels-can-detect-a-gain) to size that confirmation.
|
||||||
|
|
||||||
Keep fusion when the reranker loses to the tuned first stage. WANDS gained +0.039 over default RRF and still lost to fusion tuned on the same candidates.
|
Keep fusion when the reranker loses to the tuned first stage. WANDS gained +0.039 over default RRF and still lost to fusion tuned on the same candidates.
|
||||||
|
|
||||||
@@ -158,7 +162,7 @@ The table shows CPU throughput for the four [FastEmbed cross-encoders](/document
|
|||||||
|
|
||||||
Document length explains each range. DBPedia-entity has short entity abstracts, while SciFact has full paper abstracts.
|
Document length explains each range. DBPedia-entity has short entity abstracts, while SciFact has full paper abstracts.
|
||||||
|
|
||||||
Weigh those rates against the held-out gain. At 100 candidates, one CPU process spends between half a second and five seconds per query with the three smaller models, where the second prefetch behind [tuned fusion](/articles/how-to-tune-hybrid-search/) added 0.6 to 1.5 ms in the same setup. The 10-candidate test itself stays fast even on CPU, at 47 to 156 ms per query with the smallest model, so run it before you plan any serving work.
|
Weigh those rates against the held-out gain. At 100 candidates, one CPU process spends between half a second and five seconds per query with the three smaller models, where the second prefetch behind [tuned fusion](/documentation/search-tuning/how-to-tune-hybrid-search/) added 0.6 to 1.5 ms in the same setup. The 10-candidate test itself stays fast even on CPU, at 47 to 156 ms per query with the smallest model, so run it before you plan any serving work.
|
||||||
|
|
||||||
Pick the model on fit rather than size. `bge-reranker-base` and `jina-reranker-v2` are nearly the same size, and only the second ever beat tuned fusion. Training data and context window separated them.
|
Pick the model on fit rather than size. `bge-reranker-base` and `jina-reranker-v2` are nearly the same size, and only the second ever beat tuned fusion. Training data and context window separated them.
|
||||||
|
|
||||||
@@ -185,6 +189,6 @@ Test a [late interaction model](/documentation/fastembed/fastembed-colbert/) whe
|
|||||||
|
|
||||||
## What to Tune Next
|
## What to Tune Next
|
||||||
|
|
||||||
After a win, the work moves to throughput, where the candidate count you can serve decides how much of the gain survives. After a loss, the gap is still there and the candidates are what to change, so revisit retrieval and [candidate depth](/articles/candidate-depth/) before adding another ranking stage.
|
After a win, the work moves to throughput, where the candidate count you can serve decides how much of the gain survives. After a loss, the gap is still there and the candidates are what to change, so revisit retrieval and [candidate depth](/documentation/search-tuning/candidate-depth/) before adding another ranking stage.
|
||||||
|
|
||||||
Next, if memory is the constraint, [measure what memory placement and rescoring add to query latency](/articles/when-your-collection-outgrows-ram/).
|
Next, if memory is the constraint, [measure what memory placement and rescoring add to query latency](/documentation/search-tuning/when-your-collection-outgrows-ram/).
|
||||||
+21
-15
@@ -1,23 +1,29 @@
|
|||||||
---
|
---
|
||||||
title: "When Your Collection Outgrows RAM"
|
title: When Your Collection Outgrows RAM
|
||||||
short_description: "Keep the quantized copy in RAM and the original vectors on disk, then measure what rescoring reads back on your own deployment."
|
short_description: Keep the quantized copy in RAM and the original vectors on disk, then measure what rescoring reads back on your own deployment.
|
||||||
description: "Set quantization and memory placement in Qdrant once a collection outgrows RAM: what the rescoring disk read costs and what quality it recovers."
|
description: 'Set quantization and memory placement in Qdrant once a collection outgrows RAM: what the rescoring disk read costs and what quality it recovers.'
|
||||||
preview_dir: /articles_data/when-your-collection-outgrows-ram/preview
|
preview_dir: /articles_data/when-your-collection-outgrows-ram/preview
|
||||||
social_preview_image: /articles_data/when-your-collection-outgrows-ram/preview/social_preview.jpg
|
social_preview_image: /articles_data/when-your-collection-outgrows-ram/preview/social_preview.jpg
|
||||||
weight: -209
|
weight: 160
|
||||||
author: Dylan Couzon
|
author: Dylan Couzon
|
||||||
author_link: https://www.linkedin.com/in/dcouzon/
|
author_link: https://www.linkedin.com/in/dcouzon/
|
||||||
date: 2026-08-24T00:00:00+03:00
|
date: 2026-08-24 00:00:00+03:00
|
||||||
draft: false
|
draft: false
|
||||||
keywords:
|
keywords:
|
||||||
- memory tiers
|
- memory tiers
|
||||||
- quantization
|
- quantization
|
||||||
- rescoring
|
- rescoring
|
||||||
- oversampling
|
- oversampling
|
||||||
- TurboQuant
|
- TurboQuant
|
||||||
category: search-quality
|
partition: learn
|
||||||
|
learning_kind: guides
|
||||||
|
aliases:
|
||||||
|
- /articles/when-your-collection-outgrows-ram/
|
||||||
|
guide_series: true
|
||||||
---
|
---
|
||||||
|
|
||||||
|
# When Your Collection Outgrows RAM
|
||||||
|
|
||||||
Once a collection no longer fits in RAM, the kernel evicts vector pages, and the next query waits on a disk read to get them back. Quantization buys that memory back. Qdrant keeps a compressed copy of each dense vector in RAM and moves the full-precision originals to disk.
|
Once a collection no longer fits in RAM, the kernel evicts vector pages, and the next query waits on a disk read to get them back. Quantization buys that memory back. Qdrant keeps a compressed copy of each dense vector in RAM and moves the full-precision originals to disk.
|
||||||
|
|
||||||
[TurboQuant](/documentation/manage-data/quantization/#turboquant-quantization) is the method measured here. It rotates each vector before compressing it, which spreads the error evenly across coordinates, and its `bits` parameter sets the depth from `bits4` down to `bits1`. Start at `bits4`, a good default for many workloads at eight times compression.
|
[TurboQuant](/documentation/manage-data/quantization/#turboquant-quantization) is the method measured here. It rotates each vector before compressing it, which spreads the error evenly across coordinates, and its `bits` parameter sets the depth from `bits4` down to `bits1`. Start at `bits4`, a good default for many workloads at eight times compression.
|
||||||
@@ -49,7 +55,7 @@ The extra 50% covers metadata, indexes, point versions, and temporary segments c
|
|||||||
|
|
||||||
Qdrant [recommends pinning the quantized copy with `cold` originals](/documentation/manage-data/quantization/#memory-and-speed-tuning) to shrink the footprint while keeping search fast. The following two sections measure what that pairing costs in disk reads and what rescoring recovers.
|
Qdrant [recommends pinning the quantized copy with `cold` originals](/documentation/manage-data/quantization/#memory-and-speed-tuning) to shrink the footprint while keeping search fast. The following two sections measure what that pairing costs in disk reads and what rescoring recovers.
|
||||||
|
|
||||||
Step 4 needs a [labeled set](/articles/before-tuning-a-qdrant-collection/). Compare `nDCG@k` with `k` set to the number of results you return, pick the configuration on one part of the set, then confirm it on queries that took no part in the selection. Use `Recall@k` against exact search to explain a loss.
|
Step 4 needs a [labeled set](/documentation/search-tuning/before-tuning-a-qdrant-collection/). Compare `nDCG@k` with `k` set to the number of results you return, pick the configuration on one part of the set, then confirm it on queries that took no part in the selection. Use `Recall@k` against exact search to explain a loss.
|
||||||
|
|
||||||
## Rescoring Adds the Disk Read
|
## Rescoring Adds the Disk Read
|
||||||
|
|
||||||
@@ -182,7 +188,7 @@ First, compute the exact dense top `k` once for a representative sample of your
|
|||||||
|
|
||||||
Then run your existing dense prefetch with each `rescore` and `oversampling` variant, changing nothing else. `Recall@k` against the exact result shows what quantization changed in the dense prefetch. Without labels, that check and the latency numbers still stand on their own.
|
Then run your existing dense prefetch with each `rescore` and `oversampling` variant, changing nothing else. `Recall@k` against the exact result shows what quantization changed in the dense prefetch. Without labels, that check and the latency numbers still stand on their own.
|
||||||
|
|
||||||
For hybrid search, keep the prefetches, [fusion settings](/articles/how-to-tune-hybrid-search/), and filters your service already uses, then compare the final `nDCG@k`.
|
For hybrid search, keep the prefetches, [fusion settings](/documentation/search-tuning/how-to-tune-hybrid-search/), and filters your service already uses, then compare the final `nDCG@k`.
|
||||||
|
|
||||||
### Self-Hosted
|
### Self-Hosted
|
||||||
|
|
||||||
@@ -198,6 +204,6 @@ Keep the first configuration that meets your held-out `nDCG@k` and latency targe
|
|||||||
|
|
||||||
On a multi-shard hybrid collection, rerun the full request on your deployed shard layout once the dense-vector placements are set. Each shard runs the prefetch and rescoring against its own data.
|
On a multi-shard hybrid collection, rerun the full request on your deployed shard layout once the dense-vector placements are set. Each shard runs the prefetch and rescoring against its own data.
|
||||||
|
|
||||||
With a `limit` of 200 and `oversampling` 1, rescoring can read up to 200 original vectors per shard, or up to 2,400 across 12 shards. [Candidate depth](/articles/candidate-depth/) covers how to set the limit that total scales with.
|
With a `limit` of 200 and `oversampling` 1, rescoring can read up to 200 original vectors per shard, or up to 2,400 across 12 shards. [Candidate depth](/documentation/search-tuning/candidate-depth/) covers how to set the limit that total scales with.
|
||||||
|
|
||||||
If you do not have a labeled query set yet, [What to Check Before Tuning a Qdrant Collection](/articles/before-tuning-a-qdrant-collection/) covers how to build one.
|
If you do not have a labeled query set yet, [What to Check Before Tuning a Qdrant Collection](/documentation/search-tuning/before-tuning-a-qdrant-collection/) covers how to build one.
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
title: "Qdrant Learn Portal"
|
title: Qdrant Learn Portal
|
||||||
description: "Tutorials, Courses, Articles"
|
description: Grow your search engineering skills with practical Qdrant guides, working examples, structured courses, and research on retrieval and engine internals.
|
||||||
hideTOC: true
|
hideTOC: true
|
||||||
breadcrumb: false
|
breadcrumb: false
|
||||||
partition: learn
|
partition: learn
|
||||||
@@ -8,101 +8,96 @@ feedback: false
|
|||||||
build:
|
build:
|
||||||
render: always
|
render: always
|
||||||
cascade:
|
cascade:
|
||||||
- build:
|
- build:
|
||||||
list: local
|
list: local
|
||||||
publishResources: false
|
publishResources: false
|
||||||
render: never
|
render: never
|
||||||
content:
|
content:
|
||||||
# - partial: documentation/banners/banner-b
|
- partial: documentation/banners/banner-a
|
||||||
# title: Welcome to Qdrant Learn
|
title: Grow as a Search Engineer
|
||||||
# description: Learn Portal
|
description: Make better search decisions, adapt a working example, or understand how Qdrant works beneath the API.
|
||||||
# image:
|
linkDescription: Choose the resource that answers your question today.
|
||||||
# src: /img/dev-portal-cloud/dev-portal-cloud-hero.png
|
cloudButton:
|
||||||
# alt: Qdrant Course
|
text: Explore Practical Guides
|
||||||
# startedButton:
|
url: /documentation/guides/
|
||||||
# text: Start Learning
|
localButton:
|
||||||
# url: /courses/essentials
|
text: Start with Qdrant Essentials
|
||||||
- partial: documentation/sections/cards-section
|
url: /course/essentials/
|
||||||
title: Learn
|
- partial: documentation/sections/cards-section
|
||||||
description: Master vector search with Qdrant through comprehensive documentation, structured courses, and hands-on tutorials.
|
title: Choose How to Learn
|
||||||
cardsPartial: documentation/cards/docs-cards
|
description: Each resource serves a different purpose. Start with the one that fits your task.
|
||||||
cards:
|
cardsPartial: documentation/cards/docs-cards
|
||||||
- id: 1
|
cardsPerRow: 2
|
||||||
image:
|
cards:
|
||||||
src: /img/dev-portal-learn/articles.png
|
- title: Guides
|
||||||
alt: Articles
|
description: Evaluate search quality, choose embedding models, and plan how your Qdrant application grows.
|
||||||
title: Articles
|
link:
|
||||||
description: In-depth technical documentation covering vector search concepts, system architecture, and advanced techniques.
|
url: /documentation/guides/
|
||||||
|
text: Find Practical Guidance
|
||||||
list:
|
image:
|
||||||
title: "Featured articles:"
|
src: /img/dev-portal-learn/articles.png
|
||||||
elements:
|
alt: ''
|
||||||
- Data Exploration with Qdrant's Distance Matrix API
|
- title: Tutorials & Examples
|
||||||
- Why Vector Search Needs a Dedicated Database
|
description: Open working code and walkthroughs, then adapt the implementation to your data and application stack.
|
||||||
- Semantic Search As You Type
|
link:
|
||||||
link:
|
url: /learn/examples/
|
||||||
url: /articles/
|
text: Browse Tutorials & Examples
|
||||||
text: Browse Articles
|
image:
|
||||||
- id: 2
|
src: /img/dev-portal-learn/tutorials.png
|
||||||
image:
|
alt: ''
|
||||||
src: /img/dev-portal-learn/courses.png
|
- title: Courses
|
||||||
alt: Courses
|
description: Build your understanding through structured lessons and exercises, starting with Qdrant Essentials.
|
||||||
title: Courses
|
link:
|
||||||
description: Structured learning paths with progressive difficulty levels, from beginner fundamentals to advanced implementations.
|
url: /course/
|
||||||
list:
|
text: Explore Courses
|
||||||
title: "Available topics:"
|
image:
|
||||||
elements:
|
src: /img/dev-portal-learn/courses.png
|
||||||
- "Beginner: Vector search basics"
|
alt: ''
|
||||||
- "Intermediate: Advanced querying"
|
- title: Articles
|
||||||
- "Advanced: Production optimization"
|
description: Examine retrieval experiments and the mechanisms behind Qdrant indexing, storage, and search.
|
||||||
|
link:
|
||||||
link:
|
url: /articles/
|
||||||
url: /course/
|
text: Explore Articles
|
||||||
text: View Courses
|
image:
|
||||||
- id: 3
|
src: /img/dev-portal-learn/articles.png
|
||||||
image:
|
alt: ''
|
||||||
src: /img/dev-portal-learn/tutorials.png
|
- partial: documentation/sections/cards-section
|
||||||
alt: Tutorials
|
title: Start with Your Task
|
||||||
title: Tutorials
|
description: Take a direct path to a common search engineering task.
|
||||||
description: Step-by-step guides and video content for hands-on learning with practical examples and real-world applications.
|
cardsPartial: documentation/cards/docs-cards
|
||||||
list:
|
cardsPerRow: 2
|
||||||
title: "Tutorial categories:"
|
cards:
|
||||||
elements:
|
- title: Build Your First Search
|
||||||
- "Search Engineering"
|
description: Start with a small semantic search application and adapt it to your own data.
|
||||||
- "Operations and Scale"
|
link:
|
||||||
- "Develop and Implement"
|
url: /documentation/tutorials-basics/search-beginners/
|
||||||
|
text: Open the Example
|
||||||
link:
|
- title: Evaluate Search Quality
|
||||||
url: /documentation/tutorials-lp-overview/
|
description: Choose an evaluation baseline before changing embeddings, retrieval, or ranking.
|
||||||
text: Explore Tutorials
|
link:
|
||||||
- partial: documentation/sections/cards-section
|
url: /documentation/search-quality/
|
||||||
title: Quickstart
|
text: Explore Search Evaluation
|
||||||
description:
|
- title: Prepare for Production
|
||||||
cardsPartial: documentation/cards/docs-cards
|
description: Plan tenant growth and large imports around the workload you need to serve.
|
||||||
cardsPerRow: 2
|
link:
|
||||||
cards:
|
url: /documentation/production-patterns/
|
||||||
- id: 1
|
text: Explore Production & Performance
|
||||||
icon:
|
- title: Design and Tune Search
|
||||||
src: /icons/outline/rocket-blue.svg
|
description: Choose embeddings and retrieval strategies, then follow the tuning series to test improvements.
|
||||||
alt: Rocket icon
|
link:
|
||||||
title: New to Vector Search?
|
url: /documentation/search-tuning/
|
||||||
description: Start with our beginner-friendly exercises on vector embeddings and basic concepts.
|
text: Explore Search Design & Tuning
|
||||||
link:
|
|
||||||
text: Start Learning
|
|
||||||
url: /documentation/tutorials-basics/
|
|
||||||
- id: 2
|
|
||||||
icon:
|
|
||||||
src: /icons/outline/hacker-purple.svg
|
|
||||||
alt: Hacker icon
|
|
||||||
title: Ready to Build?
|
|
||||||
description: Build out practical projects using our example prototypes and integration guides.
|
|
||||||
link:
|
|
||||||
text: View Examples
|
|
||||||
url: /documentation/examples/
|
|
||||||
---
|
---
|
||||||
|
|
||||||
# Learn
|
# Learn
|
||||||
|
|
||||||
- **[Articles](/articles/index.md)**: Long-form technical pieces covering vector search concepts, system architecture, RAG pipelines, quantization, hybrid retrieval, and Qdrant internals.
|
Choose Guides for practical decisions, Tutorials & Examples for an implementation, Courses for structured study, or Articles for new evidence and engine mechanisms.
|
||||||
- **[Courses](/course/index.md)**: Structured, self-paced learning paths through Qdrant Academy. Two courses are currently available: *Qdrant Essentials* (9–12 hours) and *Multi-Vector Search* (4–6 hours), with beginner, intermediate, and advanced courses planned. Free, with certification.
|
|
||||||
- **[Tutorials](/documentation/tutorials-lp-overview/index.md)**: Step-by-step guides organized into five categories: Basic, Search Engineering, Operations & Scale, Develop & Implement, and Migrate to Qdrant.
|
## Read More
|
||||||
|
|
||||||
|
- [Guides](/documentation/guides/)
|
||||||
|
- [Tutorials & Examples](/learn/examples/)
|
||||||
|
- [Courses](/course/)
|
||||||
|
- [Articles](/articles/)
|
||||||
|
|
||||||
|
Start with the resource that answers your current question.
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
---
|
||||||
|
title: Tutorials & Examples
|
||||||
|
layout: examples
|
||||||
|
description: Find Qdrant tutorials and examples by goal and stack. Open working code for search, RAG, filtering, and operations, then follow the steps relevant to your application.
|
||||||
|
short_description: Find tutorials and examples by goal and stack, open the available notebooks, and adapt the implementation to your search application.
|
||||||
|
partition: learn
|
||||||
|
weight: 310
|
||||||
|
hideTOC: true
|
||||||
|
breadcrumb: false
|
||||||
|
feedback: false
|
||||||
|
build:
|
||||||
|
render: always
|
||||||
|
content:
|
||||||
|
- partial: documentation/banners/banner-a
|
||||||
|
title: Find a Tutorial or Example
|
||||||
|
description: Search, RAG, recommendations, and operations, with code and walkthroughs you can adapt.
|
||||||
|
linkDescription: Choose your goal and stack, then open the example or its available notebook.
|
||||||
|
cloudButton:
|
||||||
|
text: Build Your First Search
|
||||||
|
url: /documentation/tutorials-basics/search-beginners/
|
||||||
|
localButton:
|
||||||
|
text: Browse Tutorials & Examples
|
||||||
|
url: '#example-library'
|
||||||
|
- partial: documentation/examples/catalog
|
||||||
|
---
|
||||||
@@ -0,0 +1,309 @@
|
|||||||
|
- page: /documentation/tutorials-basics/search-beginners/
|
||||||
|
goal: Get Started
|
||||||
|
stack:
|
||||||
|
- Python
|
||||||
|
- Cloud Inference
|
||||||
|
resources:
|
||||||
|
- label: Open Notebook
|
||||||
|
url: https://githubtocolab.com/qdrant/examples/blob/master/semantic-search-in-5-minutes/semantic_search_in_5_minutes.ipynb
|
||||||
|
- page: /documentation/tutorials-basics/search-beginners-local/
|
||||||
|
goal: Get Started
|
||||||
|
stack:
|
||||||
|
- Python
|
||||||
|
- Sentence Transformers
|
||||||
|
- page: /documentation/tutorials-basics/cloud-inference-hybrid-search/
|
||||||
|
goal: Search Quality
|
||||||
|
stack:
|
||||||
|
- Python
|
||||||
|
- Cloud Inference
|
||||||
|
- page: /documentation/tutorials-develop/hybrid-search-fastembed/
|
||||||
|
goal: Search Quality
|
||||||
|
stack:
|
||||||
|
- Python
|
||||||
|
- FastEmbed
|
||||||
|
- FastAPI
|
||||||
|
resources:
|
||||||
|
- label: View Code
|
||||||
|
url: https://github.com/qdrant/qdrant_demo/
|
||||||
|
- page: /documentation/tutorials-basics/reranking-hybrid-search/
|
||||||
|
goal: Search Quality
|
||||||
|
stack:
|
||||||
|
- Python
|
||||||
|
- FastEmbed
|
||||||
|
- page: /documentation/tutorials-develop/neural-search/
|
||||||
|
goal: Get Started
|
||||||
|
stack:
|
||||||
|
- Python
|
||||||
|
- FastAPI
|
||||||
|
resources:
|
||||||
|
- label: Open Notebook
|
||||||
|
url: https://colab.research.google.com/drive/1kPktoudAP8Tu8n8l-iVMOQhVmHkWV_L9?usp=sharing
|
||||||
|
- page: /documentation/tutorials-develop/code-search/
|
||||||
|
goal: Search Quality
|
||||||
|
stack:
|
||||||
|
- Python
|
||||||
|
- FastEmbed
|
||||||
|
resources:
|
||||||
|
- label: Open Notebook
|
||||||
|
url: https://colab.research.google.com/github/qdrant/examples/blob/master/code-search/code-search.ipynb
|
||||||
|
- page: /documentation/tutorials-basics/huggingface-datasets/
|
||||||
|
goal: Data & Filtering
|
||||||
|
stack:
|
||||||
|
- Python
|
||||||
|
- Hugging Face
|
||||||
|
- page: /documentation/tutorials-develop/async-api/
|
||||||
|
goal: Operations
|
||||||
|
stack:
|
||||||
|
- Python
|
||||||
|
- page: /documentation/tutorials-search-engineering/ann-recall/
|
||||||
|
goal: Search Quality
|
||||||
|
stack:
|
||||||
|
- Python
|
||||||
|
- Web UI
|
||||||
|
description: Compare approximate search with exact results, inspect ANN recall in the Web UI, and add a repeatable Python check.
|
||||||
|
keywords: evaluation index quality hnsw approximate exact recall
|
||||||
|
- page: /documentation/tutorials-search-engineering/branch-aware-search/
|
||||||
|
goal: Data & Filtering
|
||||||
|
stack:
|
||||||
|
- Python
|
||||||
|
keywords: versioned documents branches filters inherited data
|
||||||
|
- page: /documentation/tutorials-search-engineering/index-dynamic-payloads/
|
||||||
|
goal: Data & Filtering
|
||||||
|
stack:
|
||||||
|
- Python
|
||||||
|
keywords: arbitrary dynamic attributes filters payload schema
|
||||||
|
- page: /documentation/tutorials-search-engineering/multi-representation-search/
|
||||||
|
goal: Search Quality
|
||||||
|
stack:
|
||||||
|
- Python
|
||||||
|
- FastEmbed
|
||||||
|
description: Combine title, summary, and chunk vectors in one retrieval pipeline, then compare the effect of each representation.
|
||||||
|
resources:
|
||||||
|
- label: Open Notebook
|
||||||
|
url: https://githubtocolab.com/qdrant/examples/blob/master/multi-representation-search/multi-representation-search.ipynb
|
||||||
|
- page: /documentation/tutorials-search-engineering/pdf-retrieval-at-scale/
|
||||||
|
goal: Multimodal Search
|
||||||
|
stack:
|
||||||
|
- Python
|
||||||
|
- ColPali
|
||||||
|
resources:
|
||||||
|
- label: Open Notebook
|
||||||
|
url: https://githubtocolab.com/qdrant/examples/blob/master/pdf-retrieval-at-scale/ColPali_ColQwen2_Tutorial.ipynb
|
||||||
|
- page: /documentation/tutorials-search-engineering/using-multivector-representations/
|
||||||
|
goal: Search Quality
|
||||||
|
stack:
|
||||||
|
- Python
|
||||||
|
- FastEmbed
|
||||||
|
- page: /documentation/tutorials-search-engineering/using-relevance-feedback/
|
||||||
|
goal: Search Quality
|
||||||
|
stack:
|
||||||
|
- Python
|
||||||
|
resources:
|
||||||
|
- label: Open Notebook
|
||||||
|
url: https://githubtocolab.com/qdrant/examples/blob/master/using-relevance-feedback/Customizing_Relevance_Feedback.ipynb
|
||||||
|
- page: /documentation/tutorials-search-engineering/static-embeddings/
|
||||||
|
goal: Search Quality
|
||||||
|
stack:
|
||||||
|
- Python
|
||||||
|
- page: /documentation/tutorials-search-engineering/turbo4-multivector-search/
|
||||||
|
goal: Search Quality
|
||||||
|
stack:
|
||||||
|
- Python
|
||||||
|
resources:
|
||||||
|
- label: Open Notebook
|
||||||
|
url: https://githubtocolab.com/qdrant/examples/blob/master/multivector-turbo4/Multivector_Turbo4.ipynb
|
||||||
|
- page: /documentation/tutorials-search-engineering/collaborative-filtering/
|
||||||
|
goal: Recommendations
|
||||||
|
stack:
|
||||||
|
- Python
|
||||||
|
resources:
|
||||||
|
- label: Open Notebook
|
||||||
|
url: https://githubtocolab.com/qdrant/examples/blob/master/collaborative-filtering/collaborative-filtering.ipynb
|
||||||
|
- page: /documentation/tutorials-operations/embedding-model-migration/
|
||||||
|
goal: Operations
|
||||||
|
stack:
|
||||||
|
- Python
|
||||||
|
keywords: replace change embedding models migration serving traffic
|
||||||
|
- page: /documentation/tutorials-operations/create-snapshot/
|
||||||
|
goal: Operations
|
||||||
|
stack:
|
||||||
|
- Python
|
||||||
|
- page: /documentation/tutorials-operations/blue-green-deployment/
|
||||||
|
goal: Operations
|
||||||
|
stack:
|
||||||
|
- Python
|
||||||
|
- page: /documentation/tutorials-operations/gpu-accelerated-hnsw-indexing/
|
||||||
|
goal: Operations
|
||||||
|
stack:
|
||||||
|
- Python
|
||||||
|
- Qdrant Cloud
|
||||||
|
resources:
|
||||||
|
- label: Open Notebook
|
||||||
|
url: https://githubtocolab.com/qdrant/examples/blob/master/gpu-accelerated-hnsw-indexing/Gpu_Accelerated_HNSW_Indexing.ipynb
|
||||||
|
- page: /documentation/tutorials-operations/incremental-embedding-updates/
|
||||||
|
goal: Data & Filtering
|
||||||
|
stack:
|
||||||
|
- Python
|
||||||
|
resources:
|
||||||
|
- label: Open Notebook
|
||||||
|
url: https://githubtocolab.com/qdrant/examples/blob/master/temporal-data-drift/sync_raw_data_to_embeddings.ipynb
|
||||||
|
- page: /documentation/tutorials-operations/large-scale-search/
|
||||||
|
goal: Operations
|
||||||
|
stack:
|
||||||
|
- Python
|
||||||
|
resources:
|
||||||
|
- label: View Code
|
||||||
|
url: https://github.com/qdrant/laion-400m-benchmark
|
||||||
|
- page: /documentation/tutorials-operations/migration/
|
||||||
|
goal: Operations
|
||||||
|
stack:
|
||||||
|
- Migration Tool
|
||||||
|
- page: /documentation/tutorials-operations/prevent-unoptimized-usage/
|
||||||
|
goal: Operations
|
||||||
|
stack:
|
||||||
|
- Python
|
||||||
|
resources:
|
||||||
|
- label: Open Notebook
|
||||||
|
url: https://githubtocolab.com/qdrant/examples/blob/master/prevent_unoptimized_usage/prevent_unoptimized.ipynb
|
||||||
|
- page: /documentation/tutorials-operations/secure-qdrant/
|
||||||
|
goal: Operations
|
||||||
|
stack:
|
||||||
|
- Docker
|
||||||
|
- page: /documentation/tutorials-operations/time-based-sharding/
|
||||||
|
goal: Data & Filtering
|
||||||
|
stack:
|
||||||
|
- Python
|
||||||
|
- page: /documentation/tutorials-build-essentials/rag-deepseek/
|
||||||
|
goal: RAG & Agents
|
||||||
|
stack:
|
||||||
|
- Python
|
||||||
|
- DeepSeek
|
||||||
|
resources:
|
||||||
|
- label: View Notebook
|
||||||
|
url: https://github.com/qdrant/examples/blob/master/rag-with-qdrant-deepseek/deepseek-qdrant.ipynb
|
||||||
|
- page: /documentation/tutorials-build-essentials/qdrant-n8n/
|
||||||
|
goal: RAG & Agents
|
||||||
|
stack:
|
||||||
|
- n8n
|
||||||
|
- page: /documentation/tutorials-build-essentials/multimodal-search/
|
||||||
|
goal: Multimodal Search
|
||||||
|
stack:
|
||||||
|
- Python
|
||||||
|
- LlamaIndex
|
||||||
|
resources:
|
||||||
|
- label: Open Notebook
|
||||||
|
url: https://githubtocolab.com/qdrant/examples/blob/master/multimodal-search/Multimodal_Search_with_LlamaIndex.ipynb
|
||||||
|
- page: /documentation/tutorials-build-essentials/data-ingestion-beginners/
|
||||||
|
goal: Data & Filtering
|
||||||
|
stack:
|
||||||
|
- Python
|
||||||
|
- LangChain
|
||||||
|
- AWS
|
||||||
|
- page: /documentation/tutorials-build-essentials/agentic-rag-camelai-discord/
|
||||||
|
goal: RAG & Agents
|
||||||
|
stack:
|
||||||
|
- Python
|
||||||
|
- CAMEL-AI
|
||||||
|
resources:
|
||||||
|
- label: Open Notebook
|
||||||
|
url: https://colab.research.google.com/drive/1Ymqzm6ySoyVOekY7fteQBCFCXYiYyHxw#scrollTo=QQZXwzqmNfaS
|
||||||
|
- page: /documentation/tutorials-build-essentials/video-anomaly-edge-part-1/
|
||||||
|
goal: Multimodal Search
|
||||||
|
stack:
|
||||||
|
- Qdrant Edge
|
||||||
|
- Twelve Labs
|
||||||
|
resources:
|
||||||
|
- label: View Code
|
||||||
|
url: https://github.com/qdrant/video-anomaly-edge
|
||||||
|
- page: /documentation/tutorials-build-essentials/video-anomaly-edge-part-2/
|
||||||
|
goal: Multimodal Search
|
||||||
|
stack:
|
||||||
|
- Qdrant Edge
|
||||||
|
- Twelve Labs
|
||||||
|
resources:
|
||||||
|
- label: View Code
|
||||||
|
url: https://github.com/qdrant/video-anomaly-edge
|
||||||
|
- page: /documentation/tutorials-build-essentials/video-anomaly-edge-part-3/
|
||||||
|
goal: Multimodal Search
|
||||||
|
stack:
|
||||||
|
- Qdrant Edge
|
||||||
|
- Twelve Labs
|
||||||
|
resources:
|
||||||
|
- label: View Code
|
||||||
|
url: https://github.com/qdrant/video-anomaly-edge
|
||||||
|
- page: /documentation/examples/graphrag-qdrant-neo4j/
|
||||||
|
goal: RAG & Agents
|
||||||
|
stack:
|
||||||
|
- Python
|
||||||
|
- Neo4j
|
||||||
|
resources:
|
||||||
|
- label: View Code
|
||||||
|
url: https://github.com/qdrant/examples/blob/master/graphrag_neo4j/graphrag.py
|
||||||
|
- page: /documentation/examples/hybrid-search-llamaindex-jinaai/
|
||||||
|
goal: RAG & Agents
|
||||||
|
stack:
|
||||||
|
- Python
|
||||||
|
- LlamaIndex
|
||||||
|
- Jina
|
||||||
|
resources:
|
||||||
|
- label: Open Notebook
|
||||||
|
url: https://githubtocolab.com/infoslack/qdrant-example/blob/main/HC-demo/HC-DO-LlamaIndex-Jina-v2.ipynb
|
||||||
|
keywords: PDF documents manuals LlamaIndex Jina RAG
|
||||||
|
- page: /documentation/examples/Qdrant-DSPy-medicalbot/
|
||||||
|
goal: RAG & Agents
|
||||||
|
stack:
|
||||||
|
- Python
|
||||||
|
- DSPy
|
||||||
|
resources:
|
||||||
|
- label: View Notebook
|
||||||
|
url: https://github.com/qdrant/examples/blob/master/DSPy-medical-bot/medical_bot_DSPy_Qdrant.ipynb
|
||||||
|
- page: /documentation/examples/cohere-rag-connector/
|
||||||
|
goal: RAG & Agents
|
||||||
|
stack:
|
||||||
|
- Python
|
||||||
|
- Cohere
|
||||||
|
- page: /documentation/examples/natural-language-search-oracle-cloud-infrastructure-cohere-langchain/
|
||||||
|
goal: RAG & Agents
|
||||||
|
stack:
|
||||||
|
- LangChain
|
||||||
|
- Cohere
|
||||||
|
- Oracle Cloud
|
||||||
|
- page: /documentation/examples/rag-chatbot-red-hat-openshift-haystack/
|
||||||
|
goal: RAG & Agents
|
||||||
|
stack:
|
||||||
|
- Haystack
|
||||||
|
- OpenShift
|
||||||
|
- page: /documentation/examples/rag-chatbot-scaleway/
|
||||||
|
goal: RAG & Agents
|
||||||
|
stack:
|
||||||
|
- LangChain
|
||||||
|
- OpenAI
|
||||||
|
- Scaleway
|
||||||
|
resources:
|
||||||
|
- label: View Notebook
|
||||||
|
url: https://github.com/qdrant/examples/blob/langchain-lcel-rag/langchain-lcel-rag/Langchain-LCEL-RAG-Demo.ipynb
|
||||||
|
- page: /documentation/examples/rag-chatbot-vultr-dspy-ollama/
|
||||||
|
goal: RAG & Agents
|
||||||
|
stack:
|
||||||
|
- DSPy
|
||||||
|
- Ollama
|
||||||
|
- Vultr
|
||||||
|
- page: /documentation/examples/rag-contract-management-stackit-aleph-alpha/
|
||||||
|
goal: RAG & Agents
|
||||||
|
stack:
|
||||||
|
- Aleph Alpha
|
||||||
|
- STACKIT
|
||||||
|
- page: /documentation/examples/rag-customer-support-cohere-airbyte-aws/
|
||||||
|
goal: RAG & Agents
|
||||||
|
stack:
|
||||||
|
- Cohere
|
||||||
|
- Airbyte
|
||||||
|
- AWS
|
||||||
|
- page: /documentation/examples/recommendation-system-ovhcloud/
|
||||||
|
goal: Recommendations
|
||||||
|
stack:
|
||||||
|
- Python
|
||||||
|
- OVHcloud
|
||||||
|
resources:
|
||||||
|
- label: View Notebook
|
||||||
|
url: https://github.com/infoslack/qdrant-example/blob/main/HC-demo/HC-OVH.ipynb
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
> Use this file to discover all available pages: https://qdrant.tech/llms.txt
|
> Use this file to discover all available pages: https://qdrant.tech/llms.txt
|
||||||
|
|
||||||
{{- $content := printf "# %s\n\n" .Title -}}
|
{{- $content := printf "# %s\n\n" .Title -}}
|
||||||
{{- range .RegularPages.ByPublishDate.Reverse -}}
|
{{- range (partial "documentation/articles/list" .) -}}
|
||||||
{{- $content = printf "%s- [%s](%s)\n" $content .Title .RelPermalink -}}
|
{{- $content = printf "%s- [%s](%s)\n" $content .Title .RelPermalink -}}
|
||||||
{{- end -}}
|
{{- end -}}
|
||||||
{{- $content = replaceRE `\]\((/[^):]*/)([\)#?])` `](https://qdrant.tech${1}index.md${2}` $content -}}
|
{{- $content = replaceRE `\]\((/[^):]*/)([\)#?])` `](https://qdrant.tech${1}index.md${2}` $content -}}
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
# Articles
|
||||||
|
|
||||||
|
Browse current Qdrant articles in [Articles](/articles/index.md).
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
# Tutorials & Examples
|
||||||
|
|
||||||
|
Find an implementation by goal and stack. Open the example for its prerequisites and procedure, or use its available code.
|
||||||
|
|
||||||
|
{{ range site.Data.examples }}
|
||||||
|
{{ $page := site.GetPage .page }}
|
||||||
|
## {{ .title | default $page.Title }}
|
||||||
|
|
||||||
|
{{ .description | default $page.Params.short_description | default $page.Description }}
|
||||||
|
|
||||||
|
Goal: {{ .goal }}. Stack: {{ delimit .stack ", " }}.
|
||||||
|
|
||||||
|
[Open Example]({{ $page.RelPermalink }}index.md){{ range .resources }} | [{{ .label }}]({{ .url }}){{ end }}
|
||||||
|
{{ end }}
|
||||||
|
|
||||||
|
Choose an example that matches your data, stack, and operating requirements.
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
{{ .Inner }}
|
||||||
@@ -76,14 +76,14 @@
|
|||||||
/documentation/tutorials-develop/bulk-upload/* /documentation/manage-data/bulk-upload/:splat 301
|
/documentation/tutorials-develop/bulk-upload/* /documentation/manage-data/bulk-upload/:splat 301
|
||||||
|
|
||||||
# Articles category reorganization (technical articles taxonomy)
|
# Articles category reorganization (technical articles taxonomy)
|
||||||
/articles/vector-search-manuals/ /articles/mastering-search/ 301
|
/articles/vector-search-manuals/ /articles/search-quality/ 301
|
||||||
/articles/machine-learning/ /articles/embedding-research/ 301
|
/articles/machine-learning/ /articles/embedding-research/ 301
|
||||||
/articles/ecosystem/ /articles/demos-and-tutorials/ 301
|
/articles/ecosystem/ /articles/ 301
|
||||||
/articles/practicle-examples/ /articles/demos-and-tutorials/ 301
|
/articles/practicle-examples/ /articles/ 301
|
||||||
/articles/rag-and-genai/ /articles/rag-and-agents/ 301
|
/articles/rag-and-genai/ /articles/ 301
|
||||||
|
|
||||||
# Unpublished indexing-optimization article superseded by bulk-uploads-in-qdrant
|
# Unpublished indexing-optimization article superseded by bulk-uploads-in-qdrant
|
||||||
/articles/indexing-optimization/ /articles/bulk-uploads-in-qdrant/ 301
|
/articles/indexing-optimization/ /documentation/production-patterns/bulk-data-import/ 301
|
||||||
|
|
||||||
# ACORN blog post converted into an internals article
|
# ACORN blog post converted into an internals article
|
||||||
/blog/filtered-vector-search-acorn/ /articles/filtered-vector-search-acorn/ 301
|
/blog/filtered-vector-search-acorn/ /articles/filtered-vector-search-acorn/ 301
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
@import 'partials/theme-switch';
|
@import 'partials/theme-switch';
|
||||||
@import 'partials/documentation/documentation-menu';
|
@import 'partials/documentation/documentation-menu';
|
||||||
@import 'partials/documentation/docs-card';
|
@import 'partials/documentation/docs-card';
|
||||||
|
@import 'partials/documentation/learning';
|
||||||
@import 'partials/documentation/documentation';
|
@import 'partials/documentation/documentation';
|
||||||
@import 'partials/documentation/documentation-article';
|
@import 'partials/documentation/documentation-article';
|
||||||
@import 'partials/documentation/docs';
|
@import 'partials/documentation/docs';
|
||||||
|
|||||||
+68
-1
@@ -2,6 +2,55 @@
|
|||||||
@use 'sass:math';
|
@use 'sass:math';
|
||||||
|
|
||||||
.docs-articles {
|
.docs-articles {
|
||||||
|
&__topics {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
align-items: center;
|
||||||
|
gap: $spacer * 0.75;
|
||||||
|
margin-top: $spacer * 1.5;
|
||||||
|
|
||||||
|
> span {
|
||||||
|
color: $neutral-70;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
padding: $spacer * 0.5 $spacer;
|
||||||
|
border: pxToRem(1) solid $neutral-30;
|
||||||
|
border-radius: $spacer * 2;
|
||||||
|
color: $neutral-90;
|
||||||
|
font-size: pxToRem(14);
|
||||||
|
|
||||||
|
&:hover,
|
||||||
|
&[aria-current='page'] {
|
||||||
|
border-color: $primary-50;
|
||||||
|
color: $primary-50;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__guide-callout {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
align-items: center;
|
||||||
|
gap: $spacer;
|
||||||
|
padding: $spacer * 1.5;
|
||||||
|
border: pxToRem(1) solid $neutral-30;
|
||||||
|
border-radius: $spacer * 0.5;
|
||||||
|
|
||||||
|
> div { flex: 1 1 100%; }
|
||||||
|
}
|
||||||
|
|
||||||
|
.post-type-badge {
|
||||||
|
display: inline-block;
|
||||||
|
font-size: pxToRem(11);
|
||||||
|
padding: $spacer * 0.2 $spacer * 0.6;
|
||||||
|
border: pxToRem(1) solid $neutral-50;
|
||||||
|
border-radius: $spacer;
|
||||||
|
margin-bottom: $spacer * 0.5;
|
||||||
|
color: $neutral-70;
|
||||||
|
}
|
||||||
|
|
||||||
&__title {
|
&__title {
|
||||||
margin-bottom: math.div($spacer, 2);
|
margin-bottom: math.div($spacer, 2);
|
||||||
font-size: $spacer * 1.5;
|
font-size: $spacer * 1.5;
|
||||||
@@ -17,8 +66,11 @@
|
|||||||
&__blocks {
|
&__blocks {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: $spacer * 5;
|
gap: $spacer * 3;
|
||||||
padding-bottom: $spacer * 3;
|
padding-bottom: $spacer * 3;
|
||||||
|
|
||||||
|
.docs-core__hero { margin-bottom: 0; }
|
||||||
|
> .docs-articles__topics { margin-top: 0; }
|
||||||
}
|
}
|
||||||
|
|
||||||
&__block {
|
&__block {
|
||||||
@@ -148,6 +200,21 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
[data-theme='light'] & {
|
[data-theme='light'] & {
|
||||||
|
&__topics a,
|
||||||
|
&__topics > span,
|
||||||
|
.post-type-badge {
|
||||||
|
color: $neutral-30;
|
||||||
|
border-color: $neutral-70;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__topics a:hover,
|
||||||
|
&__topics a[aria-current='page'] {
|
||||||
|
color: $primary-50;
|
||||||
|
border-color: $primary-50;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__guide-callout { border-color: $neutral-90; }
|
||||||
|
|
||||||
&__title {
|
&__title {
|
||||||
color: $neutral-30;
|
color: $neutral-30;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,56 @@
|
|||||||
|
.example-library {
|
||||||
|
scroll-margin-top: 7rem;
|
||||||
|
[hidden] { display: none !important; }
|
||||||
|
&__filters {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
align-items: end;
|
||||||
|
gap: 1rem;
|
||||||
|
margin: 1.5rem 0;
|
||||||
|
> div { flex: 1 1 12rem; }
|
||||||
|
label { display: block; margin-bottom: .5rem; }
|
||||||
|
input, select {
|
||||||
|
width: 100%;
|
||||||
|
min-height: 2.75rem;
|
||||||
|
padding: .65rem;
|
||||||
|
border: 1px solid $neutral-50;
|
||||||
|
border-radius: .5rem;
|
||||||
|
background: $neutral-10;
|
||||||
|
color: $neutral-98;
|
||||||
|
color-scheme: dark;
|
||||||
|
}
|
||||||
|
:focus-visible { outline: 2px solid $secondary-blue-50; outline-offset: 3px; }
|
||||||
|
}
|
||||||
|
.docs-card__title a { color: inherit; }
|
||||||
|
&__stack { font-size: .875rem; margin: 0; }
|
||||||
|
&__actions { display: flex; gap: 1rem; flex-wrap: wrap; margin-top: auto; }
|
||||||
|
[data-theme='light'] & {
|
||||||
|
input, select { background: $neutral-98; color: $neutral-10; color-scheme: light; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.guide-read-more { margin-bottom: 1.5rem; }
|
||||||
|
|
||||||
|
// Leave room for the disclosure arrow when a guide topic wraps.
|
||||||
|
.guide-topic > summary > a { padding-right: 2.5rem; }
|
||||||
|
|
||||||
|
.guide-series-navigation {
|
||||||
|
border-top: 1px solid $neutral-50;
|
||||||
|
margin-top: 2rem;
|
||||||
|
padding-top: 1rem;
|
||||||
|
&__links {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 1rem 2rem;
|
||||||
|
a { flex: 1 1 15rem; }
|
||||||
|
span { display: block; font-size: .875rem; color: $neutral-70; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.guide-series-label {
|
||||||
|
list-style: none;
|
||||||
|
margin: .75rem 0 .25rem;
|
||||||
|
font-size: .75rem;
|
||||||
|
font-weight: 600;
|
||||||
|
color: $neutral-70;
|
||||||
|
}
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
const library = document.querySelector('.example-library');
|
||||||
|
if (library) {
|
||||||
|
const form = library.querySelector('form');
|
||||||
|
const query = form.elements.namedItem('q');
|
||||||
|
const goal = form.elements.namedItem('goal');
|
||||||
|
const stack = form.elements.namedItem('stack');
|
||||||
|
const cards = [...library.querySelectorAll('[data-example]')];
|
||||||
|
const count = library.querySelector('[data-example-count]');
|
||||||
|
const empty = library.querySelector('[data-example-empty]');
|
||||||
|
|
||||||
|
function readURL() {
|
||||||
|
const params = new URLSearchParams(window.location.search);
|
||||||
|
query.value = params.get('q') || '';
|
||||||
|
for (const select of [goal, stack]) {
|
||||||
|
const value = params.get(select.name) || '';
|
||||||
|
select.value = [...select.options].some(option => option.value === value) ? value : '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function filter(updateURL = true) {
|
||||||
|
const terms = query.value.toLowerCase().trim().split(/\s+/).filter(Boolean);
|
||||||
|
let visible = 0;
|
||||||
|
cards.forEach(card => {
|
||||||
|
const match = (!goal.value || card.dataset.goal === goal.value)
|
||||||
|
&& (!stack.value || card.dataset.stack.split('|').includes(stack.value))
|
||||||
|
&& terms.every(term => card.dataset.search.includes(term));
|
||||||
|
card.hidden = !match;
|
||||||
|
if (match) visible += 1;
|
||||||
|
});
|
||||||
|
count.textContent = `${visible} ${visible === 1 ? 'result' : 'results'}`;
|
||||||
|
empty.hidden = visible !== 0;
|
||||||
|
document.querySelectorAll('[data-example-nav-goal]').forEach(item => {
|
||||||
|
const active = item.dataset.exampleNavGoal === goal.value;
|
||||||
|
item.classList.toggle('active', active);
|
||||||
|
const link = item.querySelector('a');
|
||||||
|
if (active) link.setAttribute('aria-current', 'location');
|
||||||
|
else link.removeAttribute('aria-current');
|
||||||
|
});
|
||||||
|
if (updateURL) {
|
||||||
|
const url = new URL(window.location.href);
|
||||||
|
for (const control of [query, goal, stack]) {
|
||||||
|
const value = control.value.trim();
|
||||||
|
if (value) url.searchParams.set(control.name, value);
|
||||||
|
else url.searchParams.delete(control.name);
|
||||||
|
}
|
||||||
|
window.history.replaceState(null, '', url);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
form.hidden = false;
|
||||||
|
readURL();
|
||||||
|
filter(false);
|
||||||
|
form.addEventListener('submit', event => { event.preventDefault(); filter(); });
|
||||||
|
form.addEventListener('input', () => filter());
|
||||||
|
form.addEventListener('change', () => filter());
|
||||||
|
form.addEventListener('reset', () => {
|
||||||
|
query.value = goal.value = stack.value = '';
|
||||||
|
filter();
|
||||||
|
});
|
||||||
|
window.addEventListener('popstate', () => { readURL(); filter(false); });
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="robots" content="noindex">
|
||||||
|
<title>Articles - Qdrant</title>
|
||||||
|
<link rel="canonical" href="{{ .Params.redirect_to | absURL }}">
|
||||||
|
<meta http-equiv="refresh" content="0; url={{ .Params.redirect_to | absURL }}">
|
||||||
|
</head>
|
||||||
|
<body><p>Continue to <a href="{{ .Params.redirect_to }}">Articles</a>.</p></body>
|
||||||
|
</html>
|
||||||
@@ -7,3 +7,5 @@
|
|||||||
{{ $old := "<table>" }}
|
{{ $old := "<table>" }}
|
||||||
{{ $new := printf "<table class=\"%s\">" "table mb-5" }}
|
{{ $new := printf "<table class=\"%s\">" "table mb-5" }}
|
||||||
{{ $contentWithWrappedTables | replaceRE $old $new | safeHTML }}
|
{{ $contentWithWrappedTables | replaceRE $old $new | safeHTML }}
|
||||||
|
|
||||||
|
{{ if eq .Params.learning_kind "guides" }}{{ partial "documentation/guides/series-navigation" . }}{{ end }}
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
{{ $page := .page }}
|
||||||
|
<article class="{{ .column | default "col-12 col-lg-4" }} post post-sm">
|
||||||
|
<a class="post-link" href="{{ $page.RelPermalink }}">
|
||||||
|
{{ with $page.Params.preview_dir }}
|
||||||
|
<div class="post-preview"><img src="{{ . }}/preview.jpg" alt="" loading="lazy" /></div>
|
||||||
|
{{ end }}
|
||||||
|
{{ with partial "documentation/articles/topic" $page }}
|
||||||
|
{{ with site.GetPage (printf "/articles/%s/" .) }}
|
||||||
|
<span class="post-type-badge">{{ .Title }}</span>
|
||||||
|
{{ end }}
|
||||||
|
{{ end }}
|
||||||
|
<h3 class="post-title">{{ $page.Title }}</h3>
|
||||||
|
<p class="post-description">{{ $page.Params.short_description | default $page.Description }}</p>
|
||||||
|
<div class="post-about">
|
||||||
|
<p>{{ $page.Params.author }}</p>
|
||||||
|
<p>{{ time.Format "January 2, 2006" $page.Date }}</p>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
</article>
|
||||||
+22
@@ -0,0 +1,22 @@
|
|||||||
|
<div class="docs-articles docs-articles__list">
|
||||||
|
{{ partial "documentation/breadcrumbs" . }}
|
||||||
|
<h1 class="docs-articles__title">{{ .Title }}</h1>
|
||||||
|
<p class="docs-articles__description">{{ .Description }}</p>
|
||||||
|
<nav class="docs-articles__topics" aria-label="Article Topics">
|
||||||
|
<a href="/articles/">All Articles</a>
|
||||||
|
{{ range (site.GetPage "/articles").Sections.ByWeight }}
|
||||||
|
{{ if and .Params.isCategoryPage (not .Params.article_collection) (not .Params.hideFromList) }}
|
||||||
|
<a href="{{ .RelPermalink }}" {{ if eq $.Params.category .Params.category }}aria-current="page"{{ end }}>{{ .Title }}</a>
|
||||||
|
{{ end }}
|
||||||
|
{{ end }}
|
||||||
|
</nav>
|
||||||
|
{{ $paginator := .Paginate (partial "documentation/articles/list" .) 14 }}
|
||||||
|
<div class="docs-articles__posts row gx-4 gy-5">
|
||||||
|
{{ range $paginator.Pages }}
|
||||||
|
{{ partial "documentation/articles/card" (dict "page" . "column" "col-12 col-lg-6") }}
|
||||||
|
{{ end }}
|
||||||
|
</div>
|
||||||
|
{{ if gt $paginator.TotalPages 1 }}
|
||||||
|
<div class="docs-articles__pagination">{{ partial "pagination" $paginator }}</div>
|
||||||
|
{{ end }}
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
<div class="docs-articles docs-articles__blocks">
|
||||||
|
<div class="docs-core">
|
||||||
|
{{ partial "documentation/banners/banner-a" (dict "title" "Articles" "description" "Explore search quality, embedding research, Qdrant internals, and production operations." "linkDescription" "Read experiments, mechanisms, and engineering decisions from the Qdrant team." "cloudButton" (dict "text" "Browse Articles" "url" "#article-topics") "localButton" (dict "text" "Explore Guides" "url" "/documentation/guides/")) }}
|
||||||
|
</div>
|
||||||
|
<nav id="article-topics" class="docs-articles__topics" aria-label="Article Topics">
|
||||||
|
<span>Browse by Topic</span>
|
||||||
|
{{ range (site.GetPage "/articles").Sections.ByWeight }}
|
||||||
|
{{ if and .Params.isCategoryPage (not .Params.article_collection) (not .Params.hideFromList) }}
|
||||||
|
<a href="{{ .RelPermalink }}">{{ .Title }}</a>
|
||||||
|
{{ end }}
|
||||||
|
{{ end }}
|
||||||
|
</nav>
|
||||||
|
{{ range slice "/articles/search-quality" "/articles/embedding-research" "/articles/qdrant-internals" "/articles/production-ops" }}
|
||||||
|
{{ with site.GetPage . }}
|
||||||
|
<section class="docs-articles__block">
|
||||||
|
<div class="docs-articles__block-header">
|
||||||
|
<div>
|
||||||
|
<h2 class="docs-articles__title">{{ .Title }}</h2>
|
||||||
|
<p class="docs-articles__description">{{ .Description }}</p>
|
||||||
|
</div>
|
||||||
|
<a href="{{ .RelPermalink }}" class="docs-articles__block-link button button_outlined button_sm">Explore {{ .Title }}</a>
|
||||||
|
</div>
|
||||||
|
<div class="docs-articles__posts row gy-4">
|
||||||
|
{{ range first 3 (partial "documentation/articles/list" .) }}
|
||||||
|
{{ partial "documentation/articles/card" (dict "page" .) }}
|
||||||
|
{{ end }}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
{{ end }}
|
||||||
|
{{ end }}
|
||||||
|
<aside class="docs-articles__guide-callout">
|
||||||
|
<div>
|
||||||
|
<h2 class="docs-articles__title">Put It into Practice</h2>
|
||||||
|
<p class="docs-articles__description">Fix a search problem or compare configurations in Guides. Use a worked example when you need code to adapt.</p>
|
||||||
|
</div>
|
||||||
|
<a class="button button_contained button_sm" href="/documentation/guides/">Explore Guides</a>
|
||||||
|
<a class="button button_outlined button_sm" href="/learn/examples/">Explore Tutorials & Examples</a>
|
||||||
|
</aside>
|
||||||
|
<section class="docs-articles__block">
|
||||||
|
<div class="docs-articles__block-header">
|
||||||
|
<div><h2 class="docs-articles__title">Recently Published</h2><p class="docs-articles__description">The latest research and engineering explanations from the team.</p></div>
|
||||||
|
</div>
|
||||||
|
<div class="docs-articles__posts row gy-4">
|
||||||
|
{{ range first 3 (partial "documentation/articles/list" .) }}
|
||||||
|
{{ partial "documentation/articles/card" (dict "page" .) }}
|
||||||
|
{{ end }}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
{{ $pages := slice }}
|
||||||
|
{{ range (site.GetPage "/articles").RegularPages }}
|
||||||
|
{{ $topic := partial "documentation/articles/topic" . }}
|
||||||
|
{{ if and $topic (not .Draft) (not .Params.hideFromList) }}
|
||||||
|
{{ if or (not $.Params.category) (eq $.Params.category $topic) }}
|
||||||
|
{{ $pages = $pages | append . }}
|
||||||
|
{{ end }}
|
||||||
|
{{ end }}
|
||||||
|
{{ end }}
|
||||||
|
{{ return (sort $pages "PublishDate" "desc") }}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
{{ $settings := (site.GetPage "/articles").Params }}
|
||||||
|
{{ $topic := .Params.category | default "" }}
|
||||||
|
{{ with index $settings.category_aliases $topic }}{{ $topic = . }}{{ end }}
|
||||||
|
{{ with index $settings.category_overrides .RelPermalink }}{{ $topic = . }}{{ end }}
|
||||||
|
{{ if not (in (slice "search-quality" "embedding-research" "qdrant-internals" "production-ops") $topic) }}{{ $topic = "" }}{{ end }}
|
||||||
|
{{ return $topic }}
|
||||||
@@ -1,4 +1,24 @@
|
|||||||
<ul class="docs-breadcrumbs">
|
<ul class="docs-breadcrumbs">
|
||||||
|
{{ if eq .Params.learning_kind "guides" }}
|
||||||
|
<li class="docs-breadcrumbs__crumb"><a href="/learn/">Learn</a></li>
|
||||||
|
<li class="docs-breadcrumbs__crumb-separator"></li>
|
||||||
|
<li class="docs-breadcrumbs__crumb"><a href="/documentation/guides/">Guides</a></li>
|
||||||
|
<li class="docs-breadcrumbs__crumb-separator"></li>
|
||||||
|
{{ if eq .Parent.Params.learning_kind "guides" }}
|
||||||
|
<li class="docs-breadcrumbs__crumb"><a href="{{ .Parent.RelPermalink }}">{{ .Parent.Title }}</a></li>
|
||||||
|
<li class="docs-breadcrumbs__crumb-separator"></li>
|
||||||
|
{{ end }}
|
||||||
|
{{ else if eq .Section "articles" }}
|
||||||
|
<li class="docs-breadcrumbs__crumb"><a href="/learn/">Learn</a></li>
|
||||||
|
<li class="docs-breadcrumbs__crumb-separator"></li>
|
||||||
|
<li class="docs-breadcrumbs__crumb"><a href="/articles/">Articles</a></li>
|
||||||
|
<li class="docs-breadcrumbs__crumb-separator"></li>
|
||||||
|
{{ else if and (eq (partial "get-partition.html" (dict "page" .)) "learn") (eq .Section "documentation") }}
|
||||||
|
<li class="docs-breadcrumbs__crumb"><a href="/learn/">Learn</a></li>
|
||||||
|
<li class="docs-breadcrumbs__crumb-separator"></li>
|
||||||
|
<li class="docs-breadcrumbs__crumb"><a href="/learn/examples/">Tutorials & Examples</a></li>
|
||||||
|
<li class="docs-breadcrumbs__crumb-separator"></li>
|
||||||
|
{{ else }}
|
||||||
{{ $rellink := "" }}
|
{{ $rellink := "" }}
|
||||||
{{ $paths := (split .RelPermalink "/") }}
|
{{ $paths := (split .RelPermalink "/") }}
|
||||||
{{ $len := sub (len $paths) 2 }}
|
{{ $len := sub (len $paths) 2 }}
|
||||||
@@ -11,5 +31,6 @@
|
|||||||
<li class="docs-breadcrumbs__crumb-separator"></li>
|
<li class="docs-breadcrumbs__crumb-separator"></li>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
{{ end }}
|
||||||
<li class="docs-breadcrumbs__crumb">{{ .Params.title | safeHTML }}</li>
|
<li class="docs-breadcrumbs__crumb">{{ .Params.title | safeHTML }}</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|||||||
+4
-142
@@ -11,148 +11,10 @@
|
|||||||
<!-- todo: update this and styles to support both types of content on the same page-->
|
<!-- todo: update this and styles to support both types of content on the same page-->
|
||||||
<div class="documentation__content-wrapper">
|
<div class="documentation__content-wrapper">
|
||||||
{{ if eq .Section "articles" }}
|
{{ if eq .Section "articles" }}
|
||||||
{{ $subject := site.GetPage "articles" }}
|
|
||||||
{{ $currentNode := .Params }}
|
|
||||||
|
|
||||||
{{ if .Params.isMainPage }}
|
{{ if .Params.isMainPage }}
|
||||||
<div class="docs-articles docs-articles__blocks">
|
{{ partial "documentation/articles/hub" . }}
|
||||||
|
|
||||||
{{/* Latest section: the 3 most recent (non-hidden, categorized) articles */}}
|
|
||||||
{{ $latest := first 0 site.Pages }}
|
|
||||||
{{ range $subject.Pages }}
|
|
||||||
{{ if and (not .Params.isCategoryPage) (not .Params.hideFromList) .Params.category }}
|
|
||||||
{{ $latest = $latest | append . }}
|
|
||||||
{{ end }}
|
|
||||||
{{ end }}
|
|
||||||
{{ $latest = $latest.ByPublishDate.Reverse }}
|
|
||||||
{{ if gt (len $latest) 0 }}
|
|
||||||
<div class="docs-articles__block docs-articles__block_featured">
|
|
||||||
<div class="docs-articles__block-header">
|
|
||||||
<div>
|
|
||||||
<h4 class="docs-articles__title">Latest Articles</h4>
|
|
||||||
<p class="docs-articles__description">The most recent publications from the Qdrant team.</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="docs-articles__posts">
|
|
||||||
<div class="row gy-4">
|
|
||||||
{{ range first 3 $latest }}
|
|
||||||
<article class="col-12 col-lg-4 post post-sm">
|
|
||||||
<a class="post-link" href="{{ .RelPermalink }}">
|
|
||||||
<div class="post-preview">
|
|
||||||
<img src="{{ .Params.preview_dir }}/preview.jpg" alt="Preview" />
|
|
||||||
</div>
|
|
||||||
<h6 class="post-title">{{ .Params.title }}</h6>
|
|
||||||
<p class="post-description">{{ .Params.description }}</p>
|
|
||||||
<div class="post-about">
|
|
||||||
<p>{{ .Params.author }}</p>
|
|
||||||
<p>{{ time.Format "January 02, 2006" .Date }}</p>
|
|
||||||
</div>
|
|
||||||
</a>
|
|
||||||
</article>
|
|
||||||
{{ end }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{{ end }}
|
|
||||||
|
|
||||||
{{ range $subject.Pages }}
|
|
||||||
{{ if .Params.isCategoryPage }}
|
|
||||||
<div class="docs-articles__block">
|
|
||||||
<div class="docs-articles__block-header">
|
|
||||||
<div>
|
|
||||||
<h4 class="docs-articles__title">{{ .Params.title }}</h4>
|
|
||||||
<p class="docs-articles__description">{{ .Params.description }}</p>
|
|
||||||
</div>
|
|
||||||
<a href="{{ .Params.url }}" class="docs-articles__block-link button button_outlined button_sm">
|
|
||||||
{{ $currentNode.learnButton }}
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{{ $category := .Params.category }}
|
|
||||||
|
|
||||||
|
|
||||||
<div class="docs-articles__posts">
|
|
||||||
<div class="row gy-4">
|
|
||||||
{{ $list := first 0 site.Pages }}
|
|
||||||
|
|
||||||
{{ range $subject.Pages }}
|
|
||||||
{{ if and (eq .Params.category $category) (not .Params.isCategoryPage) (not .Params.hideFromList) }}
|
|
||||||
{{ $list = $list | append . }}
|
|
||||||
{{ end }}
|
|
||||||
{{ end }}
|
|
||||||
|
|
||||||
{{ $all := $list.ByPublishDate.Reverse }}
|
|
||||||
{{ range first 3 $all }}
|
|
||||||
<article class="col-12 col-lg-4 post post-sm">
|
|
||||||
<a class="post-link" href="{{ .RelPermalink }}">
|
|
||||||
<div class="post-preview">
|
|
||||||
<img src="{{ .Params.preview_dir }}/preview.jpg" alt="Preview" />
|
|
||||||
</div>
|
|
||||||
<h6 class="post-title">{{ .Params.title }}</h6>
|
|
||||||
<p class="post-description">{{ .Params.description }}</p>
|
|
||||||
<div class="post-about">
|
|
||||||
<p>{{ .Params.author }}</p>
|
|
||||||
<p>{{ time.Format "January 02, 2006" .Date }}</p>
|
|
||||||
</div>
|
|
||||||
</a>
|
|
||||||
</article>
|
|
||||||
{{ end }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{{ end }}
|
|
||||||
{{ end }}
|
|
||||||
</div>
|
|
||||||
{{ else if .Params.isCategoryPage }}
|
{{ else if .Params.isCategoryPage }}
|
||||||
<div class="docs-articles docs-articles__list">
|
{{ partial "documentation/articles/category" . }}
|
||||||
{{ partial "documentation/breadcrumbs" . }}
|
|
||||||
|
|
||||||
|
|
||||||
<h4 class="docs-articles__title">{{ .Params.title }}</h4>
|
|
||||||
<p class="docs-articles__description">{{ .Params.description }}</p>
|
|
||||||
|
|
||||||
{{ $category := .Params.category }}
|
|
||||||
|
|
||||||
|
|
||||||
<div class="docs-articles__posts">
|
|
||||||
<div class="row gx-4 gy-5">
|
|
||||||
{{ $list := first 0 site.Pages }}
|
|
||||||
{{ range $subject.Pages }}
|
|
||||||
{{ if and (eq .Params.category $category) (not .Params.isCategoryPage) (not .Params.hideFromList) }}
|
|
||||||
{{ $list = $list | append . }}
|
|
||||||
{{ end }}
|
|
||||||
{{ end }}
|
|
||||||
|
|
||||||
{{ $all := $list.ByPublishDate.Reverse }}
|
|
||||||
{{ $paginator := .Paginate $all 14 }}
|
|
||||||
|
|
||||||
{{ range $paginator.Pages }}
|
|
||||||
<article class="col-12 col-lg-6 post post-md">
|
|
||||||
<a class="post-link" href="{{ .RelPermalink }}">
|
|
||||||
<div class="post-preview">
|
|
||||||
<img src="{{ .Params.preview_dir }}/preview.jpg" alt="Preview" />
|
|
||||||
</div>
|
|
||||||
<h6 class="post-title">{{ .Params.title }}</h6>
|
|
||||||
<p class="post-description">{{ .Params.description }}</p>
|
|
||||||
<div class="post-about">
|
|
||||||
<p>{{ .Params.author }}</p>
|
|
||||||
<p>{{ time.Format "January 02, 2006" .Date }}</p>
|
|
||||||
</div>
|
|
||||||
</a>
|
|
||||||
</article>
|
|
||||||
{{ end }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="row">
|
|
||||||
<div class="docs-articles__pagination col-12">
|
|
||||||
{{ if gt $paginator.TotalPages 1 }}
|
|
||||||
{{ partial "pagination" $paginator }}
|
|
||||||
{{ end }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{{ else }}
|
{{ else }}
|
||||||
<div class="documentation__article-wrapper">
|
<div class="documentation__article-wrapper">
|
||||||
{{ partial "documentation/breadcrumbs" . }}
|
{{ partial "documentation/breadcrumbs" . }}
|
||||||
@@ -160,8 +22,8 @@
|
|||||||
|
|
||||||
<article class="documentation-article">
|
<article class="documentation-article">
|
||||||
<div class="documentation-article__header">
|
<div class="documentation-article__header">
|
||||||
{{ $firstUrlElement := index (split .RelPermalink "/") 1 }}
|
{{ $url := "articles" }}
|
||||||
{{ $url := path.Join $firstUrlElement .Params.category }}
|
{{ with partial "documentation/articles/topic" . }}{{ $url = printf "articles/%s" . }}{{ end }}
|
||||||
|
|
||||||
|
|
||||||
<a href="/{{ $url }}/" class="documentation-article__header-link">
|
<a href="/{{ $url }}/" class="documentation-article__header-link">
|
||||||
|
|||||||
+60
@@ -0,0 +1,60 @@
|
|||||||
|
{{ $goals := slice }}
|
||||||
|
{{ $stacks := slice }}
|
||||||
|
{{ range site.Data.examples }}
|
||||||
|
{{ $goals = $goals | append .goal }}
|
||||||
|
{{ $stacks = $stacks | append .stack }}
|
||||||
|
{{ end }}
|
||||||
|
<section class="example-library" id="example-library" aria-labelledby="examples-title">
|
||||||
|
<h2 id="examples-title">Browse Tutorials & Examples</h2>
|
||||||
|
<p>Choose the result you want, then filter the collection by your stack.</p>
|
||||||
|
<form class="example-library__filters" role="search" aria-label="Find a Tutorial or Example" hidden>
|
||||||
|
<div>
|
||||||
|
<label for="example-query">Search This Collection</label>
|
||||||
|
<input id="example-query" name="q" type="search" placeholder="Try recall, PDF, or filters" autocomplete="off">
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label for="example-goal">Goal</label>
|
||||||
|
<select id="example-goal" name="goal">
|
||||||
|
<option value="">All Goals</option>
|
||||||
|
{{ range sort (uniq $goals) }}<option value="{{ . }}">{{ . }}</option>{{ end }}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label for="example-stack">Stack</label>
|
||||||
|
<select id="example-stack" name="stack">
|
||||||
|
<option value="">All Stacks</option>
|
||||||
|
{{ range sort (uniq $stacks) }}<option value="{{ . }}">{{ . }}</option>{{ end }}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<button type="reset" class="button button_outlined button_sm">Clear Filters</button>
|
||||||
|
</form>
|
||||||
|
<p data-example-count role="status" aria-live="polite" aria-atomic="true">{{ len site.Data.examples }} results</p>
|
||||||
|
<p data-example-empty hidden>No tutorials or examples match these filters. Try a broader term or clear the filters.</p>
|
||||||
|
<div class="row g-4">
|
||||||
|
{{ range site.Data.examples }}
|
||||||
|
{{ $entry := . }}
|
||||||
|
{{ $page := site.GetPage .page }}
|
||||||
|
{{ if not $page }}{{ errorf "Unknown example page %s" .page }}{{ end }}
|
||||||
|
{{ $title := .title | default $page.Title }}
|
||||||
|
{{ $description := .description | default $page.Params.short_description | default $page.Description }}
|
||||||
|
{{ if not $description }}{{ errorf "Missing example description: %s" .page }}{{ end }}
|
||||||
|
<div class="col-12 col-lg-6" data-example data-goal="{{ .goal }}" data-stack="{{ delimit .stack "|" }}" data-search="{{ printf "%s %s %s %s %s" $title $description .goal (delimit .stack " ") (.keywords | default "") | lower }}">
|
||||||
|
<article class="docs-card example-library__card">
|
||||||
|
<span class="docs-card__tag">{{ .goal }}</span>
|
||||||
|
<h3 class="docs-card__title"><a href="{{ $page.RelPermalink }}">{{ $title }}</a></h3>
|
||||||
|
<p class="docs-card__description">{{ $description }}</p>
|
||||||
|
<p class="example-library__stack">{{ delimit .stack " · " }}</p>
|
||||||
|
<div class="example-library__actions">
|
||||||
|
<a href="{{ $page.RelPermalink }}" class="link link_sm">Open Example<span class="visually-hidden">: {{ $title }}</span></a>
|
||||||
|
{{ range .resources }}
|
||||||
|
{{ if not (strings.Contains $page.RawContent .url) }}{{ errorf "Example resource is no longer linked from %s: %s" $entry.page .url }}{{ end }}
|
||||||
|
<a href="{{ .url }}" class="link link_sm">{{ .label }}<span class="visually-hidden">: {{ $title }}</span></a>
|
||||||
|
{{ end }}
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
</div>
|
||||||
|
{{ end }}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
{{ $script := resources.Get "js/example-library.js" | js.Build | minify | fingerprint }}
|
||||||
|
<script src="{{ $script.RelPermalink }}" defer></script>
|
||||||
+44
@@ -0,0 +1,44 @@
|
|||||||
|
{{ $section := site.GetPage .section }}
|
||||||
|
{{ $standalone := where $section.RegularPages.ByWeight "Params.guide_series" "ne" true }}
|
||||||
|
{{ $series := where $section.RegularPages.ByWeight "Params.guide_series" true }}
|
||||||
|
{{ $groups := slice (dict "title" "Find Your Guide" "description" "Find practical guidance for the search problem or design decision you are working on." "pages" $standalone "series" false) }}
|
||||||
|
{{ if $series }}
|
||||||
|
{{ $groups = $groups | append (dict "title" $section.Params.guide_series_title "description" "Follow the complete series in order, or choose the part that matches your next decision." "pages" $series "series" true) }}
|
||||||
|
{{ end }}
|
||||||
|
{{ range $groups }}
|
||||||
|
{{ $isSeries := .series }}
|
||||||
|
{{ $cards := slice }}
|
||||||
|
{{ range $index, $page := .pages }}
|
||||||
|
{{ $tag := .Params.guide_kind }}
|
||||||
|
{{ if $isSeries }}{{ $tag = printf "Part %d" (add $index 1) }}{{ end }}
|
||||||
|
{{ $cards = $cards | append (dict "title" .Title "description" .Params.short_description "tag" $tag "icon" (dict "src" $section.Params.guide_icon "alt" "") "link" (dict "text" "Open Guide" "url" .RelPermalink)) }}
|
||||||
|
{{ end }}
|
||||||
|
{{ if $cards }}
|
||||||
|
{{ partial "documentation/sections/cards-section" (dict "title" .title "description" .description "cardsPartial" "documentation/cards/docs-cards" "cardsPerRow" (cond (eq (len $cards) 2) 2 3) "cards" $cards) }}
|
||||||
|
{{ end }}
|
||||||
|
{{ end }}
|
||||||
|
{{ $examples := slice }}
|
||||||
|
{{ range $section.Params.worked_examples }}
|
||||||
|
{{ $url := . }}
|
||||||
|
{{ with site.GetPage . }}
|
||||||
|
{{ $description := .Params.short_description }}
|
||||||
|
{{ range where site.Data.examples "page" $url }}
|
||||||
|
{{ $description = .description | default $description }}
|
||||||
|
{{ end }}
|
||||||
|
{{ $examples = $examples | append (dict "title" .Title "description" $description "link" (dict "text" "Open Example" "url" .RelPermalink)) }}
|
||||||
|
{{ else }}{{ errorf "Unknown worked example %s" . }}{{ end }}
|
||||||
|
{{ end }}
|
||||||
|
{{ if $examples }}
|
||||||
|
{{ partial "documentation/sections/cards-section" (dict "title" "Apply These Techniques" "description" "Use a worked example to put the guidance into practice." "cardsPartial" "documentation/cards/docs-cards" "cards" $examples "button" (dict "text" "Browse Tutorials & Examples" "url" "/learn/examples/")) }}
|
||||||
|
{{ end }}
|
||||||
|
{{ $references := slice }}
|
||||||
|
{{ range $section.Params.related }}
|
||||||
|
{{ with site.GetPage . }}
|
||||||
|
{{ $references = $references | append (dict "title" .Title "description" .Params.short_description "link" (dict "text" "Explore Documentation" "url" .RelPermalink)) }}
|
||||||
|
{{ else }}
|
||||||
|
{{ errorf "Unknown related page %s in %s" . $section.Path }}
|
||||||
|
{{ end }}
|
||||||
|
{{ end }}
|
||||||
|
{{ if $references }}
|
||||||
|
{{ partial "documentation/sections/cards-section" (dict "title" "Read More" "description" "Use the full reference when you need configuration details or an operating procedure." "cardsPartial" "documentation/cards/docs-cards" "cards" $references) }}
|
||||||
|
{{ end }}
|
||||||
+19
@@ -0,0 +1,19 @@
|
|||||||
|
{{ if and (eq .Params.learning_kind "guides") .Params.guide_series }}
|
||||||
|
{{ $current := . }}
|
||||||
|
{{ $pages := where .Parent.RegularPages.ByWeight "Params.guide_series" true }}
|
||||||
|
{{ range $index, $page := $pages }}
|
||||||
|
{{ if eq $page $current }}
|
||||||
|
<nav class="guide-series-navigation" aria-label="Series Navigation">
|
||||||
|
<p><a href="{{ $current.Parent.RelPermalink }}">{{ $current.Parent.Params.guide_series_title }}</a> · Part {{ add $index 1 }} of {{ len $pages }}</p>
|
||||||
|
<div class="guide-series-navigation__links">
|
||||||
|
{{ if gt $index 0 }}
|
||||||
|
{{ with index $pages (sub $index 1) }}<a rel="prev" href="{{ .RelPermalink }}"><span>Previous</span>{{ .Title }}</a>{{ end }}
|
||||||
|
{{ end }}
|
||||||
|
{{ if lt (add $index 1) (len $pages) }}
|
||||||
|
{{ with index $pages (add $index 1) }}<a rel="next" href="{{ .RelPermalink }}"><span>Next</span>{{ .Title }}</a>{{ end }}
|
||||||
|
{{ end }}
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
{{ end }}
|
||||||
|
{{ end }}
|
||||||
|
{{ end }}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
{{ $cards := slice }}
|
||||||
|
{{ range (site.GetPage "/documentation").Sections.ByWeight }}
|
||||||
|
{{ if eq .Params.learning_kind "guides" }}
|
||||||
|
{{ $cards = $cards | append (dict "title" .Title "description" .Params.short_description "icon" (dict "src" .Params.guide_icon "alt" "") "link" (dict "text" "Explore Guides" "url" .RelPermalink)) }}
|
||||||
|
{{ end }}
|
||||||
|
{{ end }}
|
||||||
|
{{ partial "documentation/sections/cards-section" (dict "title" "Grow Your Search Engineering Skills" "description" "Learn the tradeoffs, apply a pattern, and test it against the needs of your application." "cardsPartial" "documentation/cards/docs-cards" "cardsPerRow" 2 "cards" $cards) }}
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
{{ $current := .RelPermalink }}
|
||||||
|
{{ $isGuide := eq .Params.learning_kind "guides" }}
|
||||||
|
{{ $goals := slice }}
|
||||||
|
{{ range site.Data.examples }}{{ $goals = $goals | append .goal }}{{ end }}
|
||||||
|
{{ $exampleLinks := slice }}
|
||||||
|
{{ range sort (uniq $goals) }}
|
||||||
|
{{ $exampleLinks = $exampleLinks | append (dict "title" . "url" (printf "/learn/examples/?%s" (querify "goal" .)) "goal" . "active" false) }}
|
||||||
|
{{ end }}
|
||||||
|
{{ $guideLinks := slice }}
|
||||||
|
{{ range (site.GetPage "/documentation").Sections.ByWeight }}
|
||||||
|
{{ if eq .Params.learning_kind "guides" }}
|
||||||
|
{{ $pages := slice }}
|
||||||
|
{{ range .RegularPages.ByWeight }}
|
||||||
|
{{ if not .Params.hideInSidebar }}
|
||||||
|
{{ $pages = $pages | append (dict "title" .Title "url" .RelPermalink "series" .Params.guide_series) }}
|
||||||
|
{{ end }}
|
||||||
|
{{ end }}
|
||||||
|
{{ $active := or (eq .RelPermalink $current) (and $isGuide (eq $.Parent .)) }}
|
||||||
|
{{ $guideLinks = $guideLinks | append (dict "title" .Title "url" .RelPermalink "active" $active "children" $pages "seriesTitle" .Params.guide_series_title) }}
|
||||||
|
{{ end }}
|
||||||
|
{{ end }}
|
||||||
|
{{ $articleLinks := slice }}
|
||||||
|
{{ range (site.GetPage "/articles").Sections.ByWeight }}
|
||||||
|
{{ if and .Params.isCategoryPage (not .Params.hideFromList) }}
|
||||||
|
{{ $active := or (eq .RelPermalink $current) (and (eq $.Section "articles") (eq .Params.category (partial "documentation/articles/topic" $))) }}
|
||||||
|
{{ $articleLinks = $articleLinks | append (dict "title" .Title "url" .RelPermalink "active" $active) }}
|
||||||
|
{{ end }}
|
||||||
|
{{ end }}
|
||||||
|
{{ $items := slice
|
||||||
|
(dict "title" "Guides" "url" "/documentation/guides/" "active" $isGuide "children" $guideLinks)
|
||||||
|
(dict "title" "Tutorials & Examples" "url" "/learn/examples/" "active" (eq $current "/learn/examples/") "children" $exampleLinks)
|
||||||
|
(dict "title" "Courses" "url" "/course/" "active" (eq .Section "course") "children" (slice
|
||||||
|
(dict "title" "Qdrant Essentials" "url" "/course/essentials/")
|
||||||
|
(dict "title" "Multivector Search" "url" "/course/multi-vector-search/")))
|
||||||
|
(dict "title" "Articles" "url" "/articles/" "active" (eq .Section "articles") "children" $articleLinks)
|
||||||
|
}}
|
||||||
|
<nav class="docs-menu__links" aria-label="Learning Resources">
|
||||||
|
<h3 class="docs-menu__links-title">Learn</h3>
|
||||||
|
<div class="docs-menu__links-group{{ if eq $current "/learn/" }} active{{ end }}">
|
||||||
|
<div class="docs-menu__links-group-heading"><a href="/learn/" {{ if eq $current "/learn/" }}aria-current="page"{{ end }}>Overview</a></div>
|
||||||
|
</div>
|
||||||
|
{{ range $items }}
|
||||||
|
<details class="docs-menu__links-group link-group{{ if .active }} active{{ end }}" {{ if .active }}open{{ end }}>
|
||||||
|
<summary class="docs-menu__links-group-heading">
|
||||||
|
<a href="{{ .url }}" {{ if eq .url $current }}aria-current="page"{{ end }}><span>{{ .title }}</span></a>
|
||||||
|
</summary>
|
||||||
|
<nav aria-label="{{ .title }}">
|
||||||
|
<ul class="docs-menu__links-submenu">
|
||||||
|
<li class="docs-menu__links-submenu-item{{ if eq .url $current }} active{{ end }}"><a href="{{ .url }}" {{ if eq .url $current }}aria-current="page"{{ end }}>Explore {{ .title }}</a></li>
|
||||||
|
{{ range .children }}
|
||||||
|
{{ if .children }}
|
||||||
|
<li class="docs-menu__links-submenu-item docs-menu__links-submenu-item--section">
|
||||||
|
<details class="guide-topic{{ if .active }} active{{ end }}" {{ if .active }}open{{ end }}>
|
||||||
|
<summary{{ if eq .url $current }} class="active"{{ end }}><a href="{{ .url }}"><span>{{ .title }}</span></a></summary>
|
||||||
|
<ul class="docs-menu__links-sub-submenu">
|
||||||
|
{{ $seriesTitle := .seriesTitle }}
|
||||||
|
{{ $seriesStarted := false }}
|
||||||
|
{{ range .children }}
|
||||||
|
{{ if and .series (not $seriesStarted) }}
|
||||||
|
<li class="guide-series-label">{{ $seriesTitle }}</li>
|
||||||
|
{{ $seriesStarted = true }}
|
||||||
|
{{ end }}
|
||||||
|
<li class="docs-menu__links-sub-submenu-item{{ if eq .url $current }} active{{ end }}">
|
||||||
|
<a href="{{ .url }}" data-guide-link {{ if eq .url $current }}aria-current="page"{{ end }}>{{ .title }}</a>
|
||||||
|
</li>
|
||||||
|
{{ end }}
|
||||||
|
</ul>
|
||||||
|
</details>
|
||||||
|
</li>
|
||||||
|
{{ else }}
|
||||||
|
<li class="docs-menu__links-submenu-item{{ if .active }} active{{ end }}" {{ with .goal }}data-example-nav-goal="{{ . }}"{{ end }}>
|
||||||
|
<a href="{{ .url }}" {{ if eq .url $current }}aria-current="page"{{ end }}>{{ .title }}</a>
|
||||||
|
</li>
|
||||||
|
{{ end }}
|
||||||
|
{{ end }}
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
</details>
|
||||||
|
{{ end }}
|
||||||
|
</nav>
|
||||||
@@ -3,24 +3,8 @@
|
|||||||
{{ $currentNode := .context }}
|
{{ $currentNode := .context }}
|
||||||
<div class="docs-menu">
|
<div class="docs-menu">
|
||||||
<div id="sidebar" class="docs-menu__content">
|
<div id="sidebar" class="docs-menu__content">
|
||||||
{{ if eq $currentNode.Section "articles" }}
|
{{ if eq $partition "learn" }}
|
||||||
<h3 class="docs-menu__links-title">Articles</h3>
|
{{ partial "documentation/learn-menu" $currentNode }}
|
||||||
<nav>
|
|
||||||
<div class="docs-menu__links-group">
|
|
||||||
{{ $subject := site.GetPage "articles" }}
|
|
||||||
|
|
||||||
{{ range $subject.Pages }}
|
|
||||||
{{ if .Params.isCategoryPage }}
|
|
||||||
<div
|
|
||||||
class="docs-menu__articles-link docs-menu__links-group-heading
|
|
||||||
{{ if eq .File.UniqueID $currentNode.File.UniqueID }}active{{ end }}"
|
|
||||||
>
|
|
||||||
<a href="{{ .Params.url }}">{{ .Params.title }}</a>
|
|
||||||
</div>
|
|
||||||
{{ end }}
|
|
||||||
{{ end }}
|
|
||||||
</div>
|
|
||||||
</nav>
|
|
||||||
{{ else }}
|
{{ else }}
|
||||||
|
|
||||||
{{ $pathSegments := split (trim $currentNode.RelPermalink "/") "/" }}
|
{{ $pathSegments := split (trim $currentNode.RelPermalink "/") "/" }}
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
<div class="row g-3 guide-read-more">
|
||||||
|
{{ range split (trim .Inner "\n ") "\n" }}
|
||||||
|
{{ $line := trim . " " }}
|
||||||
|
{{ if $line }}
|
||||||
|
{{ $pattern := `^- \[([^\]]+)\]\(([^)]+)\)\s*(.*)$` }}
|
||||||
|
{{ if not (findRE $pattern $line) }}{{ errorf "Invalid Read More card: %s" $line }}{{ end }}
|
||||||
|
{{ $title := replaceRE $pattern "${1}" $line }}
|
||||||
|
{{ $url := replaceRE $pattern "${2}" $line }}
|
||||||
|
{{ $description := replaceRE $pattern "${3}" $line | replaceRE `^:\s*` "" }}
|
||||||
|
{{ if $description }}{{ $description = printf "%s%s" (upper (substr $description 0 1)) (substr $description 1) }}{{ end }}
|
||||||
|
{{ $description = $.Page.RenderString $description }}
|
||||||
|
{{ partial "documentation/cards/docs-cards" (dict "cardsPerRow" 2 "card" (dict "title" $title "description" $description "link" (dict "text" "Read More" "url" $url))) }}
|
||||||
|
{{ end }}
|
||||||
|
{{ end }}
|
||||||
|
</div>
|
||||||
Reference in New Issue
Block a user