JSONSchemaBench: Сложность реальных схем нарушает гарантии структурированного вывода LLM
Большинство команд рассматривают ограниченное декодирование как решенную проблему: добавьте JSON-схему и получите валидный JSON. JSONSchemaBench (arXiv:2501.10868) — это первая систематическая попытка проверить это предположение на 9 558 реальных схемах, и результаты оказались менее обнадеживающими, чем обещает маркетинг.
Исследование
Сайбо Генг, Хадсон Купер, Михал Москаль и их коллеги из Microsoft Research представляют JSONSchemaBench — бенчмарк из 9 558 схем, взятых из реальных производственных источников: сигнатур вызовов функций GlaiveAI, репозиториев GitHub (разбитых по сложности от тривиальных до сверхсложных), конфигураций Kubernetes API, схем аналитики событий Snowplow и коллекции JSONSchemaStore. Они оценивают шесть фреймворков ограниченного декодирования — Guidance, Outlines, Llamacpp, XGrammar, OpenAI Structured Outputs и Gemini — по трем осям: покрытие (какую долю схем фреймворк вообще может обработать), эффективность (накладные расходы на токены в секунду по сравнению с неограниченной генерацией) и качество (точность выполнения последующих задач). Сетка оценки также включает официальный набор тестов JSON Schema Test Suite, который документирует 45 категорий функций, которые должен поддерживать любой совместимый движок.
Основное утверждение заключается в том, что сложность схемы является решающей переменной, отделяющей способные фреймворки от хрупких, и что ни один фреймворк не доминирует по всем трем осям.
Ключевые идеи
- Покрытие обрушивается при усложнении схем. На простых схемах GlaiveAI все фреймворки набирают более 86%. Но на схемах GitHub-Hard — многоуровневая вложенность, рекурсивные определения, сложные паттерны ограничений — покрытие Guidance падает до 41%, Llamacpp до 39%, XGrammar до 28%, а Outlines до катастрофических 3%. OpenAI достигает лишь 9% на GitHub-Hard, а Gemini не выдает валидных результатов на схемах средней и высокой сложности.
- Kubernetes выявляет специфическую слабость XGrammar. Несмотря на заявления XGrammar о скорости, он достигает лишь 7% покрытия на схемах Kubernetes, вероятно, потому что эти схемы полагаются на контекстно-зависимые паттерны, которые контекстно-независимые предварительные вычисления XGrammar не могут обработать. П окрытие в бенчмарке, включающем конфиги Kubernetes, обязательно для производственных агентов.
- Недостаточные ограничения опаснее, чем ошибка компиляции. XGrammar демонстрирует 38 отказов типа «under-constrained» (недостаточное ограничение) в JSON Schema Test Suite — это означает, что он выдает JSON, нарушающий заявленную схему, при этом молча сообщая об успехе. У Guidance только 1 такой отказ. Для агента обратной записи ошибка компиляции обнаруживается на этапе разработки; отказ из-за недостаточных ограничений повреждает данные во время выполнения без какого-либо сигнала.
- Функция fast-forwarding в Guidance обеспечивает реальный прирост скорости на 50%. При наличии длинных детерминированных последовательностей (например, имен полей в фиксированной структуре объекта), Guidance может продвигаться на несколько токенов за один шаг декодирования. На Llama-3.1-8B на A100 Guidance работает со скоростью 6–9 мс на выходной токен, в то время как неограниченная генерация — 15–16 мс. Outlines работает медленнее неограниченной генерации (30–46 мс), в основном из-за предварительной компиляции автомата, занимающей 3–8 секунд на схему.
- Ограниченное декодирование незначительно повышает точность рассуждений. На GSM8K (математика) Guidance повышает точность с 80,1% (без ограничений) до 83,8%. В задачах Last Letter и Shuffle Objects прирост составляет 1–3 пункта. Это противоречит широко распространенному мнению о том, что принудительный формат JSON ухудшает качество ответов, но эффект настолько мал, что выбор формата не должен определять выбор фреймворка.
- Ни один фреймворк не охватывает все 45 категорий функций JSON Schema. Guidance охватывает 13, Llamacpp и XGrammar — по 1, а Outlines — 0. Практическое следствие: любая схема, использующая
if/then/else,unevaluatedPropertiesили рекурсивные определения$ref, будет вести себя непредсказуемо в зависимости от того, какой движок используется под капотом.
Что подтвердилось, а что нет
Самый сильный вклад бенчмарка — это источники схем. Предыдущие оценки использовали игрушечные схемы или коллекции из одного источника. Включение конфигов Kubernetes наряду с сигнатурами вызовов функций — это правильный вид состязательного разнообразия. Стратификация сложности (от тривиальной до ультра) также дает практикам кривую калибровки: если ваши схемы похожи на вызовы функций GlaiveAI, XGrammar или Guidance одинаково хороши; если они похожи на манифесты Kubernetes, выбор быстро сужается.
Главная слабость — оценка по одному жадному сэмплу (single-sample greedy evaluation). Измерение покрытия по одной генерации на схему занижает реальные возможности — фреймворк может давать сбой в 20% случаев, но срабатывать при повторной попытке. Авторы признают это, но не сообщают показатели pass@k с температурным сэмплированием, что было бы важно для производственных систем, выполняющих повторные попытки при сбое.
Сравнение также смешивает несопоставимые модели. Open-source фреймворки (Guidance, Outlines, Llamacpp, XGrammar) тестировались на Llama-3.2-1B, в то время как OpenAI и Gemini запускают свои собственные закрытые модели. Покрытие OpenAI в 9% на GitHub-Hard может отражать возможности модели так же сильно, как и архитектуру ограниченного декодирования. Для справе дливого сравнения потребовался бы контролируемый доступ к моделям, который авторы, очевидно, не могут навязать проприетарным провайдерам.
Почему это важно для ИИ в финансах
Каждый агент обратной записи Beancount генерирует структурированный вывод. Если агент выдает директивы Beancount в формате JSON перед преобразованием в синтаксис .beancount, или если он вызывает инструменты через JSON-схемы, надежность этой генерации — не деталь, а основа процесса. Исследование FinTrace показало, что передовые модели плохо рассуждают над выводами инструментов; JSONSchemaBench выявляет ортогональную проблему: еще до этапа рассуждений уровень форматирования может незаметно выдать некорректный результат.
Результат с Kubernetes особенно показателен для Beancount. Схемы главной книги — это не плоские наборы «ключ-значение». Иерархии счетов, метаданные транзакций и структуры тегов создают вложенные рекурсивные паттерны, похожие на объекты Kubernetes API. Фреймворк, набирающий 7% на Kubernetes, не готов к сложным схемам бухгалтерского учета, независимо от того, насколько малы его накладные расходы на токен.
Режим отказа из-за недостаточных ограничений — это то, из-за чего я бы лишился сна. Агент Beancount, использующий XGrammar, может выдать транзакцию, которая пройдет внутреннюю проверку валидации фреймворка, но нарушит фактическую схему — и у агента не будет причин для повторной попытки. Незаметное повреждение данных хуже, чем явная ошибка.
Что почитать дальше
- XGrammar (arXiv:2411.15100, Dong et al.) — техническая статья об одном из самых быстрых протестированных фреймворков, объясняющая разделение токенов на контекстно-зависимые и независимые, а также причины, по которым схемы Kubernetes создают для него нагрузку.
- Grammar-Aligned Decoding / ASAp (NeurIPS 2024) — показывает, что маскирование токенов при ограниченном декодировании может искажать распределение вероятностей модели, и предлагает скорректированный алгоритм сэмплирования; теоретическая база для проблем качества, которые бенчмарк измеряет лишь косвенно.
- XGrammar-2 (arXiv:2601.04426) — продолжение, расширяющее XGrammar на динамические схемы в агентных сценариях, где сама схема меняется в ходе многоэтапной сессии, что напрямую применимо к агентам Beancount, адаптирующим формат вывода в зависимости от активных типов счетов.
