Перейти к основному контенту
Tech Path Finder
КурсыИнтервьюКод-ревьюБлог
Tech Path Finder

Персонализированный путеводитель в IT. Квизы, мок-интервью, код ревью и аналитика прогресса.

@potapov_me

Платформа

  • Курсы
  • Прогресс
  • Мок-интервью
  • Код ревью
  • Живое ревью с ИИ
  • Тренажёр переговоров
  • Закладки

Контент

  • Блог
  • Главная
  • Обратная связь

Компания

  • О проекте
  • Тарифы
  • Условия использования
  • Конфиденциальность
  • Согласие на обработку данных
  • Cookie
  • Реквизиты

Аккаунт

  • Войти
  • Зарегистрироваться
  • Профиль

© 2026 Tech Path Finder. Все права защищены.

·ИП Потапов К.С.·Политика конфиденциальности·
Сделано с ❤️ в России
  1. Структурированный вывод
structured_output

Структурированный вывод

JSON, XML, Pydantic + LLM, Function Calling, Constrained Decoding — надёжный вывод данных

Структурированный вывод

Языковые модели по природе своей генерируют свободный текст. Но большинство реальных приложений — от парсеров резюме до агентов автоматизации — нуждаются в предсказуемой структуре данных, которую можно надёжно обработать программно. Структурированный вывод — это набор техник и инструментов, которые позволяют получать от LLM данные в заданном формате.

#Зачем нужен структурированный вывод

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

Структурированный вывод решает эту проблему: модель возвращает данные в формате JSON, XML или YAML, которые программа может обработать напрямую. Это критично для:

  • Интеграции с кодом: результат работы LLM становится входом для следующего шага пайплайна.
  • Валидации данных: структурированный ответ можно проверить схемой.
  • Надёжности: поведение системы становится предсказуемым и тестируемым.
  • Типизации: данные сразу имеют правильные типы (int, bool, list) вместо строк.

#JSON вывод — как просить модель возвращать JSON

Самый простой способ — явно попросить в промпте:

Извлеки из текста вакансии следующую информацию и верни строго в формате JSON:
- title (string): название должности
- skills (array of strings): список требуемых технических навыков
- salary_min (integer | null): минимальная зарплата в рублях
- salary_max (integer | null): максимальная зарплата в рублях
- remote (boolean): возможна ли удалённая работа

Верни только JSON без дополнительного текста.

Текст вакансии:
{vacancy_text}

Добавление "верни только JSON без дополнительного текста" — важная деталь. Без неё модели часто оборачивают JSON в объяснения вроде "Вот результат:" или добавляют markdown-блоки с ```json.

#Техники форсирования структуры

#Прямое указание в промпте

Самый базовый подход — явно описать ожидаемый формат:

Классифицируй отзыв пользователя.
Верни JSON с полями:
- sentiment: "positive", "negative" или "neutral"
- confidence: число от 0 до 1
- reason: одно предложение с обоснованием

#Few-shot пример JSON в промпте

Более надёжный вариант — показать пример целевого JSON:

Классифицируй отзыв. Пример ответа:
{"sentiment": "positive", "confidence": 0.92, "reason": "Пользователь хвалит скорость работы приложения"}

Теперь классифицируй:
"Загрузка занимает 10 секунд, это невыносимо"

Few-shot примеры значительно повышают точность соблюдения формата, особенно для сложных вложенных структур.

#JSON mode и Constrained decoding

OpenAI и Anthropic предоставляют специальные механизмы на уровне API:

OpenAI JSON mode:

response = client.chat.completions.create( model="gpt-4o", response_format={"type": "json_object"}, messages=[{"role": "user", "content": "Верни JSON с полем 'result'"}] )

JSON mode гарантирует, что ответ будет валидным JSON-объектом, но не контролирует его схему.

OpenAI Structured Outputs (более строгий вариант):

from pydantic import BaseModel class JobInfo(BaseModel): title: str skills: list[str] remote: bool response = client.beta.chat.completions.parse( model="gpt-4o-2024-08-06", messages=[...], response_format=JobInfo, ) job = response.choices[0].message.parsed

Constrained decoding работает на уровне генерации токенов: модель в каждый момент может выбирать только те токены, которые соответствуют грамматике JSON (или заданной схеме). Это гарантирует структуру на математическом уровне, а не просто "просит" модель соответствовать ей.

#Проблемы при запросе JSON без ограничений

Когда вы просите JSON только через промпт (без JSON mode или constrained decoding), возникает ряд типичных проблем:

Hallucinated fields — модель добавляет поля, которых не было в схеме:

{"title": "Python Developer", "skills": ["Python", "Django"], "years_required": 3}

Поле years_required не запрашивалось, но модель "решила", что оно полезно.

Wrong types — числа в виде строк, булевы как "yes"/"no":

{"remote": "yes", "salary_min": "от 100000"}

Trailing text — текст после JSON:

{"sentiment": "positive"}

Я также хотел бы отметить, что...

Невалидный JSON — незакрытые скобки, одинарные кавычки вместо двойных, trailing commas.

Nested structure errors — при глубокой вложенности модель нередко теряет структуру или смешивает уровни.

#Pydantic + LLM — структурированный вывод с валидацией

Популярный паттерн в Python: описать схему через Pydantic и передать её описание в промпт.

from pydantic import BaseModel, Field from typing import Optional class VacancyInfo(BaseModel): title: str = Field(description="Название должности") skills: list[str] = Field(description="Список технических навыков") salary_min: Optional[int] = Field(None, description="Минимальная зарплата в рублях, null если не указана") salary_max: Optional[int] = Field(None, description="Максимальная зарплата в рублях, null если не указана") remote: bool = Field(description="True если возможна удалённая работа") # Генерируем JSON Schema и включаем в промпт schema = VacancyInfo.model_json_schema() prompt = f""" Извлеки информацию из вакансии и верни JSON по схеме: {schema} Текст вакансии: {vacancy_text} """ # Парсим ответ с валидацией raw_json = llm_response.strip() vacancy = VacancyInfo.model_validate_json(raw_json)

Это даёт немедленную валидацию и правильную типизацию. Если модель вернёт невалидные данные — Pydantic выбросит исключение, которое можно обработать и повторить запрос.

#XML и YAML вывод — когда и зачем

XML исторически хорошо знаком языковым моделям по обучающим данным (HTML, XML документация). Anthropic активно использует XML-теги в промптах для Claude:

Верни ответ в следующем формате:
<analysis>
  <sentiment>positive</sentiment>
  <key_points>
    <point>Быстрая загрузка</point>
    <point>Интуитивный интерфейс</point>
  </key_points>
</analysis>

XML удобен для потоковой обработки (streaming) — можно начать обработку до получения полного ответа.

YAML используют реже, но он удобен для конфигурационных данных и более читаем при работе с вложенными структурами. Минус — модели чаще ошибаются с отступами.

В большинстве случаев предпочтителен JSON как стандарт де-факто для API и самый распространённый формат в обучающих данных.

#Function Calling и Tool Use для структурированных данных

Function Calling (OpenAI) и Tool Use (Anthropic) — это специальный механизм API, при котором модель вместо текстового ответа "вызывает функцию" с типизированными аргументами.

tools = [{ "type": "function", "function": { "name": "save_vacancy_info", "description": "Сохранить извлечённую информацию о вакансии", "parameters": { "type": "object", "properties": { "title": {"type": "string"}, "skills": {"type": "array", "items": {"type": "string"}}, "remote": {"type": "boolean"} }, "required": ["title", "skills", "remote"] } } }] response = client.chat.completions.create( model="gpt-4o", messages=[{"role": "user", "content": vacancy_text}], tools=tools, tool_choice={"type": "function", "function": {"name": "save_vacancy_info"}} ) args = json.loads(response.choices[0].message.tool_calls[0].function.arguments)

Ключевые отличия Function Calling от "верни JSON":

ПараметрPrompt JSONFunction Calling
Гарантия форматаНетДа (JSON Schema validation)
ТипизацияСлабаяСтрогая
Вероятность hallucinated fieldsВысокаяНизкая
Поддержка в APIВездеЗависит от провайдера
Потоковая обработкаСложнееНативная

#Валидация и обработка ошибок парсинга

Надёжная система работы с LLM всегда включает обработку ошибок парсинга:

import json from pydantic import ValidationError def parse_llm_response(raw: str, schema: type[BaseModel], max_retries: int = 2) -> BaseModel: for attempt in range(max_retries + 1): try: # Попытка извлечь JSON из ответа (модель могла добавить markdown) text = raw.strip() if text.startswith("```"): text = text.split("```")[1] if text.startswith("json"): text = text[4:] data = json.loads(text) return schema.model_validate(data) except (json.JSONDecodeError, ValidationError) as e: if attempt == max_retries: raise # Retry с явным указанием ошибки в промпте raw = ask_llm_to_fix(raw, str(e))

Паттерн "retry с ошибкой" — эффективный способ получить валидный ответ: вы возвращаете модели её ответ + описание ошибки парсинга, и она, как правило, исправляет проблему.

#Практические паттерны

#Extraction (Извлечение)

Извлеки из текста статьи:
- все упомянутые компании (company_mentions: list[string])
- основную тему (topic: string)
- тональность (sentiment: "positive" | "negative" | "neutral")

Верни JSON. Текст: {article}

#Classification (Классификация)

Классифицируй запрос в службу поддержки по категории:
- "billing" — вопросы об оплате и подписке
- "technical" — технические проблемы
- "feature_request" — запросы на новые функции
- "other" — всё остальное

Верни: {"category": "...", "confidence": 0.0-1.0, "summary": "одно предложение о сути запроса"}

#Transformation (Трансформация)

Преобразуй неструктурированный адрес в стандартный формат:
{"street": "...", "city": "...", "postal_code": "...", "country": "..."}

Если поле не определяется — используй null.
Адрес: {address_text}

Далее: RAG и промптинг с контекстом