
Jev — модель принятия решений от TypeSafe. Она принимает неструктурированные входные данные и возвращает типизированные суждения с вероятностями, а что делать дальше — решает ваш код. В этой статье разберём применение Jev от начала до конца на одном сквозном примере: модерация объявлений на маркетплейсе. Все запросы идут через Decisions API на API ВЕГА.
Каждый запрос к Jev состоит из state (состояния) и набора questions (вопросов).
Состояние — это входные данные, которые Jev должен оценить: строка, JSON-объект или массив строк. Для последовательности элементов, например сообщений в переписке, правильный выбор — массив.
Вопросы — это список суждений, которые Jev должен выполнить над состоянием. Вопрос состоит из instructions (инструкции) и criteria (критериев). Инструкция описывает суждение, критерии — возможные ответы. Оценивая состояние, Jev определяет, насколько хорошо каждый критерий к нему подходит, и возвращает это как результат. Если состояние — JSON-объект, инструкция может ссылаться на именованное поле через обратные кавычки: listing.description. Это называется путём к полю (field path) — Jev прочитает значение указанного поля и выполнит суждение именно по нему.
Типы вопросов:
Jev оценивает каждый вопрос отдельно и параллельно по одному и тому же состоянию и возвращает ровно один ответ на каждый вопрос:
choice: победивший вариант, вероятность каждого варианта и уверенность (confidence) от 0 до 1 — сводка по всему распределению. Если вероятность сконцентрирована на победителе, уверенность высокая; если распределение размазано — низкая, даже когда победивший вариант сам по себе имеет наибольшую вероятность. Уверенность — это не вероятность победителя.noul: единственная вероятность истинности утверждения — без отдельной уверенности, потому что она уже выражает степень уверенности модели.score: взвешенное по вероятностям среднее номеров уровней, вероятность каждого уровня, уверенность и legend — сопоставление номеров уровней с их описаниями.Каждый ответ — вариант или число, которые код может сравнивать, отсекать по порогу и комбинировать. Никакого текста для разбора не генерируется — поэтому Jev предпочтительнее чат-модели, которой предложено ответить «ДА» или «НЕТ». Вы получаете распределение по ответам, которые сами определили, и неопределённость — это число, по которому можно маршрутизировать логику.
Jev доступен через Decisions API с идентификатором модели typesafe/jev-1.13. Запрос состоит исключительно из текста, а токенный бюджет расходуется и на состояние, и на вопросы. Текущие цены и контекст — на странице модели в каталоге VEGA.
Допустим, вы строите маркетплейс, где пользователи продают бывшие в употреблении вещи. Продавец пишет заголовок и описание, выбирает категорию, указывает состояние товара и загружает фотографии. Прежде чем объявление будет опубликовано, нужно убедиться, что никто не продаёт запрещённый товар и не просит оплатить покупку вне площадки. Плюс отловить товары в неверной категории и описания, противоречащие заголовку или заявленному состоянию.
Часть этих проверок — просто код. Другие требуют действительно прочитать объявление и понять, что оно означает — именно для этого нужен Jev. Каждый раздел ниже сначала описывает часть работы с Jev вообще, а затем — как она выглядит на примере маркетплейса. Та же схема применима к маршрутизации тикетов, ограничению доступа агентов к инструментам и любой другой задаче суждения.
Jev — модель типа System One: так TypeSafe называет модель, которая выдаёт типизированные решения и откалиброванные вероятности. Вставляйте Jev туда, где в цепочке обычного программного workflow требуется суждение. Код управляет потоком, детерминированными правилами и побочными эффектами; Jev даёт типизированные ответы на небольшой набор вопросов, подготовленных кодом; код превращает ответы в действия. Jev не выбирает следующее действие самостоятельно.
На маркетплейсе этот принцип даёт конвейер между нажатием продавцом кнопки «Опубликовать» и появлением объявления в продаже. Конвейер принимает объявление и возвращает одно из трёх решений: опубликовать, отправить на удержание человеку или отклонить с указанием причин.
flowchart LR
A["1. Жёсткие правила"] --> B["2. Построение состояния"]
B --> C["3. Суждение (Jev)"]
C --> D["4. Валидация ответа"]
D --> E["5. Политика (пороги)"]
E --> F["Опубликовать"]
E --> G["На удержание (человек)"]
E --> H["Отклонить с причинами"]Этапы 1, 2, 4 и 5 выполняет ваш код; только этап 3 выполняет Jev.
Этап 3 — это Jev, остальные четыре — ваши. Так устроены все интеграции с Jev: это функция «состояние → свидетельства», а всё остальное — обычное программное обеспечение.
Первый вопрос проектирования: что отправлять модели на суждение. Критерий проверки такой — может ли код вычислить ответ, не читая никакого текста? Если может (подсчёт, сравнение дат, поиск по справочнику) — оставляйте это в коде, где результат точен и бесплатен. Jev предназначен для суждений, требующих интерпретации естественного языка.
На маркетплейсе граница проходит так: количество фото, диапазон цены, дата публикации и длина описания считаются кодом на этапе жёстких правил. А вот «просит ли продавец оплатить вне площадки» и «противоречит ли описание заголовку» — вопросы для Jev: код здесь бессилен, потому что ответ зависит от смысла текста. Страница TypeSafe о «рваности» (jaggedness) Jev 1.13 перечисляет арифметику, точный подсчёт и сравнение дат как задачи, которые следует оставить в коде.
Правило для state — полнота без лишнего. Jev видит только то, что вы передали: если важный факт не попал в состояние, модель о нём не узнает, а каждый лишний блок текста тратит токены и размывает суждение. Поэтому в состояние удобно складывать два слоя: сами данные (объявление) и facts — факты, которые код уже вычислил и которые помогают интерпретировать текст.
Пример состояния для объявления:
{
"listing": {
"title": "Горный велосипед Stels Navigator, б/у",
"description": "Продаю велосипед после сезонной езды, есть мелкие царапины на раме. Торг уместен. Пишите в WhatsApp по номеру в подписи, договоримся об оплате наличными при встрече.",
"category": "Электроника",
"condition": "Как новый",
"photoCount": 4,
"price": 12000,
"currency": "RUB",
"createdAt": "2026-09-23T10:15:00Z"
},
"facts": {
"photoCountOk": true,
"priceInBounds": true,
"descriptionLength": 187,
"minDescriptionLength": 30
}
}Для каждого суждения выбирайте тип вопроса по форме ответа, который нужен политике:
noul: одна вероятность, решение порогом.choice, когда исходов несколько и они взаимоисключающие. Всегда добавляйте вариант вроде ambiguous, иначе модель вынуждена выбирать из вариантов, которые не подходят.score: упорядоченная шкала, где среднее значение даёт градацию, которой нет у «да/нет».Критерии должны выдерживать буквальное прочтение: каждый вариант формулируется так, чтобы его можно было применить к тексту дословно, без домысливания контекста. «Продавец предлагает оплатить товар вне маркетплейса (наличные, перевод на карту, сторонние мессенджеры)» — хороший критерий. «Подозрительное объявление» — плохой: каждый запуск модели будет понимать его по-своему. Варианты должны быть взаимоисключающими и вместе покрывать все случаи, включая «невозможно определить».
Эндпоинт — POST https://api.vega.chat/api/v1/decisions, авторизация ключом VEGA. Один запрос — все пять вопросов над одним состоянием:
const response = await fetch("https://api.vega.chat/api/v1/decisions", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.VEGA_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
model: "typesafe/jev-1.13",
state: {
listing: {
title: "Горный велосипед Stels Navigator, б/у",
description:
"Продаю велосипед после сезонной езды, есть мелкие царапины на раме. " +
"Торг уместен. Пишите в WhatsApp по номеру в подписи, договоримся " +
"об оплате наличными при встрече.",
category: "Электроника",
condition: "Как новый",
photoCount: 4,
price: 12000,
currency: "RUB",
createdAt: "2026-09-23T10:15:00Z",
},
facts: {
photoCountOk: true,
priceInBounds: true,
descriptionLength: 187,
minDescriptionLength: 30,
},
},
questions: {
off_platform_payment: {
type: "choice",
instructions:
"Оцени поле `listing.description`: содержит ли объявление " +
"попытку организовать оплату вне площадки?",
criteria: {
off_platform:
"Продавец прямо или косвенно предлагает оплатить товар вне " +
"маркетплейса (наличные, перевод на карту, сторонние мессенджеры, " +
"номер телефона для сделки).",
platform_only:
"Оплата предполагается через маркетплейс; внешние контакты " +
"упоминаются только для связи, но не для расчётов.",
ambiguous:
"Текст допускает оба прочтения и не даёт основания для " +
"однозначного вывода.",
},
},
contradiction: {
type: "choice",
instructions:
"Оцени поля `listing.title` и `listing.description` вместе: " +
"противоречит ли описание заголовку или заявленному состоянию товара?",
criteria: {
contradiction:
"Описание описывает предмет, отличный от заголовка, или явно " +
"противоречит заявленному состоянию.",
consistent:
"Заголовок, описание и состояние согласованы между собой.",
ambiguous: "Согласованность определить невозможно.",
},
},
category_fit: {
type: "choice",
instructions:
"Соответствует ли содержимое объявления выбранной категории " +
"`listing.category`?",
criteria: {
fits: "Товар логично отнесён к указанной категории.",
wrong_category: "Товар явно относится к другой категории.",
needs_review: "Товар находится на границе нескольких категорий.",
},
},
banned_item: {
type: "noul",
instructions:
"Объявление предлагает запрещённый товар (оружие, наркотические " +
"вещества, поддельные документы, краденое и т. п.).",
},
condition_score: {
type: "score",
instructions:
"Оцени, насколько точно состояние товара в `listing.condition` " +
"соответствует реальному состоянию, описанному в `listing.description`.",
criteria: [
"Полное несоответствие: описанные дефекты опровергают заявленное состояние.",
"Заметное несоответствие.",
"Частичное несоответствие.",
"Мелкие расхождения.",
"Полное соответствие заявленного и описанного состояния.",
],
},
},
}),
});
const verdict = await response.json();Ответ приходит синхронно — объект answers с одним типизированным ответом на каждый вопрос:
{
"id": "gen-dec-1790475301-kQ8mPwRtXvN2bHzLcYeA",
"model": "typesafe/jev-1.13-20260917",
"provider": "TypeSafe",
"answers": {
"off_platform_payment": {
"type": "choice",
"choice": "off_platform",
"confidence": 0.88,
"probabilities": {
"off_platform": 0.93,
"platform_only": 0.05,
"ambiguous": 0.02
}
},
"contradiction": {
"type": "choice",
"choice": "contradiction",
"confidence": 0.31,
"probabilities": {
"contradiction": 0.42,
"consistent": 0.33,
"ambiguous": 0.25
}
},
"category_fit": {
"type": "choice",
"choice": "wrong_category",
"confidence": 0.9,
"probabilities": {
"fits": 0.06,
"wrong_category": 0.9,
"needs_review": 0.04
}
},
"banned_item": { "type": "noul", "noul": 0.04 },
"condition_score": {
"type": "score",
"score": 3.7,
"confidence": 0.71,
"probabilities": { "0": 0, "1": 0.01, "2": 0.05, "3": 0.19, "4": 0.75 },
"legend": {
"0": "Полное несоответствие: описанные дефекты опровергают заявленное состояние.",
"1": "Заметное несоответствие.",
"2": "Частичное несоответствие.",
"3": "Мелкие расхождения.",
"4": "Полное соответствие заявленного и описанного состояния."
}
}
},
"usage": { "input_tokens": 812, "output_tokens": 154 }
}Как это читать. Первый вопрос уверенно (confidence 0.88 при концентрации 0.93) указывает на оплату вне площадки — сработали «WhatsApp по номеру в подписи» и «наличными при встрече». Категория определённо неверна: велосипед лежит в «Электронике». Запрещённого товара почти наверняка нет (noul = 0.04) — у noul нет отдельной уверенности, потому что вероятность сама её выражает. По шкале состояния средний балл 3.7 при максимуме 4: «мелкие царапины на раме» согласуются с «б/у» в заголовке и расходятся с «Как новый». А вот contradiction показывает размазанное распределение — победитель есть, но confidence 0.31 говорит, что модель колеблется; такой сигнал логичнее отдать человеку, чем автоматически отклонять объявление.
Обратите внимание на поле model: отправляли typesafe/jev-1.13, ответил датированный снапшот typesafe/jev-1.13-20260917 — версия резолвится в конкретный релиз в момент запроса. В usage видно, что на всё ушло меньше тысячи входных токенов: при цене jev-1.13 в $0.042 за миллион входных одно объявление стоит доли цента.
Последний этап — код, который сравнивает ответы с порогами. Порядок проверок имеет значение: сначала самые дорогие ошибки (запрещённый товар), потом явные нарушения, в конце — то, что спокойно уйдёт человеку:
// Пороги откалиброваны на размеченной выборке решений модераторов
const THRESHOLDS = {
offPlatformPayment: 0.85, // вероятность «оплата вне площадки»
contradiction: 0.8, // вероятность «противоречие»
wrongCategory: 0.75, // вероятность «неверная категория»
bannedItem: 0.3, // noul: даже низкая вероятность требует проверки
conditionScoreMin: 2.5, // средний балл ниже — расхождение состояния
minConfidence: 0.5, // ниже — суждению нельзя доверять без человека
};
function decide(answers: JevAnswers): Decision {
const { off_platform_payment, contradiction, category_fit, banned_item, condition_score } =
answers;
if (banned_item.noul > THRESHOLDS.bannedItem)
return hold("возможный запрещённый товар");
if (
off_platform_payment.probabilities.off_platform > THRESHOLDS.offPlatformPayment &&
off_platform_payment.confidence > THRESHOLDS.minConfidence
)
return reject("оплата вне площадки", off_platform_payment.probabilities);
if (condition_score.score < THRESHOLDS.conditionScoreMin)
return hold("расхождение состояния товара");
if (category_fit.probabilities.wrong_category > THRESHOLDS.wrongCategory)
return hold("неверная категория");
if (
contradiction.probabilities.contradiction > THRESHOLDS.contradiction ||
contradiction.confidence < THRESHOLDS.minConfidence
)
return hold("противоречие описания");
return publish();
}Ключевые правила, которые здесь отражены:
contradiction выше) — повод отправить объявление человеку, а не отклонять автоматически.banned_item — это осознанная асимметрия: цена пропущенного запрещённого товара несравнимо выше цены лишней ручной проверки.POST https://api.vega.chat/api/v1/decisions. Работает и https://api.vega.chat/api/alpha/decisions — этот путь повторяет оригинальный адрес метода, поэтому если код уже рассчитан на такой base URL, достаточно поменять домен и ключ.@typesafe-ai/sdk или typesafe_sdk), укажите baseURL: 'https://api.vega.chat/api' — SDK сам добавит /v1/systemone. Либо задайте переменные TYPESAFE_BASE_URL и TYPESAFE_API_KEY с ключом VEGA.В запросах указывайте typesafe/jev-1.13, а если нужна всегда свежая версия — алиас ~typesafe/jev-latest. В TypeSafe SDK можно писать короткие id: jev-1.13 или jev-latest. Страница модели в каталоге: api.vega.chat/aimodels/jev-1.13.
[!NOTE] Jev — не чат-модель. Если отправить её в
/chat/completions, API вернёт ошибкуwrong_endpointс подсказкой использовать/api/v1/decisions.
Статья подготовлена по материалам туториала OpenRouter «How to use Jev» (автор — Kenny Rogers).