Новые паттерны при выводе GenAI-продуктов в продакшн (часть 1)

от автора

Когда генеративные ИИ-продукты переходят из стадии прототипов в боевые системы, начинают всплывать повторяющиеся архитектурные решения. 

Главное — научиться держать под контролем их непредсказуемость. Тут на сцену выходят evals — тесты, которые проверяют, укладывается ли поведение модели в допустимые рамки.

Сами по себе большие языковые модели (LLM) — как энциклопедия без интернета: знают много, но только то, чему их однажды научили. Чтобы вытащить их за пределы обучающей выборки, их усиливают. Чаще всего — через Retrieval-Augmented Generation (RAG), где модель подхватывает свежую информацию из внешней базы. Правда, даже у базового RAG хватает слабых мест, и чтобы его раскочегарить, приходится применять дополнительные паттерны. Когда и этого не хватает — в ход идёт тонкая настройка (fine-tuning).

Перевод генеративных ИИ-продуктов из состояния PoC в боевые системы оказался куда сложнее, чем ожидалось. И дело не только в технологиях — корень многих проблем в том, что команды мыслят слишком по-старому. Они воспринимают GenAI как надстройку над привычными транзакционными или аналитическими системами. На практике же всё иначе: здесь свои баги, свои грабли и свои демоны. ИИ галлюцинирует, лезет туда, куда не просили, и вечно отвечает чуть-чуть не так. А ещё он непредсказуем — от слова совсем.

Со временем мы заметили, что команды начинают вырабатывать повторяющиеся подходы к решению этих задач. Это статья — попытка собрать эти паттерны воедино. Всё, что вы прочитаете — не догма. GenAI-системы молоды, инструменты появляются буквально каждую неделю, и многие открытия ещё впереди. Как и с любыми паттернами, здесь важно не только что делать, но и когда.

В статье мы кратко описываем паттерны, сопровождая их контекстом и связками. Меткой ✣ отмечены названия разделов, в которых описывается конкретный паттерн. Завершаются такие блоки символами ✣ ✣ ✣.

Это не учебник и не справочник. Мы не пытаемся объяснить, как устроены LLM в целом — таких материалов уже хватает. Здесь — практический конспект. То, что реально работало (или не работало) в наших проектах. Где-то будет лакуна, потому что мы туда ещё не добирались. Где-то — только наброски. Материал будет дополняться по мере работы, а обновления будут появляться в наших лентах.

Паттерны, описанные в статье

Вот перевод таблицы с паттернами из статьи — в сжатом и понятном виде, с сохранением технической точности и добавлением пояснений по-русски:

Паттерн

Что делает

Прямое промптингование (Direct Prompting)

Отправка запросов напрямую в базовую LLM без дополнительной обработки.

Эмбеддинги (Embeddings)

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

Оценки (Evals)

Проверка качества ответов модели на конкретных задачах, чтобы отловить ошибки и нестабильное поведение.

Тонкая настройка (Fine Tuning)

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

Ограничители (Guardrails)

Вызов вспомогательных LLM-запросов для фильтрации опасного ввода или очистки результата — своего рода «санитарный кордон».

Гибридный ретривер (Hybrid Retriever)

Комбинация поиска по эмбеддингам с другими техниками (например, full-text или rule-based).

Переформулировка запросов (Query Rewriting)

Использование LLM для генерации альтернативных версий одного и того же запроса и параллельного поиска по ним.

Переоценщик (Reranker)

Сортировка найденных фрагментов документов по релевантности и передача лучших в LLM.

RAG (Retrieval Augmented Generation)

Поиск релевантных фрагментов и их подмешивание в prompt, чтобы улучшить ответы модели.

✣ Прямое промптингование (Direct Prompting) ✣

Самый прямолинейный способ использовать LLM — просто подключить её напрямую к пользователю. Без прослоек, фильтров и обработчиков: пользователь пишет запрос, модель отвечает. Всё. Такой формат часто предлагают поставщики LLM «из коробки» — чистый чат с моделью, в котором нет ничего, кроме диалога.

Это удобно для демонстрации возможностей, экспериментов или личных задач. Но как только появляется реальный продукт с бизнес-требованиями — этого становится мало.

✣ ✣ ✣

Когда использовать прямое промптингование

Прямое взаимодействие с LLM — это то, с чего всё началось. Именно оно породило волну интереса к генеративному ИИ. Но несмотря на простоту, у этого подхода есть серьёзные ограничения.

Во-первых, модель ограничена рамками обучающей выборки. Всё, что произошло после даты обучения — для неё терра инкогнита. Она не знает актуальных событий, не имеет доступа к внутренним данным компании, не понимает специфики контекста, в котором используется. Даже если нужная информация есть в её «голове», она не умеет выставлять приоритеты — просто потому что не знает, что для пользователя важно прямо сейчас.

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

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

✣ Evals (Оценки) ✣

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

С LLM так не выйдет. Модель — не детерминированная система. Один и тот же запрос может дать разные ответы. Но это не значит, что мы не можем её проверять — просто подход будет другим.

В мире GenAI это называют evals — системой оценивания. Её задача — не просто понять, дал ли ответ «в точку», а протестировать поведение модели в разных ситуациях. Оценки могут быть автоматическими или ручными, но почти всегда строятся на множестве кейсов, а не единичных репликах.

Оценка и ранжирование

Чтобы это работало, нужен компонент, который умеет выставлять баллы. Его задача — измерить качество генерации по критериям: релевантность, связность, фактичность, соответствие ожиданиям. Всё это сводится к числам — по сути, модель оценивает саму себя или её проверяет другая система.

✣ ✣ ✣

Когда мы начинаем оценивать поведение языковой модели, возникает базовый вопрос: кто будет выносить вердикт? Самая простая идея — поручить это другой модели. Но чем глубже смотришь, тем сложнее становятся варианты.

Самооценка (Self-evaluation). Модель проверяет себя сама. Она же сгенерировала ответ — она же его оценивает. Звучит удобно, но на практике это лотерея. Если у модели сбоит механизм оценки, она может «отшлифовать» неадекватный ответ так, что тот будет выглядеть уверенно и красиво — но останется таким же ошибочным. Такой подход существует, но мы не рекомендуем полагаться на него без оглядки. Это способ закрепить баг как норму.

LLM в роли судьи. Более рабочий вариант — оценку производит другая модель. Это может быть более продвинутый LLM, а может — специализированная малая модель (SLM). Идея в том, что у двух разных моделей вряд ли совпадут одни и те же искажения и ошибки. Такой подход даёт автоматизацию без замкнутой петли самоподтверждения. Сейчас это один из самых популярных способов оценки в продуктах.

Оценка человеком (Human evaluation). И наконец — старая добрая «проверка вайба». Это когда человек смотрит на ответ и оценивает: «а модель вообще поняла, что от неё хотели?» В такой проверке важны не только факты, но и тон, стиль, интонация, уместность. Да, это не масштабируется. Но если нужно понять, не звучит ли бот как заучка с аутоформой, — лучше человека всё равно пока никто не справится.

✣ ✣ ✣

На практике лучше всего работает связка: модель оценивает, человек — проверяет. Это даёт масштабируемость за счёт автоматизации и глубину за счёт человеческого чутья. Особенно когда важно не просто точность факта, а соответствие тону, цели и контексту продукта.

Пример: как протестировать релевантность в LLM для нутрициолога

Вот как можно использовать библиотеку DeepEval, чтобы проверить, насколько точно модель отвечает на вопрос в нашем приложении по питанию:

Здесь мы сравниваем ответ модели с контекстом из базы знаний и рассчитываем метрику релевантности. Такой подход можно обернуть в интеграционные тесты, чтобы отслеживать поведение модели на «живых» данных.

Где и как запускать evals

Мы запускаем evals как часть сборки GenAI-продукта. Это не бинарные тесты «прошёл/не прошёл», а скоринговая система с порогами, которую нужно мониторить на предмет деградации. По сути, это ближе к нагрузочному тестированию, чем к юнитам.

Причём оценки важны не только до выката. Модель может деградировать в продакшене — и мы обязаны ловить это заранее. Запускаем регулярные замеры качества прямо на боевых системах.

Evals можно применять к любому компоненту, где задействована LLM: main prompt, RAG, guardrails, query rewriting — все эти штуки можно (и нужно) проверять как отдельно, так и в связке.

Evals и бенчмарки

Статья [Shayan Mohanty, John Singleton и Parag Mahajani] подробно разбирает, как сравнивать модели, как они принимают решения и как ведут себя в продакшене.

Бенчмаркинг — это способ сравнивать модели по стандартным задачам. Для этого нужны: – типовые датасеты,

– чёткие метрики,

– одинаковые условия.

Так можно понять: новая версия модели лучше или хуже старой.

А evals — это про конкретику. Они не оценивают модель в вакууме, а тестируют, как она работает в вашей задаче. Типового набора здесь нет — придётся собрать свой.

Когда использовать evals

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

Evals — это способ держать систему в рамках. Но чтобы эти рамки работали, надо понять, на чём вообще стоит весь этот ИИ-аттракцион — и как он обращается с данными. К этому и переходим дальше.

✣ Эмбеддинги (Embeddings) ✣

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

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

Эмбеддинги: как из яблока получить вектор

Допустим, вы делаете приложение о питании. Пользователь фотографирует еду, а система выдаёт советы, что съесть вместо бургеров. Даже простое фото яблока — это уже 3,6 миллиона пикселей (1280 × 960 × 3). Анализировать такой массив напрямую — всё равно что искать порядок в муравейнике.

Чтобы с этим можно было работать, изображение надо сжать без потери смысла. Именно это делает эмбеддинг: превращает картинку в вектор из сотен чисел. При этом похожие картинки оказываются рядом в пространстве эмбеддингов.

Это не просто сжатие, а умное сжатие: яблоки ложатся рядом с яблоками, а не с батарейками.

Пример: эмбеддинг изображения с помощью CLIP

Ручные признаки (например, яркость, форма) устарели. Сейчас для эмбеддинга используют модели глубокого обучения. Например, CLIP ViT-L/14 — модель, которая одновременно понимает и картинки, и текст. Вот как выглядит код:

Результат: вы получаете вектор из 768 чисел, который описывает яблоко куда лучше, чем 3.6 млн пикселей.

А теперь проверим гипотезу: яблоко должно быть рядом с яблоком

Раз у нас векторы — можно измерить расстояние между ними. Один из самых популярных способов — косинусное сходство. Оно показывает, насколько два вектора «смотрят» в одном направлении. Значения от -1 (противоположные) до 1 (полное совпадение).

Значение

Направление векторов

Интерпретация

1

Полное совпадение (aligned)

Изображения почти идентичны

0

Перпендикулярны (orthogonal)

Изображения никак не связаны по смыслу

-1

Противоположны (anti-aligned)

Изображения максимально непохожи друг на друга

Два эмбеддинга можно сравнить с помощью косинусного сходства:

Теперь проверим гипотезу на четырёх изображениях:

Сравнение всех изображений с apple 1 дало следующие результаты:

Изображение

Косинусное сходство

Комментарий

apple 1

1.0

То же самое фото — идеальное совпадение

apple 2

0.9229323

Очень похоже — почти полное совпадение

apple 3

0.8406111

Похоже, но чуть дальше

burger

0.58842075

Существенно отличается

На практике возможны вариации: а если яблоко разрезано? А если оно на тарелке? А если зелёное? А если фотография сверху?

Модель эмбеддингов должна уметь кодировать смысловую близость — и располагать изображения, похожие по сути, рядом в векторном пространстве.

Модели машинного обучения спокойно переваривают векторы с сотнями измерений. Но человеку это не под силу — даже 4 измерения уже вызывают легкое головокружение. Чтобы взглянуть на структуру эмбеддингов и проверить, действительно ли похожие изображения группируются рядом, нужно «сплющить» пространство до 2D или 3D.

Для этого применяются техники снижения размерности, например:
– T-SNE (t-Distributed Stochastic Neighbor Embedding)
– UMAP (Uniform Manifold Approximation and Projection)

Эти методы сжимают многомерные эмбеддинги в 2–3 измерения, сохраняя при этом локальную близостьобъектов — то, что нам и нужно.

Пример: визуализация эмбеддингов с помощью T-SNE

from sklearn.manifold import TSNE  tsne = TSNE(random_state=0, metric='cosine', perplexity=2, n_components=3)  embeddings_3d = tsne.fit_transform(array_of_embeddings)

Теперь у нас трёхмерный массив, в котором каждый вектор представляет изображение — например, из датасета с фруктами с Kaggle. Можно построить 3D-график и посмотреть: яблоки сбились в один угол? Бананы не затесались к помидорам? Всё ли в этой фруктовой вселенной на своих местах?

С картинками всё ясно: эмбеддинг показывает, что яблоко ближе к другому яблоку, чем к бургеру. Но работает ли это с текстами? Ответ — да, и очень даже хорошо.

Текст — это такие же данные, как и пиксели. Кусок текста, абзац, страница с таблицами и картинками — всё это можно представить в виде эмбеддинга. Главное, чтобы модель понимала контекст, а не просто пересчитывала слова.

Фраза «У Мэри был маленький ягнёнок» для детского рассказчика и для владельца ресторана — это два совершенно разных смысла. Хорошая модель эмбеддингов (например, text-embedding-3-large или all-MiniLM-L6-v2) умеет это различать. Она кодирует не слова, а смыслы, включая иронию, настроение, жанр, сферу применения.

Эмбеддинги внутри LLM

Большие языковые модели (LLM) — это трансформеры. Упрощённо говоря, они состоят из трёх частей: – входной слой,

  • скрытые слои,

  • выходной слой.

И вот на входе — эмбеддинги словаря. То есть каждый токен (слово, символ) внутри LLM уже имеет векторное представление. Это и есть внутренние эмбеддинги модели — их ещё называют параметрическими или статическими.

Возвращаемся к нашему приложению о питании. Допустим, пользователь делает фото еды и спрашивает:

«Это полезная еда?»

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

LLM выполняет следующие логические шаги для генерации ответа

На входном слое токенизатор преобразует входные тексты (промпты) и изображения в эмбеддинги.

Затем эти эмбеддинги передаются во внутренние скрытые слои LLM, также называемые слоями внимания (attention layers), которые извлекают релевантные признаки, содержащиеся во входных данных. Если предположить, что наша модель обучена на данных по питанию, разные слои внимания анализируют входные данные с точки зрения здоровья и питания.

Наконец, выход из последнего скрытого состояния, то есть последнего слоя внимания, используется для предсказания ответа.

Когда использовать эмбеддинги

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

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

Тем не менее, эмбеддинги не идеальны для работы со структурированными или реляционными данными, где лучше подходят точное сопоставление или традиционные запросы к базе данных. Задачи вроде поиска точных совпадений, числовых сравнений или выполнения запросов к отношениям лучше решаются с помощью SQL и традиционных СУБД, а не эмбеддингов и векторных хранилищ.

Мы начали этот разговор с описания ограничений прямого промптинга. Evals дают нам способ оценить общие возможности нашей системы, а эмбеддинги предоставляют способ индексировать большие объёмы неструктурированных данных. LLM обучаются (или, как принято говорить, «предобучаются») на корпусах таких данных. Для общего случая этого достаточно, но если мы хотим, чтобы модель использовала более специфическую или актуальную информацию, нам нужно, чтобы LLM имела доступ к данным за пределами обучающего набора.

Один из способов адаптировать модель под конкретную задачу или домен — провести дополнительное обучение, известное как Fine Tuning.

Проблема в том, что это очень дорого, и обычно не является лучшим вариантом. (Позже мы рассмотрим, когда это может быть оправдано.) В большинстве случаев мы считаем, что наилучшим подходом является RAG.

✣ Retrieval-Augmented Generation (RAG) ✣

Извлечение релевантных фрагментов документов и включение их в промпт LLM

Распространённая метафора для LLM — младший научный сотрудник. Человек, который красноречив, начитан в целом, но плохо осведомлён в деталях темы — и при этом чрезмерно самоуверен, предпочитая выдумывать правдоподобный ответ, чем признать незнание.

С помощью RAG мы задаём этому «исследователю» вопрос и одновременно передаём ему досье с наиболее релевантными документами, говоря: «Прочитай это, прежде чем отвечать».

Мы считаем, что RAG — это эффективный способ использовать LLM со специализированными знаниями. Но он также приводит нас к классическим задачам информационного поиска (IR): как найти правильные документы, чтобы передать их нашему старательному «исследователю»?

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

Первая часть этого процесса — построение индекса. Мы делаем это, разделяя документы на фрагменты (chunks), создавая эмбеддинги для этих фрагментов и сохраняя сами фрагменты и их эмбеддинги в векторную базу данных.

Затем мы обрабатываем пользовательские запросы, используя модель эмбеддингов для создания эмбеддинга запроса. Этот эмбеддинг применяется для поиска похожих фрагментов в векторном хранилище с помощью приблизительного поиска ближайших соседей (ANN — Approximate Nearest Neighbors).

Далее мы используем шаблон промпта RAG, чтобы объединить найденные фрагменты с исходным запросом и отправить полный ввод в LLM.

Шаблон RAG

После того как мы получили фрагменты документов из механизма извлечения (retriever), мы объединяем пользовательский запрос с этими фрагментами, используя шаблон промпта. Мы также добавляем инструкции, которые явно направляют LLM использовать данный контекст и распознавать, когда данных недостаточно.

Такой шаблон может выглядеть следующим образом:

Пользовательский запрос: {{user_query}}

Релевантный контекст: {{retrieved_text}}

Инструкции:

  1. Предоставьте полный, точный и связный ответ на пользовательский запрос, используя предоставленный контекст.  

  2. Если извлечённого контекста достаточно, сосредоточьтесь на предоставлении точной и релевантной информации.  

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

  4. Избегайте включения неподтверждённой информации или предположений.

Когда использовать это

Предоставляя LLM релевантную информацию в запросе, RAG преодолевает ограничение, связанное с тем, что LLM может отвечать только на основе своих обучающих данных. Этот подход объединяет сильные стороны извлечения информации и генеративных моделей.

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

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

Контекст, предоставляемый извлечёнными документами, может также смягчить предвзятость, присутствующую в обучающих данных. Кроме того, RAG может использовать обучение «в контексте» (in-context learning, ICL), встраивая в извлечённый контент примеры или шаблоны, специфичные для задачи, что позволяет модели динамически адаптироваться к новым задачам или запросам.

Альтернативным способом расширения базы знаний LLM является тонкая настройка (Fine Tuning), которую мы рассмотрим позже. Тонкая настройка требует значительно больше ресурсов, и поэтому в большинстве случаев мы считаем RAG более эффективным.

RAG на практике

Описанный выше процесс — это то, что мы считаем базовым вариантом RAG, в духе того, как это было описано в оригинальной статье[^1]. Мы использовали RAG в ряде проектов и пришли к выводу, что это эффективный способ использовать LLM для взаимодействия с большими и плохо структурированными наборами данных. Однако мы также обнаружили, что необходимо внести множество улучшений в базовую идею, чтобы она работала в серьёзных задачах.

Один из примеров — работа, которую мы проделали по созданию системы запросов для транснациональной компании в области наук о жизни. Исследователи в этой компании часто нуждаются в обзоре деталей прошлых исследований по различным соединениям и видам. Эти исследования велись в течение двух десятилетий и привели к появлению 17 000 отчётов, каждый из которых содержит тысячи страниц с текстом и табличными данными. Мы создали чат-бота, позволившего исследователям делать запросы по этой коллекции, содержащей разрозненно структурированные данные.

До этого проекта, чтобы ответить на сложные вопросы, часто приходилось вручную просматривать множество PDF-документов. Это могло занимать от нескольких дней до недель. Теперь исследователи могут использовать multi-hop-запросы в нашем чат-боте и находить нужную информацию за считанные минуты. Мы также добавили визуализации там, где это необходимо, чтобы упростить исследование набора данных, использованных в отчётах.

Это был успешный пример применения RAG, но для того чтобы превратить его из proof-of-concept в жизнеспособное промышленное приложение, нам пришлось преодолеть несколько серьёзных ограничений.

Ограничение

Паттерн ослабления

Неэффективное извлечение

Когда вы только начинаете работать с системами извлечения, становится шоком осознать, что опора только на эмбеддинги фрагментов документов в векторном хранилище не приведёт к эффективному извлечению. Распространено предположение, что одних только эмбеддингов чанков достаточно, но на практике они полезны, хотя и не очень эффективны сами по себе. Когда мы создаём один вектор эмбеддинга для чанка документа, мы сжимаем несколько абзацев в один плотный вектор. Хотя плотные эмбеддинги хорошо находят похожие абзацы, они неизбежно теряют часть семантической детализации. Никакое количество тонкой настройки не может полностью восполнить этот разрыв[^2].

Минималистичный пользовательский запрос

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

Перегрузка контекста

Статья Lost in the Middle показывает, что LLM в настоящее время с трудом эффективно используют информацию внутри длинных входных контекстов. Производительность, как правило, наилучшая, когда релевантные детали находятся в начале или в конце контекста. Однако она значительно падает, когда модели необходимо извлечь ключевую информацию из середины длинного ввода. Это ограничение сохраняется даже у моделей, специально спроектированных для работы с большим контекстом.

Доверчивость

Мы ранее охарактеризовали LLM как младшего исследователя: красноречивого, начитанного, но плохо осведомлённого в деталях. Существует ещё одно подходящее прилагательное: доверчивый. Наши ИИ-исследователи легко убеждаются сказать то, чего лучше было бы не говорить — раскрывая секреты или выдумывая ответы, чтобы казаться более знающими, чем они есть.

Как видно из вышеизложенного, каждое ограничение — это проблема, которая порождает соответствующий паттерн для её устранения.

✣ Гибридный извлекатель (Hybrid Retriever) ✣

Комбинируйте поиск по эмбеддингам с другими методами поиска

Хотя операции с векторами эмбеддингов текста — это мощная и продвинутая техника, многое можно сказать и в пользу простого поиска по ключевым словам. Такие методы, как TF/IDF и BM25, — это зрелые подходы к эффективному сопоставлению точных терминов. Мы можем использовать их для более быстрого и менее ресурсоёмкого поиска по большому набору документов, находя кандидатов, которых один лишь векторный поиск не выявил бы. Комбинирование этих кандидатов с результатами векторного поиска даёт более качественный итоговый набор документов.

Недостатком такого подхода может стать слишком большой объём документов, которые придётся отправить в LLM. Но это можно компенсировать с помощью переоценщика (reranker).

Когда мы используем гибридный извлекатель, необходимо доработать процесс индексации, чтобы подготовить данные к векторному поиску. Мы экспериментировали с разными размерами чанков и остановились на 1000 символах с перекрытием в 100 символов. Это позволило сфокусировать внимание LLM на наиболее релевантных частях контекста. Несмотря на то, что длина контекста в моделях увеличивается, современные исследования показывают, что точность ухудшается с ростом длины промпта.

Для эмбеддингов мы использовали модель text-embedding-3-large от OpenAI, обрабатывая чанки и сохраняя эмбеддинги в AWS OpenSearch.

Рассмотрим простой JSON-документ вида:

{    "Title": "title of the research",    "Description": "chunks of the document approx 1000 bytes"  }

Для обычного текстового поиска по ключевым словам достаточно просто вставить этот документ и создать текстовый индекс по title или description.

Однако для векторного поиска по description необходимо явно добавить дополнительное поле для хранения соответствующего эмбеддинга:

{    "Title": "title of the research",    "Description": "chunks of the document approx 1000 bytes",    "Description_Vec": [1.23, 1.924, ...] // вектор эмбеддинга, созданный моделью  }

С такой конфигурацией можно выполнять как текстовый поиск по title и description, так и векторный поиск по description_vec.

Когда использовать

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

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

Тем не менее, эмбеддинги всё же сыграли свою роль: мы использовали их вместе с LLM во время этапа загрузки (ингеста), чтобы сопоставить фрагменты документов с узлами графа.

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

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

✣ Переформулировка запроса (Query Rewriting) ✣

Используйте LLM для создания нескольких альтернативных формулировок запроса и выполните поиск по всем вариантам

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

Мы можем воспользоваться этим поведением, попросив LLM переформулировать запрос несколько раз и отправить каждый из этих вариантов в векторный поиск. Затем мы можем объединить результаты, чтобы включить их в промпт для LLM (часто с помощью переоценщика (reranker), о котором мы поговорим далее).

Пользователь может начать с такого запроса, чтобы исследовать десятки тысяч результатов исследований:

«Были ли зафиксированы в исследовании XYZ-1234 какие-либо из следующих клинических проявлений: эрекция волос (пилоэрекция), атаксия, частично закрытые глаза и жидкий стул?»

Переформулировщик (rewriter) отправляет это в LLM с задачей сгенерировать альтернативные формулировки. Вот несколько примеров:

«Можете ли вы предоставить сведения о клинических симптомах, указанных в исследовании XYZ-1234, включая наличие «гусиной кожи», нарушение координации, полуоткрытые веки или диарею?»

«В результатах эксперимента XYZ-1234, были ли зафиксированы наблюдения, такие как поднятие волосков, неустойчивое движение, не полностью открытые глаза или водянистый стул?»

«Какие клинические наблюдения были отмечены в ходе испытания XYZ-1234, особенно касающиеся наличия взъерошенной шерсти, нарушенного равновесия, полуопущенных век или мягких испражнений?»

Оптимальное количество альтернатив зависит от датасета:

Обычно 3–5 вариантов подходят для разнообразных наборов данных, тогда как более простые наборы могут потребовать до 3 переформулировок. По мере настройки переформулировок запроса рекомендуется использовать evals, чтобы отслеживать прогресс.

Когда использовать

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

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

В нашей задаче из области наук о жизни мы сочли полезным использовать GPT-4o для создания пяти вариантов переформулировки.


ссылка на оригинал статьи https://habr.com/ru/articles/900468/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *