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.

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

Прогноз цены Ethereum: Цена ETH удерживает ключевую зону $2 708–$2 808, пока фокус смещается на прорыв $3 500, $9 300 рассматривается как макросценарий

Прогноз цены Ethereum: Цена ETH удерживает ключевую зону $2 708–$2 808, пока фокус смещается на прорыв $3 500, $9 300 рассматривается как макросценарий

Ethereum (ETH) демонстрирует осторожную силу вблизи критической поддержки, при этом быки нацелены на краткосрочный прорыв выше $3 500, в то время как долгосрочные прогнозы предполагают
Поделиться
Brave Newcoin2025/12/19 22:00
Уильямс из Федеральной резервной системы обращается к опасениям по поводу искажения данных ИПЦ

Уильямс из Федеральной резервной системы обращается к опасениям по поводу искажения данных ИПЦ

Статья «Уильямс из Федеральной резервной системы обращается к проблемам искажения данных ИПЦ» появилась на BitcoinEthereumNews.com. Ключевые моменты: Уильямс из ФРС обращается к искажению данных ИПЦ
Поделиться
BitcoinEthereumNews2025/12/20 04:12
Новые правила безопасности ChatGPT для подростков появились на фоне приближающегося регулирования ИИ

Новые правила безопасности ChatGPT для подростков появились на фоне приближающегося регулирования ИИ

Статья «Новые правила безопасности ChatGPT для подростков в условиях надвигающегося регулирования ИИ» появилась на BitcoinEthereumNews.com. В переломный момент для управления искусственным интеллектом
Поделиться
BitcoinEthereumNews2025/12/20 04:30