Структурированный ответ вместо разбора свободного текста
Почему не стоит вытаскивать данные из ответа модели регулярками, как описать схему, проверить результат и что делать, когда проверка не прошла.
В первой версии почти любой функции с моделью есть строчка вроде «верни ответ в формате: Категория: …, Приоритет: …». Дальше код режет текст по двоеточиям. Неделю это работает. Потом модель отвечает «Конечно! Вот результат:», добавляет звёздочки для жирного шрифта или пишет «приоритет высокий» по-русски вместо high, и разбор молча ломается.
Проблема не в модели. Ты попросил текст и получил текст. Если коду нужны данные, их и надо просить.
Попроси JSON по схеме
Большинство крупных провайдеров умеют возвращать ответ, который соответствует заданной JSON-схеме: либо через специальный режим структурированного вывода, либо через вызов инструмента, аргументы которого описаны схемой. Если такой режим есть — используй его, а не просьбу в промпте. Разница как между «пожалуйста, заполни аккуратно» и формой с обязательными полями.
Схема описывает, что именно тебе нужно:
{
"type": "object",
"properties": {
"category": { "type": "string", "enum": ["refund", "delivery", "other"] },
"priority": { "type": "string", "enum": ["low", "normal", "high"] },
"order_id": { "type": ["string", "null"] },
"summary": { "type": "string", "maxLength": 200 }
},
"required": ["category", "priority", "order_id", "summary"]
}
Несколько привычек, которые экономят время:
- перечисления вместо свободных строк — если вариантов конечное число, перечисли их. Тогда «высокий», «High» и «срочно» не появятся;
- явный
null— если значения может не быть, разрешиnullи скажи об этом в описании поля. Иначе модель придумает номер заказа, лишь бы поле не пустовало; - описания у полей — короткая подсказка, что класть в поле, работает лучше длинного абзаца в промпте;
- плоская структура — чем глубже вложенность, тем больше мест для ошибки. Если можно обойтись одним уровнем, обойдись.
Проверяй всё равно
Даже в строгом режиме проверь результат своим кодом. Во-первых, не все провайдеры и не все модели гарантируют соответствие схеме одинаково. Во-вторых, схема проверяет форму, а не смысл: поле order_id может оказаться заполнено номером, которого нет во входном тексте.
Валидация состоит из двух слоёв:
- форма — тип, обязательные поля, допустимые значения. Для этого есть готовые библиотеки в любом языке, и одна и та же схема может описывать и запрос к модели, и проверку;
- смысл — бизнес-правила: номер заказа существует и принадлежит этому клиенту, сумма не отрицательная, дата не в прошлом веке.
Всё, что прошло проверку, становится обычными данными с типами. Всё, что не прошло, не уходит дальше ни в базу, ни к пользователю.
Когда проверка не прошла
Не надо бесконечно перезапрашивать. Разумный порядок:
- один повтор с ошибкой — отправь модели её же ответ и конкретную ошибку валидации: «поле priority должно быть одним из low, normal, high». Чаще всего этого хватает;
- запасной вариант — если и повтор не прошёл, функция должна вести себя предсказуемо: значение по умолчанию, пометка «нужна проверка человеком» или честная ошибка в интерфейсе;
- запись в журнал — каждый провал сохраняй с входом и ответом. Если одно поле падает регулярно, дело в схеме или описании, и это видно только по накопленным случаям.
Не смешивай рассуждение и данные
Иногда хочется, чтобы модель сначала «подумала», а потом выдала результат. Это нормально, но держи рассуждение в отдельном поле схемы или в отдельном шаге, а не в тексте вокруг JSON. Код читает поля, человек при отладке — рассуждение, и никто не путается.
Итог
Свободный текст — для людей, структура — для кода. Опиши схему, используй режим структурированного вывода, если провайдер его даёт, проверяй форму и смысл и заранее реши, что делать при провале. После этого ответ модели перестаёт быть строкой, которую страшно трогать, и становится обычным входом с валидацией.
SUBXCAT может перевести функции твоего продукта со свободного текста на схемы с проверкой и посмотреть, где разбор сейчас тихо ломается.