JSON от российских LLM: как я довёл парсинг до 99.4% и перестал терять данные
Пятница, 19:47. Задача простая: AI-агент должен разобрать письма и закинуть заявки в CRM. На выходе — «Конечно, вот результат:» перед JSON, лишние запятые и строки вместо чисел. В итоге из сорока писем только пятнадцать дошли до системы, остальное ушло в логи с JSONDecodeError.
Знакомая боль? Я тоже через это прошёл. Расскажу, как мы перестали терять заявки и подняли успешный парсинг до 99.4% на семи тысячах вызовов в сутки.
Почему «верни JSON» почти никогда не работает
LLM — это не JSON-сериализатор. Она просто предсказывает токены. Пишешь в промпте «отдай строго JSON» — модель старается, но на длинных текстах начинает добавлять ```json, одинарные кавычки или вообще забывает про структуру.
С английскими моделями проще — там есть structured output. У нас с GigaChat, YandexGPT и прочими всё печальнее. response_format работает не везде, function calling — только для инструментов. А нам нужна структура без лишних телодвижений.
Реальный кейс: разбор обращений в дилерский центр
Дилер автотехники, 200–400 сообщений в сутки. Нужно вытаскивать intent, класс машины, бюджет, urgency и телефоны. До переделки успех был 73%. 27% писем уходили менеджеру на ручной ввод — семь часов в неделю.
Мы попробовали два подхода. Первый почти не помог, второй выстрелил.
Попытка №1: жёсткий промпт + pydantic
Добавили few-shot примеры, запретили markdown, подключили валидацию. Получили 84%. Уже лучше, но всё равно много ошибок на длинных жалобах — модель начинала пересказывать текст и только потом вспоминала про JSON.
Что реально сработало: три слоя защиты
Слой 1. Tolerant extractor. Не ждём чистый JSON. Берём первый блок, похожий на объект, через простой счётчик скобок (30 строк кода). Снимает все «Конечно, вот ответ» и обёртки.
Слой 2. Мягкая коррекция. Пару регулярок: убираем trailing comma, чиним одинарные кавычки, выкидываем комментарии. +5% к успеху.
Слой 3. Retry с фидбэком. Если валидация падает — шлём второй запрос: «Ты ошибся в поле budget_rub, ожидалось число». Температуру ставим в 0.0. Обычно чинит с первого раза.
Цифры до и после
- Tolerant extractor → 91%
- мягкая коррекция → 97%
- retry → 99.4%
Оставшиеся 0.6% — совсем безнадёжные сообщения, их сразу в ручную очередь.
Ручная работа упала с семи часов до двадцати минут в неделю. Время от письма до карточки — с 4 часов до 90 секунд.
Сколько это стоит и когда не стоит городить свой парсер
Retry добавляет ~4% к счёту за токены. Одна попытка максимум, дальше — в DLQ. Раз в неделю смотрим, что туда попало, и правим промпты.
Если используешь GigaChat Pro с response_format — бери, будет 96–98%. Но свой tolerant extractor всё равно оставляй как страховку, особенно когда нужно работать с несколькими провайдерами или сложными схемами.
Итог
Структурированный вывод от LLM в проде — это не одна функция в SDK, а небольшой инженерный пайплайн. Промпт делает 80%, остальное — валидация и retry. Кто надеется только на промпт, теряет четверть данных и потом удивляется, куда делись заявки.