/
Вопросы и ответы
/
Промт-инжиниринг
/

Структурированный вывод LLM: как получить предсказуемый формат

Структурированный вывод LLM: как получить предсказуемый формат

9 часов назад

Никита Вихров

Ответы

0

Структурированный вывод LLM: как получить предсказуемый формат

Свободный текст от модели — головная боль в продакшне. Парсить его ненадёжно, формат меняется от запроса к запросу. Структурированный вывод — способ заставить модель отвечать в строгом формате который можно сразу использовать в коде.


Проблема свободного текста

from anthropic import Anthropic

client = Anthropic()

# Один и тот же запрос — разные форматы ответа
for _ in range(3):
    response = client.messages.create(
        model="claude-opus-4-5",
        max_tokens=256,
        messages=[{"role": "user", "content": "Извлеки имя и возраст: 'Иван Петров, 32 года'"}]
    )
    print(response.content[0].text)
    print("---")

# Первый запрос:  "Имя: Иван Петров\nВозраст: 32"
# Второй запрос:  "Иван Петров (32 года)"
# Третий запрос:  "{"name": "Иван Петров", "age": 32}"
# Парсить это программно — nightmare

Уровень 1: JSON через промпт

from pydantic import BaseModel
import json
import re


class Person(BaseModel):
    name: str
    age: int
    city: str | None = None


def extract_person(text: str) -> Person | None:
    response = client.messages.create(
        model="claude-opus-4-5",
        max_tokens=256,
        system="""Извлекай данные и возвращай ТОЛЬКО валидный JSON.
Без markdown, без пояснений, только JSON-объект.""",
        messages=[{
            "role": "user",
            "content": f"""Извлеки из текста: name (string), age (integer), city (string или null).

Текст: {text}

JSON:"""
        }]
    )

    try:
        raw = re.sub(r'```(?:json)?|```', '', response.content[0].text).strip()
        return Person(**json.loads(raw))
    except Exception as e:
        print(f"Ошибка парсинга: {e}, сырой ответ: {response.content[0].text}")
        return None


result = extract_person("Иван Петров, 32 года, живёт в Москве")
print(result)
# → name='Иван Петров' age=32 city='Москва'

Уровень 2: JSON Schema в системном промпте

Передай схему явно — модель следует ей точнее:

from pydantic import BaseModel
from typing import Literal


class CodeReviewResult(BaseModel):
    verdict: Literal["APPROVE", "REQUEST_CHANGES", "NEEDS_WORK"]
    critical_issues: list[str]
    warnings: list[str]
    suggestions: list[str]
    summary: str


def review_with_schema(code: str) -> CodeReviewResult | None:
    schema = CodeReviewResult.model_json_schema()

    response = client.messages.create(
        model="claude-opus-4-5",
        max_tokens=1024,
        system=f"""Ты — code reviewer. Отвечай ТОЛЬКО валидным JSON по этой JSON Schema:

{json.dumps(schema, ensure_ascii=False, indent=2)}

Никакого текста вне JSON.""",
        messages=[{"role": "user", "content": f"Проверь код:\n```python\n{code}\n```"}]
    )

    try:
        raw = re.sub(r'```(?:json)?|```', '', response.content[0].text).strip()
        return CodeReviewResult(**json.loads(raw))
    except Exception as e:
        print(f"Ошибка: {e}")
        return None


result = review_with_schema("def foo(x): return x*2")
if result:
    print(f"Вердикт: {result.verdict}")
    print(f"Критические: {result.critical_issues}")

Уровень 3: XML для сложных структур

JSON не всегда удобен когда в данных есть многострочный код или текст с кавычками:

import xml.etree.ElementTree as ET
from dataclasses import dataclass


@dataclass
class BugReport:
    severity: str
    location: str
    description: str
    fix: str


def extract_bugs_xml(code: str) -> list[BugReport]:
    response = client.messages.create(
        model="claude-opus-4-5",
        max_tokens=2048,
        system="""Анализируй код и возвращай баги в XML.
Формат ответа — только XML, без другого текста:

<bugs>
  <bug>
    <severity>CRITICAL|WARNING|INFO</severity>
    <location>имя функции или строка</location>
    <description>описание проблемы</description>
    <fix>как исправить с примером кода</fix>
  </bug>
</bugs>

Если багов нет: <bugs></bugs>""",
        messages=[{"role": "user", "content": f"```python\n{code}\n```"}]
    )

    try:
        xml_text = response.content[0].text.strip()
        root = ET.fromstring(xml_text)

        bugs = []
        for bug_elem in root.findall("bug"):
            bugs.append(BugReport(
                severity=bug_elem.findtext("severity", ""),
                location=bug_elem.findtext("location", ""),
                description=bug_elem.findtext("description", ""),
                fix=bug_elem.findtext("fix", ""),
            ))
        return bugs
    except ET.ParseError as e:
        print(f"Ошибка XML: {e}")
        return []

Когда что использовать

JSON — для простых структур с примитивными типами. Работает в 95% случаев, легко валидировать через Pydantic.

XML — когда в данных есть многострочный текст, код, символы которые ломают JSON (кавычки, обратные слэши).

Свободный текст — когда результат читает человек, а не парсит код.

Главное правило: если результат LLM идёт на вход другой системы или функции — всегда используй структурированный формат с валидацией.


На курсе «ИИ для разработчиков» на Хекслете разбирают как интегрировать LLM в реальные приложения: структурированный вывод, обработка ошибок, продакшн-архитектура.

9 часов назад

Никита Вихров

+7 800 100 22 47

бесплатно по РФ

+7 495 085 21 62

бесплатно по Москве

108813 г. Москва, вн.тер.г. поселение Московский,
г. Московский, ул. Солнечная, д. 3А, стр. 1, помещ. 20Б/3
ОГРН 1217300010476
ИНН 7325174845