3.5Kпросмотров
27 августа 2025 г.
questionScore: 3.9K
Почему SGR в агентных задачах - плохая идея? Ринат в последнее время пишет про SGR и его применение в агентных задачах. Про сам SGR подробнее можно посмотреть здесь. TL;DR: SGR — частный случай Structured Output, где перед финальным ответом задаются «поля», которые позволяют вести LLM более контролируемо к нужной области ответа, а затем, учитывая пункты, которые она написала «выше», LLM формирует финальный ответ (или выполняет действие, которое также жёстко задано JSON-схемой). В чём вообще отличие? Если и SGR, и Tools приводят к JSON для вызова тула? Кажется, результат должен быть одинаковым — и в SGR мы даже можем что-то дополнительно контролировать. Звучит супер!
Как LLM пишет ответ в случае SGR (например, для тулзы)?
LLM генерирует ответ; отдельный бэкенд (например, xgrammar) выступает в роли конечного автомата: читает переданную схему, строит грамматику и «не даёт» LLM писать токены, не соответствующие схеме. В знаменитом chat.completions() нам вернётся сообщение вида {role: 'assistant', content: '<JSON-строка>', tool_calls: []}; потом мы парсим content и подкладываем в историю сообщение вида {role: 'tool', content: '<результат тула>'}. Я намеренно опустил пару деталей для наглядности, но в общих чертах так оно и выглядит. Не видите подвоха?
1) В chat-template поле tools пустое (если LLM поддерживает tools).
2) LLM пишет какой-то JSON, а в ответ ей прилетает следующее сообщение с ролью tool (хотя тулов у LLM «нет» — они не были явно переданы через tools). Следствие.
LLM пишет JSON, а в ответ получает результат тула. Если посмотреть на известные бенчмарки по tool calling, такого поведения вообще не ожидается: модели обычно обучаются и оцениваются в сценариях, где доступные инструменты передаются явно, а вызов идёт через структурированное поле function/tool-calls.
Представляете, что происходит в голове у LLM, когда подобных диалогов нет ни в открытых датасетах, ни в референсных туториалах провайдеров: даже семантика вызова tools теряется. В чат-истории внезапно появляются «инструменты», хотя их не передавали через tools, и «вызов» сделан абстрактным JSON в content, а не через нативное поле tool_calls. Официальные гайды OpenAI/Anthropic учат обратному: передайте список tools в шаблон, модель выберет нужную функцию и сформирует аргументы в структурированном поле; не вызывайте того, чего нет в tools. Как работает TOOLS?
Tools - это поле, которое подмешивается в chat-template. На этапе SFT/RL модель учится работать именно с таким протоколом: не вызывать то, чего нет, и вызывать то, что доступно. Это зафиксировано и в провайдерских практиках (OpenAI/Anthropic), и в ресерчерских наборах для оценки агентости (When2Call (NVIDIA) tool hallucination rate тому пример внутри бенча, BFCL/Gorilla включает специальную категорию Function Relevance Detection: когда ни один из переданных тулов не подходит - модель должна не делать call. Есть и Chatting Capability: вообще без переданных тулов, проверяется, что модель пишет ответ как чат-бот, не вызывая функции).
Модель не должна пользоваться тулами, если их не передали в tools. Какие tools передали — такими и пользуется. «Но мы же теряем reasoning и отладку?»
Нет, не теряем [Пояснение к лучшей реализации находится следующим постом]. Никто не запрещает первыми аргументами (по аналогии с SGR) сделать поля в функции — «reasoning», ключевые «якоря» и т. п. За счёт этого вы получаете: 1) более нативное использование инструментов (внутри официального "протокола" tool-calling),
2) более прозрачную историю сообщений,
3) более стабильную систему. Да, здесь reasoning идёт в аргументы функции (которых может быть много), а не в выборе нужной функции. Но даже крупные компании не рекомендуют засовывать слишком много функций в один промпт - если модель «теряется», лучше декомпозировать систему/поправить промпты, а не «эмулировать» tool-calls через SGR. Ради эксперимента можете измерить перплексию на диалогах с параллельными вызовами тулов в форматах SGR vs Tool