Начиная с версии 0.35 Ollama поддерживает decision-модели, построенные на Jev API от TypeSafe. Это не чат-модели в привычном смысле: они не пишут текст, а отвечают на набор типизированных вопросов о переданном состоянии. Для классификации, маршрутизации, модерации и триажа это оказывается быстрее и надёжнее, чем просить обычную LLM вернуть JSON и потом разбирать её ответ.
В этой заметке я разберу, что такое decision-модели, как они устроены на уровне HTTP, какие модели уже доступны, и как встроить их в существующий код так, чтобы обычные генеративные модели продолжали работать.
Чем decision-модели отличаются от обычных
Обычная LLM получает промпт и генерирует текст токен за токеном. Даже если попросить её ответить одним словом, внутри всё равно происходит генерация, а формат ответа приходится контролировать схемой, инструкциями и последующей проверкой. Decision-модель устроена иначе: вы передаёте состояние и вопросы с типами, а модель возвращает вероятности и выбранные варианты. Никакого свободного текста, никакого парсинга JSON из ответа.
Новый endpoint называется /v1/systemone. В одном запросе можно задать сразу несколько вопросов, и модель ответит на все. Это удобно для задач, где нужно принять несколько связанных решений: например, определить команду, необходимость возврата и срочность тикета.
Какие модели доступны
Сейчас через Ollama можно запустить три decision-модели:
- nimble — открытая модель на 9 миллиардов параметров от Bespoke Labs. Это самая крупная и самая точная из доступных.
- tev1 — экспериментальная модель на 4 миллиарда параметров от Together AI.
- tev1:0.8b — экспериментальная модель на 0.8 миллиарда параметров от Together AI. Самая быстрая и самая лёгкая.
Все они работают локально, не требуют оплаты и не отправляют данные во внешний сервис. Поддерживаются Ollama 0.35 и новее.
Установка
Сначала проверьте версию Ollama:
ollama --version
Затем загрузите нужную модель. Для самой лёгкой:
ollama pull tev1:0.8b
Для полной версии tev1:
ollama pull tev1
Для nimble:
ollama pull nimble
Модель nimble занимает около 9.5 ГБ на диске, поэтому перед загрузкой стоит освободить место. tev1:0.8b занимает меньше гигабайта.
Первый запрос через curl
Простейший пример — определить, является ли текст объявлением о вакансии:
curl http://localhost:11434/v1/systemone -d '{
"model": "tev1:0.8b",
"state": {
"message": "Senior Swift developer, Berlin, remote friendly"
},
"questions": {
"isJob": {
"type": "noul",
"instructions": "Does the message advertise a job posting that someone can apply to?"
}
}
}'
Ответ выглядит так:
{
"model": "tev1:0.8b",
"answers": {
"isJob": {
"type": "noul",
"noul": 0.921
}
},
"usage": {
"input_tokens": 42,
"output_tokens": 0
}
}
Поле noul — это вероятность от 0 до 1. Решение принимается сравнением с порогом, обычно 0.5. В отличие от генеративной модели, здесь не нужно просить ответ в формате JSON и потом проверять, что модель его не сломала.
Типы вопросов
Decision-модели поддерживают три типа вопросов.
noul — бинарный вопрос. Возвращает вероятность того, что утверждение истинно:
{
"questions": {
"refund": {
"type": "noul",
"instructions": "Does the customer explicitly ask for a refund?"
}
}
}
choice — выбор одного варианта из заданного набора. Возвращает выбранный вариант, вероятности всех вариантов и confidence:
{
"questions": {
"team": {
"type": "choice",
"instructions": "Which team should handle this ticket?",
"criteria": {
"billing": "Payments and refunds",
"technical": "Bugs and integrations",
"other": "None of the above"
}
}
}
}
score — оценка по шкале. Возвращает число, легенду и вероятности для каждой точки шкалы:
{
"questions": {
"urgency": {
"type": "score",
"instructions": "How urgent is this ticket?",
"criteria": ["Routine", "Soon", "Urgent"]
}
}
}
Один запрос может содержать сразу несколько вопросов разных типов. Именно это делает decision-модели удобными для триажа: одна операция, одно состояние, несколько независимых решений.
Python без дополнительных библиотек
Для запросов достаточно стандартного urllib, отдельный SDK не обязателен:
import json
from urllib.request import Request, urlopen
OLLAMA_DECISION_URL = "http://localhost:11434/v1/systemone"
def ask_decision(state, questions, model="tev1:0.8b"):
payload = {
"model": model,
"state": state,
"questions": questions,
}
request = Request(
OLLAMA_DECISION_URL,
data=json.dumps(payload).encode(),
headers={"Content-Type": "application/json"},
method="POST",
)
with urlopen(request, timeout=3600) as response:
return json.loads(response.read().decode())["answers"]
Использование:
answers = ask_decision(
{"message": "Senior Swift developer, Berlin, remote friendly"},
{
"isJob": {
"type": "noul",
"instructions": "Does the message advertise a job posting that someone can apply to?"
}
},
)
probability = answers["isJob"]["noul"]
is_job = probability >= 0.5
Если нужен официальный Python SDK, он называется typesafe-sdk. Для localhost его можно направить на Ollama переменными окружения TYPESAFE_BASE_URL, TYPESAFE_API_KEY и TYPESAFE_DEFAULT_MODEL. Но для простых задач хватает и прямого HTTP-запроса.
Как понять, что модель поддерживает decision
Decision-модели не умеют отвечать через /api/generate или /api/chat. Если отправить такой запрос, Ollama вернёт ошибку 400 с текстом вида:
"tev1:0.8b" does not support generate
Это важно, если у вас уже есть код, который работает с обычными моделями через /api/generate: при смене модели на decision-модель он перестанет работать. Правильнее заранее узнать возможности модели через /api/show и выбрать подходящий способ вызова:
import json
from urllib.request import Request, urlopen
OLLAMA_SHOW_URL = "http://localhost:11434/api/show"
def supports_decision(model):
request = Request(
OLLAMA_SHOW_URL,
data=json.dumps({"model": model}).encode(),
headers={"Content-Type": "application/json"},
method="POST",
)
try:
with urlopen(request, timeout=30) as response:
capabilities = json.loads(response.read().decode()).get("capabilities", [])
except (OSError, TypeError, ValueError):
return False
return "decision" in capabilities
У decision-моделей в capabilities будет только decision. У обычных моделей там будут completion, tools или другие возможности. Если Ollama недоступна, разумно считать модель обычной и использовать генеративный путь.
Как встроить в существующий код
Самый простой способ — не переписывать классификатор, а разделить его на две стратегии. Генеративная стратегия работает как раньше: собирает промпт, отправляет его в /api/generate и разбирает JSON. Decision-стратегия отправляет состояние и типизированный вопрос в /v1/systemone и сравнивает вероятность с порогом.
Выбор стратегии происходит в одном месте, при создании классификатора:
def create_classifier(model):
if supports_decision(model):
return DecisionClassifier(model)
return GenerativeClassifier(model)
У такого подхода есть несколько преимуществ. Генеративные модели продолжают работать без изменений. Decision-модель можно включить сменой одной переменной окружения. А вся разница между двумя способами сосредоточена в двух небольших классах, а не размазана по приложению.
Для бинарной классификации decision-стратегия выглядит примерно так:
async def classify(text):
answers = await ask_decision(
{"message": text},
{
"isJob": {
"type": "noul",
"instructions": instructions,
}
},
model,
)
probability = answers["isJob"]["noul"]
return probability >= 0.5, probability
Обратите внимание, что decision-модель не возвращает объяснение. Если в вашем формате результата есть поле reason, его можно построить из вероятности: например, сохранить саму вероятность или строку вида «модель оценила сообщение в 0.935 как вакансию». Это честнее, чем выдумывать причину, которой модель не давала.
Инструкции должны быть вопросом
Это самый важный практический вывод. Decision-модели плохо понимают длинные промпты в стиле «ты классифицируешь сообщения, отвечай true, если…, иначе false». Им нужен короткий прямой вопрос.
На одном и том же сообщении про вакансию я получил такие вероятности:
- Прямой вопрос «Is this message a job posting?» — 0.921.
- Длинная инструкция с объяснением, что считать вакансией — 0.026.
- Длинная инструкция с вопросом в конце — 0.391.
Разница почти в тридцать раз. Модель не ошибается в классификации, она просто не понимает, что от неё хотят, когда вопрос спрятан внутри абзаца. Поэтому для decision-моделей стоит держать отдельные короткие инструкции, а длинные промпты оставить генеративным моделям.
Удобно завести отдельную переменную окружения:
OLLAMA_JOB_CLASSIFIER_DECISION_INSTRUCTIONS=Does the message advertise a job posting that someone can apply to?
А обычный промпт использовать только для генеративного пути.
Порог и вероятности
Порог 0.5 подходит как значение по умолчанию, но его полезно вынести в конфигурацию. Для разных задач цена ложноположительного и ложноотрицательного ответа разная. Для модерации безопаснее поднять порог, для поиска вакансий — снизить, чтобы не пропустить полезные сообщения.
Полезно также логировать саму вероятность, а не только итоговый boolean. Когда классификатор ошибается, по логу видно, был ли это уверенный ответ или решение на границе. Для choice-вопросов стоит сохранять probabilities и confidence: они показывают, насколько модель колеблется между вариантами.
Производительность и память
Скорость сильно зависит от размера модели и железа. На CPU в моих тестах получились такие цифры:
- tev1:0.8b — доли секунды на решение, легко помещается в память.
- tev1 — 4B, заметно медленнее, но всё ещё пригодна для интерактивных задач.
- nimble — 9B в Q8_0, около 10 ГБ в памяти. Первый запрос вместе с загрузкой модели занял около 137 секунд, последующие решения — от 12 до 32 секунд на CPU.
Nimble требует много оперативной памяти. На машине с 16 ГБ и Docker/WSL с лимитом около 8 ГБ модель не помещается: система начинает свопиться и контейнеры перестают отвечать. Помогло увеличение лимита WSL до 12 ГБ через файл .wslconfig:
[wsl2]
memory=12GB
swap=8GB
После изменения нужен wsl —shutdown и перезапуск Docker Desktop. Но даже с 12 ГБ запас остаётся небольшим: модель занимает около 10 ГБ, и на систему остаётся не так много.
По умолчанию Ollama выгружает модель после пяти минут простоя, освобождая память. Это удобно: nimble не держит 10 ГБ занятыми постоянно, но каждый новый запуск после паузы снова включает загрузку модели.
Когда использовать decision-модели
Decision-модели хорошо подходят для задач, где нужен быстрый и дешёвый ответ на чётко сформулированный вопрос:
- Классификация сообщений, тикетов и заявок.
- Маршрутизация по командам или исполнителям.
- Модерация контента и проверка на безопасность.
- Быстрые бинарные и многовариантные решения в игровых или интерактивных приложениях.
Они плохо подходят для задач, где нужен развёрнутый текст: объяснение, извлечение полей, суммаризация, генерация ответа. Для этого остаются обычные генеративные модели. Лучший результат получается при сочетании: decision-модель принимает решение, а генеративная модель объясняет его или извлекает данные.
Итог
Decision-модели в Ollama — это отдельный, более узкий инструмент, чем обычные LLM. Они не заменяют генеративные модели, но забирают у них рутинную часть работы: типизированные ответы, вероятности вместо парсинга текста и предсказуемый формат. Если у вас уже есть пайплайн классификации, переход сводится к проверке capabilities модели, отдельной стратегии вызова и коротким инструкциям-вопросам.
Источники
https://ollama.com/blog/ollama-now-supports-jev-style-decision-models
https://ollama.com/library/nimble
https://ollama.com/library/tev1
https://typesafe.ai
Добавить комментарий