Instructor или Outlines: JSON от LLM — это выбор между двумя подходами. Instructor строит работу вокруг Pydantic-модели, валидации и повторов запроса при ошибке. Outlines ограничивает форму генерации под конкретным backend. Ниже — карта выбора, полный учебный вход и проверка результата.
Карта выбора под ваши ограничения
Начните с вопроса о том, что уже есть в вашем процессе. Документация Instructor описывает библиотеку для извлечения структурированных данных из LLM с проверкой типов, валидацией и автоматическими повторами. Она построена поверх Pydantic и работает с OpenAI, Anthropic, Google, Mistral, Cohere, Ollama, DeepSeek и другими провайдерами. Если у вас уже есть Pydantic-модели и вы хотите получать ошибки валидации как обычные исключения Python, Instructor встаёт в этот процесс естественно.
Outlines решает другую задачу. Документация проекта заявляет, что библиотека гарантирует структурированный вывод во время генерации. Она работает с OpenAI, Ollama, vLLM, Transformers, llama.cpp, MLX-LM, SgLang, TGI, Anthropic, Gemini и Dottxt. Подход строится на ограничении формы вывода под конкретный backend: вы передаёте желаемый тип, и генерация идёт в его рамках.
Карта выбора выглядит так:
| Ваше ограничение | Кандидат | Что проверить |
|---|---|---|
| Есть Pydantic-процесс с документированными validation errors и повторами | Instructor | Как обрабатываются ошибки валидации в вашем коде |
| Нужна constrained generation под проверенным backend | Outlines | Поддерживает ли ваш backend ограничение формы |
| Нужно гарантировать правильные факты в ответе | Ни один из двух | Сверку с исходным текстом |
Третья строка — ключевая. Валидная схема подтверждает форму ответа. Она не подтверждает, что город назван верно и число взято из текста. Для этого нужна отдельная проверка содержания.
Ручной эталон и фактический выход модели
Возьмите полный учебный вход: «В Осло 3 заявки». Схема результата: city — строка, count — целое число >= 0 либо null. Ручной эталон: {"city": "Осло", "count": 3}.
Промпт для этого входа:
Извлеки город и число заявок из текста. Верни JSON с полями city и count.
count — целое неотрицательное число; если число в тексте отсутствует,
верни null. Не угадывай значения. Текст: В Осло 3 заявки
Ручной эталон — это то, что вы записали сами до запуска. Фактический выход модели — это то, что вернёт конкретная модель через конкретный backend. Эти две вещи нужно разделять в отчёте и в тестах. Ручной эталон фиксирует ваше ожидание. Выход модели показывает, что произошло на самом деле.
Этот пример задаёт учебный договор. Он задаёт вход, схему и ожидаемый ответ. Дальше вы подставляете свою модель и свой backend и смотрите, что получилось.
Для неизвестного числа схема должна явно разрешать null. Вход «В Осло поступили заявки» не содержит числа. Ручной эталон для него: count = null. Модель, которая вернёт 3, нарушит договор, даже если JSON окажется синтаксически корректным.
Проверка положительного и отрицательного случаев
Разделите проверки на два слоя. Первый слой — формат: корректный JSON, допустимые поля, строгий тип count, ограничение >= 0. Второй слой — содержание: city и count совпадают с исходным текстом.
Отрицательные случаи с точными данными:
| Случай | Что проверяем | Ожидаемый результат |
|---|---|---|
count = "три" |
Тип поля | Ошибка типа |
count = -1 |
Граница значения | Ошибка диапазона |
city = "Берлин", count = 3 |
Содержание | Схема проходит, факт неверен |
| Вход без числа | Поведение при отсутствии данных | Ручной эталон count = null |
Третий случай показывает границу возможностей схемы. Ответ {"city": "Берлин", "count": 3} может пройти валидацию полностью: строка на месте, целое неотрицательное число на месте. Схема подтвердила форму. Содержание при этом расходится с текстом, где назван Осло.
Четвёртый случай проверяет, умеет ли модель признавать отсутствие данных. Здесь и помогает явное разрешение null в схеме.
Отдельно про строгий режим. Документация Pydantic описывает strict mode как настройку, которую включают явно: на уровне вызова валидации, на уровне поля или в конфигурации модели. По умолчанию Pydantic работает в lax-режиме и приводит строку '123' к числу 123. Если вам нужна ошибка типа на строковую цифру, strict mode задаётся в схеме или в вызове. Приписывать это поведение настройкам по умолчанию нельзя.
Продуктовый путь, API и условия доступа
Документация Instructor показывает единый интерфейс from_provider. Один и тот же код работает с разными провайдерами:
import instructor
from pydantic import BaseModel
class ExtractUser(BaseModel):
name: str
age: int
client = instructor.from_provider("openai/gpt-4o-mini")
resp = client.create(
response_model=ExtractUser,
messages=[{"role": "user", "content": "Jason is 25 years old."}],
)
Для локального запуска документация приводит instructor.from_provider("ollama/llama3.2"). Асинхронный режим включается параметром async_client=True. Библиотека поддерживает стриминг частичных ответов и списков.
Документация Outlines описывает другой интерфейс: вы передаёте промпт и желаемый тип вывода. Ниже адаптирован пример с главной страницы документации. Клиент OpenAI создаётся отдельно от клиента Instructor.
import outlines
import openai
from pydantic import BaseModel
from typing import Literal
class Customer(BaseModel):
name: str
urgency: Literal["high", "medium", "low"]
issue: str
outlines_client = openai.OpenAI()
model = outlines.from_openai(outlines_client, "gpt-4o")
customer = model("Alice needs help with login issues ASAP", Customer)
Для этого фрагмента нужен отдельный клиент OpenAI с настроенным доступом к провайдеру.
README проекта перечисляет интеграции: серверные — vLLM и Ollama, локальные — transformers и llama.cpp, API — OpenAI, Gemini и Dottxt. Формат возвращаемого значения зависит от backend: в README показан model_validate_json, на главной странице описан объект.
Условия доступа различаются. Локальные backend вроде Ollama и llama.cpp запускаются на вашем железе. API-провайдеры требуют ключа и оплаты вызовов. Dottxt API, по описанию на сайте проекта, находится в раннем доступе и требует запроса.
Чего не обещают ни Instructor, ни Outlines
Универсального качества ответов не даёт ни один из подходов. Валидная схема подтверждает структуру. Правильность фактов проверяется отдельно, сверкой с источником.
Скорость зависит от backend, модели и схемы. Документация Outlines говорит об однократной компиляции ограничений. Это утверждение относится к конкретной реализации и конкретному backend. Переносить его на все модели и провайдеры нельзя.
Приватность определяется местом запуска. Локальный backend держит данные на вашем железе. API-провайдер обрабатывает запрос на своей стороне. Выбор между этими вариантами — вопрос вашей инфраструктуры.
Юридическая достаточность не следует из технической валидации. Корректный JSON с полями city и count не делает извлечённые данные юридически значимыми.
Повторы запроса могут требовать дополнительных платных вызовов. Документация Instructor описывает два параметра с одинаковым именем max_retries: настройка SDK управляет транспортными повторами после первого HTTP-запроса, а параметр в client.create управляет повторами валидации после первой попытки извлечения. Целочисленное значение max_retries в вызове задаёт повторы при ошибках разбора и валидации. Исчерпанная ошибка rate-limit, соединения или сервера оборачивается в InstructorRetryException и автоматически не перезапускается. Умножать настроенные лимиты для отчёта о фактических вызовах нельзя: реальное число запросов определяют решения SDK и успешные ответы. В учебном примере из этого текста платных вызовов не было.
Согласование scope, схемы и критериев приёмки
Зафиксируйте договор до запуска. Он состоит из пяти частей.
Scope: сравнить валидацию с повторами и ограничение формы генерации под конкретным поддерживаемым backend. Возможности Outlines не сводятся к одному локальному режиму, и одинаковые гарантии для всех провайдеров не предполагаются.
Пример: полный вход «В Осло 3 заявки», схема с city и count, ручной эталон {"city": "Осло", "count": 3}.
Схема данных: city — строка, count — целое число >= 0 либо null. Для неизвестного числа схема явно разрешает null.
Критерии приёмки: формат проверяется отдельно от содержания. Формат — корректный JSON, допустимые поля, строгий тип, граница значения. Содержание — совпадение с исходным текстом. Отрицательные случаи прогоняются по таблице выше.
Матрица решений: нужен существующий Pydantic-процесс с документированными ошибками валидации и повторами — Instructor подходит как кандидат. Требуется constrained generation под проверенным backend — Outlines подходит как кандидат. Нужно гарантировать правильный город и число — форма вывода сама этого не доказывает, нужна сверка с источником.
Частые вопросы
Instructor или Outlines: что выбрать для JSON от LLM?
Выбор зависит от вашего процесса. Instructor встраивается в существующий Pydantic-контур с валидацией и повторами. Outlines ограничивает форму генерации под конкретным backend. Оба варианта требуют отдельной проверки содержания ответа.
Чем Instructor отличается от Outlines по подходу?
Instructor строит работу вокруг Pydantic-модели и повторяет запрос при ошибке валидации. Outlines ограничивает генерацию так, чтобы вывод сразу соответствовал заданной структуре. Документация Instructor описывает повторы через Tenacity, документация Outlines — компиляцию ограничений.
Можно ли использовать Outlines с OpenAI?
README проекта перечисляет OpenAI среди API-интеграций. Пример из документации показывает outlines.from_openai(client, "gpt-4o"). Формат возвращаемого значения зависит от backend, поэтому проверьте его в своём коде.
Как проверить, что JSON содержит правильные данные?
Разделите проверки. Формат: корректный JSON, допустимые поля, типы, границы. Содержание: сверка значений с исходным текстом. Ответ {"city": "Берлин", "count": 3} для текста про Осло проходит схему и расходится с фактом.
Нужен ли strict mode в Pydantic?
Strict mode включается явно, когда нужна ошибка типа на строковую цифру. По умолчанию Pydantic приводит строку '123' к числу 123. Если вам важно отвергать строковые цифры, задайте строгость в схеме или в вызове валидации.
