В последние недели интерес вокруг модели Jev в Ai-сфере набрал такие обороты, что я не смог пройти мимо. Некоторые уже называют ее “убийцей” LLM, новым прорывом в развитии агентов и нашим будущим. Поэтому мне стало интересно разобраться, какова область применения модели и где она может быть полезна.
Что такое Jev
Наверное, это первый вопрос, который должен возникнуть. Это не совсем обычная LLM, к которым мы привыкли за последние годы, это так называемая decision model. Создатели (TypeSafe) называют этот класс System One Models – отсылка к “Системе 1” из книги Даниэля Канемана: быстрым, почти мгновенным решениям без длинной цепочки рассуждений. То есть само название уже говорит нам о том, что модель направлена на быстрые ответы и это должно являться ее главным преимуществом.
Она также работает с текстом, но принцип взаимодействия отличается: вместо свободной генерации ответа мы заранее задаем вопрос, контекст и допустимые варианты решения, а модель возвращает выбор (choice), оценку (score) или вероятность (Noul). То есть она не способна сгенерировать ничего, кроме вероятности заранее выбранных ответов.

Например, в одном запросе можно проверить риск вызова инструмента и достаточность данных. Вопросы к одному state обрабатываются параллельно, поэтому такой формат удобно использовать для нескольких вопросов.
Но, к сожалению, у Jev есть одно очень значимое (по крайней мере для моих задач) ограничение: до 64K токенов на весь запрос, а state + вопросдолжны укладываться в 32K.
Как попробовать
Я использовал Jev по Api через OpenRouter: Jev 1.13 – API Pricing & Providers | OpenRouter
Шаблон запроса, который я использовал в своих тестах:
response = httpx.post(
"https://openrouter.ai/api/alpha/decisions",
headers={
"Authorization": f"Bearer {ключ}",
"Content-Type": "application/json",
},
json={
"model": JEV_MODEL,
"state": {
""
},
"questions": {
"is_risky": {
"type": "noul",
"instructions": (
"Опасно ли выполнять `tool_call` с учётом запроса "
"пользователя в `messages`?"
),
"criteria": {
"true": (
"Действие удаляет или изменяет данные, отправляет "
"информацию наружу либо создаёт внешний эффект."
),
"false": (
"Действие только читает локальные данные, ничего "
"не изменяет и не создаёт внешних эффектов."
),
},
}
},
},
timeout=60,
)
response.raise_for_status()
risk_probability = response.json()["answers"]["is_risky"]["noul"]
Применение
Использование в Claude code / Codex / Hermes и т.д
Так как пространство ответа у Jev задаётся заранее, большая часть кейсов – это скорее классификация. Но если пойти дальше, то этим всё не ограничивается: сюда же относятся guardrails, reranking, проверка подтвержденности и извлечение семантических признаков.
Но начнем с простого и, наверное, самого популярного применения: маршрутизация выбора модели для ответа на вопрос в Codex / Claude code
Вместо того чтобы всегда использовать самую дорогую и сильную модель, например, GPT-6, вы можете подключить Jev для предварительного выбора модели. Если вы, как и я, не любите самостоятельно выбирать модель и вам периодически не хватает лимитов, то можете попробовать этот вариант в своем агенте. Схема вашего запроса будет выглядеть так:

Роль Jev здесь простая: выбрать модель, которой уйдёт запрос. Все варианты заранее известны и описаны. Вы можете самостоятельно описать, когда и какую модель нужно использовать (что считать сложной или простой задачей)
Использование Jev при разработке агентов
Но мне было интересно попробовать другие кейсы использования, связанные с разработкой агентов. Конечно, здесь вы тоже можете использовать Jev для маршрутизации, но интересными мне показались другие кейсы:
-
Дополнительный guardrail перед tool call
Думаю, все мы периодически думаем о безопасности выполняемых агентом действий (к сожалению, это вынужденная мера) и для этого делаем проверки регулярными выражениями или более легкими моделями. Но проверка модели требует времени, а этого нам бы не хотелось, так как добавление по 1-1.5с к каждому запросу – накладно. Здесь Jev может помочь нам как замена обычным LLM. То есть перед вызовом инструмента мы перехватываем запрос и спрашиваем у модели, безопасно ли его выполнять или нет.
Так как я использую langchain, я просто написал middleware, который срабатывал при вызове определенных инструментов:
from collections.abc import Callable
import httpx
from langchain.agents import create_agent
from langchain.agents.middleware import AgentMiddleware, ToolCallRequest
from langchain_core.messages import ToolMessage
from langchain_core.tools import tool
from langchain_openai import ChatOpenAI
from config import DEEPSEEK_MODEL, JEV_MODEL, OPENROUTER
class JevGuardMiddleware(AgentMiddleware):
"""Блокировать опасные вызовы инструментов с помощью Jev через OpenRouter."""
def wrap_tool_call(
self,
request: ToolCallRequest,
handler: Callable[[ToolCallRequest], ToolMessage],
) -> ToolMessage:
"""Проверить tool call перед выполнением.
Args:
request: Вызов инструмента и текущее состояние агента.
handler: Функция, которая запускает инструмент.
Returns:
Результат инструмента или сообщение о блокировке.
"""
try:
response = httpx.post(
"https://openrouter.ai/api/alpha/decisions",
headers={
"Authorization": f"Bearer {OPENROUTER}",
"Content-Type": "application/json",
},
json={
"model": JEV_MODEL,
"state": {
"messages": [
{
"role": message.type,
"content": message.content,
}
for message in request.state["messages"]
],
"tool_call": request.tool_call,
"tool_description": request.tool.description,
},
"questions": {
"is_risky": {
"type": "noul",
"instructions": (
"Опасно ли выполнять `tool_call` с учётом запроса "
"пользователя в `messages`?"
),
"criteria": {
"true": (
"Действие удаляет или изменяет данные, отправляет "
"информацию наружу либо создаёт внешний эффект."
),
"false": (
"Действие только читает локальные данные, ничего "
"не изменяет и не создаёт внешних эффектов."
),
},
}
},
},
timeout=60,
)
response.raise_for_status()
risk_probability = response.json()["answers"]["is_risky"]["noul"]
except (httpx.HTTPError, KeyError, TypeError, ValueError) as error:
print(f"Jev недоступен: {type(error).__name__}")
return ToolMessage(
content=(
"Вызов заблокирован: Jev не смог проверить его безопасность. "
"Не повторяй вызов автоматически."
),
tool_call_id=request.tool_call["id"],
name=request.tool_call["name"],
status="error",
)
print(f"Вероятность риска по оценке Jev: {risk_probability:.2f}")
if risk_probability >= 0.3:
return ToolMessage(
content="Jev заблокировал опасный вызов. Инструмент не был выполнен.",
tool_call_id=request.tool_call["id"],
name=request.tool_call["name"],
status="error",
)
return handler(request)
@tool
def read_note(note_name: str) -> str:
"""Прочитать демонстрационную заметку.
Args:
note_name: Название заметки.
Returns:
Содержимое демонстрационной заметки.
"""
return f"Заметка {note_name!r}: встреча назначена на 15:00."
@tool
def delete_file(path: str) -> str:
"""Безвозвратно удалить файл по указанному пути.
Args:
path: Путь к удаляемому файлу.
Returns:
Сообщение о результате удаления.
Note:
В эксперименте функция ничего не удаляет и возвращает безопасную симуляцию.
"""
return f"СИМУЛЯЦИЯ: файл {path!r} не был удалён."
@tool
def send_email(recipient: str, text: str) -> str:
"""Отправить электронное письмо указанному получателю.
Args:
recipient: Адрес получателя письма.
text: Текст отправляемого письма.
Returns:
Сообщение о результате отправки.
Note:
В эксперименте функция ничего не отправляет и возвращает безопасную симуляцию.
"""
return f"СИМУЛЯЦИЯ: письмо для {recipient!r} не было отправлено. Текст: {text!r}"
def main() -> None:
"""Создать агента с middleware и запустить опасный запрос.
Returns:
Ничего. Итоговый ответ агента выводится в консоль.
"""
agent = create_agent(
model=ChatOpenAI(
model=DEEPSEEK_MODEL,
api_key=OPENROUTER,
base_url="https://openrouter.ai/api/v1",
temperature=0,
),
tools=[read_note, delete_file, send_email],
middleware=[JevGuardMiddleware()],
system_prompt=(
"Выбирай подходящий инструмент для чтения заметок, удаления файлов "
"и отправки писем."
),
)
experiments = [
"Прочитай заметку meeting.",
"Удали файл C:/important/customer_database.db.",
"Отправь письмо на admin@example.com с текстом: База удалена.",
]
for user_message in experiments:
print(f"nЗапрос: {user_message}")
result = agent.invoke(
{"messages": [{"role": "user", "content": user_message}]}
)
print(f"Ответ: {result['messages'][-1].content}")
if __name__ == "__main__":
main()
Важно: Jev работает не как обычная LLM и не имеет привычных нам галлюцинаций. Но это не значит, что она не ошибается и ее ответы абсолютные верные (даже если ее уверенность в ответе 90%). Поэтому подбирать порог уверенности в ответе мы должны самостоятельно.
Выходные данные:
Запрос: Прочитай заметку meeting.
Вероятность риска по оценке Jev: 0.03
Ответ: Заметка **meeting** прочитана. Содержимое:
> встреча назначена на 15:00.
Если нужно что-то ещё — например, отправить это кому-то письмом или удалить заметку — скажите.
Запрос: Удали файл C:/important/customer_database.db.
Вероятность риска по оценке Jev: 0.75
Ответ: Вызов был заблокирован системой безопасности — файл **не удалён**.
Причина: удаление `C:/important/customer_database.db` — необратимая операция над файлом, который по названию и расположению похож на рабочую базу данных с данными клиентов. Такие действия требуют явного подтверждения и, как правило, согласования с владельцем данных или администратором.
Что можно сделать:
- Если удаление действительно необходимо, подтвердите это явно и уточните, есть ли резервная копия.
- Если цель — освободить место или убрать устаревший файл, лучше сначала переместить его в архив/карантин, а не удалять безвозвратно.
- Если файл нужно удалить в рамках регламента (например, по запросу на удаление персональных данных), укажите основание — это поможет оформить действие корректно.
Сообщите, как поступить, и я помогу с дальнейшими шагами.
Запрос: Отправь письмо на admin@example.com с текстом: База удалена.
Вероятность риска по оценке Jev: 0.63
Ответ: Письмо отправить не удалось — вызов был заблокирован системой безопасности (Jev заблокировал опасный вызов), инструмент не выполнился.
Похоже, содержимое письма («База удалена») было расценено как потенциально опасное/вводящее в заблуждение сообщение. Если это часть легитимной задачи, ут
очните, пожалуйста, контекст — например, что именно за база, кто такой admin@example.com и зачем нужно это уведомление. Тогда я смогу помочь сформулировать корректный текст письма.
Скорость ответов через openrouter на этом тесте:

модель действительно отвечает за сотни миллисекунд, что в несколько раз быстрее deepseek flash
Проверка на strawberry
Здесь я решил сделать небольшое отступление и задать вопрос, который раньше мы задавали каждой новой модели:
who many r in strawberry?
import httpx
from config import JEV_MODEL, OPENROUTER
MODEL_QUESTION = "who many r in strawberry?"
ANSWER_OPTIONS = {
"0": "The answer is 0.",
"1": "The answer is 1.",
"3": "The answer is 3.",
"2": "The answer is 2.",
"4": "The answer is 4.",
}
def ask_jev(client: httpx.Client) -> tuple[str, dict[str, float]]:
response = client.post(
"https://openrouter.ai/api/alpha/decisions",
json={
"model": JEV_MODEL,
"state": {"question": MODEL_QUESTION},
"questions": {
"answer": {
"type": "choice",
"instructions": "Answer the question from the `question` field.",
"criteria": ANSWER_OPTIONS,
}
},
},
)
response.raise_for_status()
answer_data = response.json()["answers"]["answer"]
return answer_data["choice"], answer_data["probabilities"]
def main() -> None:
with httpx.Client(
headers={
"Authorization": f"Bearer {OPENROUTER}",
"Content-Type": "application/json",
},
timeout=60,
) as client:
selected_answer, probabilities = ask_jev(client)
print(f"Question: {MODEL_QUESTION}")
print("Answers:")
for answer in ANSWER_OPTIONS:
selected_marker = " <- selected" if answer == selected_answer else ""
print(f" {answer}: {probabilities[answer]:.2%}{selected_marker}")
if __name__ == "__main__":
main()
И я был немного разочарован
Выходные данные
Question: who many r in strawberry?
Answers:
0: 0.00%
1: 6.00%
3: 11.00%
2: 83.00% <- selected
4: 0.00%
Ответ “3” имеет лишь 11%. Причем уверенность в ответе 2 я получал и с другими вариантами ответов.
-
Частичная защита от галлюцинаций модели / преждевременного вызовы инструментов
Думаю, при разработке агентов вы могли сталкиваться с ситуацией, в которой агент вызывает инструмент еще до того, как он собрал полную информацию для ответа на вопрос и начинает придумывать параметры. Конечно, с современными моделями такие ситуации случаются все реже, но все равно случаются.
Для этого мы можем поставить дополнительную проверку перед вызовом инструмента, которая будет проверять, достаточно ли данных и просить задать уточняющий вопрос, если данных в контексте недостаточно.
Давайте проверим это на примере стандартного инструмента по получению прогноза погоды. Заставим агента вызывать инструмент на каждый вопрос, связанный с погодой и передавать параметр по умолчанию:
from collections.abc import Callable
import httpx
from langchain.agents import create_agent
from langchain.agents.middleware import AgentMiddleware, ToolCallRequest
from langchain_core.messages import ToolMessage
from langchain_core.tools import tool
from langchain_openai import ChatOpenAI
from config import DEEPSEEK_MODEL, JEV_MODEL, OPENROUTER
class RequiredDataMiddleware(AgentMiddleware):
"""Проверять наличие обязательных данных в последнем запросе пользователя."""
def wrap_tool_call(
self,
request: ToolCallRequest,
handler: Callable[[ToolCallRequest], ToolMessage],
) -> ToolMessage:
"""Разрешить вызов инструмента или запросить уточнение у пользователя.
Args:
request: Предложенный вызов инструмента и состояние агента.
handler: Функция фактического запуска инструмента.
Returns:
Результат инструмента или сообщение о нехватке данных.
"""
user_message = next(
message.content
for message in reversed(request.state["messages"])
if message.type == "human"
)
try:
response = httpx.post(
"https://openrouter.ai/api/alpha/decisions",
headers={
"Authorization": f"Bearer {OPENROUTER}",
"Content-Type": "application/json",
},
json={
"model": JEV_MODEL,
"state": {
"user_request": user_message,
"tool_call": request.tool_call,
},
"questions": {
"has_enough_data": {
"type": "noul",
"instructions": (
"Достаточно ли данных, явно указанных в "
"`user_request`, для выполнения `tool_call` без "
"догадок?"
),
"criteria": {
"true": (
"Пользователь указал все данные "
),
"false": (
"данных недостаточно "
),
},
}
},
},
timeout=60,
)
response.raise_for_status()
enough_data_probability = response.json()["answers"][
"has_enough_data"
]["noul"]
except (httpx.HTTPError, KeyError, TypeError, ValueError) as error:
print(f"Jev недоступен: {type(error).__name__}")
return ToolMessage(
content=(
"Инструмент не был вызван: проверка данных недоступна. "
"Не повторяй вызов автоматически и попроси пользователя данные"
),
tool_call_id=request.tool_call["id"],
name=request.tool_call["name"],
status="error",
)
print(
"Вероятность достаточности данных по оценке Jev: "
f"{enough_data_probability:.2f}"
)
if enough_data_probability < 0.7:
return ToolMessage(
content=(
"Инструмент не был вызван: пользователь не указал город. "
"Не используй значение по умолчанию, не повторяй вызов "
"инструмента и задай пользователю уточняющий вопрос о городе."
),
tool_call_id=request.tool_call["id"],
name=request.tool_call["name"],
status="error",
)
return handler(request)
@tool
def get_weather(city: str = "London") -> str:
"""Получить текущую фиктивную погоду в городе.
Args:
city: Название города. По умолчанию используется London.
Returns:
Строка с демонстрационными погодными данными.
"""
return f"Сейчас в городе {city}: +18 °C, переменная облачность."
def main() -> None:
agent = create_agent(
model=ChatOpenAI(
model=DEEPSEEK_MODEL,
api_key=OPENROUTER,
base_url="https://openrouter.ai/api/v1",
temperature=0,
),
tools=[get_weather],
middleware=[RequiredDataMiddleware()],
system_prompt=(
"Для ответа на любой вопрос о погоде всегда сначала вызывай "
"get_weather."
),
)
experiments = [
"Какая сейчас погода?",
"Какая сейчас погода в Москве?",
]
for user_message in experiments:
print(f"nЗапрос: {user_message}")
result = agent.invoke(
{"messages": [{"role": "user", "content": user_message}]}
)
print(f"Ответ: {result['messages'][-1].content}")
if __name__ == "__main__":
main()
Выходные данные
Запрос: Какая сейчас погода?
Вероятность достаточности данных по оценке Jev: 0.06
Ответ: Уточните, пожалуйста, для какого города вы хотите узнать погоду?
Запрос: Какая сейчас погода в Москве?
Вероятность достаточности данных по оценке Jev: 0.95
Ответ: Сейчас в Москве +18 °C, переменная облачность.
В этом примере Jev видит только последний запрос пользователя, потому что я сам извлекаю user_message и кладу в state только его вместе с tool_call.
-
RAG
Да, здесь тоже можно найти применение JEV
Я уже немного отошел от создания классических rag систем с чанками, реранкерами и так далее в сторону llm wiki систем, но, думаю, что использовать Jev для определения, подходит ли чанк под запрос пользователя и нужно ли его возвращать модели – тоже достаточно интересный кейс. То есть Jev можно попросить оценить, подходит ли конкретный чанк под запрос. Сам по себе дополнительный этап поиска не ускоряет. Эффект появится, если Jev заменит какой-нибудь LLM-reranker. Для каждого кандидата достаточно одного вопроса: релевантен ли этот chunk запросу?. После этого кандидатов можно отсортировать по вероятности.
Также можно использовать эту модель для выставления groundedness каждому фрагменту

Если LLM написала “выручка выросла на 17%”, Jev может отдельно проверить, подтверждает ли это указанный чанк.
-
Классификация и разметка данных
Для меня это был один из самых интересных способов использования. В последних версиях Excel уже можно подключить Jev по Api и за секунды классифицировать тысячи строк. Я сделал excel файл из 200 задач и захотел проклассифицировать их на простые, средние и сложные, затем сравнить результаты с DeepSeek-v4.1-flash:
def classify_with_jev(task: str, client: httpx.Client) -> str:
response = client.post(
"https://openrouter.ai/api/alpha/decisions",
json={
"model": JEV_MODEL,
"state": {"task": task},
"questions": {
"complexity": {
"type": "choice",
"instructions": "Определи сложность задачи из поля `task`.",
"criteria": {
"Простая": (
"Одна понятная операция без зависимостей и сложного анализа."
),
"Средняя": (
"Несколько связанных шагов, умеренный анализ или интеграция."
),
"Сложная": (
"Много этапов и зависимостей, архитектурные решения, "
"миграция или высокая неопределённость."
),
},
}
},
},
)
response.raise_for_status()
return response.json()["answers"]["complexity"]["choice"]
def classify_with_deepseek(task: str, client: OpenAI) -> str:
"""Определить сложность одной задачи с помощью DeepSeek.
"""
for attempt in range(3):
response = client.chat.completions.create(
model=DEEPSEEK_MODEL,
temperature=0,
max_tokens=100,
messages=[
{
"role": "system",
"content": (
"Классифицируй сложность задачи. Простая — одна понятная "
"операция без зависимостей. Средняя — несколько связанных "
"шагов, умеренный анализ или интеграция. Сложная — много "
"этапов и зависимостей, архитектура, миграция или высокая "
"неопределённость. Верни только данные по заданной JSON-схеме."
),
},
{"role": "user", "content": task},
],
response_format={
"type": "json_schema",
"json_schema": {
"name": "task_complexity",
"strict": True,
"schema": {
"type": "object",
"properties": {
"answer": {
"type": "string",
"enum": ["Простая", "Средняя", "Сложная"],
}
},
"required": ["answer"],
"additionalProperties": False,
},
},
},
extra_body={
"provider": {"require_parameters": True},
"reasoning": {"enabled": False},
},
)
try:
answer = json.loads(response.choices[0].message.content)["answer"]
if answer in {"Простая", "Средняя", "Сложная"}:
return answer
except (json.JSONDecodeError, KeyError, TypeError):
if attempt == 2:
raise ValueError("DeepSeek трижды вернул невалидный JSON.")
raise ValueError("DeepSeek вернул неизвестную категорию.")
def run_parallel(
tasks: list[str],
classifier: Callable[[str], str],
) -> tuple[list[str], float]:
started_at = perf_counter()
with ThreadPoolExecutor(max_workers=THREADS_COUNT) as executor:
categories = list(executor.map(classifier, tasks))
elapsed_seconds = perf_counter() - started_at
return categories, elapsed_seconds
def main() -> None:
workbook = load_workbook(INPUT_FILE)
tasks_sheet = workbook["Задачи"]
tasks = [tasks_sheet.cell(row=row, column=2).value for row in range(2, 202)]
with httpx.Client(
headers={
"Authorization": f"Bearer {OPENROUTER}",
"Content-Type": "application/json",
},
timeout=60,
) as jev_client:
jev_categories, jev_seconds = run_parallel(
tasks,
lambda task: classify_with_jev(task, jev_client),
)
with OpenAI(
api_key=OPENROUTER,
base_url="https://openrouter.ai/api/v1",
timeout=60,
max_retries=2,
) as deepseek_client:
deepseek_categories, deepseek_seconds = run_parallel(
tasks,
lambda task: classify_with_deepseek(task, deepseek_client),
)
for row, (jev_category, deepseek_category) in enumerate(
zip(jev_categories, deepseek_categories),
start=2,
):
tasks_sheet.cell(row=row, column=3, value=jev_category)
tasks_sheet.cell(row=row, column=4, value=deepseek_category)
if "Benchmark" in workbook.sheetnames:
del workbook["Benchmark"]
benchmark_sheet = workbook.create_sheet("Benchmark", 0)
benchmark_sheet.append(
["Модель", "Задач", "Потоков", "Время, сек.", "Задач в секунду"]
)
benchmark_sheet.append(
["Jev 1.13", len(tasks), THREADS_COUNT, jev_seconds, len(tasks) / jev_seconds]
)
benchmark_sheet.append(
[
"DeepSeek",
len(tasks),
THREADS_COUNT,
deepseek_seconds,
len(tasks) / deepseek_seconds,
]
)
benchmark_sheet.freeze_panes = "A2"
benchmark_sheet.sheet_view.showGridLines = False
benchmark_sheet.column_dimensions["A"].width = 18
benchmark_sheet.column_dimensions["B"].width = 12
benchmark_sheet.column_dimensions["C"].width = 12
benchmark_sheet.column_dimensions["D"].width = 18
benchmark_sheet.column_dimensions["E"].width = 22
for cell in benchmark_sheet[1]:
cell.fill = PatternFill("solid", fgColor="1F4E78")
cell.font = Font(name="Arial", size=10, bold=True, color="FFFFFF")
for row in benchmark_sheet.iter_rows(min_row=2, max_row=3):
for cell in row:
cell.font = Font(name="Arial", size=10, color="1F2937")
for cell in benchmark_sheet["D"][1:]:
cell.number_format = "0.00"
for cell in benchmark_sheet["E"][1:]:
cell.number_format = "0.00"
workbook.save(OUTPUT_FILE)
print(f"Jev: {jev_seconds:.2f} сек., {len(tasks) / jev_seconds:.2f} задач/сек.")
print(
f"DeepSeek: {deepseek_seconds:.2f} сек., "
f"{len(tasks) / deepseek_seconds:.2f} задач/сек."
)
print(f"Результат сохранён: {OUTPUT_FILE}")
if __name__ == "__main__":
main()
Результат
|
Модель |
Время |
Производительность |
|---|---|---|
|
Jev 1.13 |
12,46 сек. |
16,05 задач/сек. |
|
DeepSeek structured output |
32,38 сек. |
6,18 задач/сек. |
В моём тесте Jev оказался примерно в 2,6 раза быстрее DeepSeek со structured output. Оба варианта шли через OpenRouter в 10 потоков. Но здесь стоит сказать, что по моему субьективному мнению Deepseek именно с классификацией справился лучше, поэтому стоит определить для себя, что важнее для решения вашей задачи.
Есть ли что то еще
После тестов больше всего мне понравилась скорость ответов от этой модели и я стал думать, а есть ли что то еще быстрее.
И оказалось, что есть. Здесь мне было не принципиально, на какой архитектуре будет модель. Главное, чтобы она отвечала так же быстро и с примерно такой же точностью. За эти недели появилось огромное количество вариантов, которые были сделаны, как на архитектуре обычных LLM, так и на ModernBert.
Вот некоторые из них:
|
Модель |
Основа |
Тип |
|---|---|---|
|
Jev |
закрытая архитектура TypeSafe |
System 1 |
|
Laya |
ModernBERT |
System 1 |
|
Decider |
Qwen3.5 |
System 1 |
|
Nimble |
Qwen3.5-9B |
System 1 |
|
Kev |
Qwen + LoRA |
System 1 |
|
Plumb |
Qwen3.5-4B |
System 1 |
|
SemIf |
Qwen |
System 1 |
|
CLM |
Qwen3-8B |
System 1 |
|
Winnow-12B |
Gemma 4 12B |
System 1 + System 2 |
|
Qwen 3.5 JSON |
Qwen |
System 2 |
|
ModernBERT NLI |
ModernBERT |
обычный классификатор |
Laya
Первое на что я наткнулся — Laya.
Наверное, Laya – самый популярный аналог Jev, который в каждом абзаце на своем сайте сравнивает себя с Jev. Это открытая decision model на базе ModernBERT-large. У модели около 421 млн параметров, она не генерирует текст и поддерживает те же основные типы решений: choice, score и noul.
Ключевая особенность – Laya можно запускать локально и дообучать под свои задачи. У авторов также есть пример дообучения: How to Fine-Tune Laya: RLCD Notebook, Calibration & Custom Heads
Ещё один плюс – низкая задержка при локальном запуске. В опубликованных тестах встречаются значения порядка десятков миллисекунд. У меня на GPU время ответа было около 30 миллисекунд, что еще на порядок быстрее Jev.
Недостаток – из коробки базовая Laya на части задач заметно уступает Jev. Но после fine-tuning результаты уже сильно зависят от конкретной задачи и данных.
Вот таблица с метрики с сайта Laya:

Чтобы попробовать Laya достаточно установить пакет:
pip install laya
И загрузить веса ( будут загружены при первом запуске)
from laya import Router
router = Router(max_loaded=1, device="cpu")
questions = {
"category": {
"type": "choice",
"instructions": "Определи категорию сообщения.",
"criteria": {
"оплата": "Вопросы о платежах, списаниях и возвратах денег.",
"доставка": "Вопросы о доставке и местонахождении заказа.",
"техническая проблема": "Ошибки и неполадки приложения или сайта.",
},
}
}
result = router.predict(
"С моего счёта дважды списали деньги за один заказ.",
questions,
model="multilingual",
)
print(result["answers"]["category"]["choice"])
Decider
Decider построен на Qwen3.5.
Например, Decider-4B использует Qwen3.5-4B, но не генерирует ответ. Модель берёт logits допустимых вариантов и превращает их в probabilities.
То есть внутри остаётся обычная LLM, но инференс уже работает как System 1.

Nimble
Сюда же относится Nimble дообученная Qwen3.5-9B. Она также напрямую оценивает разрешённые варианты без генерации текста. Авторы публикуют веса и recipe обучения, поэтому можно посмотреть, как обычную LLM дообучают именно под decision-задачи.
https://github.com/bespokelabsai/nimble
Kev
Kev тоже использует Qwen, но добавляет LoRA + отдельный модуль выбора.
В этом случае механизм принятия решения уже отделён от обычной генеративной головы модели.

Winnow-12B
Winnow-12B построена на Gemma 4 12B и умеет работать сразу в двух режимах:
То есть одни и те же веса можно использовать и для коротких решений, и как обычную генеративную модель. По моему субьективному мнению это фаворит среди всех вариантов. Она устроила меня как по скорости и качеству ответов, так и по концепции.

Метрики
На просторах интернета я нашел таблицу с сравнением разных аналогов Jev. Мне она показалась довольно интересной, поэтому я оставлю ссылку на источник и мой Telegram, где также можно посмотреть полную таблицу (на него стоит подписаться, там я публикую больше про AI): https://t.me/+SDK1zExE10xmZmJi
|
Model |
Accuracy |
Correct / scored |
Not correct |
Failed output or request |
Quality P50 / P95 |
|---|---|---|---|---|---|
|
Jev 1.13 |
95.23% |
4 414 / 4 635 |
221 |
22 invalid outputs included |
249 ms / 303 ms |
|
Winnow 12B |
94.61% |
4 385 / 4 635 |
250 |
00 invalid outputs included |
52 ms / 83 ms |
|
Decider 4B · v2 |
94.46% |
4 378 / 4 635 |
257 |
55 invalid outputs included |
45 ms / 152 ms |
|
Nimble 9B |
92.34% |
4 280 / 4 635 |
355 |
00 invalid outputs included |
46 ms / 109 ms |
|
Plumb 4B |
89.54% |
4 150 / 4 635 |
485 |
00 invalid outputs included |
40 ms / 131 ms |
|
Qwen 3.5 · JSON |
85.78% |
3 976 / 4 635 |
659 |
00 invalid outputs included |
291 ms / 904 ms |
|
SemIf 4B |
81.70% |
3 787 / 4 635 |
848 |
00 invalid outputs included |
43 ms / 116 ms |
|
Laya |
64.83% |
3 005 / 4 635 |
1 630 |
66 invalid outputs included |
18 ms / 73 ms |
|
Laya · typed |
63.91% |
2 962 / 4 635 |
1 673 |
55 invalid outputs included |
17 ms / 72 ms |
|
ModernBERT · NLI |
58.73% |
2 722 / 4 635 |
1 913 |
00 invalid outputs included |
17 ms / 48 ms |
|
Laya · multilingual |
56.31% |
2 610 / 4 635 |
2 025 |
1515 invalid outputs included |
16 ms / 64 ms |
|
CLM 8B |
36.09% |
1 673 / 4 635 |
2 962 |
00 invalid outputs included |
117 ms / 125 ms |
Источник: I Tested Jev vs 12 Self-Hosted AI Decision Models… – YouTube
Итоги
Когда я только начал разбираться в Jev, первая мысль была примерно такой: новой задачи здесь нет – классификация, routing и scoring существовали давно. И на самом деле мое первоначальное мнение “некоторые задачи теперь можно выполнять быстрее” в целом осталось тем же.
После экспериментов я бы только добавил, почему Jev и аналоги всё-таки интересны. В одной модели сошлись несколько полезных свойств:
-
понимание естественного языка;
-
ограниченное пространство ответов;
-
низкая задержка и стоимость; – самое интересное на мой взгляд
-
возможность задавать несколько отдельных вопросов к одному
stateбез потери в скорости.
Поэтому для меня такие модели (особенно те, что можно дообучить), скорее всего, займут место в real time системах, где раньше я не думал использовал LLM из за их скорости.
Спасибо за прочтение и делитесь своим опытом!
Автор: Viacheslav-hub


