Какую запись нужно найти, если слова вопроса отличаются?
Чтобы понять, что именно должен сделать поиск, сначала разберёмся без чисел и моделей — на уровне обычного чтения. В нашей учебной задаче есть конкретный вопрос человека: «Куда убрали ёлочные украшения?». И есть конечная подборка из четырёх сохранённых записей, в каждой из которых целиком хранится одна заметка со своим собственным идентификатором:
- A — «Гирлянда и стеклянные шары лежат в синем ящике на верхней полке кладовки.»
- B — «Запасные лампочки лежат в белой коробке на нижней полке кладовки.»
- C — «Синяя коробка с бумагой для подарков стоит в шкафу в прихожей.»
- D — «Украшения для дня рождения лежат в красном пакете под кроватью.»
Роли здесь распределены просто: человек задаёт вопрос, программа ищет кандидатов только среди уже сохранённых текстов, а затем человек читает возвращённую запись. Разумеется, четыре короткие строчки легко просмотреть глазами за несколько секунд. Такой маленький набор нужен не для того, чтобы доказать необходимость сложного инструмента, а чтобы наглядно проследить каждый шаг механизма.
Если прочитать заметки глазами, видно, что ответ содержится в записи A: гирлянда и стеклянные шары — это и есть то, чем наряжают ёлку, и в тексте прямо названо место их хранения. При этом ни слова «ёлочные», ни слова «украшения» в записи A нет. Напротив, в записи D есть точное слово «украшения», но речь там идёт о дне рождения, поэтому нужного ответа на вопрос она не даёт. В записях B и C встречаются похожие бытовые детали — кладовка, лампочки, синяя коробка, подарки, — но они описывают другие вещи.
Пока это наша человеческая оценка смысла четырёх учебных фраз, а не результат работы алгоритма. Это наблюдение не означает, что обычный поиск по словам обязательно ошибётся или всегда ищет только буквальные совпадения. Но оно ясно показывает задачу: чтобы помочь человеку, программа должна сопоставить вопрос с записями не только по совпадению отдельных слов, но и по тому, о чём в них говорится. Возникает следующий вопрос: что именно программа может сравнивать, если сами тексты написаны по-разному?
Что получает модель из текста и что называют эмбеддингом?
Чтобы программа могла сопоставить вопрос с заметками не только по буквенному совпадению, каждую фразу переводят в форму, пригодную для математического сравнения. Здесь легко запутаться, потому что при обработке текста возникает сразу несколько разных обозначений и наборов чисел. Разведём их по ролям на примере записи A: «Гирлянда и стеклянные шары лежат в синем ящике на верхней полке кладовки.»
Сначала программе нужно сохранить связь с самой заметкой. Метка A — это идентификатор записи в нашей подборке. Он не выражает смысл фразы и не участвует в расчётах близости, а работает как ярлык: именно по нему система потом сможет открыть исходный текст.
Затем саму фразу подают на вход модели. Программа не читает предложение целиком как одну словарную единицу: сначала срабатывает токенизация. Текст разбивается на небольшие части — токены (слова, части слов или знаки препинания), и каждому токену сопоставляется его номер в словаре модели, то есть словарный идентификатор (token ID). Эти номера нужны только для того, чтобы передать последовательность текста внутрь нейросети; сами по себе они ещё не являются представлением всей заметки для поиска. Подробнее о том, как языковые модели читают токены и работают со внутренними представлениями на базовом уровне, разобрано в обзорной статье «Большие языковые модели (LLM): что это и как они работают».
После токенизации последовательность словарных ID поступает в обученную модель, которую в задачах поиска часто называют кодировщиком (encoder). Кодировщик обрабатывает весь входной фрагмент целиком и с помощью операции объединения выходов по токенам (например, усреднения — pooling) сводит результат в один упорядоченный список чисел фиксированной длины. В геометрии и машинном обучении такой упорядоченный список чисел называют вектором, а в контексте нашей задачи — текстовым эмбеддингом. Речь здесь идёт именно о плотных текстовых представлениях (dense representations), которые используют для поиска по смыслу, а не обо всех возможных разновидностях эмбеддингов.
В проведённом для этой статьи демонстрационном тесте четыре учебные заметки и три вопроса обработали моделью intfloat/multilingual-e5-small. После добавления служебного префикса роли и специальных символов токенизатор разбил запись A на 30 токенов (остальные заметки заняли от 21 до 26 токенов, а вопросы — от 14 до 19 токенов; ни один текст не превысил лимит длины в 512 токенов и не был обрезан). На выходе после объединения модель вычислила для каждой заметки и каждого вопроса вектор ровно из 384 чисел.
Таким образом, у записи A в системе есть три совершенно разные сущности:
- Идентификатор записи (
A) — адрес в базе, который жёстко связан с точным исходным текстом. - Последовательность словарных ID токенов — техническая разметка слов и частей слов для подачи на вход модели (в нашем тесте для записи A это цепочка из 30 номеров).
- Эмбеддинг текста (вектор) — вычисленный кодировщиком список из 384 дробных чисел, который отражает весь фрагмент целиком и используется для сравнения с вопросом.
Важно понимать, что отдельные числа (координаты) внутри этого списка из 384 значений не имеют закреплённых человеком бытовых названий. Там нет отдельной ячейки, которая вручную отвечала бы за «ёлку», «стекло» или «кладовку». Координаты вектора — это не список проверенных ключевых слов, а вычисленные значения во внутреннем пространстве модели. Откуда тогда у этих чисел берётся способность сближать близкие по смыслу фразы? Чтобы ответить на этот вопрос, нужно разделить обучение самой модели и её обычное применение к нашим заметкам.
Откуда берётся способность сравнивать тексты?
Если координаты вектора никто не размечает вручную как «ёлку» или «кладовку», почему списки чисел для вопроса и подходящей записи вообще оказываются сопоставимыми? Чтобы это понять, важно разделить два разных этапа в жизни модели: её предварительное обучение создателями и дальнейшее применение к нашим текстам.
Способность кодировщика строить полезные для поиска представления формируется заранее, на этапе обучения. В качестве наглядного задокументированного примера можно рассмотреть подход Dense Passage Retrieval (DPR). При таком обучении модели предъявляют множество примеров, где для вопроса есть подходящие (релевантные) и неподходящие фрагменты текста. В процессе тренировки внутренние параметры кодировщиков настраивают так, чтобы векторы вопроса и подходящего к нему фрагмента получались математически ближе друг к другу, чем векторы вопроса и неподходящих отрывков.
Разумеется, описание DPR — это конкретный пример одного из методов тренировки поисковых кодировщиков, а не универсальный рецепт, по которому создаётся абсолютно любая модель. Кроме того, модель не обязана учиться на примерах именно про домашние кладовки или ёлочные игрушки, чтобы работать с нашими заметками. На большом корпусе разнообразных пар она усваивает общие языковые закономерности того, как формулировки вопросов соотносятся с содержанием ответов.
Когда мы переходим к нашей задаче с четырьмя записями A–D, этап обучения давно завершён. Происходит другой процесс — применение уже готовой модели (inference) и индексация: кодировщик с зафиксированными параметрами просто принимает на вход каждую заметку, вычисляет для неё вектор и сохраняет его в поисковый индекс.
При добавлении новой заметки в базу модель не дообучается и не меняет своих внутренних параметров — она лишь рассчитывает числовое представление нового текста по уже выученным правилам. В проведённом техническом тесте с моделью intfloat/multilingual-e5-small выполнялось именно такое штатное вычисление векторов без какого-либо изменения самой модели. Теперь, когда понятно, откуда берутся эти списки чисел, разберёмся, как именно программа сопоставляет вектор вопроса с векторами заметок и что означает итоговая оценка.
Как сравнивают вопрос с записями и что означает оценка?
Когда у нас есть обученная модель, просто получить списки чисел одинаковой длины для вопроса и заметок ещё недостаточно. Если взять два произвольных кодировщика, которые оба выдают векторы из 384 чисел, но не обучались работать в одной связке, их результаты будут несовместимы: сравнивать такие числа напрямую бессмысленно. Поэтому поиск начинается с согласованной подготовки обоих входов.
Шаг 1. Совместимая подготовка вопроса и записей. Для сопоставления нужна задокументированная согласованная связка обработки двух ролей — поискового запроса и документа из базы. При этом сами кодировщики для вопроса и для текста не обязаны быть одной и той же нейросетью: например, в архитектуре DPR используются два отдельных совместно обученных кодировщика. В других системах, таких как применённая в нашем демонстрационном тесте модель intfloat/multilingual-e5-small, работает одна сеть, но при подаче текста ей явно указывают роль с помощью строгих входных префиксов (в нашем прогоне ко всем вопросам добавлялась метка query: , а к заметкам — passage: ), после чего вычисленные векторы приводятся к единичной длине (нормируются). Главное правило здесь общее: подготовка вопроса и документов должна точно следовать тем условиям, на которых обучалась выбранная система.
Шаг 2. Вычисление оценок и ранжирование кандидатов. Когда векторы вопроса и всех записей получены согласованным способом, программа сопоставляет вектор вопроса с вектором каждой заметки и вычисляет числовую оценку близости (score). В рассматриваемой задаче поиска эта оценка нужна для одной конкретной цели: выстроить доступных кандидатов по порядку — от наиболее близкого к наименее близкому, чтобы решить, какой сохранённый текст показать первым.
Для расчёта такой близости используют разные математические правила (метрики): косинусное сходство, скалярное произведение (dot product) или меры геометрического расстояния. Только для векторов, предварительно нормированных к единичной длине, скалярное произведение математически совпадает с косинусным сходством; расплывчатого «выравнивания масштаба» для такого равенства недостаточно.
При этом обычные расстояния работают наоборот — чем меньше расстояние, тем ближе векторы, хотя некоторые программные библиотеки для единообразия меняют знак и возвращают расстояние со знаком минус.
Посмотрим, какие оценки и какой порядок кандидатов получились в нашем фиксированном тесте с моделью intfloat/multilingual-e5-small (при точном переборе всех пар и расчёте без дополнительного умножения на 100):
- На исходный вопрос «Куда убрали ёлочные украшения?» записи выстроились в порядке A → D → B → C. Запись A заняла первое место с оценкой
0,7934, но запись D («Украшения для дня рождения…») отстала от неё совсем незначительно, получив0,7910(далее B —0,7817, C —0,7726). - Если сформулировать ту же самую потребность другими словами — «Где искать гирлянду и шары для ёлки?» — модель вычисляет для нового текста вопроса другой вектор. В нашем тесте порядок изменился на A → C → D → B: запись A снова оказалась первой с оценкой
0,8247, далее расположились C (0,7814), D (0,7748) и B (0,7688).
Эти числа наглядно показывают, как нужно и как нельзя трактовать оценку близости. Оценка 0,7934 или 0,8247 не означает, что текст на 79% или 82% совпадает с вопросом по смыслу, и не выражает вероятность того, что ответ верен. У подобных оценок нет универсального порога, общего для любых моделей и задач, а сами абсолютные значения зависят от конкретной модели и выбранной метрики. Например, в семействе моделей E5 даже подходящие тексты часто получают сравнительно близкие друг к другу высокие значения, поэтому важен прежде всего их взаимный порядок. Оценка близости — это инструмент ранжирования внутри данной коллекции, а не проверка истинности или полноты ответа. Но когда программа выстроила этот рейтинг и выбрала первого кандидата, как именно мы переходим от списка чисел и его оценки обратно к человеческому тексту?
Как из результата сравнения возвращают исходную запись?
Сам по себе результат сравнения векторов — это только таблица с идентификаторами и оценками близости. Внутри списка из 384 координат нет сжатого русского текста, который программа могла бы «распаковать» обратно в слова. Эмбеддинг не хранит точные формулировки заметки и не доказывает её происхождение — он лишь указывает место фрагмента в пространстве сравнения.
Чтобы человек увидел читаемый результат, система опирается на подготовленную заранее связку данных. Ещё до вычисления векторов в нашей подборке были заданы чёткие границы четырёх отдельных записей, и для каждой из них в памяти сохранили пару: уникальный идентификатор (A, B, C, D) и привязанный к нему точный исходный текст.
Поэтому возврат ответа устроен как прямое обращение по адресу:
- Сравнение векторов выстраивает кандидатов по оценке и ставит на первое место идентификатор выбранной записи.
- Программа берёт этот идентификатор (в общем случае — тот условный ID, который оказался выше других; в нашем проведённом тесте по обоим вопросам об украшениях на первом месте оказался идентификатор A).
- По идентификатору A система обращается к хранилищу исходных заметок и извлекает сохранённую там строку целиком: «Гирлянда и стеклянные шары лежат в синем ящике на верхней полке кладовки.»
Этот переход принципиален для понимания того, как работает поиск по эмбеддингам. Видимый на экране текст не расшифровывается обратно из дробных координат вектора и не сочиняется нейросетью заново на лету. Программа просто достаёт из доступной коллекции уже существующий фрагмент, на который указал лучший по оценке вектор. А значит, поиск в принципе может вернуть только то, что заранее сохранено в базе. Но даже когда исходная запись успешно найдена и открыта на экране, можно ли считать любую запись с первого места готовым и верным ответом на вопрос?
Почему найденная запись может не отвечать на вопрос?
То, что поиск поставил какую-то запись на первое место и вернул её исходный текст, ещё не означает, что человек получил нужный и достоверный ответ. Ранжирование по эмбеддингам выбирает наиболее близкого кандидата среди имеющихся текстов, но не проверяет ни достаточность сведений, ни их соответствие реальности. Разберём это на двух разных проверках содержания нашей подборки.
Случай 1. Нужных сведений вообще нет в базе. Зададим к тем же четырём заметкам третий контрольный вопрос: «Где чек на покупку гирлянды?». Если прочитать все записи A–D глазами, сразу видно, что ответа в подборке нет: ни в одной строке про кассовый чек или документы о покупке не говорится. Однако поисковая программа без дополнительного правила отсечения обязана отсортировать доступных кандидатов. В нашем фиксированном прогоне с моделью intfloat/multilingual-e5-small система снова выстроила записи в порядке A → C → D → B и поставила на первое место запись A с оценкой 0,8088 (далее расположились C — 0,7833, D — 0,7787, B — 0,7719). В этом конкретном запуске оценка первого места для вопроса о чеке (0,8088) оказалась даже немного выше, чем для прямого вопроса об ёлочных украшениях (0,7934). Для человека при чтении понятно, что вопрос о покупке гирлянды и запись A о самой гирлянде связаны общей темой, хотя сам эксперимент фиксирует только итоговые векторы и оценки близости, не измеряя вклад отдельных слов в решение кодировщика. Главное здесь в другом: открытие исходного текста A не добавляет в него недостающего факта — сведений о месте хранения чека там по-прежнему нет.
Случай 2. Текст подходит по смыслу, но может не совпадать с действительностью. Вернёмся к первому вопросу «Куда убрали ёлочные украшения?», для которого запись A действительно содержит прямое указание на верхнюю полку кладовки. Доказывает ли высокая оценка близости, что игрушки лежат там прямо сейчас? Нет. Заметка могла устареть, коробку могли переставить на антресоли или отдать, либо запись с самого начала была ошибочной. Сравнение векторов сопоставляет только формулировки вопроса и сохранённого текста — оно не заглядывает в настоящую кладовку.
Поэтому любой возвращённый поиском исходный текст требует базовой содержательной проверки по трём пунктам:
- Сообщает ли запись искомое сведение? Для вопроса о чеке ответ отрицательный: в тексте A говорится о самой гирлянде, а не о чеке.
- Тот ли это контекст? Даже при совпадении отдельных слов важно убедиться, что речь идёт о нужном предмете и ситуации (как в случае с записью D про украшения для дня рождения).
- Применима ли запись сейчас? Чтобы подтвердить, что вещи действительно находятся на верхней полке кладовки в настоящий момент, нужны внешние по отношению к тексту сведения или проверка на месте.
Помимо содержания самих заметок, при переходе от учебного примера к другим конфигурациям систем важно учитывать ещё две технические границы, которые могут помешать найти даже существующий в документах ответ:
- Обрезание длинного текста (truncation). У каждого кодировщика есть максимальный предел длины входа (в использованной нами модели это 512 токенов). Наши короткие заметки заняли не более 30 токенов и вошли целиком. Но если подать в такую модель длинный документ без предварительного разбиения и система просто обрежет всё, что выходит за поддерживаемый предел, нужный факт в конце текста вообще не попадёт в расчёт вектора.
- Приближённый поиск. В нашей подборке из четырёх записей программа выполняла точный перебор — сравнивала вопрос с каждой заметкой. В больших базах ради скорости нередко используют алгоритмы приближённого поиска ближайших соседей (ANN). Такой способ ускоряет работу, но по своему устройству не гарантирует абсолютной точности и иногда может пропустить даже близкого по вектору кандидата.
Эти ограничения не делают поиск по эмбеддингам бесполезным — они лишь показывают его точную роль: быстро сузить круг чтения до наиболее вероятных фрагментов исходного текста. Но почему тогда во многих современных ИИ-системах человек видит на экране не просто найденную старую заметку, а заново составленный связный ответ?
Где заканчивается поиск и начинается генерация в RAG?
В повседневных обсуждениях поиск по эмбеддингам и работу чат-ботов с документами часто смешивают в одно целое. Кажется, будто сам эмбеддинг «понимает» базу знаний и своими словами отвечает человеку. На самом деле здесь объединены две совершенно разные операции: извлечение (retrieval) и генерация (generation).
В том сценарии, который мы разобрали выше, работа поиска полностью завершилась в момент, когда программа сопоставила векторы, выбрала идентификатор A и вернула неизменный сохранённый текст заметки: «Гирлянда и стеклянные шары лежат в синем ящике на верхней полке кладовки.». Поиск по эмбеддингам ничего не пересказывает и не дописывает — он только отбирает существующие записи из коллекции.
Если же системе нужно составить для пользователя новую фразу ответа на основе найденных заметок, после извлечения подключается второй компонент — генеративная модель. Именно так устроена архитектура RAG (Retrieval-Augmented Generation, генерация, дополненная извлечением). В оригинальной работе, предложившей подход RAG, система объединяет два блока: нейросетевой механизм извлечения (retriever), который ищет подходящие фрагменты в плотном векторном индексе, и генератор последовательности (sequence-to-sequence model), который создаёт новый текст ответа с опорой на найденные отрывки. Подробнее о том, как взаимодействуют эти этапы в прикладных системах, можно прочитать в материале о том, как работает RAG.
При этом остаются два ограничения:
- Не всякий RAG устроен одинаково. Оригинальная архитектура RAG — это конкретный задокументированный способ объединить плотный векторный индекс с генератором. На практике этап поиска в RAG-системах не обязан всегда строиться только на векторах или в точности повторять исходную схему.
- Генерация не отменяет ограничений источника. В нашем демонстрационном тесте проверялось только вычисление эмбеддингов и ранжирование записей, без запуска генератора. Но даже если передать найденную запись A генеративной модели, сам факт генерации гладкого ответа не превратит тематическую близость в истину и не создаст отсутствующих в базе сведений. Если в заметке A нет информации о кассовом чеке или сама запись устарела, генератор не сможет достоверно восполнить этот пробел из одного лишь найденного текста.
В рассмотренной задаче числовое представление (эмбеддинг) даёт модели материал для математического сравнения формулировок, сохранённый идентификатор записи позволяет программе открыть исходный текст из базы, и только чтение самого текста показывает, есть ли в нём нужное сведение. Составление новой фразы ответа с помощью генератора и проверка того, соответствует ли запись действительности, — это уже отдельные действия, которые лежат за пределами самого поиска по векторам.
Чтобы проверить, насколько прочно сложилась эта картина, сделайте один короткий мысленный шаг на нашем примере с вопросом «Где чек на покупку гирлянды?»: объясните своими словами, почему возврат близкой по теме записи A с первого места рейтинга ещё не даёт ответа на вопрос, даже если программа безошибочно вычислила векторы и открыла исходную заметку.


