Structured output: JSON schema vs XML tags
Когда выбирать JSON schema, когда XML tags, как валидировать вывод и что делать при parse-fail.
Спроектируй structured output для {{use_case}}. Цель — выход, который downstream-код парсит без LLM-постпроцессинга, и где можно автоматически отбраковать невалидное.
1. Когда какой формат
| Формат | Когда брать | Когда НЕ брать |
|---|---|---|
| JSON schema (constrained / tool-call) | Жёсткая форма, downstream — типизированный код | Длинный narrative-текст, смешение prose + поля |
| XML tags | Смесь свободного текста и поименованных секций; CoT + ответ | Когда нужна машинная валидация типов |
| YAML | Человекочитаемо, многострочные строки, конфиги | Когда часто ломается на отступах |
| Markdown с якорями | Документ для человека + LLM | Когда нужен парсинг |
Правило: для машины → JSON. Для смеси "подумай + ответ" → XML tags. YAML — только если ты контролируешь, что модель не ломает отступы.
2. Минимальный JSON schema (для {{use_case}})
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"additionalProperties": false,
"required": ["status", "result", "confidence"],
"properties": {
"status": { "enum": ["ok", "partial", "failed"] },
"result": {
"type": "object",
"additionalProperties": false,
"required": ["items"],
"properties": {
"items": {
"type": "array",
"items": {
"type": "object",
"required": ["id", "value"],
"properties": {
"id": { "type": "string", "pattern": "^[a-z0-9_-]+$" },
"value": { "type": "string", "maxLength": 500 }
}
}
}
}
},
"confidence": { "type": "number", "minimum": 0, "maximum": 1 },
"errors": { "type": "array", "items": { "type": "string" } }
}
}
Обязательно:
additionalProperties: false— иначе модель добавит "полезных" полейrequiredна всём, что критичноenumдля категорий — никаких свободных строкpattern/maxLengthгде возможно — отсекает мусор на валидации
3. XML tags (когда смесь prose + поля)
<analysis>
[здесь модель думает свободно]
</analysis>
<decision>approve</decision>
<reasoning>
[одна-две причины]
</reasoning>
<next_steps>
- step 1
- step 2
</next_steps>
Плюсы: модель может "подумать" в <analysis> без поломки парсера; ты вытаскиваешь только нужные tags. Минус: типы не валидируются — <decision>foo</decision> пройдёт парсер.
Лечится: regex/enum check на содержимое каждого tag после парсинга.
4. Validation pipeline
[LLM output] → [parse] → [schema validate] → [semantic validate] → OK
│ │ │
└ malformed └ schema-fail └ business-rule-fail
│ │ │
└──────── retry policy ─────────────┘
Три уровня:
- Parse — синтаксис валидный (JSON.parse / XML parse без ошибок)
- Schema — структура соответствует (JSON Schema validator: ajv / zod / pydantic)
- Semantic — бизнес-правила (
idсуществует в БД; сумма items = total; даты в будущем)
Без всех трёх отдашь в продакшен мусор.
5. Retry on parse-fail
Не молча retry — это слепота. Дай модели diff-feedback:
Предыдущий ответ был невалидный. Ошибки:
- `result.items[2].id`: pattern `^[a-z0-9_-]+$` не совпал (был: "User Name")
- `confidence`: ожидалось число 0..1, был null
Верни валидный JSON по той же схеме. Не объясняй ошибку — только корректный output.
Политика:
- Maximum 2 retry (3-я попытка обычно те же ошибки)
- Если после 2 retry parse-fail — fallback (другой prompt / другая модель / human-in-loop)
- Логируй каждый retry для оффлайн-анализа (часто prompt сам виноват)
6. Когда constrained generation (tool-call mode) лучше промпта
Если SDK поддерживает (Anthropic tool-call, OpenAI function-call) — бери его для production:
- Гарантия синтаксической валидности (parse никогда не падает)
- Гарантия структуры (schema-fail почти невозможен)
- Остаётся только semantic-валидация
Промпт-based JSON — когда нужно multi-output или модель/раннер не поддерживают tool-call.
7. Anti-patterns
- ❌ Просить "верни JSON" без schema — модель импровизирует поля
- ❌
additionalProperties: true(default!) — модель добавит "helpful" поля и сломает downstream - ❌ Парсить вывод regex'ом по ```json — модель иногда не оборачивает / оборачивает дважды
- ❌ Свободный текст внутри JSON-строки без escape — ломается на кавычках в content
- ❌ Smart-quotes / em-dash в строках — некоторые парсеры подавятся; нормализуй на входе
- ❌ Огромный JSON одним блоком — модель урезает на лимите токенов; разбивай на части
- ❌ Retry без feedback — модель повторяет ту же ошибку
- ❌ Бесконечный retry-loop — токены сгорают, ничего не работает
- ❌ Schema validation в проде без логирования провалов — не узнаешь что prompt сломан
- ❌ XML tags без regex/enum-check внутри —
<decision>banana</decision>приедет в downstream - ❌ YAML с многоуровневыми вложенностями — модель ломает отступы регулярно; бери JSON
Deliverable
- JSON schema (или XML spec) под
{{use_case}} - Список semantic-правил
- Retry policy (max retry, feedback shape, fallback)
- Logging plan: что писать на каждом fail
- 5 "плохих" examples с ожидаемой реакцией validator'а
Аудит всех форм и input'ов
Каждая форма на сайте через 14 чек-пунктов: validation, paste/autofill/autocomplete, error states, multi-step, mobile keyboard, server-fail, accessibility. Самый частый источник тихих багов.
Микро-взаимодействия в формах
Focus, валидация, успех, восстановление: тайминг и поведение полей шаг за шагом.
Многошаговая форма: дизайн UX
Прогресс, save-state, back-поведение, validation timing, recovery после ошибки. Без «5 шагов и потерянные данные на refresh».