Структурированная генерация текста с помощью LLM и Outlines
Используйте Outlines, когда вам нужно гарантированно получить корректный JSON/XML/код от LLM, типобезопасные выходные данные Pydantic, контроль над выбором токенов на уровне грамматики или поддержку локальных моделей через Transformers, llama.cpp и vLLM.
Что понадобится
- Требуются пакет outlines и Pydantic, а также только бэкенд выбранного маршрута: Transformers, llama-cpp-python, vLLM или другой поддерживаемый провайдер.
Как применить
В версии v1 желаемый тип выходных данных передаётся непосредственно как второй аргумент при вызове модели.
Outlines поддерживает несколько локальных и удалённых бэкендов через фабрики from_*.
Outlines имеет полноценную поддержку Pydantic с автоматическим преобразованием схем: генерация возвращает строку JSON, а для получения объекта используйте model_validate_json.
Этот скилл ориентирован на API Outlines 1.x, в котором вспомогательные функции из версий до 1.0 — outlines.models.transformers(...) и outlines.generate.json/choice/regex/... — были удалены. Создайте модель с помощью outlines.from_transformers(...) (или from_vllm, from_llamacpp, from_openai), затем вызывайте её напрямую с указанием типа выходных данных, как в примере model(prompt, output_type). Результат в формате JSON/Pydantic возвращается как строка JSON — проверьте его с помощью YourModel.model_validate_json(result).
Установите базовый пакет outlines, добавив дополнительную зависимость только для нужного бэкенда: transformers для моделей Hugging Face, llama-cpp-python для llama.cpp или vllm для высокопроизводительной обработки запросов.
# Base installation
pip install outlines
# With specific backends
pip install outlines transformers # Hugging Face models
pip install outlines llama-cpp-python # llama.cpp
pip install outlines vllm # vLLM for high-throughput
Создайте простую модель классификации, обернув каузальную модель Hugging Face и её токенизатор, затем вызывайте модель напрямую с указанием типа Literal, чтобы гарантировать один из перечисленных вариантов.
import outlines
from typing import Literal
from transformers import AutoModelForCausalLM, AutoTokenizer
MODEL_NAME = "microsoft/Phi-3-mini-4k-instruct"
# v1: wrap a Transformers model + tokenizer
model = outlines.from_transformers(
AutoModelForCausalLM.from_pretrained(MODEL_NAME, device_map="auto"),
AutoTokenizer.from_pretrained(MODEL_NAME),
)
# Call the model directly with an output type
prompt = "Sentiment of 'This product is amazing!': "
sentiment = model(prompt, Literal["positive", "negative", "neutral"])
print(sentiment) # "positive" (guaranteed one of these)
Используйте модель Pydantic в качестве типа выходных данных для извлечения структурированных полей; вызов возвращает строку JSON, которую можно преобразовать в объект модели с помощью model_validate_json.
from pydantic import BaseModel
import outlines
from transformers import AutoModelForCausalLM, AutoTokenizer
class User(BaseModel):
name: str
age: int
email: str
MODEL_NAME = "microsoft/Phi-3-mini-4k-instruct"
model = outlines.from_transformers(
AutoModelForCausalLM.from_pretrained(MODEL_NAME, device_map="auto"),
AutoTokenizer.from_pretrained(MODEL_NAME),
)
# Generate structured output (returns a JSON string)
prompt = "Extract user: John Doe, 30 years old, john@example.com"
result = model(prompt, User, max_new_tokens=200)
user = User.model_validate_json(result) # parse into the Pydantic model
print(user.name) # "John Doe"
print(user.age) # 30
print(user.email) # "john@example.com"
Ограниченный отбор токенов делает невозможными некорректные результаты: передайте тип выходных данных (например, модель Pydantic) как второй аргумент и проверьте возвращённую строку JSON.
import outlines
from pydantic import BaseModel
from transformers import AutoModelForCausalLM, AutoTokenizer
class Person(BaseModel):
name: str
age: int
model = outlines.from_transformers(
AutoModelForCausalLM.from_pretrained("microsoft/Phi-3-mini-4k-instruct", device_map="auto"),
AutoTokenizer.from_pretrained("microsoft/Phi-3-mini-4k-instruct"),
)
result = model("Generate person: Alice, 25", Person)
person = Person.model_validate_json(result)
Для вариантов выбора ограничьте выходные данные фиксированным набором значений с помощью Literal, чтобы результат всегда был одним из перечисленных вариантов.
from typing import Literal
sentiment = model("Review: This is great!", Literal["positive", "negative", "neutral"])
# Result: one of the three choices
Для получения JSON-данных определите модель Pydantic с типизированными полями и преобразуйте возвращённую строку JSON в корректный экземпляр модели.
from pydantic import BaseModel
class Product(BaseModel):
name: str
price: float
in_stock: bool
result = model("Extract: iPhone 15, $999, available", Product)
product = Product.model_validate_json(result) # valid Product instance
Передайте строку регулярного выражения в качестве типа выходных данных, чтобы сгенерировать текст, соответствующий заданному шаблону, например номер телефона.
# Generate text matching a regex pattern
phone = model("Generate phone number:", r"[0-9]{3}-[0-9]{3}-[0-9]{4}")
# Result: "555-123-4567" (guaranteed to match the pattern)
Передайте напрямую тип данных Python, чтобы гарантировать числовые результаты, такие как целочисленный возраст или вещественная цена.
# Pass the Python type directly
age = model("Person's age:", int) # guaranteed integer
price = model("Product price:", float) # guaranteed float
Для бэкенда Transformers (Hugging Face) оберните предобученную каузальную модель и токенизатор, затем вызывайте модель с указанием типа выходных данных.
import outlines
from transformers import AutoModelForCausalLM, AutoTokenizer
model = outlines.from_transformers(
AutoModelForCausalLM.from_pretrained("microsoft/Phi-3-mini-4k-instruct", device_map="auto"),
AutoTokenizer.from_pretrained("microsoft/Phi-3-mini-4k-instruct"),
)
result = model(prompt, YourModel)
Для бэкенда llama.cpp загрузите модель GGUF и оберните её с помощью from_llamacpp перед вызовом с указанием типа выходных данных.
import outlines
from llama_cpp import Llama
llm = Llama("./models/llama-3.1-8b-instruct.Q4_K_M.gguf", n_gpu_layers=35, n_ctx=4096)
model = outlines.from_llamacpp(llm)
result = model(prompt, YourModel)
Для бэкенда vLLM, подходящего для высоконагруженных задач, оберните экземпляр LLM с помощью from_vllm и вызывайте модель с указанием типа выходных данных.
import outlines
from vllm import LLM
llm = LLM("meta-llama/Llama-3.1-8B-Instruct", tensor_parallel_size=2)
model = outlines.from_vllm(llm)
result = model(prompt, YourModel)
Для работы с OpenAI оберните клиент с помощью from_openai; удалённые бэкенды поддерживают ограничение выходных данных на стороне сервера по схеме JSON.
import outlines
from openai import OpenAI
client = OpenAI()
model = outlines.from_openai(client, "gpt-4o-mini")
# API backends support JSON-schema style structured output
result = model(prompt, YourModel)
Определите простые модели Pydantic с типизированными и описанными полями, затем проверьте возвращённую строку JSON и преобразуйте её в экземпляр модели.
from pydantic import BaseModel, Field
class Article(BaseModel):
title: str = Field(description="Article title")
author: str = Field(description="Author name")
word_count: int = Field(description="Number of words", gt=0)
tags: list[str] = Field(description="List of tags")
result = model("Generate article about AI", Article, max_new_tokens=300)
article = Article.model_validate_json(result)
print(article.title)
print(article.word_count) # Guaranteed > 0
Составляйте вложенные модели Pydantic, чтобы сгенерированный объект включал структурированные подобъекты, например адрес внутри человека.
class Address(BaseModel):
street: str
city: str
country: str
class Person(BaseModel):
name: str
age: int
address: Address # Nested model
result = model("Generate person in New York", Person)
person = Person.model_validate_json(result)
print(person.address.city) # "New York"
Используйте перечисления и типы Literal для полей категорий, чтобы значения всегда выбирались из фиксированного набора.
from enum import Enum
from typing import Literal
class Status(str, Enum):
PENDING = "pending"
APPROVED = "approved"
REJECTED = "rejected"
class Application(BaseModel):
applicant: str
status: Status # Must be one of enum values
priority: Literal["low", "medium", "high"] # Must be one of literals
result = model("Generate application", Application)
app = Application.model_validate_json(result)
print(app.status) # Status.PENDING (or APPROVED/REJECTED)
Применяйте шаблон извлечения данных: определите модель Pydantic для нужных полей, задайте запрос с исходным текстом и проверьте возвращённый JSON.
from pydantic import BaseModel
import outlines
from transformers import AutoModelForCausalLM, AutoTokenizer
class CompanyInfo(BaseModel):
name: str
founded_year: int
industry: str
employees: int
model = outlines.from_transformers(
AutoModelForCausalLM.from_pretrained("microsoft/Phi-3-mini-4k-instruct", device_map="auto"),
AutoTokenizer.from_pretrained("microsoft/Phi-3-mini-4k-instruct"),
)
text = """
Apple Inc. was founded in 1976 in the technology industry.
The company employs approximately 164,000 people worldwide.
"""
prompt = f"Extract company information:\n{text}\n\nCompany:"
company = CompanyInfo.model_validate_json(model(prompt, CompanyInfo, max_new_tokens=200))
print(f"Name: {company.name}")
print(f"Founded: {company.founded_year}")
print(f"Industry: {company.industry}")
print(f"Employees: {company.employees}")
Примените шаблон классификации, используя типы вывода Literal для бинарных или многоклассовых меток, либо модель Pydantic для включения значения уверенности.
from typing import Literal
from pydantic import BaseModel
# Binary classification
result = model("Email: Buy now! 50% off!", Literal["spam", "not_spam"])
# Multi-class classification
category = model(
"Article: Apple announces new iPhone...",
Literal["technology", "business", "sports", "entertainment"],
)
# With confidence
class Classification(BaseModel):
label: Literal["positive", "negative", "neutral"]
confidence: float
out = model("Review: This product is okay, nothing special", Classification)
result = Classification.model_validate_json(out)
Примените шаблон структурированных форм для извлечения полного профиля пользователя с типизированными полями и списками в виде проверенного экземпляра Pydantic.
class UserProfile(BaseModel):
full_name: str
age: int
email: str
phone: str
country: str
interests: list[str]
prompt = """
Extract user profile from:
Name: Alice Johnson
Age: 28
Email: alice@example.com
Phone: 555-0123
Country: USA
Interests: hiking, photography, cooking
"""
profile = UserProfile.model_validate_json(model(prompt, UserProfile, max_new_tokens=250))
print(profile.full_name)
print(profile.interests) # ["hiking", "photography", "cooking"]
Примените шаблон извлечения нескольких сущностей с использованием модели уровня документа, содержащей список типизированных сущностей.
from typing import Literal
class Entity(BaseModel):
name: str
type: Literal["PERSON", "ORGANIZATION", "LOCATION"]
class DocumentEntities(BaseModel):
entities: list[Entity]
text = "Tim Cook met with Satya Nadella at Microsoft headquarters in Redmond."
prompt = f"Extract entities from: {text}"
result = DocumentEntities.model_validate_json(model(prompt, DocumentEntities, max_new_tokens=300))
for entity in result.entities:
print(f"{entity.name} ({entity.type})")
Примените шаблон генерации кода, создавая спецификацию функции со структурой Pydantic и собирая код из её полей.
class PythonFunction(BaseModel):
function_name: str
parameters: list[str]
docstring: str
body: str
prompt = "Generate a Python function to calculate factorial"
func = PythonFunction.model_validate_json(model(prompt, PythonFunction, max_new_tokens=300))
print(f"def {func.function_name}({', '.join(func.parameters)}):")
print(f' """{func.docstring}"""')
print(f" {func.body}")
Для пакетной обработки версия v1 принимает список запросов в одном вызове; проверьте каждую возвращённую строку JSON на соответствие экземпляру модели.
import outlines
from transformers import AutoModelForCausalLM, AutoTokenizer
from pydantic import BaseModel
class Person(BaseModel):
name: str
age: int
model = outlines.from_transformers(
AutoModelForCausalLM.from_pretrained("microsoft/Phi-3-mini-4k-instruct", device_map="auto"),
AutoTokenizer.from_pretrained("microsoft/Phi-3-mini-4k-instruct"),
)
texts = [
"John is 30 years old",
"Alice is 25 years old",
"Bob is 40 years old",
]
# v1 accepts a list of prompts for batched generation
prompts = [f"Extract from: {t}" for t in texts]
outputs = model(prompts, Person, max_new_tokens=100)
people = [Person.model_validate_json(o) for o in outputs]
for person in people:
print(f"{person.name}: {person.age}")
В случае с Transformers параметры GPU и типа данных задаются непосредственно в модели Hugging Face при её обёртке через from_transformers.
import outlines
from transformers import AutoModelForCausalLM, AutoTokenizer
MODEL_NAME = "microsoft/Phi-3-mini-4k-instruct"
# Basic usage
model = outlines.from_transformers(
AutoModelForCausalLM.from_pretrained(MODEL_NAME, device_map="auto"),
AutoTokenizer.from_pretrained(MODEL_NAME),
)
# GPU + dtype configuration is set on the HF model itself
import torch
model = outlines.from_transformers(
AutoModelForCausalLM.from_pretrained(MODEL_NAME, device_map="cuda", torch_dtype=torch.float16),
AutoTokenizer.from_pretrained(MODEL_NAME),
)
# Popular models
for name in [
"meta-llama/Llama-3.1-8B-Instruct",
"mistralai/Mistral-7B-Instruct-v0.3",
"Qwen/Qwen2.5-7B-Instruct",
]:
model = outlines.from_transformers(
AutoModelForCausalLM.from_pretrained(name, device_map="auto"),
AutoTokenizer.from_pretrained(name),
)
Для llama.cpp настройте окно контекста, количество слоёв на GPU и потоки CPU в объекте Llama; установите n_gpu_layers в значение -1 для полной передачи вычислений на GPU.
import outlines
from llama_cpp import Llama
# Load GGUF model
llm = Llama(
"./models/llama-3.1-8b.Q4_K_M.gguf",
n_ctx=4096, # Context window
n_gpu_layers=35, # GPU layers
n_threads=8, # CPU threads
)
model = outlines.from_llamacpp(llm)
# Full GPU offload: set n_gpu_layers=-1 on the Llama object
В продакшене для vLLM используйте параметр tensor_parallel_size для настройки многопроцессорных систем и опции квантования, такие как AWQ, в экземпляре LLM.
import outlines
from vllm import LLM
# Single GPU
model = outlines.from_vllm(LLM("meta-llama/Llama-3.1-8B-Instruct"))
# Multi-GPU
model = outlines.from_vllm(LLM("meta-llama/Llama-3.1-70B-Instruct", tensor_parallel_size=4))
# With quantization
model = outlines.from_vllm(LLM("meta-llama/Llama-3.1-8B-Instruct", quantization="awq"))
Используйте конкретные типы полей Pydantic, такие как float, int и bool, вместо строк для всех данных.
# ✅ Good: Specific types
class Product(BaseModel):
name: str
price: float # Not str
quantity: int # Not str
in_stock: bool # Not str
# ❌ Bad: Everything as string
class Product(BaseModel):
name: str
price: str # Should be float
quantity: str # Should be int
Добавьте ограничения к полям в моделях Pydantic, например границы длины, числовые диапазоны и регулярные выражения.
from pydantic import Field
# ✅ Good: With constraints
class User(BaseModel):
name: str = Field(min_length=1, max_length=100)
age: int = Field(ge=0, le=120)
email: str = Field(pattern=r"^[\w\.-]+@[\w\.-]+\.\w+$")
# ❌ Bad: No constraints
class User(BaseModel):
name: str
age: int
email: str
Используйте перечисления для полей с фиксированным набором категорий вместо произвольных строк.
# ✅ Good: Enum for fixed set
class Priority(str, Enum):
LOW = "low"
MEDIUM = "medium"
HIGH = "high"
class Task(BaseModel):
title: str
priority: Priority
# ❌ Bad: Free-form string
class Task(BaseModel):
title: str
priority: str # Can be anything
Указывайте чёткий контекст в своих запросах, а не отправляйте лишь минимальный текст.
# ✅ Good: Clear context
prompt = """
Extract product information from the following text.
Text: iPhone 15 Pro costs $999 and is currently in stock.
Product:
"""
# ❌ Bad: Minimal context
prompt = "iPhone 15 Pro costs $999 and is currently in stock."
Сделайте поля модели опциональными с заданными значениями по умолчанию, чтобы извлечение данных удавалось даже при неполных сведениях.
from typing import Optional
# ✅ Good: Optional fields for incomplete data
class Article(BaseModel):
title: str # Required
author: Optional[str] = None # Optional
date: Optional[str] = None # Optional
tags: list[str] = [] # Default empty list
# Can succeed even if author/date missing
Всегда проверяйте вывод в формате JSON: версия v1 возвращает строку JSON для типов вывода Pydantic/JSON, поэтому используйте model_validate_json для получения экземпляра модели.
# v1 returns a JSON string for Pydantic/JSON output types.
result = model(prompt, Article) # str
article = Article.model_validate_json(result) # Article instance
Источник: nousresearch/hermes-agent и участники проекта. Лицензия: MIT.
Copyright (c) 2025 Nous Research
Перевод и редакционная адаптация: ClawCases.
>Ресурсы
>Похожие юзкейсы
Арбитр разрешения конфликтов слияния агентов
Разрешает конфликт слияния Git между ветками двух агентов в качестве беспристрастной третьей стороны: нейтральный арбитр, который принимает обе версии изменений и указанные намерения, после чего формирует итоговый результат слияния, не перезаписывая ни одну из сторон.
Автономное управление проектами с помощью подагентов
Координация проектов автономно: главный агент делегирует работу подагентам-менеджерам проектов, которые отслеживают задачи в общем файле STATE.yaml, обновляют его по мере выполнения, параллельно берутся за неблокируемые задачи и сохраняют всё состояние задач в файлах с контролем версий.
Пользовательская утренняя сводка
Этот рабочий процесс заставляет OpenClaw ежедневно отправлять вам персонализированную утреннюю сводку в назначенное время, включающую новости, задачи, идеи и рекомендации, чтобы вам не пришлось тратить первые 30 минут на самостоятельный сбор информации.