Гипотетически, ответ такой…

Я уже примерно год занимаюсь несколькими AI-проектами, в том числе RAG-ботом (Retrieval-Augmented Generation), и всё время выясняю для себя что-то новое. Почему бы не поделиться?

Разработка RAG-чатбота считается простой задачей — многие облачные платформы, от OpenAI до Cloudflare, предлагают даже готовые решения, где все можно настроить кликами в веб-интерфейсе. Да и для собственной разработки не заметно особых проблем — возьмите базу знаний, порежьте ее на небольшие фрагменты, однородные по смыслу (например, вопрос-ответ или раздел в большом документе), вычислите для этих фрагментов векторное представление (embeddings, в самом простом случае это очень дешево делается через запрос к API, например, OpenAI или Google), сохраните в специальный вид базы, которая называется “векторной” и ждите вопроса пользователя. Полученный вопрос тоже превращаете в эмбеддинг и теперь на запрос с ним векторная база вернет вам результаты, наиболее похожие (similar) на заданный вопрос. Остается сформулировать запрос к LLM буквально следующего содержания “Ты специалист службы поддержки (или консультант, смотря что за задача), вот вопрос пользователя, вот что мы знаем на эту тему, сформулируй ответ”.

На самом деле, как только вы выходите за рамки нескольких десятков документов в базе и пытаетесь научить бота отвечать на более сложные вопросы, начинаются детали, сильно влияющие на всю работу. Вот про одну из таких деталей я и расскажу.

Пока задача всей системы до генерации итогового ответа сводится к нахождению примерно похожих вопросов среди уже имеющихся, всё работает достаточно просто. Но, если вы решаете для улучшения качества ответа добавить в базу другие документы — например, почему бы магазину по продаже алкоголя не добавить статьи о процессе изготовления вина, винных регионах, традициях Северной Европы по производству водки и так далее, — начинаются интересные проблемы. Поиск по векторной базе почему-то не выдает эти замечательные, полные полезного контекста статьи, или ранжирует их заметно ниже банальных ответов на вопросе. Еще хуже становится, если вы решаете эту задачу для области, где есть масса информации нормативного характера — от законов до новостей.

Причина тому очень простая — хотя векторная похожесть, в отличие от точного вхождения, и учитывает семантику запроса, но всё же она не может найти результат, где все или многие слова запроса представлены синонимами, например. То есть, если вопрос задает простой пользователь, а в вашей базе ответы суперспециалистов, сформулированные в совершенно точных специальных терминах, поиск вернет что-то случайное — просто ничего похожего на вопрос “А как мне сделать так, чтобы у меня быстро работал вай-фай” в вашей базе, где подробно объяснено, какие каналы Wi-Fi 2,4 Ghz чаще испытывают влияние интерференции в плотной городской застройке, может и не найтись. Возможно, умная LLM в итоге как-то ответит без каких-либо подсказок в промпте, но достаточно часто, чтобы исключить вероятность галлюцинаций, ей запрещают пользоваться собственными знаниями и использовать только содержание контекста, а там случайные результаты.

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

Что можно сделать? Конечно, есть вариант сделать поиск гибридным. То есть проиндексировать базу еще и с построением традиционного поискового индекса и при поиске по похожести параллельно искать по ключевым словам, объединяя эти результаты в дальнейшем. Но можно ли сам поиск по похожести сделать лучше?

Оказывается, можно, и это довольно несложно с логической точки зрения. Если у нас нет идеального документа для поиска, давайте его выдумаем. Техника HyDE (Hypothetical Document Embedding) работает именно так: прежде чем стучаться в базу, мы просим быструю LLM сгенерировать гипотетический ответ на вопрос пользователя. Нам не нужна фактическая точность — пусть модель галлюцинирует, главное, чтобы она использовала правильную терминологию и структуру. Вместо вопроса “почему тормозит вай-фай” мы превращаем в вектор сгенерированный пассаж про “интерференцию каналов и диапазон 2.4 ГГц”. Векторный поиск, получив такой “мясной” контекст, находит реальные документы гораздо точнее. Результат становится заметно лучше по двум причинам:

  • такой гипотетический ответ длиннее вопроса, поэтому вы отсекаете случайные совпадения в случае коротких запросов.
  • LLM намного лучше понимает смысл вопроса, а не только похожесть, и выдает вам проект ответа в тех терминах, которые используются в документах базы. Этим ликвидируется тот самый разрыв между лексикой пользователя и документов.

Конечно, надо предупредить об очевидном риске. HyDE отлично работает на общечеловеческих знаниях, но на узкоспециализированных данных может сыграть злую шутку. Если на специфический вопрос по внутренней корпоративной политике модель сгенерирует правдоподобный, но неверный ответ из общемировой практики и вектор этого ответа утянет поиск в сторону от ваших реальных документов.

Да, скорость ответа на вопрос падает — какую LLM вы не возьмете, она добавит несколько секунд в процесс обработки вопроса, а это блокирующая операция. Но поиск, вычисление эмбеддингов тоже занимают время, а если итоговая LLM у вас рассуждающая, то несколько секунд на хорошее улучшение качества поиска себя оправдывают. Более того, лучше найдя результаты, вы можете передать итоговой LLM меньше документов в запросе и в итоге обнаружить, что общее время ответа особо не изменилось.

Но над этим стоит подумать особо — если у вас небольшая база относительно однородных документов или если текущий поиск в среднем равномерно покрывает результатами все категории документов, то, возможно, у вас и нет такой острой проблемы лексического разрыва между запросами и документами, и вам лучше посмотреть в сторону других способов улучшить релевантность поиска — например, извлекать ключевые слова или использовать query rewriting. Но если проблема есть и тема требует квалифицированного ответа, то пользователь готов мириться с несколькими лишними секундами до начала генерации более точного и полного ответа.

Вот такая интересная техника. Прекрасно сработало в нашем случае, когда выяснилось, что вопросы пользователей на бухгалтерские и налоговые темы, конечно, похожи на консультации профессиональных экспертов, но совсем не совпадают с текстами законов и постановлений правительства. Мы в итоге добавили и HyDE, и поиск по ключевым словам (и переранжирование) — и в итоговый промпт стали намного чаще попадать нормативные документы, а итоговая рассуждающая модель стала генерировать примерно на 25% лучшие (по оценкам людей-экспертов) (полные и правильные) ответы.

Stay tuned, у меня еще много неочевидных находок в запасе.