Skip to content
PПромтбук
RUEN
03Промт-инжиниринг

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 ─────────────┘

Три уровня:

  1. Parse — синтаксис валидный (JSON.parse / XML parse без ошибок)
  2. Schema — структура соответствует (JSON Schema validator: ajv / zod / pydantic)
  3. 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'а
К подразделу «Промт-инжиниринг»
Похожие промты