Всех с пятницей, у кого какие планы? Может, в бар? А LLM с собой возьмете? Пусть тогда откроет бутылку пива за вас. 🍺
Работая с языковыми моделями, часто требуется не просто получить текстовый ответ, а структурированные данные — например, в формате JSON. Вот самый эффективный способ добиться нужного формата от LLM.
Что мы имеем
С такими данными будем работать - попробуем открыть бутылку пива.
input_data = [
{"номер": 1, "название": "пиво", "характеристика": "светлое", "статус": "закрыто"},
{"номер": 2, "название": "пиво", "характеристика": "светлое", "статус": "закрыто"},
{"номер": 3, "название": "пиво", "характеристика": "светлое", "статус": "закрыто"},
{"номер": 4, "название": "пиво", "характеристика": "светлое", "статус": "закрыто"}
]
Как будем ограничивать LLM: Pydantic + Structured Outputs
Некоторые модели (например в ollama) поддерживают структурированные выходные данные через передачу схемы Pydantic (или JSON Schema). Модель гарантирует, что ответ будет строго соответствовать этой схеме — это самый надёжный способ получить валидный JSON нужной структуры.
Почему это работает
Ограничения на генерацию LLM, такие как JSON Schema или Pydantic, работают как "фильтр" или "направляющая" для модели. Вместо того чтобы выбирать любой следующий токен, LLM вынуждена выбирать только те токены, которые соответствуют заранее определённой структуре (например, число, кавычка, конкретное слово из списка). Этот процесс гарантирует, что итоговый текст будет строго соответствовать заданному формату (например, валидному JSON), исключая ошибки структуры.
Как это применить
from pydantic import BaseModel
from typing import Literal
from ollama import chat
class BeerBottle(BaseModel):
номер: int
название: str
характеристика: str
статус: Literal["открыто", "закрыто"]
class BeerList(BaseModel):
bottles: list[BeerBottle]
prompt = (
"Вот список бутылок пива:\n"
f"{input_data}\n"
"Открой только две бутылки: вторую и четвертую. "
"Первая и третья бутылка должны быть закрыты. "
"Верни результат как список объектов BeerBottle, "
)
response = chat(
model="gemma3:12b",
messages=[{'role': 'user', 'content': prompt}],
format=BeerList.model_json_schema()
)
result = BeerList.model_validate_json(response.message.content)
print(result)
Альтернативные подходы
🟢Можно использовать чистый JSON Schema, минуя Pydantic.
🟢Существуют специальные библиотеки: Outlines, Guidance, JSONFormer — они тоже ограничивают генерацию по схеме.
🟢Можно попробовать добиться нужного формата только промптом, но это менее надёжно.
Важно понимать: большинство "жестких" методов (Pydantic, JSON Schema, грамматики в Guidance/Outlines) основаны на одном и том же принципе — ограничении выбора токенов на этапе генерации.
Так что в следующий раз, когда вам понадобится точный и структурированный ответ от вашей локальной LLM, вспомните про "открытие пивных бутылок" с помощью Pydantic — это действительно работает! Теперь можно и отдохнуть. 🍺
