Агентный RAG, бенчмарк TRuST и при чём здесь Нафаня?

от автора

Всем привет! Меня зовут Николай.

Сначала отвечу на вопрос из заголовка. Нафаня — это агентный RAG-помощник по нормативно-технической документации, связанной со строительством и ценообразованием (методики Минстроя, ГОСТы, СНиПы, СП и профильные законы).

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

Железо, ограничения и почему архитектура именно такая

В моём распоряжении домашняя рабочая станция с двумя RTX 4070 по 16 ГБ и одной RTX 5060 на 16 ГБ, а также внешние API-сервисы моделей (в основном Qwen и DeepSeek).

Распределение задач на локальном железе:

  • На 5060 крутятся эмбеддеры.

  • На одной 4070 — 4B LLM (оценка релевантности чанков).

  • На второй 4070 — 4B VLM (разбор формул, картинок, реже страниц документов).

Из этого выросла архитектура Нафани:

  • Общая языковая модель через внешний API отвечает за планирование, генерацию и критику.

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

  • Агентная обвязка связывает эти роли строгими бюджетами и проверками полноты охвата.

  • Есть возможность встраивать небольшие отраслевые модели в узкие задачи RAG.

Кто такой Нафаня и каким он был «до бенчмарка»

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

Что умеет продукт:

  • Загрузка и разбор PDF, DOC, DOCX, TXT, HTML, включая тяжёлые нормативы с таблицами, списками и формулами.

  • Мультимодальная обработка: текст, таблицы, формулы, картинки.

  • Стратегии поиска: векторный, по предложениям, семантический, гибридный + специализированные доменные виды поисков.

  • Расширение контекста соседними фрагментами документа.

  • Быстрый режим «вопрос → ответ» и режим глубокого исследования.

  • Полная проверяемость: в ответе видны источники (документы, чанки) и оценки релевантности.

  • Общение с пользователем в режиме диалога.

Домен сложный: сметные нормы, расценки, таблицы с объединёнными ячейками, примечания «между строк», много pdf со старыми еще советскими сканами. Местами сдается даже продвинутый docling. Поэтому у Нафани с самого начала был сложный пайплайн разбора документов и итерационный агентный контур.

Исходная архитектура глубокого исследования

Исходная архитектура: Planner - Retriever - Answer - Critic

Исходная архитектура: Planner — Retriever — Answer — Critic
  • Planner задаёт первоначальные направления поиска, при необходимости вызывая узких доменных агентов.

  • Retriever ищет по базе и компонует контекст.

  • Оценщик оценивает релевантность найденных фрагментов.

  • Генератор готовит итоговый ответ.

  • Critic оценивает ответ, запускает перегенерацию или, при необходимости, новые направления поиска (используя около 10 специализированных инструментов).

Критик был ключевым игроком. На моих внутренних доменных примерах он часто спасал ситуацию и выглядел «взрослым» звеном. Система казалась готовой: мультиагентность есть, циклы есть, источники есть. На своих кейсах всё выглядело более чем убедительно. Но самоуверенность была избыточной.

Конференция: T-Search и бенчмарк TRuST

На конференции Т-Банка я услышал доклад про агент-ретривер T-Search и бенчмарк TRuST.

T-Search — это открытый агент-ретривер на базе Qwen3.6-35B-A3B, обученный полностью на синтетических данных. В отличие от классического RAG, который делает один запрос и выдаёт ответ, T-Search работает итеративно: он планирует и выполняет поиск в несколько раундов, накапливает подтверждающие фрагменты (evidence) и останавливается, когда считает набор достаточным. T-Search не генерирует финальный ответ, а отдаёт ранжированный список чанков, который можно передать любой другой модели для генерации.

TRuST (T-Tech Russian Search Test) — бенчмарк для оценки поисковых способностей моделей на русском языке, созданный по методологии BrowseComp-Plus. Он содержит 324 сложных вопроса, собранных вручную, с короткими однозначными ответами. Эти вопросы нельзя решить за счёт знаний модели или одного поверхностного запроса: нужно пройти поисковую цепочку, связать несколько источников и отсеять правдоподобные, но неверные документы.

Подробности — в статье Т-Банка: T-Search: как мы обучали агента многошаговому поиску. Датасет: t-tech/TRuST на Hugging Face.

Спойлер: Особенности TRuST
  • Фиксированный индекс (~23 тыс. документов) — агент ищет не в живом интернете, а по заранее собранному корпусу, что делает оценку воспроизводимой.

  • 8 тематик: бизнес и технологии, спорт, новости и политика, наука, медиа и искусство, регулирование и право, география, история и архивы.

  • 5 типов поисковой сложности:

    1. Multihop — связывание фактов из нескольких источников;

    2. Structured Evidence — ответ в таблицах, списках, PDF;

    3. emporal Tracking — учёт изменений во времени;

    4. Entity Disambiguation — различение похожих сущностей;

    5. Comparative Reasoning — сравнение нескольких объектов.

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

Как я адаптировал бенчмарк под свой RAG

TRuST предполагает классическую постановку задачи retriever’а: агент работает с готовым фиксированным набором документов, выполняет многошаговый поиск и в конце отдаёт ранжированный список чанков как результат своей работы.

Нафаня же живёт по другим правилам: у меня свой конвейер загрузки и нарезки документов, своя система оценки найденного, а главное — обязательная генерация финального ответа пользователю со ссылками на источники. Бенчмарк оценивает качество поиска, а мне нужно, чтобы система ещё и правильно ответила на вопрос.

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

В исходном бенчмарке

У меня

Готовый фиксированный индекс

Документы прогнаны через мой конвейер загрузки и индексации

Оценка по ID эталонных чанков

Мои границы чанков не совпадают. Добавил текстовые метрики покрытия по документам

Агент без финального ответа

Прогоны с полной генерацией ответа

Recall@10

Recall All

Фокус только на поиске

Добавил LLM-судью по итоговому тексту ответа

Примечание по метрикам №1 (Recall@10 vs Recall All + Answer Quality):

В публикации T-Search основная метрика — Recall@10: доля эталонных документов, чанки которых агент-ретривер поместил в топ-10 своей финальной ранжированной выдачи. Это метрика поиска, а не готового продукта.

Абсолютное значение этой метрики не однозначно: в TRuST много примеров, когда для корректного ответа достаточно только подмножества чанков эталонных документов, а не все 100%. Есть случаи, когда правильный ответ может быть сгенерирован на основе совсем других не эталонных документов/чанков. «Найти всё и по шаблону» и «найти достаточно для ответа» — это разные задачи.

Кроме того, Нафаня не ранжирует чанки вокруг одного исходного запроса так, как это делает чистый retriever. Контекст собирается из нескольких подзапросов, циклов, фильтров, слияний и дедупликаций. Подстроить RAG под Recall@10 означало бы сломать архитектуру ради сторонней метрики, а не ради конечной цели. Поэтому вместо Recall@10 я смотрю на две метрики, которые действительно важны для моего продукта:

  1. Recall All (RA) — доля эталонных документов, попавших в финальный контекст генератора. Если нужный факт оказался в контексте и помог сформировать ответ — для меня это победа, даже если он не в топ-10.

  2. Корректность ответа (Судья) — локальная модель сравнивает сгенерированный ответ с эталоном и выдает вердикт «да/нет». Это проверка достаточности собранного контекста. Высокое значение RA (совпадение по документу) не гарантирует полноту фактов: например, таблица, попавшая в контекст, может быть обрезана. В итоге RA может быть высоким, а ответ — неверным.

Примечание по метрикам №2 (Raw vs Reportable):

При работе с внешними API (например, Qwen через Dashscope) я столкнулся с работой встроенных фильтров модерации контента. В ~10% случаев API принудительно блокирует запрос (преимущественно контекст с советской тематикой), возвращая ошибку DataInspectionFailed с сообщением Input text data may contain inappropriate content. Как результат, моя система вынуждена перехватывать этот сбой и возвращать шаблонный отказ («не удалось найти информацию») с пустым контекстом. Поэтому чтобы оценка была объективной, в таблицах я привожу два варианта метрик:

  • Raw (сырые): учитывают все вопросы, включая эти инфраструктурные блокировки. Показывают реальную картину со всеми сбоями API.

  • Reportable (отчетные): считаются только по тем вопросам, где API отработал запросы без срабатываний модерации. Это более точный показатель качества именно алгоритмов RAG. (Для DeepSeek таких блокировок не возникало, поэтому его метрики Raw и Reportable полностью совпадают).

Первые прогоны: ожидаемое величие не случилось

Первые запуски на малой выборке давали 20–40% правильных ответов. Я списал это на шум и прогнал 100 вопросов.

Прогон №9 (базовый срез):

Прогон

N

RA raw

Судья raw

N rep

RA rep

Судья rep

№9

100

0.23

32%

95

0.25

34%

Это был удар. Система, в которой я был уверен, на чужом сложном поиске выглядела посредственно.

Позже стало понятно почему:

  • В моих внутренних задачах агент шёл по понятным доменным следам. TRuST же был насыщен отвлекающими факторами, омонимами и временными ловушками, на которых агент терял фокус и/или выбирал неправильную цель.

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

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

Что начал чинить: эволюция архитектуры

Этап 1. Критик покрытия (CoverageCritic)

Первым решением было декомпозировать систему и разделить моего громоздкого критика на двух:

  1. CoverageCritic (Критик покрытия): отвечает за достаточность контекста до генерации. Какие части вопроса уже подкреплены, а какие открыты? Идея: не генерировать ответ, пока не понятно, достаточно ли фактов.

  2. Final Critic: отвечает за проверку готового ответа, замечания генератору и, при необходимости, смену направления поиска, если текущие оказались тупиковыми.

Промежуточная архитектура с CoverageCritic и Final Critic

Промежуточная архитектура с CoverageCritic и Final Critic

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

Сравнение ключевых этапов (100 вопросов):

Прогон

Архитектура

Поиск

Модель

N

RA raw

Судья raw

N rep

RA rep

Судья rep

№9

Базовая (1 Critic)

Vector

100

0.23

32%

95

0.25

34%

№35

+ CoverageCritic

Hybrid + FTS

Qwen 3.7

100

0.41

51%

91

0.45

56%

Результат: Recall All вырос с 0.25 до 0.45, корректность ответов — с 34% до 56%.

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

Этап 2. Работа с экономикой и ловушка оптимизации

Сложные вопросы требовали больше циклов, что увеличивало время отклика и стоимость API-вызовов. Решение нашлось быстро (уже частично использовалось в Нафане):

  1. Кэш внешних моделей: я перенёс ключевые инструкции из system prompt в user prompt, вынес стабильный тяжёлый блок чанков в начало сообщения, а изменяемую часть (вопрос, цели, историю и инструкции) — в конец. Это позволило задействовать prompt cache API в цепочке критик → генератор → финальный критик.

  2. Фильтр переноса контекста: между циклами я перестал тащить весь накопленный «мусор» (иногда более сотни чанков). На новый цикл оставлял только фрагменты, подтвердившие цели покрытия, и чанки с высокой оценкой релевантности.

Экономику я поправил, но в части метрик получил фиаско. Вопросы, которые Нафаня научился «щёлкать как орешки», снова стали неподъёмными.

Промежуточные срезы (24 вопроса):

Прогон

N

RA raw

Судья raw

N rep

RA rep

Судья rep

Модель

№35

24

0.59

62.5%

24

0.59

62.5%

Qwen 3.7

№56

23

0.52

47.8%

23

0.52

47.8%

DeepSeek v4

№57

24

0.40

50.0%

22

0.44

54.5%

Qwen 3.7

Разбор полётов показал: перенос задач/правил в user prompt и агрессивная фильтрация чанков привели к тому, что модели стали хуже следовать инструкциям. Агенты начали зацикливаться на одной (часто ошибочной) идее, а фрагменты вне этой линии безжалостно отсекались. Final Critic, промпт которого размывался неверными цитатами из предыдущих шагов и ответом генератора с неправильной идеей, перестал выполнять одну из своих главных функций — предлагать новые направления поиска.

Это подтолкнуло к следующей итерации, на которой роли пришлось поделить окончательно.

Этап 3. Research Critic и сужение Final Critic

Текущая архитектура с Research Critic и суженным Final Critic

Текущая архитектура с Research Critic и суженным Final Critic

Циклы разведены на три независимых бюджета:

  1. Раунды CoverageCritic: локальная доводка покрытия в рамках заданных целей.

  2. Циклы Research Critic: глобальная оценка того, “правильным ли путём пошли товарищи”. Если товарищи пошли не туда — новые цели и новый поиск.

  3. Revision Final Critic: проверка ответа без нового поиска. Отдельно от поискового бюджета.

Research Critic в чистом виде, как это случилось с критиком покрытия, не оказался серебряной пулей. Метрики встали. Выяснилось, что сложность вопросов бенчмарка требует слаженности и точности работы всей цепочки участников. Пришлось лезть во всё сразу: переписывать инструкции, добавлять правила, калибровать фильтры, менять стратегии.

Эволюция метрик (полные прогоны, 100 вопросов, Qwen):

Прогон

Архитектура

Поиск

Модель

N

RA raw

Судья raw

N rep

RA rep

Судья rep

№35

+CC, без RC

hybrid+FTS 0.5/0.5

qwen3.7-plus

100

0.41

51%

91

0.45

56%

№85

+RC

recursive

qwen3.7-plus

100

0.42

49%

89

0.48

55%

№90

+RC

recursive

deepseek-v4-pro

100

0.43

48%

100

0.43

48%

№114

+RC, extend=false

recursive

qwen3.7-plus

100

0.42

43%

87

0.48

49%

№122

+промпт CC

recursive

qwen3.7-plus

99

0.40

46.5%

87

0.46

53%

№123

carryover 40

recursive

qwen3.7-plus

100

0.44

52%

93

0.47

56%

№124

hybrid

hybrid+BM25 0.8/0.2

qwen3.7-plus

100

0.46

54%

93

0.50

58%

Итоговый прогон (№124, 100 вопросов, Qwen, hybrid):

  • Recall All (reportable): 0.50

  • Судья (reportable): 58%

Это абсолютный рекорд для текущей архитектуры, превзошедший ранний baseline №35.

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

6 приёмов работы с промптами для агентных систем

1. Поле analysis в начале JSON — управляемая цепочка рассуждений

Что лечит: импульсивные выводы, пропуск шагов рассуждения, галлюцинации в структурированных ответах.
Как: добавьте в начало JSON-схемы поле analysis (строка) перед итоговыми полями решения. Внутри промпта явно укажите, что именно модель должна проанализировать, какие шаги выполнить и что вписать в итоговые поля.
Почему это работает: LLM генерирует текст токен за токеном слева направо — она физически не может «подумать», если сразу начинает выдавать итоговое решение. Поле analysis заставляет её сначала выполнить заданные вами шаги, и только потом, опираясь на этот уже сгенерированный контекст, выдать финальный JSON.
Важный нюанс: это не просто дешёвая замена CoT (chain-of-thought). Фишка в том, что вы сами направляете, в какую сторону модель должна подумать.

2. Алгоритмы вместо правил

Что лечит: непоследовательное применение инструкций, хаотичное поведение.
Как: формулируйте инструкции как чёткий пошаговый алгоритм («Шаг 1: проверь X. Шаг 2: если X истинно, сделай Y…»), а не как набор разрозненных правил.
Почему это работает: LLM лучше всего справляются с задачами, когда их ведут за руку. Набор абстрактных правил («будь точным», «проверяй факты») размывается в длинном промпте. Пошаговый алгоритм хорошо ложится на авторегрессивную природу модели.

3. Позитивные формулировки вместо негативных

Что лечит: игнорирование запретов, парадоксальное усиление запрещённого поведения.
Как: вместо «не выдумывай сущности» пишите «используй только сущности, явно упомянутые в вопросе или найденных чанках». Вместо «не игнорируй таблицы» — «всегда проверяй наличие таблиц в контексте».
Почему это работает: механизм внимания модели активируется на ключевые слова. Фраза «Не выдумывай факты» сильно активирует концепты «выдумывать» и «факты», что повышает шанс галлюцинации. Фраза «Используй только факты из текста» дает модели четкий, позитивный сигнал.

4. Явное перечисление вариантов выбора

Что лечит: произвольный выбор без обоснования.
Как: когда агент должен сделать выбор, явно укажите варианты: «Выбери один из вариантов: а) продолжить поиск, б) сохранить чанк и перейти в новый раунд, в) финализировать результат. Условия: выбирай а), если покрытие < 50%; выбирай б), если найден критический факт…».
Почему это работает: варианты превращают задачу открытой генерации (где модель может придумать что угодно) в задачу классификации с условиями (где пространство решений строго ограничено).

5. Примеры с однозначной демонстрацией желаемого поведения

Что лечит: расхождение между «что я имел в виду» и «что поняла модель».
Как: всегда включайте в промпт 2–3 конкретных примера (вопрос → контекст → правильный ответ агента) с комментариями, почему именно такой ответ корректен. Примеры должны быть однозначными — без серых зон, где модель могла бы интерпретировать поведение по-разному.
Почему это работает: показать всегда лучше, чем рассказать. А ещё лучше показать так, чтобы у модели не осталось сомнений в том, что от неё требуется.

6. System prompt: идеал против экономики

Что лечит: потерю фокуса модели, «забывание» глобальных инструкций в длинных агентных циклах.
Как (в идеале): роль агента, глобальные правила и строгие ограничения лучше всего хранить в system prompt. Модели придают ему наибольший вес, и он не размывается историей диалога или огромным массивом чанков.
Но (реальность): в длинных агентных циклах с тяжелым контекстом, чтобы задействовать prompt caching API, мне пришлось перенести инструкции в user prompt и поставить их после тяжелых повторяющихся блоков контекста. Это вынужденный компромисс и моя боль: я пожертвовал идеальной иерархией промптов ради снижения стоимости вызовов, при этом впоследствии был вынужден расплатиться за это созданием и выверкой более жестких алгоритмов.

Qwen vs DeepSeek

Сравнение моделей (Research Critic, recursive, 100 вопросов):

Прогон

Модель

N

RA raw

Судья raw

N rep

RA rep

Судья rep

№85

Qwen3.7-plus

100

0.42

49%

89

0.48

55%

№90

DeepSeek-v4-pro

100

0.43

48%

100

0.43

48%

Выводы:

  1. В сырых метриках на 100 вопросах Qwen и DeepSeek примерно сопоставимы по качеству (49% против 48% у DeepSeek). Но если исключить 11 жестких отказов API (модерация контента), реальная эффективность Qwen оказывается выше. DeepSeek в этом плане предсказуемее и стабильнее как API-провайдер.

  2. Qwen немного лучше конвертирует контекст в ответ. На чистых данных (без инфраструктурных сбоев) Qwen выигрывает по корректности ответа (55% против 48%). Он эффективнее использует найденные фрагменты для генерации.

Обе модели на моей архитектуре упираются в потолок ~55% корректности. Выбор между ними — это компромисс: большая стабильность (DeepSeek) против чуть лучшей генерации, но с риском инфраструктурных сбоев (Qwen). Ансамбль из работы двух моделей мог бы улучшить показатели, но тогда возникает вопрос экономики.

Сравнение с T-Search

T-Search на TRuST показывает порядка 44% Recall@10 на всем бенчмарке (324 вопроса). У меня на моих полных прогонах (100 запросов) Recall All (reportable) достигает 50%, а корректность ответа (Судья) — 58%.

Эти значения нельзя сравнивать как позиции в одном лидерборде:

  • Замеры T-Search сделаны на полном датасете — 324 вопроса.

  • T-Search оптимизируется как ретривер: найти и отдать ранжированные опорные фрагменты. Его метрика поощряет накопление всех возможных эталонных чанков.

  • Нафаня оптимизируется как продукт: собрать достаточный контекст и дать проверяемый ответ.

Мой Recall All считает только то, что осталось в финальном контексте после фильтрации шума. Если чанк помог навести на верную цель на раннем этапе, но был отброшен фильтром перед генерацией, для T-Search это потеря, а для Нафани — осознанная экономия. Поэтому мой приоритет: качество ответа (Судья) > Recall All.

Честно говоря, я считаю достигнутые метрики невероятным успехом. Не поленитесь, посмотрите примеры TRuST: это непростые и интересные кейсы, требующие от ретривера проявления чудес интуиции, а от модели-генератора — выстраивания сложных логических цепочек.

Что дальше

Публично продукт доступен на ainafanya.ru.

Планы по развитию:

  • Перенести удачные куски агентной обвязки обратно в основной доменный контур Нафани.

Спойлер: Почему эта архитектура особенно хорошо ложится на мой домен?

Строительная нормативка переполнена узкой терминологией. Без достаточного контекста открытым моделям сложно сразу выбрать верное направление поиска. Но теперь, даже если Planner ошибся в начале, CoverageCritic всё равно вытащит полезные фрагменты, а Research Critic, опираясь на накопленный контекст, скорректирует курс. На текущей архитектуре я уже видел этот эффект, а в новой он возможен на системном уровне.

  • Собственные датасеты по строительной тематике: уже двигаюсь в этом направлении, хотя пока сталкиваюсь с проблемами качества и объема исходных данных.

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

  • Собственный бенчмарк RAG в строительстве: нормативные обоснования, таблицы, формулы, связанные нормы и расценки. TRuST остаётся отличным тренажёром для отладки общих принципов, но отраслевой бенчмарк, учитывающий доменную специфику, он заменить не сможет.

Вместо заключения

Я пришёл к внешнему бенчмарку уверенным в Нафане. Ушёл с пострадавшим эго, но с модернизированной архитектурой.

Если вы делаете своего агентного RAG и перед вами стоит задача отвечать на сложные многоходовые вопросы — старайтесь объективно оценивать вашу систему, как вариант, на бенчмарке TRuST или подобных ему. К счастью, такие инструменты теперь доступны, и за эту открытость огромная благодарность команде Т-Банка.

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

Теги: rag, агенты, бенчмарк, поиск, языковые модели, нафаня

ссылка на оригинал статьи https://habr.com/ru/articles/1067136/