Jev-модели решений в Ollama: практическое использование /v1/systemone

Написано

в

Начиная с версии 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

Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

DemensDeum
Обзор конфиденциальности

На этом сайте используются файлы cookie, что позволяет нам обеспечить наилучшее качество обслуживания пользователей. Информация о файлах cookie хранится в вашем браузере и выполняет такие функции, как распознавание вас при возвращении на наш сайт и помощь нашей команде в понимании того, какие разделы сайта вы считаете наиболее интересными и полезными.