- BrainTools - https://www.braintools.ru -

Почему агент на самом деле дешевле RAG

Наивная арифметика, которая всех обманывает

Классический RAG-ассистент работает так: пользователь пишет вопрос → эмбеддинг → поиск в векторе → один вызов LLM для синтеза. Одна итерация. Чисто, быстро, дёшево. Если ответ правильный.

Проблема в том, что с первой попытки он правильный не всегда. Пользователь переспрашивает: «нет, я имел в виду другое». Ассистент делает второй запрос. Потом третий. К четвёртому переспросу пользователь машет рукой и зовёт живого оператора. Оператор тратит 10–15 минут, находит нужный документ, копирует абзац в чат. Вопрос закрыт. Стоимость вопроса — не 1 вызов LLM, а:

  • 4 вызова LLM на переспросы

  • 10–15 минут времени оператора

  • испорченное настроение пользователя

А теперь представим агента, который сам перебирает гипотезы внутри ReAct-цикла. Он не ждёт переспроса пользователя — он сам понимает, что первый результат неполный, и копает дальше. 4 итерации — и ответ готов. Вызовов LLM может быть больше, чем у ассистента. Но пользователь получил ответ с первой попытки, а оператор вообще не понадобился.

Главный тезис этой статьи: считать надо не стоимость одного ответа, а стоимость закрытого вопроса. И в этой метрике агент часто выигрывает.

Как устроен ReAct-цикл в ISTOK

Ядро агента — класс AgentEngine с методом execute. На входе — вопрос пользователя и системный промпт, на выходе — финальный ответ и лог всех действий.

Почему агент на самом деле дешевле RAG - 1

Пять инструментов агента

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

  1. VectorSearch — семантический поиск. Гибрид Dense (эмбеддинг) + BM25 через Milvus. Для концептуальных запросов: «как настроить», «проблемы с производительностью».

  2. Search — полнотекстовый поиск. ILIKE по PostgreSQL, поддержка регулярных выражений (~*). Для точных терминов: коды ошибок, идентификаторы, названия.

  3. OpenChunk — открыть полный текст найденного чанка. После VectorSearch или Search агент видит только summary; чтобы прочитать содержимое, нужен OpenChunk.

  4. GetDocumentChunks — оглавление документа. Позволяет агенту понять структуру, не открывая всё подряд.

  5. CallOperator — вызов живого оператора. Разрешён только после 2–3 собственных попыток поиска.

Агент сам решает, какой инструмент вызвать следующим. Жёсткого сценария нет: в одном вопросе он начнёт с VectorSearch, в другом — с Search по точному коду ошибки [1].

Честная история: как мы контекст держали (и теряли)

У агента есть проблема: каждый вызов LLM в цикле добавляет сообщения в историю. 15 итераций с результатами поиска — и контекстное окно забито. Что делать?

Версия 1: LLM-суммаризация

Первым решением была суммаризация: когда контекст подходил к лимиту, мы просили модель сжать историю в короткий итог. Логика [2] понятная: модель лучше всех знает, что в её же контексте важно, а что нет.

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

Версия 2: скользящее окно

Переписали на тупое, зато надёжное решение — скользящее окно:

DEFAULT_MAX_TOKENS = 120_000    # ~120K токенов — порог срабатывания
MIN_KEEP_MESSAGES = 6           # Минимум сообщений всегда сохраняется

При достижении порога просто удаляем старейшие tool-сообщения. Системный промпт и последние 6 сообщений неприкосновенны. Никаких дополнительных вызовов LLM. Грубо, но дёшево и без потери данных.

Суммаризация не выброшена полностью — она осталась как fallback, но с жёстким лимитом:

  • До 2 суммаризаций за сессию (после — переход на скользящее окно)

  • Каждая успешная суммаризация добавляет +5 к лимиту итераций

  • Промпт суммаризации требует сохранить: исходный вопрос дословно, ключевые находки, текущий лучший частичный ответ и ровно 2–3 шага плана

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

Не все вызовы LLM стоят одинаково

Ещё одно упрощение из наивной арифметики: считать, что каждый вызов LLM стоит одну и ту же цену. В ISTOK запросы к LLM-шлюзу разделены по типам:

class RequestType(str, Enum):
    EMBEDDING = "EMBEDDING"                 # Эмбеддинги — дёшево
    GENERATION_LIGHT = "GENERATION_LIGHT"   # Простые шаги цикла — дёшево
    GENERATION_MEDIUM = "GENERATION_MEDIUM" # Основной режим (по умолчанию)
    GENERATION_HEAVY = "GENERATION_HEAVY"   # Сложный синтез — дорого
    RERANK = "RERANK"                       # Реранжирование — дёшево

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

Правильная арифметика: стоимость закрытого вопроса

Давайте соберём всё вместе. Считаем не стоимость ответа, а стоимость вопроса, после которого пользователю больше не нужна помощь.

Вопрос пользователя — типичный для линии поддержки 1С-продуктов:

«После обновления на релиз 2.4 перестали создаваться обращения из писем. В журнале регистрации ошибка “Поле объекта не обнаружено (КаналСвязи)”. Настройки обмена не трогали. Что случилось?»

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

Сценарий A: ассистент (один запрос → синтез)

Попытка 1: ассистент находит общую инструкцию «Настройка загрузки
           писем» и отвечает: проверьте учётную запись, пароль
           и расписание регламентного задания            — 1 × LLM
Пользователь: «настройки не менялись, ошибка про поле КаналСвязи,
              появилась сразу после обновления»
Попытка 2: ассистент находит описание справочника «Каналы связи»
           и советует проверить его заполнение          — 1 × LLM
Пользователь: «справочник заполнен, ошибка именно при загрузке писем»
Попытка 3: ассистент добирается до заметок релиза 2.4 и наконец
           отвечает про переименование поля             — 1 × LLM
──────────────────────────────────────────────────────────────
Итого: 3 вызова LLM + 2 переспроса пользователя
Время пользователя до получения ответа: ~5–8 минут

Сценарий Б: агент (ReAct-цикл) — тот же вопрос, та же база знаний:

Итерация 1: VectorSearch по полному тексту вопроса → 5 чанков: общая
            инструкция по загрузке писем, справочник каналов и заметки
            релиза 2.4. Агент видит по summary: в заметках релиза,
            скорее всего, причина                       — 1 × LLM (GENERATION_MEDIUM)
Итерация 2: OpenChunk «Изменения в релизе 2.4» → находит: «поле
            КаналСвязи переименовано в КаналСвязиИсточника, требуется
            обновление соответствий в настройках обмена»
                                                      — 1 × LLM (GENERATION_MEDIUM)
Итерация 3: Search по точному термину «КаналСвязиИсточника» →
            инструкция «Миграция настроек обмена после обновления 2.4»
                                                      — 1 × LLM (GENERATION_MEDIUM)
Итерация 4: OpenChunk инструкции по миграции → три конкретных шага
            для обмена с почтой                         — 1 × LLM (GENERATION_MEDIUM)
Итерация 5: синтез финального ответа со ссылками      — 1 × LLM (GENERATION_HEAVY)
──────────────────────────────────────────────────────────────
Итого: 5 вызовов LLM (4 средних + 1 тяжёлый)

Ответ, который пользователь получил через ~40 секунд:

Причина найдена: в релизе 2.4 поле «КаналСвязи» переименовано в «КаналСвязиИсточника», поэтому настройки обмена, созданные до обновления, больше не совпадают со структурой данных [1]. Чтобы восстановить загрузку писем:

  1. откройте настройки обмена,

  2. запустите обработку «Обновление соответствий каналов связи»,

  3. перезапустите регламентное задание загрузки [2].

Источники: [1] «Изменения в релизе 2.4», [2] «Миграция настроек обмена после обновления 2.4».

Да, у агента на два вызова LLM больше. Но:

  • 4 из 5 вызовов идут как GENERATION_MEDIUM (дешевле тяжёлого синтеза)

  • Пользователь получил ответ с первой попытки — и не абстрактный «проверьте настройки», а причину, три шага решения и ссылки на источники

  • Ни один оператор не отвлёкся от своей работы

А теперь добавим сценарий, где ассистент не справляется вообще:

  • 4 переспроса × LLM → ответа нет

  • Эскалация на оператора → 15 минут рабочего времени → стоимость ~150₽ (при средней ставке 600₽/час)

  • Агент справляется за 6 итераций → меньше минуты → стоимость только LLM-токенов

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

Почему агент на самом деле дешевле RAG - 2

Контроль: что мы считаем и как

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

  • Каждый вызов LLM фиксируется вместе с usage из ответа провайдера: сколько токенов ушло в промпт, сколько вернулось в ответе. Ни один вызов не теряется — ни поисковый, ни синтез, ни суммаризация.

  • Итоги по сессии. По завершении ReAct-цикла агент складывает всё в четыре числа: prompt-токены, completion-токены, общая сумма и количество вызовов LLM. Умножаем на тариф модели — получаем себестоимость сессии с точностью до копейки.

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

Сверху всё это накрыто трейсингом: каждый ReAct-цикл уходит в Arize Phoenix, где можно разложить сессию по итерациям — каждый вызов инструмента, каждый запрос к LLM, итоговые токены. Именно оттуда взяты цифры из следующего раздела.

Результаты: цифры одного небольшого проекта

Хватит теории — покажу данные. Один из наших пилотных проектов: техподдержка небольшой продуктовой компании, база знаний около 1200 документов, порядка 1800 обращений в месяц. Проект удобен для сравнения тем, что первые две недели система проработала в режиме простого ассистента (один запрос → синтез), а следующие две — в режиме агента. Объёмы и профиль обращений в оба периода сопоставимы.

Метрика

Ассистент

Агент

Вызовов LLM на сессию

1

6,4

Переспросов пользователя до закрытия вопроса

2,3

0,3

Сессий с эскалацией на оператора

34%

11%

Среднее время до закрытия вопроса

27 минут

4 минуты

Токенов на закрытый вопрос

~18 000

~15 500

Наивная арифметика из начала статьи подтвердилась: агент действительно делает в 6 раз больше вызовов LLM. Но дальше цифры расходятся с интуицией [3]. Переспросы — главный скрытый множитель стоимости ассистента — упали в 7 раз: агент перебирает гипотезы сам, не дожидаясь уточнений пользователя. Эскалации упали втрое. А токены оказались почти одинаковыми: у ассистента каждый переспрос — это повторный вызов с раздутым контекстом, а у агента бо́льшая часть вызовов дешёвые.

Теперь переведём в деньги. Сами токены стоят копейки: закрытый вопрос ассистента обходится в ~0,9₽ токенов, агента — в ~0,5₽. Другое дело — оператор. Пятнадцать минут работы специалиста при средней ставке ~600₽/час — это 150₽ на каждую эскалацию. Раскладываем на все вопросы:

  • Ассистент: 0,34 × 150₽ + 0,9₽ ≈ 52₽ за закрытый вопрос

  • Агент: 0,11 × 150₽ + 0,5₽ ≈ 17₽ за закрытый вопрос

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

Почему агент на самом деле дешевле RAG - 3

Ограничения

Агент — не серебряная пуля, и у нашей арифметики есть границы. Честно про все три.

Простые фактологические вопросы. «Какой порт у сервиса X?», «Какая версия PostgreSQL поддерживается?» — на таких вопросах агент объективно проигрывает. Ассистент отвечает за один запрос и пару секунд. Агент сделает минимум две итерации — поиск и синтез — и потратит вдвое больше токенов за ровно тот же результат. Если вся ваша входящая линия состоит из справочных вопросов, ReAct-цикл вам не нужен: хватит простого ассистента. Экономия агента рождается из сложности — там, где ответ не лежит в одном чанке, а собирается из трёх документов и кода ошибки.

Пустая или нерелевантная база знаний. Никакой цикл рассуждений не компенсирует отсутствие данных. Если искать нечего, ассистент честно скажет «не знаю» за один вызов, а агент перед тем же ответом пройдёт 2–3 итерации вхолостую: переберёт VectorSearch, Search, попробует синонимы — и только потом признает поражение. Дороже и медленнее при том же исходе. Поэтому агент включается там, где база знаний уже наполнена — в нашем случае импортом истории обращений, но это тема отдельной статьи.

Неопределённые вопросы. «Расскажите что-нибудь про интеграции» — худший сценарий для агента. У него нет точки опоры: любой поиск что-то вернёт, любой результат можно «уточнить». Агент добросовестно исследует все направления, пока не упрётся в лимит итераций, и выдаст обзор, который пользователю, скорее всего, не нужен. Хорошая новость: на реальной линии поддержки таких вопросов немного — люди приходят с конкретной болью [4], а не за лекцией.

Заключение

Всё, что вы видели в разделе «Результаты», — один небольшой проект, один месяц, одна база знаний. Мы сознательно не экстраполируем эти данные на «среднюю инсталляцию»: профиль обращений, качество базы знаний и доля сложных вопросов у каждого заказчика свои. Разрыв в три раза — наш опыт [5]. По мере накопления статистики с других проектов картина может сместиться — тогда обновим статью.

Автор: Semen-Chernov

Источник [6]


Сайт-источник BrainTools: https://www.braintools.ru

Путь до страницы источника: https://www.braintools.ru/article/33775

URLs in this post:

[1] ошибки: http://www.braintools.ru/article/4192

[2] Логика: http://www.braintools.ru/article/7640

[3] интуицией: http://www.braintools.ru/article/6929

[4] болью: http://www.braintools.ru/article/9901

[5] опыт: http://www.braintools.ru/article/6952

[6] Источник: https://habr.com/ru/articles/1064958/?utm_source=habrahabr&utm_medium=rss&utm_campaign=1064958

www.BrainTools.ru

Rambler's Top100