Продвинутые RAG-паттерны в n8n

Продвинутые RAG-паттерны в n8n

Я собрал свой первый RAG-бот по всем канонам. Взял Google Sheets с базой знаний в формате «Вопрос-Ответ», прикрутил Supabase для векторного хранения, а в центре поставил n8n, чтобы связать все это воедино. Запустил.

Первые два ответа — успех. Бот мгновенно находил нужную информацию и давал четкие, релевантные ответы. Я уже предвкушал, как эта штука закроет 80% рутинных вопросов для вымышленного отдела технической поддержки. А на третьем вопросе, чуть более сложном, система поплыла... Он взял кусок из одного документа, кусок из другого и синтезировал уверенный, но абсолютно неверный ответ.

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

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

Почему «простой» RAG не работает

Базовая механика RAG (Retrieval-Augmented Generation) проста и состоит из четырех шагов:

  1. Загрузка: Вы берете свои документы (статьи, тикеты, FAQ) и нарезаете их на небольшие куски (чанки).
  2. Векторизация: Специальная модель (embeddings model) превращает каждый чанк в набор цифр — вектор, который отражает его семантический смысл. Все это складывается в векторную базу данных.
  3. Поиск: Когда приходит вопрос пользователя, он тоже превращается в вектор. Система ищет в базе данных наиболее близкие по смыслу векторы чанков. Это как Ctrl+F, но по смыслу, а не по буквам.
  4. Ответ: Найденные чанки (контекст) и исходный вопрос пользователя отправляются в большую языковую модель (LLM), которая на их основе генерирует финальный ответ.

В нашем workflow из n8n это блок «Simple RAG». Цепочка выглядит так: Chat Input → Embeddings → Search in Supabase → Basic LLM Chain.

Продвинутые RAG-паттерны в n8n

У этого подхода есть три врожденных дефекта:

  • Семантический разрыв. Пользователь спрашивает: «как починить оплату?». В базе знаний у вас статья называется «Устранение проблем с транзакциями». Для человека это синонимы, но для векторного поиска — это могут быть достаточно далекие друг от друга векторы, и релевантный документ просто не найдется.
  • Сложные вопросы. Запрос «Какие есть тарифы и как перейти на новый?» по сути содержит два вопроса. Простой поиск, скорее всего, найдет информацию либо про тарифы, либо про процесс перехода, но не про то и другое вместе. Ответ будет неполным.
  • Нерелевантный контекст. Иногда поиск находит документ, который лишь косвенно касается темы. Но LLM сказали генерировать ответ на основе этого контекста — он сгенерирует. Получается «вода» или откровенная чушь.

Чтобы это исправить, нам нужно прокачать нашего бота, добавив ему несколько новых «модулей» в его пайплайн. Если вы ещё не успели создать векторную базу в Supabase, можете воспользоваться готовым SQL шаблоном (у n8n есть особые требования к формату векторов внутри базы Supabase).

Четыре апгрейда для нашего RAG-пайплайна в n8n

Я разберу четыре паттерна, которые превращают нашего «ленивого» бота в толкового ассистента.

Query Transformation

Проблема: Семантический разрыв, о котором мы говорили выше. Пользователи формулируют мысли по-разному, используя сленг, синонимы или просто неточные выражения.

Решение: Вместо того чтобы сразу отправлять «сырой» запрос пользователя на поиск, мы сначала отдаем его LLM с простой задачей: переформулируй. Мы заставляем LLM поработать «переводчиком» с человеческого на язык, более понятный для векторного поиска. Новый модуль преобразует нечеткий запрос в несколько канонических, точных формулировок.

Продвинутые RAG-паттерны в n8n

Цепочка немного меняется: User Input → LLM Chain (Query Transformation) → Search in Supabase.

Prompt для первой LLM-ноды: «Вы искусственный интеллект, задача которого переформулировать запросы пользователей для улучшения поиска... Перефразируйте его, чтобы он был более точным, подробным...». Так, запрос «оплата не пашет» превратится в «как исправить ошибку при проведении платежа», что с гораздо большей вероятностью найдет нужный документ.

Query Decomposition

Проблема: Пользователь задает не один вопрос, а несколько. Например: «Как мне обновить подписку, какие есть способы оплаты и где найти чек после этого?»

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

Продвинутые RAG-паттерны в n8n

Это блок «Query Decomposition (RAG)». Пайплайн здесь становится заметно сложнее: LLM Chain (Query Decomposition) → Split Out → Loop → Aggregate → Basic LLM Chain.

  1. Query Decomposition: Первая LLM-нода получает промпт: «Твоя задача — разбить сложный вопрос на несколько простых... Верни результат в виде строгого JSON-массива строк». На выходе мы получаем массив ["Как обновить подписку?", "Какие есть способы оплаты?", "Где найти чек после оплаты?"].
  2. Split Out и Loop: Мы разбираем этот массив на отдельные элементы и в цикле для каждого под-вопроса выполняем векторный поиск в Supabase.
  3. Aggregate: После цикла мы собираем все найденные чанки из разных документов в один большой кусок контекста.
  4. Basic LLM Chain: И только теперь, собрав всю релевантную информацию по всем частям исходного вопроса, мы отправляем ее в финальную LLM для генерации связного ответа.

Score Filter

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

Решение: Ввести простой, но эффективный контроль качества. Каждый документ, который возвращает векторный поиск, имеет score — числовой показатель его релевантности запросу. Мы можем просто установить порог и отсекать все, что не дотягивает до нужного уровня уверенности.

Продвинутые RAG-паттерны в n8n

Это самый простой в реализации, но один из самых важных апгрейдов. Смотрим на блок «... + Score Filter». Сразу после ноды Search in Supabase мы добавляем ноду Filter. В ее настройках мы прописываем простое условие: пропускать дальше только те документы, у которых {{ $json.score }} больше, чем 0.5 (или 0.7 — это значение нужно подбирать экспериментально под вашу базу).

Если после фильтра не осталось ни одного документа, мы можем сразу выдать ответ «Извините, я не нашел информации по вашему вопросу», а не отправлять пустой контекст в LLM.

Предварительный генератор контекста

Проблема: Иногда сам по себе чанк текста слишком узкий и вырван из контекста. Например, кусок текста: «Для этого необходимо перейти в раздел «Профиль» и нажать кнопку «Сменить тариф»». Поиск по запросу «Как оплатить подписку?» может не найти этот чанк, потому что в нем нет слов «оплата» или «подписка».

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

Продвинутые RAG-паттерны в n8n

Для этого используется отдельная ветка workflow «Supabase Vector Creates + Context Generator».Get Data → LLM Chain (Context) → Supabase Vector Store.

  1. Get Data: Берем очередную строку из нашей Google-таблицы.
  2. LLM Chain (Context): Отправляем ее в LLM с промптом вроде: «Дай краткий, лаконичный контекст для этого фрагмента, чтобы улучшить его поиск. Отвечай только контекстом и ничем больше».
  3. Supabase: Полученный контекст мы добавляем в метаданные документа и только после этого векторизуем и сохраняем в Supabase.Это разовая операция, которая выполняется только при наполнении базы знаний, но она значительно повышает шансы найти нужный документ в будущем.

Подводные камни

Обычно такие подходы дают +10-30% к качеству (цифры на основе моего датасета). Звучит здорово, но за все приходится платить. Эти апгрейды – не бесплатные.

  • Цена и скорость. Каждый дополнительный вызов LLM (для трансформации, декомпозиции, генерации контекста) – это дополнительные центы и секунды задержки. Бот становится умнее, но медленнее и дороже. Простой RAG отвечает за 2-3 секунды. RAG с декомпозицией сложного вопроса может думать и 10, и 15 секунд. Это нужно учитывать.
  • Сложность и хрупкость. Наш красивый линейный workflow превратился в сложный, ветвящийся граф. Появляются новые точки отказа. Например, если в шаге декомпозиции LLM решит добавить к JSON-массиву вежливое «Вот ваш список вопросов:», нода парсинга сломается, и вся цепочка остановится. Тут требуется уже больше отладки и мониторинга.
  • Настройка. Нет никаких «магических» значений. Порог отсечки score, промпты для трансформации, модели для разных задач — все это требует экспериментов и подгонки под вашу конкретную базу знаний и типичные вопросы пользователей. Благо не так давно в n8n появилась новая фича – Evaluation, позволяет легко тестировать ваши AI-цепочки и понимать как изменяется качество ответов. Мои мысли и советы по этой фиче.

Как итог

Создание по-настояшему умного RAG-ассистента – это не про выбор самой «умной» LLM на рынке. Это про построение правильного пайплайна обработки запроса. Простой RAG – подходит для простых и примитивных систем. Если хотите что-то более сложное – нужны доработки и дополнительные модули. Именно они превращают «примитивный» поисковик в ассистента, который действительно пытается понять, что от него хотят.

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

4 комментария