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

Данные, которые нельзя выдумать: как мы собираем трейсы для тренировки агентских навыков Cotype

Данные, которые нельзя выдумать: как мы собираем трейсы для тренировки агентских навыков Cotype - 1

На днях мы выкатили третье поколение языковых моделей Cotype — флагманскую Cotype Pro 3 и ее облегченную версию Cotype Light 3 (к слову, лайт мы выкатили чутка раньше, но это не имеет значения). Почитать о характеристиках моделей можно вот в этом посте. Тут же мы расскажем вам о нюансах обучения [1] — точнее о том, как мы учим наши модели выполнять многошаговые сценарии без запинок, что совершенно необходимо, так как они выступают ключевым компонентом наших корпоративных ИИ-агентов и мультиагентных систем.

Давайте сначала проблему опишу, для наглядности

Представим, что клиент просит изменить заказ и называет свой e-mail. ИИ-агент, подключенный к каналу обслуживания, ищет по нему клиента и получает «не найдено» — адрес устарел. Дальше есть три варианта развития событий:

  • можно повторить тот же запрос и снова получить ту же ошибку [2]

  • можно сообщить, что клиент не найден, и закрыть диалог; 

  • можно изменить признак поиска — найти клиента по имени и индексу — и продолжить.

Получить довольного клиента можно, только если пойти по третьему пути. 

Большие языковые модели обычно без проблем понимают, что от них хотят, и успешно пробегают все шаги по этой дорожке: свериться со статусом перед записью, не потерять идентификатор между вызовами, не переспрашивать уже названное, не повторять [3] упавший вызов вместо обходного пути. А вот компактные модельки с такими механическими вещами не дружат, хотя и не хуже больших понимают, что нужно сделать. 

Эта проблема — не проблема для компаний, которые могут позволить себе LLM на сотни миллиардов параметров. Нашим же клиентам нужны модели, которые можно установить в контур и которые не разорят бизнес на инференсе. Поэтому даже наша флагманская модель Cotype содержит всего лишь 27 млрд параметров, и это меньше, чем у предыдущего поколения. А облегченная версия — и вовсе 9 млрд. 

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

TL;DR

Наш пайплайн собран на τ²-bench (tau2) — открытом фреймворке для оценки tool-calling-агентов в режиме dual-control, где роли агента и клиента играют отдельные модели. Под свою задачу мы его существенно доработали: добавили генерацию доменов, сбор трейсов с исполнением вызовов в базе и тренировочный контур. RL-обучение (GRPO) идет поверх verl-agent gym.

На отложенном тесте (телеком, финансы, HR) дообучение модели на 9 млрд параметров через GRPO поднимает основную метрику с 0,794 до 0,856; причем основной прирост — на автономных задачах: ST 0,727 → 0,821.

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

1. Карта типичных ошибок

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

Класс ситуации

Что требуется от агента

Доля в корпусе

Перенос данных через контекст

Удерживать и переносить значения — идентификаторы, суммы, статусы — из ответов инструментов и реплик клиента в аргументы следующих вызовов

40%

Ветвление по состоянию

Перед действием свериться с состоянием сущности и выбрать ветку: операция допустима или нет

25%

Действие без лишних уточнений

Если нужное уже сказано — действовать, а не переспрашивать по второму кругу

20%

Восстановление после ошибки инструмента

После неудачного вызова сменить стратегию, а не повторять то же действие

10%

Корректное завершение диалога

Закрыть многошаговую задачу: подвести итог сделанного и завершить

5%

Таблица 1. Типичные ошибки ИИ-агентов на примере сценариев клиентской поддержки

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

2. Подход к созданию обучающих трейсов

Обучающие трейсы можно получать двумя путями:

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

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

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

  • Развилка — это статус сущности, который агент прочитал из базы и на который отреагировал.

  • Разные варианты прохождения задачи возникают сами: на разные данные база отвечает по-разному.

  • Идентификаторы и суммы переходят из шага в шаг, потому что их вернул вызов. Другого источника у них нет.

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

Мы пошли по второму пути. Но давайте опишу пайплайн целиком. 

3. Пайплайн целиком

Вся работа по созданию трейсов у нас разделена на два этапа:

  • Подготовка среды. LLM собирает статический мир: скиллами проектирует домены, наполняет базу, пишет инструменты и политику, формулирует задачи, первые сообщения и сценарии.

  • Собственно генерация трейсов. Диалог разыгрывают две LLM — одна за агента, другая за клиента. Каждый предложенный агентом вызов уходит в исполнитель инструментов, тот загружает состояние диалога, исполняет вызов со всеми проверками и изменениями в базе и возвращает фактический результат. Сценарий при этом лишь расставляет ловушку — подставляет устаревший e-mail, переводит заказ в статус «доставлен», а проходит ее LLM-агент сам. Его попытки, ошибки и развилки попадают в трейс как есть.

Смотрите Схему 1 с комментариями для наглядности.

Схема 1. Пайплайн подготовки обучающих трейсов для ИИ-агентовГлавный узел на схеме — исполнитель инструментов справа. К нему сходятся три стадии генерации (сценарии, диалоги, проверка), через него все вызовы инструментов идут в базу. Домен здесь — маленький самодостаточный мир под одну предметную область. В нем три части: записи в базе (клиенты, заказы, карты и т. п.); системные промпты с политикой — что агенту можно и в каком порядке; и набор инструментов — обычных Python-функций со своей логикой, проверками и предсказуемыми ошибками-фолбэками на недопустимый ввод. Вызовы агента исполняются именно на этих функциях, взаимодействуя с настоящей БД.

Схема 1. Пайплайн подготовки обучающих трейсов для ИИ-агентов
Главный узел на схеме — исполнитель инструментов справа. К нему сходятся три стадии генерации (сценарии, диалоги, проверка), через него все вызовы инструментов идут в базу. Домен здесь — маленький самодостаточный мир под одну предметную область. В нем три части: записи в базе (клиенты, заказы, карты и т. п.); системные промпты с политикой — что агенту можно и в каком порядке; и набор инструментов — обычных Python-функций со своей логикой [4], проверками и предсказуемыми ошибками-фолбэками на недопустимый ввод. Вызовы агента исполняются именно на этих функциях, взаимодействуя с настоящей БД.

Собственно, всего в рамках этих двух этапов выполняется 6 шагов: 

Подготовка среды

① ARCHITECT (/architect-train-domains) проектирует набор доменов — спецификации, не код — по нескольким предметным темам. Каждый домен сконструирован так, чтобы провоцировать целевые классы ошибок: инструменты, возвращающие идентификаторы; операции, завязанные на состояние; вызовы, которые умеют падать.

② BUILD (/build-mini-domain) компилирует каждую спецификацию в исполняемый домен: модель данных, инструменты, база, политика. Отрапортовать об успехе субагент может только после самотестирования — домен регистрируется, инструменты загружаются, проверяется реакция [5] на недопустимое состояние.

③ ROUTE (/route-corpus) раскладывает целевые диалоги по слотам вида «домен × класс ситуации» и выдает каждому слоту непересекающийся пул сущностей, чтобы параллельные генераторы не конфликтовали за одни и те же данные.

Генерация трейсов

④ SCENARIO (/gen-scenario) пишет по сценарию на слот: задача, первое сообщение клиента, что он знает и сообщит, что «не помнит» (последнее заставляет агента искать данные, а не получать их готовыми), и эталонная цепочка действий. Цепочку прогоняют в базе от начала до конца — провалов нет.

⑤ РОЗЫГРЫШ — диалог разыгрывают две LLM, одна агентом, другая клиентом. Каждый предложенный агентом вызов уходит в базу, фактический результат возвращается в реплику.

⑥ ПРОВЕРКА (verify_grounding.py [6]) переисполняет каждый вызов из готового диалога и сверяет с записанным результатом. Разошлось — диалог в брак. Часть диалогов так отсеивается.

На выходе — сырые проверенные трейсы. Приведение к конкретному формату обучения — отдельный технический шаг SFT/DPO, в сам конвейер он не входит.

Стадии сборки и написания сценариев (2 и 4) идут волнами субагентов (это когда кодовый агент вызывает других агентов самостоятельно). Каждый работает в своем пуле сущностей, так что волны не конфликтуют за данные.

Покажу, как все это выглядит, на примерах.

4. ПримерыПример 1. Простой перенос данных

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

# Клиент: зачислить карту GC-000001 на заказ ORD-10001, списать весь баланс.

lookup_gift_card(“GC-000001”)

  → {“balance”: 25.0, “status”: “active”}

# сумму 25.0 агент берет из ответа базы и подставляет в следующий вызов

apply_gift_card_to_order(“GC-000001”, “ORD-10001”, amount=25.0)

  → {“redemption_id”: “RED-001”, “remaining_balance”: 0.0}

# идентификатор RED-001 уходит клиенту дословно

# Агент: готово, списано 25 EUR, операция RED-001, остаток 0.

Суммы и идентификаторы агент не выдумывает, а переносит из ответа базы в следующий вызов и в финальную реплику. Каждый диалог стартует со своей копии базы: это независимые сессии, а не один общий мир.

Ниже еще три трейса по разных классам ошибок: ветвление по состоянию, более сложный перенос и восстановление после ошибки.

Пример 2. Ветвление по состоянию: перенос экзамена

Студент просит: «Перенесите мой экзамен EX-0024 на попозже». Сценарий заранее перевел экзамен в статус «уже сдан»:

lookup_exam(exam_id=”EX-0024″)  →  {“status”: “taken”, “reschedule_count”: 1, …}

# status == “taken”  →  ветка отказа, в базе ничего не меняем

“Экзамен EX-0024 уже сдан, перенести его нельзя.”

Агент сначала читает статус и только потом решает: переносить нечего.

Пример 3. Перенос данных через контекст: диспут по банкомату

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

lookup_transaction(“TXN-000001″)        → amount=100.0, atm_id=”ATM-001″, status=”posted”

lookup_atm_log(“ATM-001”, “10:59:00”)   → dispensed=false        # по политике диспут допустим

file_dispute(“TXN-000001″, reason)      → dispute_id=”DSP-0001”

issue_provisional_credit(“DSP-0001″, amount=100.0) → credit_id=”CR-0001”

Здесь два переноса сразу: dispute_id из третьего вызова в четвертый и сумма 100.0 из первого. Ветвление по состоянию зашито в политику.

Пример 4. Восстановление после ошибки инструмента

Тот самый случай из начала статьи, теперь в виде трейса. Клиент назвал неактуальный e-mail, первый поиск падает, агент меняет признак поиска:

find_user_by_email(“john.smith@unknown.com [7]“)   → ошибка: пользователь не найден

# вместо повтора того же вызова — другой признак поиска:

find_user_by_name_zip(“John”, “Smith”, “10024”)  → user_id=”U-7829″   # продолжаем

5. Эксперименты и результаты

5.1. Подход

Мы дообучили на сгенерированных по вышеописанному пайплайну трейсах модель Qwen3.5-9B,  и прогнали ее вместе с конкурентами большего размера на отложенном тесте из трех областей: телеком, финансы и HR (кадровые задачи). 

Тест и обучение не пересекаются. Каждая задача идет в двух режимах: MT — диалог с симулятором клиента; и ST — вся информация выдается одним сообщением, дальше агент работает автономно. Итого 90 задач, по 5 прогонов на каждую на русском языке.

  • Роль клиента играет Qwen3.5-9B, следуя структурированному сценарию с персоной и границей знаний.

  • Роль ИИ-агента — Cotype Light 3 (та же Qwen3.5-9B, но уже дообученная).

  • Роль судьи по текстовым утверждениям — GPT-OSS-120B с голосованием по трем прогонам.

Тут возможен вопрос про циркулярность: клиент-симулятор и обучаемая модель — обе из семейства Qwen3.5-9B (клиент — базовая модель, агент под оценкой — обученная; модель не судит сама себя). На скоринг это все равно не влияет. Оси DB и ACTION детерминированы и считаются против эталона, а не через клиента; текстовые требования судит gpt-oss-120B — другое семейство, работа судьи провалидирана и не имеет определенного байеса. Клиент следует фиксированному сценарию с заданной персоной и границей знаний, то есть подыгрывать агенту не волен.

Метрика — мультипликативная тройка, где провал по любой оси обнуляет результат:

primary=DB×ACTION×NL_frac

primary=DB×ACTION×NL_frac

DB — совпадение итогового состояния базы с эталоном, ACTION — совпадение цепочки вызовов без приоритета очереди, NL_frac (NL — natural language) — доля выполненных требований на естественном языке, сюда относим комментирование результатов и общение с пользователем.

5.2. Базовые модели

Модель

Параметры

ALL

MT

ST

Qwen3.5-122B-A10B

122B MoE

0,833

0,844

0,822

Qwen3.5-27B

27B

0,817

0,855

0,779

GPT-OSS-120B

120B MoE

0,802

0,817

0,787

Qwen3.5-9B (база)

9B

0,794

0,861

0,727

Qwen3-VL-32B

32B

0,729

0,724

0,733

Qwen3.5-4B

4B

0,669

0,817

0,520

Qwen3-VL-8B

8B

0,592

0,564

0,620

Из таблицы видно две вещи. Первое: на этих задачах размер сам по себе помогает слабо. Базовая 9B (0,794) идет вплотную к 27B (0,817) и 122B MoE (0,833) — разрыв в десятки раз по параметрам дает единицы процентов по метрике. Второе — и для нас главное: режим ST стабильно тяжелее MT. В диалоге агент может переспросить и неявно исправиться, в автономном режиме такой страховки нет. Резче всего это на 4B — 0,817 в диалоге против 0,520 автономно. Автономные задачи и есть то место, где обучение должно дать максимум.

5.3. SFT против DPO против GRPO

Базовая модель для обучения — Qwen3.5-9B, все методы на LoRA rank 16.

  • SFT — 600 образцовых трасс от учителей (учителя — Qwen3.5-9B и 27B).

  • DPO — тот же корпус, что и в SFT, в паре с трассы с нулевой наградой.

  • GRPO — онлайн-RL в той же среде с исполнением вызовов в базе, с полностью детерминированной наградой, без LLM-судьи: формат, совпадение действий, аргументы, состояние базы. Роллауты генерируются на лету на автономных задачах — это в 5–10 раз быстрее.

Метод

ALL

MT

ST

База (9B)

0,794

0,861

0,727

SFT

0,819

0,867

0,770

DPO

0,827

0,907

0,748

GRPO (Cotype Light 3)

0,856

0,890

0,821

Qwen3.5-27B (ориентир)

0,817

0,855

0,779

Qwen3.5-122B MoE (ориентир)

0,833

0,844

0,822

Главный результат — на автономных задачах: GRPO поднимает ST с 0,727 до 0,821. Именно там у базовой 9B был провал, и такие данные его закрывают. По агрегату GRPO дает 0,856 против 0,794 у базы.

Навык при этом переносится с ST на MT: GRPO учился только на автономных задачах, но подтянул и диалоговый режим (0,861 → 0,890). После обучения 9B оказывается сопоставима по метрике с 27B и 122B MoE.

Где именно прибавляет, видно по динамике обучения. Какие инструменты звать, модель знает с самого начала — ось действий держится около 0,91 и почти не растет. А вот доводить изменение базы до конца она учится: ось состояния растет с 0,53 до 0,97.

Динамика обучения. SFT и DPO прибавляют меньше — ALL 0,819 и 0,827 против 0,856 у GRPO. DPO делает агента осторожнее: он реже трогает операции записи, что помогает на read-задачах и мешает на write (отсюда лучший MT 0,907 и слабый ST 0,748).

Динамика обучения. SFT и DPO прибавляют меньше — ALL 0,819 и 0,827 против 0,856 у GRPO. DPO делает агента осторожнее: он реже трогает операции записи, что помогает на read-задачах и мешает на write (отсюда лучший MT 0,907 и слабый ST 0,748).

5.4. Устойчивость: pass^k

pass^k — вероятность, что успешны все k случайно выбранных прогонов задачи. Чем больше k, тем строже планка: одной удачной попытки уже недостаточно. Прогон здесь засчитывается по строгому бинарному критерию: NL оценивается бинарно — все утверждения должны быть выполнены, а не дробной долей NL_frac, как в основной метрике. Поэтому абсолютные значения тут ниже, чем primary из § 5.3, — это разные шкалы, и сравнивать pass^k нужно между моделями, а не с primary.

Данные, которые нельзя выдумать: как мы собираем трейсы для тренировки агентских навыков Cotype - 4

GRPO держит первое место во всем диапазоне, и с ростом k отрыв растет: к k = 5 он заметно опережает и базу, и SFT с DPO. RL дает не разовый успех, а воспроизводимый — а в проде задача должна решаться каждый раз, а не в среднем по прогонам.

5.5. Человеческая валидация качества корпуса

Для ручной проверки мы взяли 30 случайных диалогов из тестового набора и отдали их команде из 11 аннотаторов, с перекрестной проверкой.

Метрика

Значение

Качество сценария (среднее)

3,83/5

Доля «отлично» (=5)

47,8%

Доля «непригодно» (=1)

17,4%

Согласие судьи и эксперта

95,6%

Выход симулятора-юзера из роли

4,3%

Ложные срабатывания судьи

1

Пропуски судьи

2

Ключевая цифра — согласие автоматического судьи с экспертами, 95,6%. Ручная проверка подтверждает сразу две вещи: качество собранных данных и согласованность человека и LLM-судьи.

6. Итог

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

На отложенном тесте (телеком, финансы, HR) дообучение через GRPO (в таблицах и графиках мы обозначили так Cotype Light 3) поднимает основную метрику с 0,794 до 0,856. Главный прирост — на автономных задачах, где у компактной модели меньше всего права на ошибку: ST 0,727 → 0,821. Cotype Light 3 выходит на 0,856 — выше базовой 27B (0,817) и 122B MoE (0,833), при заметно меньшем инференсе, чем у 27B. А человеческая валидация (95,6% согласия судьи с экспертами) показывает, что и сами данные качественные.

Автор: sir-timio

Источник [8]


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

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

URLs in this post:

[1] обучения: http://www.braintools.ru/article/5125

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

[3] повторять: http://www.braintools.ru/article/4012

[4] логикой: http://www.braintools.ru/article/7640

[5] реакция: http://www.braintools.ru/article/1549

[6] grounding.py: http://grounding.py

[7] john.smith@unknown.com: mailto:john.smith@unknown.com

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

www.BrainTools.ru

Rambler's Top100