Привет, чемпионы! Сегодня расскажу про очень интересную вещь, которая в определенных ситуаций, может быть выгоднее, чем RAG, а также свой опыт в проведении R&D данной технологии и с помощью какой надстройки можно усилить RAG, чтоб прийти к середине между CAG и RAG.

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

  • что такое CAG

  • в каких случаях он действительно применим

  • с какими подводными камнями столкнулись мы, а можете и вы при его использовании,

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

Начнем!

Что такое CAG и зачем он мне?

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

  1. Подготовка данных: тут мы выбираем наиболее важные документы, подготавливаем их и очищаем от лишнего. Важный момент: необходимость добавления данных в контекст по-прежнему остаётся с учётом ваших ограничений по контекстному окну

  2. Инициализация cache: после чего эти данные проходят через модель, формируя KV-кэш (ключ‑значение).

  3. Инференс: при поступлении запроса модель обращается к уже закэшированным данным, без запросов к внешним векторным базам, тем самым повышая скорость работы.

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

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

Данный концепт можно увидеть на изображении ниже

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

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

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

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

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

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

  1. Простота

  2. Скорость

  3. Точность (но это не точно)

Почему не точно? Об этом мы как раз и поговорим.

Что может пойти не так?

Подобную систему изготовили и мы, развернув на базе нашей модели CAG. После интеграции мы заметили следующие моменты:

Цена за токен: Хоть оплата у нас происходит и часами работы собственного железа, однако мы делаем инференс модели уже с заложенными в нее заранее данными. Тем самым при анализе возникает типичная ситуация. Ваш пользователь системы задает ей вопрос о том для кого белая каска, а для кого оранжевая...и вуаля! Вы получили первый инференс с лишними токенами. Так и в целом заполнение контекстного окна должно быть таким образом, чтоб ваш пользователь не получал ошибку от того, что его запрос переполнил контекст модели.

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

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

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

И самое главное - это качество: Если вам не критично повышение отклика до юзера на секунду или меньше, попробуйте self-RAG или классический RAG с реранкером (с которыми кстати не сравнивали оригинальную реализацию) и ответьте себе, действительно-ли вы почувствовали повышение качества?

И главное, прогоняйте ваши "знания" для вычисления кэша и оцените сколько вы оставите юзеру токенов для его запроса.

Какое решение возникло у нас в процессе сравнения

У нас были базовые вопросы, ответы на которые модель не знала, при этом были также и классические FAQ. Что же мы сделали? Надстройку над RAG пайплайном!

Мы добавили "холодный ретривер", который аналогично и классическому этапу retrieve в RAG искал сопоставления с базовыми вопросами, на которые были ответы. Тем самым при запросе пользователя, если мы находим ответ из списка, мы выдаем его без иницирования вызова LLM.

Пример простейшей надстройки ниже (модель FRIDA)

Скрытый текст
from sentence_transformers import SentenceTransformer, util
import numpy as np

embedder = SentenceTransformer("ai-forever/FRIDA")


qa_cache = {
    "Что такое Python?": "Python — это интерпретируемый язык программирования общего назначения.",
    "Как установить Python?": "Скачайте установщик с python.org и следуйте инструкциям.",
    "Что такое список в Python?": "Список — это изменяемая коллекция объектов, упорядоченная по индексу.",
}

cached_questions = list(qa_cache.keys())
cached_embeddings = embedder.encode(cached_questions, convert_to_tensor=True)
SIMILARITY_THRESHOLD = 0.85

def ask_question(question: str) -> str:
    global cached_embeddings
    print(f"\n Вопрос: {question}")
    
    q_emb = embedder.encode(question, convert_to_tensor=True)
    hits = util.semantic_search(q_emb, cached_embeddings, top_k=1)[0]
    best = hits[0]
    best_score, best_idx = best['score'], best['corpus_id']

    if best_score >= SIMILARITY_THRESHOLD:
        matched = cached_questions[best_idx]
        print(f" Похожий вопрос в кэше: '{matched}' (сходство {best_score:.2f})")
        return qa_cache[matched]
    else:
        print(f" В кэше нет подходящего (макс. схож. {best_score:.2f})")
        answer = init_pipline(question)
        qa_cache[question] = answer
        cached_questions.append(question)
        cached_embeddings = embedder.encode(cached_questions, convert_to_tensor=True)
        return answer

Что это дало? А дало повышение скорости на 24 процентов, при потере в качестве на 3 процента на наших тестах. Для сравнения интеграция CAG повысило нам скорость примерно на 60 процентов

К какому выводу мы пришли ?

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

? Ставьте лайк и пишите, какие темы разобрать дальше! Главное — пробуйте и экспериментируйте!


✔️ Присоединяйтесь к нашему Telegram-сообществу @datafeeling, где мы делимся новыми инструментами, кейсами, инсайтами и рассказываем, как всё это применимо к реальным задачам

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


  1. olegkusov
    16.07.2025 05:03

    В конце в итоге вы все равно перешли на RAG и это было ожидаемо. Забивать контекст лишней информацией это глупо и дорого. Ответы моделек от этого страдают как и стоимость запросов.


    1. ne_pridumal_nik
      16.07.2025 05:03

      Думаю, в некоторых сценариях это может быть обоснованно, например, тех. поддержка, помощник по какой-нибудь документации и т.д.