Jev: модель решений (классификация без генерации текста)
Jev — модель решений разработчика TypeSafe. Это не чат-модель: она не пишет текст, а отвечает на заранее заданные вопросы о присланных данных — вероятностями. Вы передаёте «состояние» (state — текст или JSON) и типизированные вопросы к нему; для каждого вопроса в ответ приходит число: вероятность «да», выбранный вариант с распределением вероятностей или уровень по шкале. Результат сразу готов к использованию в коде — его не нужно вытаскивать из свободного текста.
Для чего подходит: классификация и тегирование (тема обращения, категория товара), модерация, маршрутизация запросов (в какой отдел или к какой модели отправить), решения в агентах (эскалировать ли, нужен ли человек), пакетная разметка данных. Отвечает быстро — по заявлению TypeSafe, за 70–500 мс.
Jev доступен только через отдельный эндпоинт POST /v1/systemone. В чате кабинета его нет, а запрос к нему через /v1/chat/completions вернёт ошибку.
Когда брать Jev вместо языковой модели
| Задача | Что выбрать |
|---|---|
| Отнести текст к одной из категорий, ответить «да/нет», оценить по шкале | Jev — дешевле, быстрее и без разбора свободного текста |
| Разметить тысячи записей по одним и тем же вопросам | Jev — пакетная обработка стоит копейки |
| Решить в агенте, что делать дальше: эскалировать, уточнить, ответить | Jev принимает решение, затем языковая модель пишет ответ |
| Написать или переписать текст, перевести, резюмировать | языковая модель — Chat Completions |
| Точный счёт, математика, сравнение дат, многоходовые рассуждения | языковая модель — Jev для этого не предназначен |
Лучше всего Jev работает с английским текстом. Русский понимает, но точность ниже — проверьте модель на своих примерах, прежде чем запускать в работу. Вопросы и описания вариантов можно писать на русском.
Цена
8 ₽ за 1 млн входных токенов, выходные токены бесплатны. Входные токены — это state и все вопросы вместе; их число возвращается в поле usage.input_tokens ответа.
Пример: 1 000 запросов по 300 токенов — это 300 000 токенов, то есть ≈ 2,4 ₽.
Списание — с того же баланса, что и у остальных моделей, по факту каждого запроса; запрос с ошибкой не оплачивается. В разделе «История» кабинета запросы к Jev видны как модель typesafe/jev-1.14. Цена также указана на странице цен.
Эндпоинт и аутентификация
POST https://router.1-lab.online/v1/systemone — формат запроса и ответа совпадает с API TypeSafe (System One). Ключ — обычный ключ API из раздела «Ключи» (см. Аутентификация и ключи):
Authorization: Bearer sk-...
Content-Type: application/json
Имена модели для параметра model:
| Имя | Что это |
|---|---|
jev-latest |
актуальная версия, сейчас typesafe/jev-1.14 |
jev-1.14, typesafe/jev-1.14 |
версия 1.14 — единственная, которая принимает изображения (вопросы box) |
jev-1.13, typesafe/jev-1.13 |
предыдущая версия |
typesafe/jev |
то же, что jev-latest |
Формат запроса
| Поле | Что задаёт |
|---|---|
model |
имя модели (обязательное) |
state |
данные, о которых задаются вопросы: строка, JSON-объект или массив (обязательное) |
questions |
объект «имя вопроса → вопрос»; имена придумываете вы, под ними же придут ответы (обязательное, не меньше одного вопроса) |
assets |
изображения в base64 для вопросов box (необязательное) |
Вопрос — объект с полями type (noul, choice, score или box), instructions (сам вопрос) и, для choice и score, criteria. Несколько вопросов к одному state задавайте в одном запросе: state при этом оплачивается один раз.
{
"model": "jev-latest",
"state": "I was charged twice for my subscription this month. Please refund the extra charge.",
"questions": {
"billing": {
"type": "noul",
"instructions": "Is this message about billing?"
},
"tone": {
"type": "choice",
"instructions": "What is the tone of this message?",
"criteria": {
"angry": "An upset or hostile message",
"calm": "A neutral or polite message",
"excited": "An enthusiastic or eager message"
}
},
"urgency": {
"type": "score",
"instructions": "How urgent is this message?",
"criteria": ["Can wait", "Needs attention this week", "Needs attention today"]
}
}
}
Типы вопросов
noul — да или нет
Вероятность ответа «да» от 0 до 1 в поле noul. Порог выбираете сами: например, считать «да» при noul >= 0.7, а значения около 0,5 отправлять на проверку человеку.
{"type": "noul", "instructions": "Is this message about billing?"}
Ответ: {"type": "noul", "noul": 0.98}.
choice — выбор варианта
criteria — объект «вариант → описание, когда он подходит», от 1 до 255 вариантов. Описание можно не писать (null) — тогда модель ориентируется на имя варианта, но с описаниями точность выше. В ответе — выбранный вариант choice, уверенность confidence (0…1) и probabilities — вероятность каждого варианта (в сумме ≈ 1).
{
"type": "choice",
"instructions": "What is the tone of this message?",
"criteria": {
"angry": "An upset or hostile message",
"calm": "A neutral or polite message",
"excited": "An enthusiastic or eager message"
}
}
Ответ: {"type": "choice", "choice": "calm", "confidence": 0.7, "probabilities": {"angry": 0.15, "calm": 0.8, "excited": 0.05}}.
score — уровень по шкале
criteria — массив описаний уровней, от 1 до 10; порядок задаёт шкалу: первый элемент — уровень 0, второй — 1 и так далее. В ответе score — взвешенная оценка, может быть дробной (1,7 — «ближе к уровню 2, чем к 1»), confidence, legend (какой уровень что значит) и probabilities по уровням.
{
"type": "score",
"instructions": "How urgent is this message?",
"criteria": ["Can wait", "Needs attention this week", "Needs attention today"]
}
Ответ: {"type": "score", "score": 1.7, "confidence": 0.55, "legend": {"0": "Can wait", "1": "Needs attention this week", "2": "Needs attention today"}, "probabilities": {"0": 0.1, "1": 0.1, "2": 0.8}}.
box — объект на изображении
Координаты объекта, описанного в instructions, на изображении. Изображение (PNG или JPEG) передаётся в assets в base64 под придуманным вами плейсхолдером, а плейсхолдер ставится в state. Запрос отправляется с заголовком TypeSafe-Danger-Zone-Images: true и моделью jev-1.14. В ответе box — границы top, bottom, left, right долями от высоты и ширины изображения (0…1); confidence может быть null.
{
"model": "jev-1.14",
"state": "photo",
"questions": {
"damage": {"type": "box", "instructions": "The damaged area of the product"}
},
"assets": {
"photo": {"type": "image", "format": "jpeg", "data_base64": "/9j/4AAQSkZJRg..."}
}
}
Изображения принимаются только внутри JSON (base64); multipart/form-data не поддерживается — такой запрос вернёт 415.
Ответ
{
"model": "jev-latest",
"answers": {
"billing": {"type": "noul", "noul": 0.98},
"tone": {
"type": "choice",
"choice": "calm",
"confidence": 0.7,
"probabilities": {"angry": 0.15, "calm": 0.8, "excited": 0.05}
},
"urgency": {
"type": "score",
"score": 1.7,
"confidence": 0.55,
"legend": {"0": "Can wait", "1": "Needs attention this week", "2": "Needs attention today"},
"probabilities": {"0": 0.1, "1": 0.1, "2": 0.8}
}
},
"usage": {"input_tokens": 120, "output_tokens": 12}
}
answers — ответы под теми же именами, что в questions; у каждого в type — тип вопроса. Оплачиваются только usage.input_tokens.
Примеры
cURL
curl https://router.1-lab.online/v1/systemone \
-H "Authorization: Bearer $ROUTER_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "jev-latest",
"state": "I was charged twice for my subscription this month. Please refund the extra charge.",
"questions": {
"billing": {"type": "noul", "instructions": "Is this message about billing?"},
"tone": {
"type": "choice",
"instructions": "What is the tone of this message?",
"criteria": {"angry": "An upset or hostile message", "calm": "A neutral or polite message", "excited": "An enthusiastic or eager message"}
},
"urgency": {
"type": "score",
"instructions": "How urgent is this message?",
"criteria": ["Can wait", "Needs attention this week", "Needs attention today"]
}
}
}'
Python (requests)
import os
import requests
resp = requests.post(
"https://router.1-lab.online/v1/systemone",
headers={"Authorization": f"Bearer {os.environ['ROUTER_API_KEY']}"},
json={
"model": "jev-latest",
"state": "I was charged twice for my subscription this month. Please refund the extra charge.",
"questions": {
"billing": {"type": "noul", "instructions": "Is this message about billing?"},
"tone": {
"type": "choice",
"instructions": "What is the tone of this message?",
"criteria": {
"angry": "An upset or hostile message",
"calm": "A neutral or polite message",
"excited": "An enthusiastic or eager message",
},
},
"urgency": {
"type": "score",
"instructions": "How urgent is this message?",
"criteria": ["Can wait", "Needs attention this week", "Needs attention today"],
},
},
},
timeout=30,
)
resp.raise_for_status()
answers = resp.json()["answers"]
if answers["billing"]["noul"] >= 0.7:
print("обращение о платежах, тон:", answers["tone"]["choice"], "срочность:", round(answers["urgency"]["score"], 1))
JavaScript (fetch)
const resp = await fetch("https://router.1-lab.online/v1/systemone", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.ROUTER_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
model: "jev-latest",
state: "I was charged twice for my subscription this month. Please refund the extra charge.",
questions: {
billing: { type: "noul", instructions: "Is this message about billing?" },
tone: {
type: "choice",
instructions: "What is the tone of this message?",
criteria: { angry: "An upset or hostile message", calm: "A neutral or polite message", excited: "An enthusiastic or eager message" },
},
urgency: {
type: "score",
instructions: "How urgent is this message?",
criteria: ["Can wait", "Needs attention this week", "Needs attention today"],
},
},
}),
});
if (!resp.ok) throw new Error(`${resp.status}: ${await resp.text()}`);
const { answers, usage } = await resp.json();
console.log(answers.tone.choice, answers.urgency.score, usage.input_tokens);
n8n
Узел HTTP Request:
| Настройка | Значение |
|---|---|
| Method | POST |
| URL | https://router.1-lab.online/v1/systemone |
| Authentication | Generic Credential Type → Header Auth; в credential: Name Authorization, Value Bearer sk-... (ключ хранится в credentials n8n, а не в узле) |
| Send Headers | Content-Type = application/json |
| Send Body | включено; Body Content Type JSON, Specify Body Using JSON |
| JSON | тело запроса; текст из предыдущего узла удобно подставить выражением (ниже) |
Выражение в поле JSON собирает тело из данных предыдущего узла ($json.text — поле с текстом обращения):
{{ JSON.stringify({
model: "jev-latest",
state: $json.text,
questions: {
billing: { type: "noul", instructions: "Is this message about billing?" },
tone: {
type: "choice",
instructions: "What is the tone of this message?",
criteria: { angry: "An upset or hostile message", calm: "A neutral or polite message", excited: "An enthusiastic or eager message" }
}
}
}) }}
Дальше ответы доступны как {{ $json.answers.billing.noul }} и {{ $json.answers.tone.choice }}: узел IF сравнивает noul с порогом (например, больше 0,7), узел Switch разводит ветки по значению choice.
SDK TypeSafe
Официальные SDK TypeSafe умеют работать через наш адрес: укажите ключ Роутера и базовый адрес https://router.1-lab.online — без /v1, путь /v1/systemone SDK добавляет сам (как и SDK Anthropic). Сжатие тела запроса, которое SDK включает по умолчанию (zstd для запросов от 1 КБ), наш адрес принимает — выключать его не нужно. Модель по умолчанию в SDK — jev-latest.
Вместо параметров можно задать переменные окружения TYPESAFE_API_KEY (ключ Роутера) и TYPESAFE_BASE_URL=https://router.1-lab.online — тогда клиент создаётся без аргументов.
Python (pip install typesafe-sdk)
import os
from typesafe_sdk import Choice, Noul, Score, TypeSafeClient
with TypeSafeClient(
api_key=os.environ["ROUTER_API_KEY"],
base_url="https://router.1-lab.online", # без /v1: SDK сам добавит /v1/systemone
) as client:
response = client.system_one(
state="I was charged twice for my subscription this month. Please refund the extra charge.",
questions={
"billing": Noul(instructions="Is this message about billing?"),
"tone": Choice(
instructions="What is the tone of this message?",
criteria={
"angry": "An upset or hostile message",
"calm": "A neutral or polite message",
"excited": "An enthusiastic or eager message",
},
),
"urgency": Score(
instructions="How urgent is this message?",
criteria=["Can wait", "Needs attention this week", "Needs attention today"],
),
},
)
print(response.nouls["billing"].noul)
print(response.choices["tone"].choice)
print(response.scores["urgency"].score)
JavaScript / TypeScript (npm install @typesafe-ai/sdk, Node.js 22 и новее)
import { choice, noul, score, TypeSafeClient } from "@typesafe-ai/sdk";
const client = new TypeSafeClient({
apiKey: process.env.ROUTER_API_KEY,
baseURL: "https://router.1-lab.online", // без /v1: SDK сам добавит /v1/systemone
});
const { answers, usage } = await client.systemOne({
state: "I was charged twice for my subscription this month. Please refund the extra charge.",
questions: {
billing: noul("Is this message about billing?"),
tone: choice("What is the tone of this message?", {
angry: "An upset or hostile message",
calm: "A neutral or polite message",
excited: "An enthusiastic or eager message",
}),
urgency: score("How urgent is this message?", ["Can wait", "Needs attention this week", "Needs attention today"]),
},
});
console.log(answers.billing.noul, answers.tone.choice, answers.urgency.score, usage.input_tokens);
Ограничения
| Что | Значение |
|---|---|
| Объём запроса | state и вопросы вместе — до 64 тыс. токенов; с заголовком TypeSafe-Danger-Zone-Long-Context: true — до 262 тыс. |
| Длинный контекст | для state больше ~32 000 байт добавьте заголовок TypeSafe-Danger-Zone-Long-Context: true; такие запросы медленнее и менее точны |
Вариантов в choice |
от 1 до 255 |
Уровней в score |
от 1 до 10 |
| Изображения | только jev-1.14; PNG или JPEG в base64 в поле assets, заголовок TypeSafe-Danger-Zone-Images: true |
| Формат запроса | только JSON; multipart/form-data не принимается (415) |
| Задержка ответа | 70–500 мс по данным TypeSafe |
| Чат и Chat Completions | недоступны: только POST /v1/systemone |
Ошибки
Формат ошибок отличается от остальных эндпоинтов: вместо объекта error — поле detail.
| Код | Тело | Что случилось | Что делать |
|---|---|---|---|
| 400 | {"detail": "..."} |
запрос не прошёл проверку: неизвестная модель, слишком много вариантов или уровней, изображение без заголовка | исправьте запрос по тексту detail |
| 401 | {"error": {"message": "Invalid token", …}} |
ключ не передан, неверный, выключен или удалён | проверьте ключ в разделе «Ключи» |
| 403 | {"error": {"message": "… quota is not enough …", "code": "insufficient_user_quota"}} |
на балансе недостаточно денег или исчерпан лимит расходов ключа; те же коды, что у остальных эндпоинтов | пополните баланс или поднимите лимит ключа |
| 415 | тело не JSON — например, multipart/form-data |
отправьте JSON; изображения — в assets в base64 |
|
| 422 | {"detail": [...]} |
тело не соответствует схеме: нет обязательного поля, не тот тип значения, неверный type вопроса |
в каждом элементе списка detail — путь до поля (loc) и сообщение (msg) |
| 429 | слишком много запросов за короткое время | подождите, сколько указано в заголовке Retry-After, и повторите |
|
| 502 | модель не ответила или ответила с ошибкой | повторите через несколько секунд |
Повторять можно 429, 502 и сетевые сбои — с нарастающей паузой, как описано в разделе Ошибки и лимиты. Ошибки 400, 401, 403, 415 и 422 повтор не исправит — нужно поменять запрос, ключ или пополнить баланс. Запрос с ошибкой не оплачивается.
Советы
- Задавайте все вопросы к одному тексту в одном запросе:
stateоплачивается один раз, а не на каждый вопрос. - Описывайте варианты в
criteria— одно-два предложения о том, когда вариант подходит, заметно повышают точность. - Используйте
confidenceиprobabilities: уверенные ответы обрабатывайте автоматически, неуверенные — отправляйте человеку. - Формулируйте вопросы и критерии по-английски, если это возможно; для русских текстов проверьте качество на 20–50 реальных примерах.
- Сочетайте модели: Jev решает, что делать с обращением, а языковая модель пишет ответ клиенту.