Перейти к основному содержимому

Instructor

Извлекайте структурированные данные из ответов LLM с валидацией Pydantic, автоматически повторяйте неудачные извлечения, парсите сложный JSON с типобезопасностью и стримьте частичные результаты с Instructor — проверенной библиотекой структурированного вывода.

Метаданные навыка​

ИсточникОпционально — установка через vibeos skills install official/mlops/instructor
Путьoptional-skills/mlops/instructor
Версия1.0.0
АвторOrchestra Research
ЛицензияMIT
Зависимостиinstructor, pydantic, openai, anthropic
Платформыlinux, macos, windows
ТегиPrompt Engineering, Instructor, Structured Output, Pydantic, Data Extraction, JSON Parsing, Type Safety, Validation, Streaming, OpenAI, Anthropic

Справочник: полный SKILL.md​

к сведению

Ниже приведено полное определение навыка, которое VibeOS загружает при его активации. Это те инструкции, которые видит агент, когда навык активен.

Instructor: Структурированные выводы LLM

Когда использовать этот навык​

Используйте Instructor, когда вам нужно:

  • Надёжно извлекать структурированные данные из ответов LLM
  • Автоматически валидировать выводы по схемам Pydantic
  • Автоматически повторять неудачные извлечения с обработкой ошибок
  • Парсить сложный JSON с типобезопасностью и валидацией
  • Стримить частичные результаты для обработки в реальном времени
  • Поддерживать несколько LLM-провайдеров с единым API

Звёзды на GitHub: 15 000+ | Проверено в бою: 100 000+ разработчиков

Установка​

# Базовая установка
pip install instructor

# С конкретными провайдерами
pip install "instructor[anthropic]" # Anthropic Claude
pip install "instructor[openai]" # OpenAI
pip install "instructor[all]" # Все провайдеры

Быстрый старт​

Базовый пример: Извлечение данных пользователя​

import instructor
from pydantic import BaseModel
from anthropic import Anthropic

# Определяем структуру вывода
class User(BaseModel):
name: str
age: int
email: str

# Создаём клиент instructor
client = instructor.from_anthropic(Anthropic())

# Извлекаем структурированные данные
user = client.messages.create(
model="claude-sonnet-4-5-20250929",
max_tokens=1024,
messages=[{
"role": "user",
"content": "John Doe is 30 years old. His email is john@example.com"
}],
response_model=User
)

print(user.name) # "John Doe"
print(user.age) # 30
print(user.email) # "john@example.com"

С OpenAI​

from openai import OpenAI

client = instructor.from_openai(OpenAI())

user = client.chat.completions.create(
model="gpt-4o-mini",
response_model=User,
messages=[{"role": "user", "content": "Extract: Alice, 25, alice@email.com"}]
)

Основные концепции​

1. Модели ответов (Pydantic)​

Модели ответов определяют структуру и правила валидации для выводов LLM.

Базовая модель​

from pydantic import BaseModel, Field

class Article(BaseModel):
title: str = Field(description="Заголовок статьи")
author: str = Field(description="Имя автора")
word_count: int = Field(description="Количество слов", gt=0)
tags: list[str] = Field(description="Список релевантных тегов")

article = client.messages.create(
model="claude-sonnet-4-5-20250929",
max_tokens=1024,
messages=[{
"role": "user",
"content": "Analyze this article: [article text]"
}],
response_model=Article
)

Преимущества:

  • Типобезопасность с подсказками типов Python
  • Автоматическая валидация (word_count > 0)
  • Самодокументирование с описаниями Field
  • Автодополнение в IDE

Вложенные модели​

class Address(BaseModel):
street: str
city: str
country: str

class Person(BaseModel):
name: str
age: int
address: Address # Вложенная модель

person = client.messages.create(
model="claude-sonnet-4-5-20250929",
max_tokens=1024,
messages=[{
"role": "user",
"content": "John lives at 123 Main St, Boston, USA"
}],
response_model=Person
)

print(person.address.city) # "Boston"

Опциональные поля​

from typing import Optional

class Product(BaseModel):
name: str
price: float
discount: Optional[float] = None # Опционально
description: str = Field(default="No description") # Значение по умолчанию

# LLM не обязана предоставлять discount или description

Перечисления для ограничений​

from enum import Enum

class Sentiment(str, Enum):
POSITIVE = "positive"
NEGATIVE = "negative"
NEUTRAL = "neutral"

class Review(BaseModel):
text: str
sentiment: Sentiment # Разрешены только эти 3 значения

review = client.messages.create(
model="claude-sonnet-4-5-20250929",
max_tokens=1024,
messages=[{
"role": "user",
"content": "This product is amazing!"
}],
response_model=Review
)

print(review.sentiment) # Sentiment.POSITIVE

2. Валидация​

Pydantic автоматически валидирует выводы LLM. Если валидация не удаётся, Instructor повторяет попытку.

Встроенные валидаторы​

from pydantic import Field, EmailStr, HttpUrl

class Contact(BaseModel):
name: str = Field(min_length=2, max_length=100)
age: int = Field(ge=0, le=120) # 0 <= age <= 120
email: EmailStr # Валидирует формат email
website: HttpUrl # Валидирует формат URL

# Если LLM предоставляет неверные данные, Instructor автоматически повторяет попытку

Пользовательские валидаторы​

from pydantic import field_validator

class Event(BaseModel):
name: str
date: str
attendees: int

@field_validator('date')
def validate_date(cls, v):
"""Проверяет, что дата в формате ГГГГ-ММ-ДД."""
import re
if not re.match(r'\d{4}-\d{2}-\d{2}', v):
raise ValueError('Дата должна быть в формате ГГГГ-ММ-ДД')
return v

@field_validator('attendees')
def validate_attendees(cls, v):
"""Проверяет положительное количество участников."""
if v < 1:
raise ValueError('Должен быть хотя бы 1 участник')
return v

Валидация на уровне модели​

from pydantic import model_validator

class DateRange(BaseModel):
start_date: str
end_date: str

@model_validator(mode='after')
def check_dates(self):
"""Проверяет, что end_date после start_date."""
from datetime import datetime
start = datetime.strptime(self.start_date, '%Y-%m-%d')
end = datetime.strptime(self.end_date, '%Y-%m-%d')

if end < start:
raise ValueError('end_date должна быть после start_date')
return self

3. Автоматические повторные попытки​

Instructor автоматически повторяет попытку при неудачной валидации, передавая LLM информацию об ошибке.

# До 3 повторных попыток при неудачной валидации
user = client.messages.create(
model="claude-sonnet-4-5-20250929",
max_tokens=1024,
messages=[{
"role": "user",
"content": "Extract user from: John, age unknown"
}],
response_model=User,
max_retries=3 # По умолчанию 3
)

# Если age не удаётся извлечь, Instructor сообщает LLM:
# "Validation error: age - field required"
# LLM пытается снова с более точным извлечением

Как это работает:

  1. LLM генерирует вывод
  2. Pydantic валидирует
  3. Если неверно: сообщение об ошибке отправляется обратно LLM
  4. LLM пытается снова с учётом ошибки
  5. Повторяется до max_retries

4. Стриминг​

Стримьте частичные результаты для обработки в реальном времени.

Стриминг частичных объектов​

from instructor import Partial

class Story(BaseModel):
title: str
content: str
tags: list[str]

# Стриминг частичных обновлений по мере генерации LLM
for partial_story in client.messages.create_partial(
model="claude-sonnet-4-5-20250929",
max_tokens=1024,
messages=[{
"role": "user",
"content": "Write a short sci-fi story"
}],
response_model=Story
):
print(f"Title: {partial_story.title}")
print(f"Content so far: {partial_story.content[:100]}...")
# Обновление интерфейса в реальном времени

Стриминг итерируемых объектов​

class Task(BaseModel):
title: str
priority: str

# Стриминг элементов списка по мере их генерации
tasks = client.messages.create_iterable(
model="claude-sonnet-4-5-20250929",
max_tokens=1024,
messages=[{
"role": "user",
"content": "Generate 10 project tasks"
}],
response_model=Task
)

for task in tasks:
print(f"- {task.title} ({task.priority})")
# Обработка каждой задачи по мере поступления

Конфигурация провайдеров​

Anthropic Claude​

import instructor
from anthropic import Anthropic

client = instructor.from_anthropic(
Anthropic(api_key="your-api-key")
)

# Использование с моделями Claude
response = client.messages.create(
model="claude-sonnet-4-5-20250929",
max_tokens=1024,
messages=[...],
response_model=YourModel
)

OpenAI​

from openai import OpenAI

client = instructor.from_openai(
OpenAI(api_key="your-api-key")
)

response = client.chat.completions.create(
model="gpt-4o-mini",
response_model=YourModel,
messages=[...]
)

Локальные модели (Ollama)​

from openai import OpenAI

# Указываем на локальный сервер Ollama
client = instructor.from_openai(
OpenAI(
base_url="http://localhost:11434/v1",
api_key="ollama" # Обязательно, но игнорируется
),
mode=instructor.Mode.JSON
)

response = client.chat.completions.create(
model="llama3.1",
response_model=YourModel,
messages=[...]
)

Часто используемые шаблоны​

Шаблон 1: Извлечение данных из текста​

class CompanyInfo(BaseModel):
name: str
founded_year: int
industry: str
employees: int
headquarters: str

text = """
Tesla, Inc. was founded in 2003. It operates in the automotive and energy
industry with approximately 140,000 employees. The company is headquartered
in Austin, Texas.
"""

company = client.messages.create(
model="claude-sonnet-4-5-20250929",
max_tokens=1024,
messages=[{
"role": "user",
"content": f"Extract company information from: {text}"
}],
response_model=CompanyInfo
)

Шаблон 2: Классификация​

class Category(str, Enum):
TECHNOLOGY = "technology"
FINANCE = "finance"
HEALTHCARE = "healthcare"
EDUCATION = "education"
OTHER = "other"

class ArticleClassification(BaseModel):
category: Category
confidence: float = Field(ge=0.0, le=1.0)
keywords: list[str]

classification = client.messages.create(
model="claude-sonnet-4-5-20250929",
max_tokens=1024,
messages=[{
"role": "user",
"content": "Classify this article: [article text]"
}],
response_model=ArticleClassification
)

Шаблон 3: Извлечение нескольких сущностей​

class Person(BaseModel):
name: str
role: str

class Organization(BaseModel):
name: str
industry: str

class Entities(BaseModel):
people: list[Person]
organizations: list[Organization]
locations: list[str]

text = "Tim Cook, CEO of Apple, announced at the event in Cupertino..."

entities = client.messages.create(
model="claude-sonnet-4-5-20250929",
max_tokens=1024,
messages=[{
"role": "user",
"content": f"Extract all entities from: {text}"
}],
response_model=Entities
)

for person in entities.people:
print(f"{person.name} - {person.role}")

Шаблон 4: Структурированный анализ​

class SentimentAnalysis(BaseModel):
overall_sentiment: Sentiment
positive_aspects: list[str]
negative_aspects: list[str]
suggestions: list[str]
score: float = Field(ge=-1.0, le=1.0)

review = "The product works well but setup was confusing..."

analysis = client.messages.create(
model="claude-sonnet-4-5-20250929",
max_tokens=1024,
messages=[{
"role": "user",
"content": f"Analyze this review: {review}"
}],
response_model=SentimentAnalysis
)

Шаблон 5: Пакетная обработка​

def extract_person(text: str) -> Person:
return client.messages.create(
model="claude-sonnet-4-5-20250929",
max_tokens=1024,
messages=[{
"role": "user",
"content": f"Extract person from: {text}"
}],
response_model=Person
)

texts = [
"John Doe is a 30-year-old engineer",
"Jane Smith, 25, works in marketing",
"Bob Johnson, age 40, software developer"
]

people = [extract_person(text) for text in texts]

Продвинутые возможности​

Union-типы​

from typing import Union

class TextContent(BaseModel):
type: str = "text"
content: str

class ImageContent(BaseModel):
type: str = "image"
url: HttpUrl
caption: str

class Post(BaseModel):
title: str
content: Union[TextContent, ImageContent] # Любой из типов

# LLM выбирает подходящий тип на основе содержимого

Динамические модели​

from pydantic import create_model

# Создание модели во время выполнения
DynamicUser = create_model(
'User',
name=(str, ...),
age=(int, Field(ge=0)),
email=(EmailStr, ...)
)

user = client.messages.create(
model="claude-sonnet-4-5-20250929",
max_tokens=1024,
messages=[...],
response_model=DynamicUser
)

Пользовательские режимы​

# Для провайдеров без нативной поддержки структурированного вывода
client = instructor.from_anthropic(
Anthropic(),
mode=instructor.Mode.JSON # Режим JSON
)

# Доступные режимы:
# - Mode.ANTHROPIC_TOOLS (рекомендуется для Claude)
# - Mode.JSON (запасной)
# - Mode.TOOLS (инструменты OpenAI)

Управление контекстом​

# Одноразовый клиент
with instructor.from_anthropic(Anthropic()) as client:
result = client.messages.create(
model="claude-sonnet-4-5-20250929",
max_tokens=1024,
messages=[...],
response_model=YourModel
)
# Клиент закрывается автоматически

Обработка ошибок​

Обработка ошибок валидации​

from pydantic import ValidationError

try:
user = client.messages.create(
model="claude-sonnet-4-5-20250929",
max_tokens=1024,
messages=[...],
response_model=User,
max_retries=3
)
except ValidationError as e:
print(f"Не удалось после повторных попыток: {e}")
# Корректная обработка

except Exception as e:
print(f"Ошибка API: {e}")

Пользовательские сообщения об ошибках​

class ValidatedUser(BaseModel):
name: str = Field(description="Полное имя, 2-100 символов")
age: int = Field(description="Возраст от 0 до 120", ge=0, le=120)
email: EmailStr = Field(description="Действительный email-адрес")

class Config:
# Пользовательские сообщения об ошибках
json_schema_extra = {
"examples": [
{
"name": "John Doe",
"age": 30,
"email": "john@example.com"
}
]
}

Лучшие практики​

1. Чёткие описания полей​

# ❌ Плохо: Расплывчато
class Product(BaseModel):
name: str
price: float

# ✅ Хорошо: Описательно
class Product(BaseModel):
name: str = Field(description="Название продукта из текста")
price: float = Field(description="Цена в USD, без символа валюты")

2. Используйте подходящую валидацию​

# ✅ Хорошо: Ограничение значений
class Rating(BaseModel):
score: int = Field(ge=1, le=5, description="Рейтинг от 1 до 5 звёзд")
review: str = Field(min_length=10, description="Текст отзыва, минимум 10 символов")

3. Приводите примеры в промптах​

messages = [{
"role": "user",
"content": """Извлеките информацию о человеке из: "John, 30, engineer"

Пример формата:
{
"name": "John Doe",
"age": 30,
"occupation": "engineer"
}"""
}]

4. Используйте перечисления для фиксированных категорий​

# ✅ Хорошо: Enum гарантирует допустимые значения
class Status(str, Enum):
PENDING = "pending"
APPROVED = "approved"
REJECTED = "rejected"

class Application(BaseModel):
status: Status # LLM должна выбрать из enum

5. Корректно обрабатывайте отсутствующие данные​

class PartialData(BaseModel):
required_field: str
optional_field: Optional[str] = None
default_field: str = "default_value"

# LLM нужно предоставить только required_field

Сравнение с альтернативами​

ФункцияInstructorРучной JSONLangChainDSPy
Типобезопасность✅ Да❌ Нет⚠️ Частично✅ Да
Автовалидация✅ Да❌ Нет❌ Нет⚠️ Ограничено
Автоповтор✅ Да❌ Нет❌ Нет✅ Да
Стриминг✅ Да❌ Нет✅ Да❌ Нет
Мультипровайдерность✅ Да⚠️ Вручную✅ Да✅ Да
Порог входаНизкийНизкийСреднийВысокий

Когда выбирать Instructor:

  • Нужны структурированные, валидированные выводы
  • Важны типобезопасность и поддержка IDE
  • Требуются автоматические повторные попытки
  • Создание систем извлечения данных

Когда выбирать альтернативы:

  • DSPy: Нужна оптимизация промптов
  • LangChain: Построение сложных цепочек
  • Вручную: Простые, разовые извлечения

Ресурсы​

См. также​

  • references/validation.md — Продвинутые шаблоны валидации
  • references/providers.md — Конфигурация провайдеров
  • references/examples.md — Примеры из реальной жизни