Поиск: полнотекстовый и векторный

Зачем:Вы понимаете, как сайт ищет по словам и по смыслу: токены, указатель, ранжирование, эмбеддинги и ближайшие соседи. После урока вы выберете между поиском внутри Postgres, отдельным движком и векторной базой и поймёте, как ИИ-ответы используют ваши тексты.

30 минут

Озвучено синтезом речи (Qwen3-TTS), голос не принадлежит человеку.

Полнотекстовый поиск находит тексты по словам через обратный индекс и основы слов, а векторный — по смыслу, через эмбеддинги и ближайших соседей.

Поиск это не LIKE

Допустим, на сайте строка поиска. Самое простое, что приходит в голову: WHERE title LIKE '%услуга%'. Это ищет подстроку буква в букву. Он не найдёт «услуги» (последняя буква другая), не отличит главное от упомянутого мимоходом, ничего не упорядочит по важности, а на большой таблице будет медленным: обычный индекс с «%» в начале не помогает. Для поиска по тексту придумано отдельное устройство.

Поиск по словам: как он устроен

Полнотекстовый поиск готовит текст заранее, как указатель в конце книги (вы помните это по уроку об индексах).

  1. Токены. Текст режут на слова.
  2. Нормализация. Всё приводят к одному регистру и отбрасывают служебные слова: «и», «в», «на», «для», «как». Они есть везде и ничего не различают.
  3. Основы. Слова сводят к основе: «услуга», «услуги», «услугу» превращаются в «услуг». Это называют стеммингом или лемматизацией (лемма это словарная форма). Для русского это важнее, чем для английского: у слова много окончаний.
  4. Указатель. Строят таблицу «основа → в каких текстах она встречается». Это обратный индекс.
  5. Ранжирование. Найденное упорядочивают. Идея такая: слово важнее, если оно часто встречается в этом тексте и редко во всей коллекции (так считают TF-IDF и его улучшение BM25), а совпадение в заголовке весомее, чем в теле.

Подвигайте схему. На вкладке «По словам» поиск идёт по вашим услугам. Нажмите «Другая форма слова», а потом выключите «Учитывать формы слов»: именно это сделал бы LIKE.

Поиск по словам на ваших услугах

Упрощённая модель: она срезает типичные русские окончания и считает совпадения (заголовок весит вдвое больше текста). Настоящий движок использует полные словари.

Из запроса получились основы: услуг. Служебные слова (и, в, на…) отброшены.

  1. 3 Первая услуга
    основы заголовка: перв, услуг
  2. 3 Вторая услуга
    основы заголовка: втор, услуг
  3. 0 Консультация
    основы заголовка: консультац

С формами слов найдено: 2 из 3. Попробуйте «Другая форма слова»: основа та же, результат тот же.

Текстовая альтернатива: поиск по словам сводит слова запроса и текстов к основам, строит указатель и ранжирует по числу совпадений; он находит разные формы слова, но не синонимы. Поиск по смыслу сравнивает числовые «координаты смысла» и находит ближайшие по значению тексты, даже если слова разные, но плохо находит точные коды и номера.

В PostgreSQL всё это встроено. Тип tsvector хранит текст, разложенный на основы, tsquery хранит запрос, оператор @@ проверяет совпадение, ts_rank считает вес, а GIN-индекс ускоряет всё это на больших таблицах.

SELECT title,
       ts_rank(to_tsvector('russian', title || ' ' || text), q) AS rank
FROM items, plainto_tsquery('russian', 'услуга') AS q
WHERE to_tsvector('russian', title || ' ' || text) @@ q
ORDER BY rank DESC;

CREATE INDEX items_fts_idx ON items
  USING GIN (to_tsvector('russian', title || ' ' || text));

Слово 'russian' выбирает русскую конфигурацию: словарь окончаний и список служебных слов. Без неё PostgreSQL применит конфигурацию по умолчанию, и русские слова не сведутся к основам.

У встроенного поиска есть пределы: он не исправляет опечатки и не подсказывает по мере набора. Когда это важно, берут отдельный поисковый движок. Meilisearch и Typesense просты в установке, умеют опечатки и «мгновенный» поиск. Elasticsearch и OpenSearch нужны, когда данных очень много, а фильтры и настройки сложны, и когда есть, кому это обслуживать. Цена та же, что у любой новой базы: ещё один сервер, ещё одна копия данных, которую надо держать в согласии с основной (вспомните про намеренную копию из урока 3).

Проверьте себя: почему перед поиском служебные слова отбрасывают?

Они есть почти в каждом тексте, поэтому не помогают отличить один текст от другого, а указатель из-за них разбухает.

Поиск по смыслу

Теперь вопрос, на который слова не отвечают. Посетитель пишет «сколько это стоит», а страница называется «Цены и оплата». Общих слов нет, но смысл тот же.

Для этого придумали эмбеддинги. Специальная модель превращает текст в список из сотен или тысяч чисел: «координаты смысла». Тексты, близкие по значению, получают близкие координаты, как города на карте. Чтобы ответить на вопрос, его тоже превращают в координаты и берут ближайших соседей: тексты, лежащие рядом. Так находят и тогда, когда слова другие.

Переключитесь на вкладку «По смыслу». Там карта нарисована вручную, на ней всего две оси вместо сотен, и она показывает идею: вопрос о цене оказывается рядом с «Ценами и оплатой» и «Способами оплаты», а вопрос о возврате рядом с «Условиями возврата». Внизу видно, что нашёл бы поиск по словам: часто ничего.

Хранить эти числа и искать среди них ближайшие умеют векторные базы (Qdrant, Pinecone, Weaviate) и расширение pgvector для PostgreSQL. Чтобы не сравнивать вопрос с каждым текстом, они строят особый индекс приближённого поиска (HNSW, IVF), и снова платят: ответ «почти самый близкий», зато быстро.

Что поиск по смыслу делает плохо:

  • Точные идентификаторы. Номер договора, артикул, номер статьи закона это не смысл. Векторный поиск вернёт «похожие», а нужен точный. Поэтому часто объединяют оба способа в гибридный поиск.
  • Объяснимость. Почему нашлось именно это, сказать трудно: сходство это число.
  • Ошибки модели. Она может счесть близкими тексты, которые противоположны («можно вернуть» и «нельзя вернуть»).

RAG: как ИИ отвечает по вашим текстам

Эти же эмбеддинги стоят за большинством чат-ботов, которые отвечают «по базе знаний» компании. Схема называется RAG (retrieval-augmented generation, генерация с подбором материала):

  1. Ваши тексты заранее режут на куски и считают эмбеддинги.
  2. Пришёл вопрос. Его эмбеддинг сравнивают с кусками и берут несколько ближайших.
  3. Модели отдают вопрос и найденные куски: «ответь, используя только это».

Модель при этом не переучивают, ей просто показывают нужные страницы. Поэтому ответ можно проверить по источнику, а чтобы бот знал новое, достаточно обновить тексты.

Чьи тексты уходят и куда

Практический вопрос, который часто забывают. Эмбеддинги многие считают на чужом сервисе: тексты отправляются стороннему провайдеру по сети. Если в этих текстах есть персональные данные (имена, телефоны, подробности заявок), то отправка это уже обработка персональных данных третьей стороной, с требованиями закона (об этом урок 7). Что с этим делать:

  • Не отправлять лишнее: для эмбеддингов страниц услуг и ответов на частые вопросы персональных данных обычно нет.
  • Узнать, где провайдер обрабатывает и хранит тексты, и на каких условиях.
  • Если данные нужны, получить основание и согласие или считать эмбеддинги у себя, на своём сервере, открытой моделью.

Решение здесь принимает не разработчик, а владелец сайта вместе с юристом. Но задать вопрос «что именно мы отправляем» должен тот, кто строит поиск.

Практика

Вы в эпохе 2022 года: у сайтов появилась строка поиска, которая понимает формы слов. Добавьте её своему проекту.

В странице демо поиска по вашим услугам. Выпишите три формы одного слова и их основу, напишите для PostgreSQL запрос с русской конфигурацией, ранжированием и индексом, а затем ответьте коротко, когда вам хватит поиска по словам и когда понадобился бы смысл. Настоящий PostgreSQL в браузере есть на сайте PGlite; здесь же проверяется текст запроса.

Что было не так раньше

Встроенный поиск у сайтов 2010-х обычно был простым: искал слова в заголовке и тексте и ранжировал слабо. Многие подключали внешний движок. В Рунете много лет популярным был Sphinx, который написал Андрей Аксёнов. Поиск по смыслу тогда оставался уделом исследовательских лабораторий. Сегодня векторный поиск доступен любому сайту, и главный вопрос уже не «как это сделать», а «что именно мы отправляем и кому».

Практика

Готовлю вашу страницу…

Тест

Вопрос 1 из 7

Почему поиск LIKE '%услуга%' не находит текст со словом «услуги»?

Один ответ

Карточки

Эти карточки попадут в повторение, когда вы завершите урок.

  • Чем LIKE не годится для поиска по сайту?

    Ответ

    Он ищет подстроку буква в букву: не знает форм слова, не ранжирует и на больших таблицах медленный. Для поиска нужен полнотекстовый механизм.

  • Как устроен полнотекстовый поиск?

    Ответ

    Текст делят на слова, убирают служебные, сводят к основе, строят указатель «основа → тексты» (обратный индекс) и упорядочивают найденное по релевантности. В PostgreSQL это tsvector, tsquery и GIN-индекс.

  • Когда хватит PostgreSQL, а когда нужен отдельный движок?

    Ответ

    Для небольшого и среднего сайта хватает поиска внутри PostgreSQL. Meilisearch и Typesense дают быстрый поиск с поправкой опечаток, Elasticsearch нужен на больших объёмах и сложных настройках.

  • Что такое эмбеддинг и поиск по смыслу?

    Ответ

    Модель превращает текст в список чисел, «координаты смысла». Близкие по смыслу тексты оказываются рядом, и ближайших соседей запроса находят даже при разных словах. Слабое место: точные коды и номера.

  • Что учесть, отправляя тексты на эмбеддинги стороннему сервису?

    Ответ

    Это передача текстов третьей стороне. Если в них есть персональные данные, нужны основание и согласие, и надо знать, где сервис хранит данные. Альтернатива: считать эмбеддинги на своём сервере.

Источники

Видео

Векторные базы данных: самое понятное объяснение (AI RANEZ)

Видео загрузится с youtube-nocookie.com только после нажатия.

Открыть на сайте

Дальше

Теперь вы знаете: полнотекстовый поиск, векторные базы и эмбеддинги.