Релевантность поиска
Обработка запроса, веса queryBy, typo tolerance, synonyms, curations, ранжирование — developer-справочник, как движок решает, что вернуть.
Страница Relevance tuning объясняет как пользоваться ручками relevance из UI. Эта страница объясняет, как движок принимает решение, что отдавать — developer-side справочник за спиной дашборда.
Прочитать один раз при интеграции и пересмотреть каждый раз, когда аналитика покажет регрессию.
Pipeline обработки запроса
POST /api/search идёт в таком порядке:
1. валидация ввода (Zod)
2. tenant + scoped filter resolve ← Инварианты 4 + 5
3. expand synonyms ← per-index synonym sets
4. tokenize + drop stopwords ← per-locale stopword list
5. typo tolerance ← numTypos default
6. retrieve по queryBy полям ← weighted match
7. curation rules ← pin / hide overrides
8. score через _text_match ← BM25-flavoured ranking
9. sort через sortBy (или default)
10. paginate, highlight, returnШаги 3–5 на стороне движка AACSearch; шаг 7 поверх сырого результата до ранжирования. Tenant isolation (шаг 2) применяется всегда — обойти нельзя.
queryBy и веса полей
queryBy объявляет, какие поля движок ищет. Порядок важен — раньше = выше вес по умолчанию. Per-field веса переопределяются через queryByWeights:
{
"queryBy": "title,brand,description,tags",
"queryByWeights": "10,5,2,1"
}- Это относительные веса, не абсолютные.
10,5,2,1=100,50,20,10. - Без
queryByWeightsвеса геометрические по умолчанию: примерно8, 4, 2, 1, …. - Поля, не указанные в
queryBy, не ищутся — их можно фильтровать и возвращать, но не искать.
Дефолтный queryBy для product-схемы: "title,brand,description,tags". Переопределяйте per request на storefront только при измеренной причине — платформенная консистентность ценнее микротюнинга.
Когда менять веса
По сигналам:
| Сигнал в аналитике | Что попробовать |
|---|---|
| Brand-запросы дают low-CTR | Поднять brand (например, title:10, brand:8). |
| SKU-запросы падают | Добавить sku в queryBy с максимальным весом. |
| Long-tail descriptive — нерелевантные заголовки | Добавить description; рассмотреть stem: true на нём. |
| Tag-driven browse падает | Поднять вес tags или добавить, если нет. |
| Только title-матчи доминируют, теряются описания | Сократить разрыв title/description. |
Никогда не меняйте веса без eval-сета — Relevance quality описывает workflow.
Typo tolerance
Typo tolerance включён по умолчанию. Кол-во опечаток зависит от длины токена:
| Длина токена | Default numTypos |
|---|---|
| 1–4 символа | 0 (только exact) |
| 5–7 символов | 1 |
| 8+ символов | 2 |
Hard cap: numTypos ≤ 3. Переопределение per request:
{ "q": "iphn", "numTypos": 2 }Строже:
prioritizeExactMatch: true— буст точных матчей над typo-матчами.dropTokensThreshold: 1— если ничего не совпало со всеми токенами, отбросить последний и повторить.typoTokensThreshold: 0— применять typo-tolerance, только когда 0 точных матчей.
Где typo-tolerance вредит: SKU и brand-запросы, где опечатка скорее «другой товар», чем «опечатка в запросе». Частый паттерн — ниже numTypos для SKU-sub-search в multi-search и дефолт для основного.
Synonyms
Synonyms мапят эквивалентные термины на этапе запроса:
"pants" ↔ ["jeans", "trousers"]
"sneakers" ↔ ["trainers"]
"mobile" ↔ ["phone"] # locale-specificПри "trousers" движок раскроет запрос и матчит и "pants", и "jeans". Synonyms per index; управляются в Relevance tuning или через search.synonyms oRPC.
Три правила:
- Привязка к локали. Не делайте
"pants" ↔ "trousers"синонимами в обоихenиru— смысл разный. - Избегайте одностороннего.
"laptop" → "macbook"— это curation, не synonym. Если нужно одно направление — curation. - Периодический аудит. Synonym применяется ко всем запросам с корневым словом; забытый synonym полугодовой давности — постоянный low-grade шум.
Synonyms идут до retrieval (шаг 3). Они не вытащат документы, которых нет в индексе — content gap чинится контентом.
Curation rules
Curation pin-ит или hide-ит конкретные ID для точной строки запроса:
Query "summer sale":
pin = ["tshirt-123", "shorts-456"]
hide = ["winter-coat-789"]Curations — после retrieval (шаг 7): pinned ID вставляются в топ по порядку; hidden — убираются.
Когда брать:
- Брендовый запрос, branded landing. «summer sale» — всегда сезонный landing-пин.
- Compliance / regulatory. Скрыть товары, которые не должны появляться по определённым запросам.
- Merchandising. Пин in-stock вариантов популярного SKU над out-of-stock.
Когда не надо:
- «Все running-shoe запросы — сначала Nike» — это вес или synonym, не curation. Curations — exact-query.
- «Поднять новые товары» — это
sortBy: created_at:desctiebreak.
Ранжирование и взаимодействие с sort
Дефолтный порядок — relevance-first:
sortBy: "_text_match:desc"_text_match — BM25-style score, посчитанный по queryBy + весам + typo + позициям матча. Multi-field sort — для tiebreak-ов:
sortBy: "_text_match:desc,popularity_score:desc,price:asc"Три поведения:
- Чистый field-sort перебивает relevance.
sortBy: "price:asc"отдаст самые дешёвые матчи — relevance игнорируется. Только для facet-driven browse. - Pin-curations игнорируют sort. Pinned документы остаются в топе. Hide-правила тоже работают при любом sort.
- Wildcard
q: "*"не имеет relevance-сигнала. Приq: "*"_text_matchодинаков для всех. Комбинируйте с осмысленнымsortByи агрессивнымfilterBy.
Для e-commerce типичный дефолт storefront-а:
sortBy: "_text_match:desc,popularity_score:desc"popularity_score — поле, которое вы поддерживаете при ingest (обычно клики за 30 дней; см. popularity-ranking job в packages/search/lib/popularity-ranking.ts).
Обработка no-results
Поиск возвращает found: 0 либо потому что ничего не сматчилось, либо потому что фильтры сузили выдачу до нуля. Движок возвращает пустой результат с эхо-параметрами, чтобы клиент мог различить.
Три паттерна:
1. Retry с relaxed filters
Если filterBy был — попробовать без него. Виджет делает это автоматом при found === 0 и непустом filterBy.
2. Retry с dropTokensThreshold
Для длинных запросов — отбросить последний токен:
{ "q": "wireless noise cancelling over ear headphones", "dropTokensThreshold": 2 }До 2 токенов дроп. Полезно при over-specific запросе.
3. AI / semantic fallback
Для storefront-ов с AI Search — fallback на semantic search (hybrid). Пользователь получает что-то вместо ничего.
Комбинируйте: filter-relaxation сначала (бесплатно), token-drop потом (бесплатно), semantic в конце (платно). Всегда инструментируйте fallback-путь, чтобы измерять окупаемость — No-results loop.
Workflow тестирования relevance
Релевант-правки против eval-сета — 30–100 запросов с известно-хорошими топ-результатами. Workflow:
- Snapshot. Прогоните все eval-запросы, сохраните текущий top-5 ID.
- Тюнинг. Меняете synonym / weight / curation в staging-индексе.
- Re-run. Прогоняете eval против staging.
- Diff. Для каждого запроса — staging top-5 vs production top-5. Скор:
- +1 если ранее отсутствовавший target теперь в top-5.
- −1 если ранее присутствовавший target выпал.
- Roll out, если net-score положительный и ни один query не упал до 0.
Eval-сет — самый ценный артефакт релевант-проекта. Переиспользуется через деплои, A/B и upgrades движка.
Аналогичный workflow для RAG — Knowledge evaluation; структура та же.
Пример e-commerce
Product-индекс с title, brand, description, sku, categories, tags, popularity_score. Разумный дефолт storefront search:
{
"indexSlug": "products",
"q": "running shoes",
"queryBy": "title,brand,description,sku,tags",
"queryByWeights": "10,5,3,8,2",
"sortBy": "_text_match:desc,popularity_score:desc",
"facetBy": "brand,categories,price",
"filterBy": "availability:=in_stock"
}- SKU взвешен жирно — SKU-shaped запрос (
"WH-2024") даёт точный мач первым. - Description ниже title/brand — descriptive-запросы матчат, но title всегда обгоняет description на одном товаре.
popularity_score— tiebreaker.
Пример help-center / контент
Article-индекс с title, excerpt, body, tags, published_at. Разумный дефолт help-center search:
{
"indexSlug": "help-center",
"q": "reset my password",
"queryBy": "title,excerpt,body,tags",
"queryByWeights": "8,4,2,3",
"sortBy": "_text_match:desc,published_at:desc",
"filterBy": "locale:=`en`"
}titleиtagsвыше — help-center заголовки короткие и осмысленные.bodyищется сstem: true(объявлено в схеме) —"reset"найдёт"resetting".published_attiebreaker — свежие статьи в случае ничьей.
Связанные страницы
- Multi-search и querying —
queryBy,queryByWeights, typo-крутилки - Filters, sorting & pagination — filter-сторона relevance
- Index schema — флаги
sort: true,facet: true,stem: true - Relevance tuning — dashboard-панель
- Relevance quality — eval workflow
- No-results loop — recovery-ветка