Привет, Хабр!
В последние недели интерес вокруг модели 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 из за их скорости.
Спасибо за прочтение и делитесь своим опытом!

