Large Language Models
December 12, 2024

Обзор техник RAG: Retrieval Augmented Generation

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

Этим блогом можно пользоваться как шпаргалкой для проектирования своего RAG-а и/или для подготовки к собеседованиям.

Все полезные ссылки и материалы, на которые я опирался будут в конце.

Что такое RAG и зачем нужен

RAG - это фреймворк взаимодействия предобученной LLM с базой знаний. То есть при ответе LLM на запрос пользователя модель отвечает используя актуальный контекст из базы и свои pre-trained знания.

RAG framework

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

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

Базовый пайплайн подготовки системы RAG:

  • Загрузить документы
  • Нарезать на куски
  • Построить базу данных
  • Подготовить ретривер и, возможно, эмбеддер
  • Развернуть LLM для инференса

Базовый пайплайн применения RAG:

  • Аутентифицировать пользователя
  • Обработать входной запрос
  • Найти релеватные куски из базы данных
  • Отранжировать контексты
  • Собрать промпт из запроса и контекстов
  • Запромптить LLM
  • Получить от LLM ответ на вопрос
  • Верифицировать и отдать пользователю
Базовый пайплайн использования RAG системы

Метрики RAG-а:

  • Приверженность - то, как сильно ответ похож на контекст, поданный в модель. Чем ниже метрика, тем выше вероятность галлюцинаций.
  • Полнота - то, насколько полный ответ для заданного вопроса и предоставленного контекста.
  • Учёт контекстов - то, какая доля контекстов использовалась при ответе.
  • Утилизация контекстов - то, какую долю контекста модель использовала при ответе на вопрос.
  • токсичность ответа
  • тональность ответа - например, по категориям эмоций

https://www.galileo.ai/blog/mastering-rag-improve-performance-with-4-powerful-metrics - больше про метрики можно почитать тут

Загрузка документов

Это первый этап построения RAG системы.

Еще одна схема, как выглядит RAG, если забыли

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

Если в документе есть таблицы, или картинки, то из них можно извлечь полезную информацию в том числе - воспользоваться OCR и / или TableTransformer.

Библиотека, которая сама все делает за вас: https://unstructured-io.github.io/unstructured/introduction.html

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

Нарезка документов

Mastering RAG: Advanced Chunking Techniques for LLM Applications

Урок по разделению с помощью LangChain

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

На что влияет нарезка на куски:

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

Факторы, влияющие на разделения документа на куски:

  • Структура текста
    пунктуация, переносы строки, маркдаун верстка и др.
  • Контекстное окно LLM и эмбеддера
  • Сложность и специфика запросов

Параметра функции разделения документа на куски:

  • размер куска по символам или по токенам
  • размер пересечения (наложения) кусков
  • желаемый разделитель
Cunking techniques

Виды нарезок:

  • По символам / токенам без специфичного разделителя
    Разделяем документ согласно длины контекста и размере пересечения.
    Работает быстро, но глупо.
  • По символам / токенам с желаемыми разделителями
    Пытаемся разделять на куски, например, по переносу строки, по точке или хотя бы по пробелу. Но все равно есть ограничение на длину контекста и длину наложения.
    Чуть дольше, но намного умнее, потому что не обрывает слова или даже предложения.
  • По Маркдаун разметке
    Примерно как предыдущий метод, только разделитель заголовки маркдауна
    Получается более структурированное разделение, а также обновляет метадату кусков - добавляет поле названия.
  • Семантически
    Разделяем текст на предложения.
    Добавляем в кусок текста новое предложение, если оно похоже семантически на уже имеющийся кусок.
    Ограничиваем количество предложений в куске.
    Разделение довольно умное, но расчет более ресурсоемкий из-за модели схожести.
  • Переписываем текст как утверждение
    С помощью специальной модели или через LLM переписываем исходные предложения так, чтобы каждой из них по отдельности имело смысл, было понятно, о чем идет речь и не могло больше разделится на более мелкое утверждение.
    Потенциально очень умное разделение, которое сразу же помогает отвечать на вопрос. Но есть вероятность испортить хороший текст, а также требует дополнительных ресурсов на обработку документов.
Proposition chuncking
  • Мульти-векторная индексация
    Иногда полезно для каждого документа иметь несколько векторов представления.
    Например, поделить документ на куски и для каждого посчитать векторы, делать ретрив по векторам кусков, а возвращать сам документ, таким образом уменьшается влияние шума и разнообразие топиков в документе.
    Также можно заменить или добавить к вектору документа вектор представление суммаризации этого документа.
    Также частая практика это придумывать гипотетические вопросы, ответом на которые может быть документ, и складывать в индекс вектор представления этих вопросов.
    https://python.langchain.com/docs/how_to/multi_vector/

Кстати, метадата документа должна наследоваться каждому его куску, а также иногда дополняться свойствами отдельного куска, например, заголовок маркдаун при соответствующем методе разделения.

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

  • Приверженность - то, как сильно ответ похож на контекст, поданный в модель. Чем ниже метрика, тем выше вероятность галлюцинаций.
  • Полнота - то, насколько полный ответ для заданного вопроса и предоставленного контекста.
  • Учёт контекстов - то, какая доля контекстов использовалась при ответе.
  • Утилизация контекстов - то, какую долю контекста модель использовала при ответе на вопрос.

https://www.galileo.ai/blog/mastering-rag-improve-performance-with-4-powerful-metrics - больше про метрики можно почитать тут

Как подбирать параметры для нарезки на куски:

RAG metrics ops

База данных

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

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

Ключевые факторы выбора базы данных:

  • Открытая ли база или закрытая
  • Язык программирования, на котором можно создать клиента для использования базы данных
  • Лицензия
  • Фичи для организаций:
    • лимиты
    • пользовательская аутентификация
    • многое другое
  • Продуктовые фичи:
    • точный поиск
    • приближенный поиск - когда можно пожертвовать качеством для ускорения и масштабирования
    • префильтрация - когда до векторного поиска нужно уменьшить количество кандидатов
    • постфильтрация - дополнительный фильтр для улучшения точности результатов
    • гибридный поиск
    • поддержка разреженных векторов
    • поддержка поиска напрямую по тексту, например, через bm25
  • Возможность инференса моделей эмбеддингов.
    Например, sentence transformers, Mixedbread, BGE, OpenAI.
  • Возможность инференса модели реранкера
  • Скорость добавление новых объектов
  • Скорость поиска
    • индексация
    • кэширование
    • другие оптимизации
  • Затраты на обслуживание
    • disk-based базы данных VS in-memory
    • Serverless базы данных
    • квантизация эмбеддингов
  • Поддержка, мониторинг, бэкапы и тд

Выбор эмбеддера

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

Что такое эмбеддинги

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

  • кодировать вопросы и контексты эмбеддером, чтобы для вопроса находить самые релевантные куски информации
  • находить few-shot примеры для in context learning (ICL)
  • определять намерения пользователя, чтобы, например провести по какой-нибудь ветке заготовленного сценария общения, или вызвать какой-нибудь инструмент

На что смотреть при выборе эмбеддера:

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

Виды эмбеддингов:

  • dense векторы
    классика
  • разряженные
    Извлекают из текста только самую релевантную информацию, а в других размерностях значения просто 0.
    Часто используется в задачах со специфической терминологией.
    Работает примерно как bag-of-words, но обходит многие его недостатки.
    https://arxiv.org/abs/2109.10086
  • матрешка эмбеддинги
    Позволяют выбирать размерность вектора на инференсе.
    можно почитать тут лонгрид про них: https://teletype.in/@abletobetable/embeds_ops
Матрешка эмбеддинги
  • long-context эмбеддинги
    Если мы можем эффективно и без потери качества кодировать более длинные куски текста, то будем уменьшать задержку при поиске и косты на хранение векторов, так как их будет меньше.
  • code эмбеддинги
    специально натренированные модели для работы с кодом

Примеры как, на каких задачах измерить качество эмбеддингов:

Embedding benchmark

Метрики рага, которые подходят и для метрик эмбеддера:

  • Приверженность - то, как сильно ответ похож на контекст, поданный в модель. Чем ниже метрика, тем выше вероятность галлюцинаций.
  • Полнота - то, насколько полный ответ для заданного вопроса и предоставленного контекста.
  • Учёт контекстов - то, какая доля контекстов использовалась при ответе.
  • Утилизация контекстов - то, какую долю контекста модель использовала при ответе на вопрос.

https://www.galileo.ai/blog/mastering-rag-improve-performance-with-4-powerful-metrics - больше про метрики можно почитать тут

Извлечение, поиск

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

Retrieve scheme

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

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

Техники для улучшения извлечения:

  • Hypothetical document embeddings (HyDE) https://arxiv.org/abs/2212.10496
    На вопрос пользователя генерируем с помощью LLM такой текст, в котором гипотетически мог бы содержаться ответ на вопрос.
    Такой документ будет недостоверным в большинстве случаев, но зато текстовый энкодер сможет построить очень близкий эмбеддинг для реального контекста.
  • Maximal Marginal Relevance (MMR)
    Техника для увеличения разнообразия в найденном множестве контекстов. То есть мы скорее отдадим предпочтение менее релевантному контексту, но еще незнакомому.
MRR scheme
Recursive retriever scheme
  • SelfQuery
    Техника для таких вопросов, где полезно будет сделать фильтрацию по какому-нибудь атрибуту, например, по дате.
Self query scheme
  • Сжатие контекстов
    После извлечения самый релевантных контекстов, мы их суммаризируем при условии вопроса юзера с помощью LLM. Таким образом финальная LLM получает на вход более плотную информацию, с минимумом  шума, но, скорее всего, довольно полную. Хотя тут мы делаем дополнительные вызовы суммаризатора.
Compresion scheme
  • Классический методы поиска
    • svm
    • tf-idf
    • и другие

Работают напрямую с текстом.

https://learn.deeplearning.ai/courses/langchain-chat-with-your-data/lesson/5/retrieval - урок по извлечению от LangChain

Question Answering / generation

На этапе генерации мы отдаем релевантные контексты вместе с вопросом пользователя в LLM, а от нее уже получаем ответ. Это центральный элемент RAG системы, поэтому тут также нужно аккуратно рассмотреть следующие пункты:

  • Выбор LLM
    При выборе LLM стоит опираться на то, на каких данных и задачах была обучена модель, с какими языками она хорошо работает. Желательно проверить несколько моделей самостоятельно перед выкаткой, также можно опираться на результаты бенчмарков.
  • Open-source или проприетарная модель
    С одной стороны использование закрытых апи упрощает разработку системы, но с другой стороны возникает вопрос конфиденциальности данных, а также зависимости от внешней апи.
  • Размер модели
    При большем размере растет качество, но растет задержка и падает пропускная способность.
  • Параметры генерации
    Такие параметры как температура, top p, top k, могу сильно влиять на ответы моделей, их креативность и разнообразие.
  • Способ инференса
    Этот вопрос может отпасть, если мы будем использовать закрытые апи, но в случае с моделями, которые мы сами хотим разворачивать и поддерживать, этот вопрос является очень существенным.
    Так как разные фреймворки инференса поддерживают разные модели, способы оптимизации и ускорения инференса.
    Основные решения: tensorrt-llm, vllm, tgi, deepspeed-mii.

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

Prompt engineering techniques
  • Few-shot prompting
    Показываем несколько примеров, как могут выглядеть ответы.
    Можно улучшить выбор этих нескольких примеров через поиск ближайших соседей.
  • Chain-Of-Thoughts
    Заставлять LLM генерировать цепочку мыслей и только после размышления давать финальный ответ: “Take a deep breath and let’s think step by step”.
  • Map reduce
    делаем суммаризацию каждого контекста, и только потом по всем суммаризациям генерируем ответ на вопрос.
  • Map refine
    Начинаем с 1го контекста, отвечаем на вопрос по нему, затем обновляем ответ с учетом 2го контекста и так далее.
  • Thread of Thought
    Разбиваем длинные куски текста на контексты, модель извлекает из них релевантную информацию, затем просим модель суммаризировать и проанализировать информацию, а не просто прочитать и понять.
ToT scheme
  • Chain of Note
    Примерно то же самое, что и ToT, но здесь для каждого извлеченного куска текста генерируем суммаризацию и оцениваем его релевантность касательно вопроса. И уже на основании таких заметок отвечаем.
CoN
  • Chain of Verification(CoVe)
    На запрос генеририруем бейзлайн, далее подбираем проверочные вопросы, отвечаем на них и редактируем ответ.
CoVe
  • Эмоциональное давление
    Удивительно, но работает все: представь, что ты эксперт, я дам тебе 200 долларов за правильный ответ и др.)
Emotion prompt

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

Chat, user experience

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

Это можно сделать, сохраняя предыдущие вопросы и ответы на них.

  • Можно переписывать вопрос с учетом истории, чтобы поиск релевантных контекстов работал корректно.
  • Можно вместе с вопросом и релевантным контекстом также предоставить доступ LLM к истории чата.
  • Если чат разрастается, его можно суммаризировать: либо просто извлечь самое главное, либо суммаризировать при условии текущего вопроса, чтобы точно не потерять ничего важного из истории.

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

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

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

А также использовать техники оптимизации инференса как эмбеддинг модели (например, вот лонгрид: https://teletype.in/@abletobetable/embeds_ops ), так и LLM (начиная от continuous batching до speculative decoding).

Заключение

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

RAG ecosystem summary
Улучшение RAG
Добавление доп знаний VS Дополнительное дообучение

Полезные ссылки

Мои другие лонгриды:

https://teletype.in/@abletobetable

A Survey on Retrieval-Augmented Generation for Large Language Models:

https://arxiv.org/abs/2312.10997

HF blogposts:

RAG vs Fine-Tuning for LLMs: A Comprehensive Guide with Examples

Better RAG 1: Advanced Basics

Better RAG 2: Single-shot is not good enough

Better RAG 3: The text is your friend

Mastering RAG series:

How To Architect An Enterprise RAG System

RAG Vs Fine-Tuning Vs Both: A Guide For Optimizing LLM Performance

Advanced Chunking Techniques for LLM Applications

Improve RAG Performance With 4 Powerful RAG Metrics

LLM Prompting Techniques For Reducing Hallucinations

How To Evaluate LLMs For RAG

How to Select A Reranking Model

How to Select an Embedding Model

Choosing the Perfect Vector Database

Generate Synthetic Data for RAG in Just $10

Mastering RAG: 8 Scenarios To Evaluate Before Going To Production

DeepLearning.AI courses:

LangChain: Chat with Your Data