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

Больше деталей о развии проекта, RAG и ИИ-агентах в @aigentto.

Иными словами, если на вход LLM дан один конкретный вопрос, то есть шанс, близкий к 100%, что будет получен качественный ответ. И наоборот, чем больше данных (вопросов, контекста и прочего) на вход LLM вы даёте, тем больше вы понижаете качество ответа.

Поэтому в каждом конкретном случае нужно найти баланс минимального количества данных на вход для RAG-системы. То есть нужно оптимизировать промт и контекст, получаемый из векторной БД для ответа на запрос пользователя.
При этом нужно определить качество ответов, то есть определить, как мерять качество в конкретной RAG-системе.

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

  1. Позитивный вопрос — это вопрос, ответ на который 100% есть в этом документе.

  2. Негативный вопрос — это вопрос, ответа на который нет в этом документе и нет в никаких других документах.

Идеальным и наиболее полным вариантом будет следующий подход:
Один позитивный и один негативный вопрос к каждому чанку, то есть к каждому минимальному куску документа, сохранённому в векторной БД.

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

Сложности здесь стоит ожидать только по негативным вопросам: объём документов, который нужно скормить LLM в этом случае, может быть очень велик (все документы, кроме текущего), а результат всё равно не будет 100%.

Поэтому можно просто просить LLM задать негативный вопрос по контексту текущего документа/чанка, игнорируя тот факт, что вопрос может оказаться позитивным для других документов/чанков. Из опыта наших экспериментов вероятность этого достаточно низкая, и те 3%–5% негативных вопросов, на которые случайно найдётся ответ в других документах, можно просто удалить из выборки вопросов, так как у вас их и так будет более чем достаточно для качественного тестирования.

Скоринг

Я обычно использую следующий скоринг для оценки текущего качества ответов RAG-системы.

+1 — был получен ответ на позитивный вопрос.
+0 — не был получен ответ на позитивный вопрос.
-1 — был получен ответ на негативный вопрос (то есть LLM врет).

Ответ на негативный вопрос значительно хуже, чем не ответ на позитивный, поэтому тут −1, а там 0. По факту это значит, что LLM уверенно соврала, и пользователь этого не поймёт.

Для прогона ответов не нужно задавать вопросы через адаптеры (Telegram, Slack, MS Teams, прочие), проще и быстрее прогнать это всё напрямую в схеме вопрос → RAG → LLM → ответ.

Качество работы RAG

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

Чанки и топ X векторов

На вход LLM мы будем отдавать не все документы, а только топ X (5–50) наиболее семантически схожих чанков. Поэтому метод чанкования очень сильно может повлиять на результат ответов.

Например, можно резать ваши документы на стандартные 1500 символов без перекрытия чанков, то есть все чанки будут уникальны и не будут учитывать реальный контекст внутри каждого чанка. Абзацы, предложения и даже слова будут обрезаться на середине.

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

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

Можно добавить перекрытие чанков, например, в 300 символов — тогда, несмотря на обрезанные абзацы, предложения и слова, само перекрытие увеличит вероятность попадания нужной информации в контекст LLM.

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

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

Промты

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

Ещё важный момент — это поиск в промте семантически схожих с контекстом конструкций, которые могут заставить LLM проигнорировать инструкции в промте и использовать контекст как инструкцию.

Такие моменты тоже можно искать автоматически и предлагать корректировку промта для семантического отдаления от контекста.

Параметры вызов LLM

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

Температура. Для RAG-систем обычно устанавливается значение 0, то есть чистая фактология без креативности. Но бывает, что хочется оживить ответы, при этом сохраняя фактологию, и тогда можно поиграть с температурой до 0.3–0.5. После каждого изменения рекомендуется прогонять тестовые вопросы.

Top-K. Количество выбираемых чанков из векторной БД. Идеальная ситуация — когда есть один конкретный чанк, который на 100% точно отвечает на запрос пользователя.
Например: «Какой телефон у Петрова Петра?» И чанк содержит: «телефон Петрова Петра +7-XXX-YYYY». Это вырожденный случай, но он хорошо иллюстрирует идеальный кейс и понимание того, к чему нужно стремиться: один вопрос = один чанк = один ответ.

В реальности приходится выбирать минимум топ 3–5 чанков, а иногда до 50, чтобы предоставить LLM достаточно контекста для ответа на заданный вопрос. Почему же не дать ей максимум, например 50? Дело в том, что это скорее ухудшит качество ответа: чем больше чанков, которые по определению будут семантически схожи с запросом пользователя и, соответственно, схожи между собой, тем выше вероятность, что LLM использует их все и сделает некоторый «мерж» ответа.

Пример из практики: пользователь спрашивает «Куда обратиться по нашей HR-системе». В документах есть контакты поддержки HR-системы и общая поддержка компании. При выборе 10 чанков в выборку попадают только данные об HR-системе, а при выборке 11 чанков и более появляются контакты общей поддержки. LLM, видя семантическое сходство, вставляет оба контакта в ответ.

Подбор оптимального количества чанков для выборки возможен по общему скорингу после автопрогона различных вариантов. Естественно, сама нарезка чанков будет взаимосвязана с этим параметром: 10 чанков по 300 символов или 10 чанков по 1500 — это разные 10 чанков.

Как найти оптимальное сочетаний Чанкования, Промтов, Параметров?

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

Например, задаём конфиг для перебора готовых наборов (zip) параметров:

 by_words: chunk_size and overlap in number of words
# by_paragraphs: chunk_size in chars, overlap in words
# by_sentences: chunk_size in chars, overlap in words
# raw: chunk_size and overlap in chars

sweep_mode: zip

fixed:
  embedding:
    model: text-embedding-3-small

sweep:
  chunking.strategy: [by_paragraphs, by_paragraphs]
  chunking.chunk_size: [1500, 1500]
  chunking.overlap: [50, 50]
  retrieval.top_k: [30, 20]
  prompt.system_prompt_path:
    - prompts/strong.txt
    - prompts/weak.txt
  clean_context_instructions: [false, false]
  dialog_history: [0, 10]

Или конфиг для перебора все vs все (grid) параметры:

sweep_mode: grid

fixed:
  embedding:
    model: text-embedding-3-small

sweep:
  chunking.strategy: [by_paragraphs]
  chunking.chunk_size: [1500]
  chunking.overlap: [50, 100]
  retrieval.top_k: [5, 3]
  prompt.system_prompt_path:
    - prompts/strong.txt
  clean_context_instructions: [false]
  dialog_history: [0, 5]

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

Выводы

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

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

Эти работы будут разделены на практический инструмент для использования в наших проектах по созданию RAG. И на НИОКР для поиска глобальных зависимостей.

Больше деталей о развии проекта, RAG и ИИ-агентах в @aigentto.

Комментарии (0)