Поиск: полнотекстовый и векторный
Зачем:Вы понимаете, как сайт ищет по словам и по смыслу: токены, указатель, ранжирование, эмбеддинги и ближайшие соседи. После урока вы выберете между поиском внутри Postgres, отдельным движком и векторной базой и поймёте, как ИИ-ответы используют ваши тексты.
30 минут
Озвучено синтезом речи (Qwen3-TTS), голос не принадлежит человеку.
Полнотекстовый поиск находит тексты по словам через обратный индекс и основы слов, а векторный — по смыслу, через эмбеддинги и ближайших соседей.
Поиск это не LIKE
Допустим, на сайте строка поиска. Самое простое, что приходит в голову: WHERE title LIKE '%услуга%'. Это ищет подстроку буква в букву. Он не найдёт «услуги» (последняя буква другая), не отличит главное от упомянутого мимоходом, ничего не упорядочит по важности, а на большой таблице будет медленным: обычный индекс с «%» в начале не помогает. Для поиска по тексту придумано отдельное устройство.
Поиск по словам: как он устроен
Полнотекстовый поиск готовит текст заранее, как указатель в конце книги (вы помните это по уроку об индексах).
- Токены. Текст режут на слова.
- Нормализация. Всё приводят к одному регистру и отбрасывают служебные слова: «и», «в», «на», «для», «как». Они есть везде и ничего не различают.
- Основы. Слова сводят к основе: «услуга», «услуги», «услугу» превращаются в «услуг». Это называют стеммингом или лемматизацией (лемма это словарная форма). Для русского это важнее, чем для английского: у слова много окончаний.
- Указатель. Строят таблицу «основа → в каких текстах она встречается». Это обратный индекс.
- Ранжирование. Найденное упорядочивают. Идея такая: слово важнее, если оно часто встречается в этом тексте и редко во всей коллекции (так считают TF-IDF и его улучшение BM25), а совпадение в заголовке весомее, чем в теле.
Подвигайте схему. На вкладке «По словам» поиск идёт по вашим услугам. Нажмите «Другая форма слова», а потом выключите «Учитывать формы слов»: именно это сделал бы LIKE.
Упрощённая модель: она срезает типичные русские окончания и считает совпадения (заголовок весит вдвое больше текста). Настоящий движок использует полные словари.
Из запроса получились основы: услуг. Служебные слова (и, в, на…) отброшены.
- 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, генерация с подбором материала):
- Ваши тексты заранее режут на куски и считают эмбеддинги.
- Пришёл вопрос. Его эмбеддинг сравнивают с кусками и берут несколько ближайших.
- Модели отдают вопрос и найденные куски: «ответь, используя только это».
Модель при этом не переучивают, ей просто показывают нужные страницы. Поэтому ответ можно проверить по источнику, а чтобы бот знал новое, достаточно обновить тексты.
Чьи тексты уходят и куда
Практический вопрос, который часто забывают. Эмбеддинги многие считают на чужом сервисе: тексты отправляются стороннему провайдеру по сети. Если в этих текстах есть персональные данные (имена, телефоны, подробности заявок), то отправка это уже обработка персональных данных третьей стороной, с требованиями закона (об этом урок 7). Что с этим делать:
- Не отправлять лишнее: для эмбеддингов страниц услуг и ответов на частые вопросы персональных данных обычно нет.
- Узнать, где провайдер обрабатывает и хранит тексты, и на каких условиях.
- Если данные нужны, получить основание и согласие или считать эмбеддинги у себя, на своём сервере, открытой моделью.
Решение здесь принимает не разработчик, а владелец сайта вместе с юристом. Но задать вопрос «что именно мы отправляем» должен тот, кто строит поиск.
Практика
Вы в эпохе 2022 года: у сайтов появилась строка поиска, которая понимает формы слов. Добавьте её своему проекту.
В странице демо поиска по вашим услугам. Выпишите три формы одного слова и их основу, напишите для PostgreSQL запрос с русской конфигурацией, ранжированием и индексом, а затем ответьте коротко, когда вам хватит поиска по словам и когда понадобился бы смысл. Настоящий PostgreSQL в браузере есть на сайте PGlite; здесь же проверяется текст запроса.
Что было не так раньше
Встроенный поиск у сайтов 2010-х обычно был простым: искал слова в заголовке и тексте и ранжировал слабо. Многие подключали внешний движок. В Рунете много лет популярным был Sphinx, который написал Андрей Аксёнов. Поиск по смыслу тогда оставался уделом исследовательских лабораторий. Сегодня векторный поиск доступен любому сайту, и главный вопрос уже не «как это сделать», а «что именно мы отправляем и кому».
Практика
Готовлю вашу страницу…
Тест
Вопрос 1 из 7
Карточки
Эти карточки попадут в повторение, когда вы завершите урок.
Чем LIKE не годится для поиска по сайту?
Ответ
Он ищет подстроку буква в букву: не знает форм слова, не ранжирует и на больших таблицах медленный. Для поиска нужен полнотекстовый механизм.
Как устроен полнотекстовый поиск?
Ответ
Текст делят на слова, убирают служебные, сводят к основе, строят указатель «основа → тексты» (обратный индекс) и упорядочивают найденное по релевантности. В PostgreSQL это tsvector, tsquery и GIN-индекс.
Когда хватит PostgreSQL, а когда нужен отдельный движок?
Ответ
Для небольшого и среднего сайта хватает поиска внутри PostgreSQL. Meilisearch и Typesense дают быстрый поиск с поправкой опечаток, Elasticsearch нужен на больших объёмах и сложных настройках.
Что такое эмбеддинг и поиск по смыслу?
Ответ
Модель превращает текст в список чисел, «координаты смысла». Близкие по смыслу тексты оказываются рядом, и ближайших соседей запроса находят даже при разных словах. Слабое место: точные коды и номера.
Что учесть, отправляя тексты на эмбеддинги стороннему сервису?
Ответ
Это передача текстов третьей стороне. Если в них есть персональные данные, нужны основание и согласие, и надо знать, где сервис хранит данные. Альтернатива: считать эмбеддинги на своём сервере.
Источники
- PostgreSQL: Full Text Search только ссылка, по-английски
- Postgres Pro: Полнотекстовый поиск только ссылка, по-русски
- pgvector: векторный поиск для Postgres только ссылка, по-английски
- Meilisearch: документация только ссылка, по-английски
- Векторное представление слов (Википедия) CC BY-SA, по-русски
Видео
Векторные базы данных: самое понятное объяснение (AI RANEZ)
Видео загрузится с youtube-nocookie.com только после нажатия.
Дальше
Теперь вы знаете: полнотекстовый поиск, векторные базы и эмбеддинги.