Структурированная генерация текста с помощью LLM и Outlines

Используйте Outlines, когда вам нужно гарантированно получить корректный JSON/XML/код от LLM, типобезопасные выходные данные Pydantic, контроль над выбором токенов на уровне грамматики или поддержку локальных моделей через Transformers, llama.cpp и vLLM.

Hermes OpenClaw Средний Скопировали: 0
1 ресурс

Что понадобится

  • Требуются пакет 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 между ветками двух агентов в качестве беспристрастной третьей стороны: нейтральный арбитр, который принимает обе версии изменений и указанные намерения, после чего формирует итоговый результат слияния, не перезаписывая ни одну из сторон.

Hermes Средний
Автоматизация Разработка

Автономное управление проектами с помощью подагентов

Координация проектов автономно: главный агент делегирует работу подагентам-менеджерам проектов, которые отслеживают задачи в общем файле STATE.yaml, обновляют его по мере выполнения, параллельно берутся за неблокируемые задачи и сохраняют всё состояние задач в файлах с контролем версий.

OpenClaw Средний
Автоматизация Продуктивность Общение

Пользовательская утренняя сводка

Этот рабочий процесс заставляет OpenClaw ежедневно отправлять вам персонализированную утреннюю сводку в назначенное время, включающую новости, задачи, идеи и рекомендации, чтобы вам не пришлось тратить первые 30 минут на самостоятельный сбор информации.

OpenClaw Средний

>Ещё на этом уровне: Средний