Structured Output это способ “заставить” модель отвечать в строго заданном формате.Пример. Имеется пачка неструктурированных объявлений о продаже недвижимости.ПStructured Output это способ “заставить” модель отвечать в строго заданном формате.Пример. Имеется пачка неструктурированных объявлений о продаже недвижимости.П

Виды Structured Output и способы их реализации

Structured Output это способ “заставить” модель отвечать в строго заданном формате.

Пример. Имеется пачка неструктурированных объявлений о продаже недвижимости.

И мы хотим с помощью LLM их перевести в структурированные и положить в базу данных:

{ "Площадь": 35.6, "Этаж": 11, "Кол-во комнат": 1, "Адрес": "ул. Академика Королёва, 121" }

Чтобы добиться этого, есть три основных подхода:

  1. Повторение (Instructor)

  2. Исправление (BAML)

  3. Ограничение (Outlines)

1. Повторение (Instructor)

Самый известный представитель данного метода - библиотека Instructor.

Она работает как посредник между вашим приложением и LLM:

  • Вы описываете структуру правильного ответа (через библиотеку Pydantic).

  • Instructor отправляет запрос и получает ответ:

    • Если ответ проходит проверку на структуру, то возвращает его вам.

    • Если ответ не прошел валидацию, Instructor автоматически отправляет модели ошибку и просит исправить. И так продолжается до тех пор, пока не будет получен правильный ответ или пока не закончатся попытки.

11245ba85349cf2d0d38fc12f79fbf67.png

Пример использования:

import instructor from openai import OpenAI from pydantic import BaseModel, Field, field_validator # Подключаем LLM client = instructor.from_openai( OpenAI(base_url="http://192.168.0.108:8000/v1", api_key="any"), mode=instructor.Mode.TOOLS, mode=instructor.Mode.MD_JSON ) # Определяем структуру данных, которую хотим получить class UserInfo(BaseModel): name: str = Field(..., description="Имя пользователя") age: int = Field(..., description="Возраст пользователя") skills: list[str] = Field(..., description="Список профессиональных навыков") @field_validator('age') def validate_age(cls, v): if v < 0: raise ValueError('Age must be positive') return v # Отправляем запрос result = client.chat.completions.create( model="qwen3", response_model=UserInfo, messages=[ { "role": "user", "content": "Меня зовут Иван, мне 28 лет. Я эксперт в Python, Docker и Kubernetes." } ], max_retries = 3 ) print(result.model_dump_json(indent=3))

Здесь мы:

  • Подключаемся к LLM.

  • Описываем нужный формат ответа с помощью библиотеки Pydantic.

  • Формируем и отправляем запрос.

Обратите внимание на параметр max_retries - именно столько раз Instructor будет пытаться исправить ответ, если с первого раза он был неправильный.

Instructor поддерживает два основных режима работы:

  • TOOLS - в этом режиме вывод объявляется как функция (Function Calling) и модель вызывает ее, передавая ей параметры (поля описанные через Pydantic).

  • JSON - тут мы просим модель просто напечатать ответ в JSON и парсим его.

С таким подходом instructor может работать практически с любыми моделями, как локальными, так и по API. Даже если API не поддерживает Function Calling, Instructor всегда может попросить модель напечатать ответ в виде JSON.

Недостаток очевиден - такой подход может сожрать много токенов на попытки исправить ответ (особенно с мелкими моделями). И даже это не гарантирует результат.

2. Исправление (BAML)

Данный метод пытается исправить основной недостаток предыдущего метода :)

BAML это не просто библиотека а целый фреймворк. Он состоит из своего собственного языка разметки (похожего на TS/Jinja), а также имеет свой "мягкий" парсер, который чинит сломанный JSON.

Работает он несколько сложнее, чем предыдущий метод:

1) Сначала инициируем новый проект: baml-cli init

2) BAML создаст три файла (которые вам нужно заполнить/доработать):

//baml_test/baml_src/clients.baml client<llm> Qwen3 { provider "openai-generic" options { base_url "http://192.168.0.108:8000/v1" api_key "any" model "qwen3" } }

//baml_test/baml_src/generators.baml generator target { output_type "python/pydantic" output_dir "../" version "0.214.0" default_client_mode sync }

//baml_test/baml_src/resume.baml class Resume { name string email string experience string[] skills string[] } function ExtractResume(resume: string) -> Resume { client "Qwen3" prompt #" Extract from this content: {{ resume }} {{ ctx.output_format }} "# }

Для чего они:

  • В clients.baml вы описываете как подключаться к LLM.

  • В generators.baml вы описываете как “компилировать” ваш проект.

  • В resume.baml (называться может как угодно - в данном примере мы парсим резюме поэтому и resume) мы объявляем функцию ExtractResume, в которой описываем:

    • Структуру правильного ответа

    • И функцию, которая объединяет: LLM-клиента, формат вывода и промт.

3) Затем в терминале запускаем baml-cli generate и BAML создаст в папке baml_client типизированный клиент (кучу py-файлов), который вы сможете запускать в своем коде:

import baml_client as client raw_resume = 'Иван Петров. 10 лет. Кодил 20 лет. C#' answer = client.b.ExtractResume(raw_resume)

Такой подход позволяет использовать даже мелкие модели. Почти любая LLM способна написать JSON. Но чем мельче модель, тем больше вероятность ошибки. А BAML аккуратно нивелирует этот недостаток, не тратя ни время ни токены.

Новый JSON он новый конечно не напишет, но вот мелкие типовые ошибки вполне исправит:

  • Забытые закрывающие скобки

  • Висячие запятые

  • Неверно экранированные символы

  • Лишние комментарии

  • Текст перед или после JSON

  • И т.д.

Из минусов: нужно учить новый синтаксис (DSL). Плюс требуется этап "компиляции” кода.

Работает с любыми API и локальными моделями.

Также есть удобный аддон для VS Code, в котором вы можете без запуска LLM тестировать ваши шаблоны.

67edb9b6b4a26a8c758b63f4fddd3c82.png

3. Ограничение (Outlines)

36c812e924d52fcdb943ff7d01717e09.png

По научному этот метод называется Constrained Decoding (ограниченное декодирование) - самый “надежный” метод. А самая популярная библиотека для реализации - Outlines.

Если два предыдущих способа “просят” модель написать правильно. То Constrained Decoding ничего не просит, а заставляет модель выводить строго то, что нужно. Как это работает:

  • Вы описываете структуру правильного ответа (разными способами).

  • LLM работают итеративно. На каждом шаге, выдавая по одному токену за раз. А выбирают они эти токены из огромного словаря. И на каждом шаге LLM расставляет всем токенам вероятности появления. И чем выше вероятность, тем выше шанс, что LLM выберет этот токен. А Outlines на каждом шаге "маскирует" (обнуляет вероятности) всех токенов, которые нарушили бы схему. И модели остается выбор только из допустимых токенов.

Например, если ваша схема требует {"name": string}, то:

  • На первом шаге занулит все токены кроме открывающей фигурной скобки.

  • В последующих шагах разрешено будет написать только "name".

  • И т.д.

А в коде это выглядит так:

from pydantic import BaseModel from typing import Literal from openai import OpenAI import outlines openai_client = OpenAI(base_url="http://192.168.0.108:8000/v1", api_key="any") model = outlines.from_vllm(openai_client, "qwen3") class Customer(BaseModel): name: str urgency: Literal["high", "medium", "low"] issue: str customer = model( "Alice needs help with login issues ASAP", Customer) print(customer)

Данный метод работает на уровне логитов. А значит до этих логитов надо как-то добраться. Если библиотекой инференса является transformers, то Outlines напрямую доберется до логитов и занулит их. Если Outlines работает с API, то воспользуется их возможностями. Например, для vLLM через параметр extra_body.

Outlines поддерживает множество движков инференса: OpenAI, Ollama, vLLM, LlamaCpp, Transformers. А формат вывода может описываться разными способами: Regex, JSON Schema, Context-Free Grammar.

Плюсы: 100% гарантия соответствия схеме вывода (причем с первой попытки). Что идеально для небольших локальных моделей, так как не требует от модели быть "умной", чтобы соблюдать формат.

Минусы: не всегда можно задействовать при работе с API (SO может просто не поддерживаться).

А теперь серьезная ложка дёгтя: есть исследования, которые показывают, что жесткое декодирование делает модель тупее :) Пример одного из последних: https://acl-bg.org/proceedings/2025/RANLP%202025/pdf/2025.ranlp-1.124.pdf

Но и есть парочка лайфхаков как обойти эту проблему. Например, вам нужно вывести строгий JSON, который начинается с открывающей фигурной скобки. Но модель может лучше ответить, если ей сначала дать немного подумать (CoT). Что тут можно сделать:

1) Вы можете дать ей подумать в самом JSON’е. Для этого вначале JSON заводите специальное поле для ризонинга, а уже дальше формируете нужный вам ответ:

{ "reasoning": string, "answer": string/number }

2) Второй способ работает в два этапа. Сначала вы просто задаете модели вопрос и она отвечает как хочет. Затем вы подсовываете первый ответ во второй запрос и просите вытащить из него ответ и задаете строгий формат вывода.

Вместо вывода

Начать нужно как минимум с Outlines (и Constrained Decoding). Возможно интеллекта вашей модели вполне хватит для решения ваших задач. Но если вы не можете залезть в мозги модели, то тогда переходите к Instructor. Если и он не справляется, то следующий кандидат - BAML. BAML несколько громоздкий для простых задач, его лучше использовать комплексно на больших проектах.


Мои курсы: Разработка LLM с нуля | Алгоритмы Машинного обучения с нуля

Источник

Отказ от ответственности: Статьи, размещенные на этом веб-сайте, взяты из общедоступных источников и предоставляются исключительно в информационных целях. Они не обязательно отражают точку зрения MEXC. Все права принадлежат первоисточникам. Если вы считаете, что какой-либо контент нарушает права третьих лиц, пожалуйста, обратитесь по адресу service@support.mexc.com для его удаления. MEXC не дает никаких гарантий в отношении точности, полноты или своевременности контента и не несет ответственности за любые действия, предпринятые на основе предоставленной информации. Контент не является финансовой, юридической или иной профессиональной консультацией и не должен рассматриваться как рекомендация или одобрение со стороны MEXC.

Вам также может быть интересно

Обзор рынка США в середине дня: акции растут на фоне поддержки AI-сектора со стороны Nvidia, Nasdaq лидирует по росту

Обзор рынка США в середине дня: акции растут на фоне поддержки AI-сектора со стороны Nvidia, Nasdaq лидирует по росту

Американские акции продемонстрировали устойчивость в пятницу, поскольку Nvidia и другие управляемые ИИ полупроводниковые компании обеспечили технологический отскок, подняв индекс Nasdaq Composite выше, в то время как
Поделиться
Coinstats2025/12/20 03:41
Основатель альткоина, листингованного на Binance, ответил на заявление о том, что "криптовалюты мертвы"

Основатель альткоина, листингованного на Binance, ответил на заявление о том, что "криптовалюты мертвы"

Сидни Пауэлл, основатель альткоина Maple Finance, который также торгуется на Binance, высказался о криптовалютах. Читать далее: Основатель проекта, торгующегося на Binance
Поделиться
Coinstats2025/12/20 04:11
XAG/USD достигает рекордных $67,45, цель — $68,00

XAG/USD достигает рекордных $67,45, цель — $68,00

Публикация XAG/USD достигает рекордного уровня $67,45, цель $68,00 появилась на BitcoinEthereumNews.com. Цена серебра растет до нового исторического максимума $67,45, несмотря на то, что US
Поделиться
BitcoinEthereumNews2025/12/20 04:32