Представьте себе продукт, который получает 40 000 обращений в службу поддержки ежемесячно. Каждое из них требует немедленной обработки: нужно определить тему обращения, решить, следует ли эскалировать его на уровень старшего специалиста, и подготовить черновик ответа. Традиционный подход с использованием передовых больших языковых моделей (LLM) и JSON-схем для структурирования вывода работает, но может быть дорогим и медленным. В нашем бенчмарке такая система обходилась в $2,88 за 1000 тикетов при медианной задержке в две секунды, что в пересчете на месяц составляет около $115. Более простая модель снижала стоимость до $0,09 за 1000 тикетов, но все равно требовала около секунды на обработку.
Однако существует альтернативный подход: использование специализированных моделей решений, таких как Jev от TypeSafe. В тех же условиях Jev обрабатывал триаж за 2,5 цента за 1000 тикетов при медианной задержке всего 194 миллисекунды. Это примерно один доллар в месяц. Кроме того, Jev возвращает вероятности для каждого варианта ответа, что исключает необходимость парсинга текста. Но у Jev есть и ограничения: он не умеет писать развернутые ответы. Для этого по-прежнему нужны LLM. В этой статье мы подробно разберем, как объединить эти технологии для создания эффективной, быстрой и дешевой системы поддержки.
01Что такое Jev и что возвращают модели
Когда вы просите языковую модель принять решение, она возвращает текст. Даже если вы попросите формат JSON, модель по своей природе создана для генерации текста. Ваш код должен парсить этот вывод, проверять, ответила ли модель на вопрос, а не просто объяснила что-то, и извлекать нужные данные. Это добавляет сложность и риск ошибок.
Jev работает иначе. Он возвращает типизированный ответ. Вы отправляете ему состояние (контекст) и вопросы, построенные из трех базовых примитивов:
- choice (выбор): выбирает один вариант из заданного вами набора.
- noul (yes/no probability): возвращает вероятность ответа «да» в виде числа.
- score (оценка): размещает контент на уровнях, которые вы описываете. Ответы типа choice и score могут включать вероятности по вашим ключам, а noul — это просто одно число.
Вот пример ответа для двух вопросов по одному биллинговому тикету:
{
"answers": {
"intent": {
"type": "choice",
"choice": "billing_dispute",
"confidence": 0.95,
"probabilities": {
"billing_dispute": 0.95,
"order_status": 0.03,
"other": 0.02
}
},
"escalate": {
"type": "noul",
"noul": 0.04
}
},
"model": "typesafe/jev-1.13-20260917",
"provider": "TypeSafe",
"usage": {
"cost": 0.000016002,
"inputTokens": 381,
"outputTokens": 62
}
}Здесь намерение (intent) — это один из ваших ключей, а вероятность эскалации (escalate) — это число, которое вы сравниваете с порогом, установленным вами. TypeSafe называет Jev «моделью System One». Это означает, что она принимает быстрые, интуитивные суждения, а не медленный, обдуманный анализ. Она принимает только текст, поэтому изображения, аудио или PDF-файлы на вход не подаются. Она читает критерии буквально. Кроме того, она склонна терять точность, когда состояние, которое она анализирует, содержит нерелевантные детали, и ненадежна в арифметике, подсчете и сравнении дат.

02Сравнение Jev и генеративной LLM
Давайте сравним Jev 1.13 и традиционные LLM (такие как GPT Luna и Claude Opus) по ключевым параметрам. Это поможет вам понять, где каждая технология проявляет себя лучше всего.
| Параметр | Jev 1.13 | Традиционная LLM (GPT Luna, Claude Opus) |
|---|---|---|
| Вывод | Типизированный выбор, вероятность да/нет или оценка, с вероятностями по каждому опции | Свободный текст (опционально ограниченный JSON) |
| Лучше всего подходит для | Классификация, маршрутизация, проверка, ранжирование, проверки на безопасность, извлечение в bounded-пространстве | Написание, объяснение, суммирование, переписывание, код, все, где пространство вывода заранее неизвестно |
| Входные данные | Только текст (строки, JSON, массивы). 64k токенов на запрос, 32k для состояния плюс самый длинный вопрос | Текст, а для многих моделей — изображения, аудио, PDF |
| Цена (наблюдения от 2026-09-19) | $0,042 за миллион входных токенов, вывод бесплатный | GPT Luna: $0,20 вход / $1,20 выход за миллион. Claude Opus: $5 вход / $25 выход за миллион |
| Медианная задержка на триаже 60 тикетов | 194 мс | 1 106 мс (Luna), 1 957 мс (Opus) |
| Стоимость за 1000 обработанных тикетов | $0,0248 | $0,0921 (Luna), $2,88 (Opus) |
| Эндпоинт OpenRouter | POST /api/alpha/decisions | POST /api/v1/chat/completions |
Цены взяты из метаданных моделей OpenRouter на дату запуска. Перед планированием бюджета проверьте страницу модели. Как видно, Jev значительно дешевле и быстрее, но менее универсален.
03Что мы измеряли
В нашем исследовании участвовали Jev 1.13, GPT Luna (разрешенный как GPT 5.6 Luna) и Claude Opus (разрешенный как Claude Opus 5). LLM получили те же определения и правила эскалации, что и Jev, в системном промпте, с температурой 0 и запросом на чистый JSON-объект. Один запрос на пример. Это небольшие наборы: 60 и 40 примеров за один день, с одним набором промптов. Это форма компромисса, а не лидерборд.
Задача A: триаж 60 тикетов поддержки в пять намерений плюс флаг эскалации
60 тикетов поддержки по пяти намерениям: статус заказа, возврат или возмещение, спор по счетам, вопрос о продукте, доступ к аккаунту, каждое с однострочным определением. Эскалация охватывала угрозы судебных исков, чарджбэки, подозрение в мошенничестве, угрозы безопасности и угрозы публичного разглашения.
| Модель | Точность намерения | Точность эскалации | p50 задержка | p95 задержка | Общая стоимость (60) | На 1000 |
|---|---|---|---|---|---|---|
| Jev 1.13 | 59/60 (98,3%) | 60/60 (100%) | 0,194 с | 0,633 с | $0,001489 | $0,0248 |
| GPT Luna | 59/60 (98,3%) | 60/60 (100%) | 1,106 с | 2,395 с | $0,005527 | $0,0921 |
| Claude Opus | 60/60 (100%) | 59/60 (98,3%) | 1,957 с | 2,594 с | $0,17281 | $2,8802 |
Точность практически одинакова. Jev отправляет больше входных токенов, потому что каждый запрос содержит полные критерии, и все равно стоит около четверти от GPT Luna и менее одного процента от Claude Opus. Была только одна ошибка в намерении: вопрос о разделении возврата за возвращенный товар. Jev классифицировал его как «возврат или возмещение» с уверенностью 0,56. Это был единственный тикет с уверенностью ниже 0,8, поэтому порог в 0,8 отправил бы его человеку.
Задача B: проверка 40 сообщений на инъекцию промптов
Двадцать обычных сообщений поддержки и восемнадцать попыток инъекции (игнорирование инструкций, фиктивный административный размет, фрейминг ролевой игры, цитированные инструкции). Мы дали Jev один вопрос да/нет: является ли это попыткой изменить поведение ассистента, а LLM получили то же определение и вернули булево значение.

| Модель | Точность | p50 задержка | p95 задержка | Общая стоимость (40) | На 1000 |
|---|---|---|---|---|---|
| Jev 1.13 | 40/40 (100%) | 0,194 с | 0,688 с | $0,000646 | $0,0161 |
| GPT Luna | 39/40 (97,5%) | 0,805 с | 1,491 с | $0,001828 | $0,0457 |
| Claude Opus | 40/40 (100%) | 2,099 с | 4,377 с | $0,063555 | $1,5889 |
Jev четко разделил группы. Инъекции набрали от 0,86 до 0,99, а обычные сообщения — от 0,01 до 0,20. Одна ошибка Luna была связана с фреймингом ролевой игры.
04Используйте Jev, когда ответ — один из N
Любая задача, которая заканчивается оператором switch, — это вопрос для Jev. Намерение, приоритет, язык, тональность, очередь, нарушает ли это политику, подтверждается ли этот claim. В каждом случае ответ известен заранее. Вам просто нужна вероятность для каждого из возможных ответов. Вот вызов триажа из бенчмарка с использованием TypeScript SDK OpenRouter.
import { OpenRouter } from '@openrouter/sdk'
const openrouter = new OpenRouter({
apiKey: process.env.OPENROUTER_API_KEY // только на сервере
});
const INTENTS = {
order_status: 'Клиент спрашивает, где заказ, когда он будет отправлен или доставлен, хочет изменить или отменить заказ до доставки или сообщает об отсутствии или частичной доставке пакета.',
return_refund: 'Клиент хочет вернуть, обменять или заменить полученный товар, или спрашивает о статусе или правилах возврата, который он уже начал.',
billing_dispute: 'Клиент говорит, что плата, счет, налог, скидка или сумма возврата неверны, продублированы, неожиданны или несанкционированы.',
product_question: "Клиент спрашивает о функциях продукта, совместимости, размерах, материалах, наличии, гарантии или безопасности до или после покупки, не спрашивая о возврате.",
account_access: 'Клиент не может войти, нужно изменить данные входа или аккаунта, или спрашивает о слиянии, удалении, защите или совместном использовании аккаунта.'
} as const;
export type Intent = keyof typeof INTENTS;
const ESCALATE = 'Описывает ли тикет следующее: угрозу судебного иска, жалобу регулятора или чарджбэк; подозрение в мошенничестве или захвате аккаунта; угрозу безопасности, такую как пожар, дым или травма; или клиента, который говорит, что это повторяющийся сбой и угрожает публично об этом рассказать?';
export type Triage = {
intent: Intent;
confidence: number;
probabilities: Record;
escalateProbability: number;
costUsd: number;
};
function isIntent(value: string): value is Intent {
return Object.hasOwn(INTENTS, value);
}
export async function triage(ticket: string): Promise {
const result = await openrouter.alpha.decisions.create({
decisionsRequest: {
model: 'typesafe/jev-1.13',
state: { ticket },
questions: {
intent: {
type: 'choice',
instructions: 'Каково основное намерение тикета?',
criteria: INTENTS
},
escalate: { type: 'noul', instructions: ESCALATE },
},
},
});
const intent = result.answers.intent;
const escalate = result.answers.escalate;
if (intent?.type !== 'choice' || escalate?.type !== 'noul') {
throw new Error('Unexpected answer types');
}
if (!isIntent(intent.choice)) {
throw new Error(`Unknown intent ${intent.choice}`);
}
return {
intent: intent.choice,
confidence: intent.confidence ?? 0,
probabilities: intent.probabilities ?? {},
escalateProbability: escalate.noul,
costUsd: requireCost(result.usage.cost),
};
}
function requireCost(cost: number | undefined): number {
if (cost === undefined) {
throw new Error('Response did not include usage.cost');
}
return cost;
} Три вещи в этом коде, на которые стоит обратить внимание:
- Задавайте одно маленькое суждение за вопрос и объединяйте ответы в коде. Здесь намерение и эскалация — это отдельные вопросы в одном запросе.
- Пишите критерии как спецификацию. Поскольку Jev буквален, когда случай попадает в неправильную корзину, сначала исправьте текст критериев.
- Отправляйте только состояние, которое нужно каждому вопросу. Нерелевантные детали снижают точность ответа.
05Используйте LLM, когда ответ — проза
Теперь, когда нам нужно фактически сгенерировать ответ, мы используем LLM.
import { OpenRouter } from '@openrouter/sdk'
const openrouter = new OpenRouter({ apiKey: process.env.OPENROUTER_API_KEY });
const REPLY_MODEL = '~openai/gpt-luna-latest';
const REPLY_SYSTEM = 'Вы пишете короткие, теплые ответы для команды поддержки Northwind. Используйте только факты из сообщения пользователя. Не обещайте возвраты, кредиты или даты, которых нет в фактах. Максимум три предложения.';
type Draft = {
text: string;
model: string;
costUsd: number;
};
export async function draftReply(ticket: string, facts: string): Promise {
const result = await openrouter.chat.send({
chatRequest: {
model: REPLY_MODEL,
messages: [
{ role: 'system', content: REPLY_SYSTEM },
{ role: 'user', content: `Тикет: ${ticket}\n\nФакты: ${facts}` },
],
},
});
// chat.send может вернуть поток, если запрошен стриминг. Здесь его нет, поэтому отклоняем этот случай.
if (result instanceof ReadableStream) {
throw new Error('Expected a non-streaming response');
}
const text = result.choices[0]?.message.content;
if (typeof text !== 'string') {
throw new Error('Expected text content');
}
const cost = result.usage?.cost;
if (typeof cost !== 'number') {
throw new Error('Response did not include usage.cost');
}
return { text, model: result.model, costUsd: cost };
} Выбирайте модель по задаче написания. Когда вам нужен короткий ответ быстро и дешево, GPT Luna доставляет его, примерно за $0,0001 за ответ в этом запуске. Переходите на передовую модель, когда письмо требует реального рассуждения, длинного контекста или кода. LLM также является ответом там, где вы сталкиваетесь с некоторыми ограничениями Jev, такими как ввод с изображениями, аудио или PDF, или вывод, который является документом, дифом или планом.

06Маршрутизация с Jev, вычисления в коде, написание с LLM
Если посмотреть в целом, первый паттерн прост. Jev проводит триаж. Код проверяет, насколько он уверен. Только когда результат — проза, код вызывает LLM.
Обработчик отправляет все, у кого эскалация 0,5 или выше, человеку. Он также отправляет все, у кого уверенность в намерении ниже 0,8. В противном случае он отвечает на статус заказа из системы заказов без модели, и просит LLM подготовить черновик только для намерений, которым требуется проза.
import { triage, type Intent, type Triage } from './triage'
import { draftReply } from './draft-reply'
// Эти пороги — политика приложения. Настройте их на своих размеченных тикетах.
const ROUTE_CONFIDENCE = 0.8;
const ESCALATE_THRESHOLD = 0.5;
type ReplyIntent = Exclude;
type Route =
| { kind: 'human'; reason: string }
| { kind: 'deterministic'; intent: 'order_status' }
| { kind: 'reply'; intent: ReplyIntent };
function chooseRoute(t: Triage): Route {
if (t.escalateProbability >= ESCALATE_THRESHOLD) {
return { kind: 'human', reason: `вероятность эскалации ${t.escalateProbability}` };
}
if (t.confidence < ROUTE_CONFIDENCE) {
return { kind: 'human', reason: `уверенность в намерении ${t.confidence}` };
}
if (t.intent === 'order_status') {
return { kind: 'deterministic', intent: t.intent };
}
return { kind: 'reply', intent: t.intent };
}
// Заглушка для вашей системы заказов. Точные данные никогда не проходят через модель.
function lookupOrder(ticket: string): string {
const id = ticket.match(/\b\d{5}\b/)?.[0];
if (id === undefined) {
return 'Пожалуйста, ответьте с пятизначным номером заказа, и мы проверим отправку.';
}
return `Заказ ${id} отправлен 2026-09-17 через UPS, трек 1Z999AA10123456784, ожидаемая доставка 2026-09-21.`;
}
export const FACTS: Record = {
return_refund: 'Возвраты принимаются в течение 30 дней после доставки для обычных товаров. Обмены на другой размер следуют тому же 30-дневному окну. Товары со скидкой не подлежат возврату или обмену. Предоплаченные этикетки отправляются по электронной почте в течение одного рабочего дня.',
billing_dispute: 'Специалист по счетам рассмотрит платеж в течение одного рабочего дня.',
product_question: 'Характеристики продукта указаны на каждой странице продукта.',
account_access: 'Сброс паролей доступен на странице входа.'
};
export type Handled = {
triage: Triage;
route: Route;
text: string;
costUsd: number;
};
// Вызов Jev плюс вызов LLM, если он произошел
// Маршрутизирует тикет. Маршрут 'reply' содержит непроверенный черновик LLM; send.ts проверяет его перед отправкой.
export async function handle(ticket: string): Promise {
const t = await triage(ticket);
const route = chooseRoute(t);
switch (route.kind) {
case 'human':
return { triage: t, route, text: `запрошен человек: ${route.reason}`, costUsd: t.costUsd };
case 'deterministic':
return { triage: t, route, text: lookupOrder(ticket), costUsd: t.costUsd };
case 'reply': {
const reply = await draftReply(ticket, FACTS[route.intent]);
return { triage: t, route, text: reply.text, costUsd: t.costUsd + reply.costUsd };
}
default:
const _exhaustiveCheck: never = route;
return _exhaustiveCheck;
}
} Короткий бегун печатает сигналы Jev рядом с каждым результатом.
import { handle } from './handle'
const TICKETS = [
"Привет, я заказал пару беговых кроссов 3-го числа, а страница отслеживания не обновлялась пять дней. Где моя посылка? Заказ 84721.",
'Куртка, которую я получил, слишком мала. Могу ли я обменять ее на большую? Я получил ее во вторник.',
"На моей карте есть несанкционированный платеж в размере $450 от вашей компании. Я уже позвонил в банк, чтобы оспорить его, и сообщаю об этом как о мошенничестве.",
'Я оплатил подарочной картой и кредитной картой, но возврат пришел только на кредитную карту. Где баланс подарочной карты?'
];
for (const ticket of TICKETS) {
const result = await handle(ticket);
const { intent, confidence, escalateProbability } = result.triage;
console.log(`"${ticket}"`);
console.log(` intent=${intent} confidence=${confidence} escalate=${escalateProbability}`);
console.log(` route=${JSON.stringify(result.route)} text=${result.text}`);
console.log(` cost=${result.costUsd}`);
}07Проверяйте вывод LLM с помощью Jev перед запуском
Один из мощных паттернов — использовать Jev для проверки вывода LLM перед его отправкой. Например, если LLM генерирует ответ, вы можете отправить его в Jev с вопросом: «Содержит ли этот ответ обещания, которых нет в фактах?» или «Является ли этот ответ вежливым и соответствующим тону?». Jev вернет вероятность, и если она ниже порога, вы можете попросить LLM переписать ответ или отправить его человеку.
08Подключите все вместе
Итак, как это выглядит в реальной системе? Вот упрощенный поток:
- Пользователь отправляет тикет.
- Jev классифицирует тикет и определяет вероятность эскалации.
- Код проверяет пороги. Если эскалация высокая или уверенность низкая, тикет отправляется человеку.
- Если тикет — статус заказа, код отвечает из базы данных.
- В противном случае код вызывает LLM для генерации ответа.
- (Опционально) Jev проверяет ответ LLM на соответствие критериям.
- Ответ отправляется пользователю.

09Выбирайте пороги на основе своих данных
Пороги, такие как ROUTE_CONFIDENCE и ESCALATE_THRESHOLD, не являются универсальными. Они должны быть настроены на основе ваших собственных размеченных тикетов. Запустите бенчмарк на своих данных, постройте кривую ROC и выберите пороги, которые максимизируют точность и минимизируют ложные срабатывания.
10Начните с одного оператора switch
Не пытайтесь сразу же внедрить всю систему. Начните с одной задачи, которая заканчивается оператором switch. Например, классификация тикетов по категориям. Внедрите Jev, настройте пороги, протестируйте. Затем добавьте LLM для генерации ответов. Постепенно расширяйте систему.
11Что это значит на практике
Использование Jev вместе с LLM позволяет создать систему поддержки, которая является быстрой, дешевой и точной. Jev берет на себя рутинную работу по классификации и маршрутизации, экономя деньги и время. LLM берет на себя творческую работу по генерации ответов. Вместе они создают мощный симбиоз, который превосходит использование любой из технологий по отдельности.
Для компаний, обрабатывающих тысячи тикетов в месяц, это может означать экономию тысяч долларов в месяц и значительное улучшение времени отклика. Для разработчиков это означает более простой и предсказуемый код, так как Jev возвращает типизированные данные, а не свободный текст.
Не бойтесь экспериментировать. Начните с малого, настройте пороги под свои данные и постепенно масштабируйте систему. Jev и LLM — это не конкуренты, а партнеры. Используйте их вместе, и вы увидите разницу.
Источник: OpenRouter ↗
