Jev в службе поддержки: разбор и маршрутизация обращений
Каждое обращение в поддержку требует решения ещё до ответа — что это за вопрос, насколько он срочный и кто должен им заняться. Показываем, как отдать эту сортировку Jev и оставить людям и LLM только те обращения, где они действительно нужны.
Что Jev делает в поддержке
Jev встаёт на входе очереди и отвечает на вопросы об обращении: о чём оно, насколько сложное и срочное, просит ли клиент живого оператора. По ответам ваш код выбирает путь. Простой запрос закрывает код без LLM, типовой — LLM-специалист, спорный или рискованный — человек. В документации TypeSafe этот паттерн называется intent routing: дорогие ресурсы тратятся только на обращения, которым они действительно нужны. Подробный разбор паттерна — в статье о маршрутизации по намерению.
| Путь | Что туда попадает | Кто отвечает |
|---|---|---|
| Код | статус заказа, типовые справки | интеграция с CRM, шаблон |
| LLM | вопросы о продукте, стандартные возвраты | LLM с базой знаний или черновик для оператора |
| Человек | жалобы, угрозы уйти, спорные случаи | оператор или менеджер |
Схема вопросов
| Вопрос | Тип | Что даёт |
|---|---|---|
intent |
Choice | тему: статус заказа, продукт, возврат, оплата, ошибка, жалоба, другое |
complexity |
Score, 3 уровня | стандартная процедура, нужно суждение, нестандартный случай |
urgency |
Score, 3 уровня | может подождать, сегодня, немедленно |
wants_human |
Noul | клиент просит живого оператора |
churn_risk |
Noul | клиент грозит уйти или пойти в суд |
refund_requested |
Noul | клиент просит вернуть деньги |
Последний вопрос нужен только для обращений об оплате, но задавать его стоит всем. В документации TypeSafe этот приём называется speculative fan-out: все вопросы уходят одним запросом, а код потом берёт нужные ответы. Лишние вопросы почти не увеличивают задержку, а отдельный уточняющий запрос стоил бы ещё одного сетевого обращения. Подробнее — в статье о fan-out в Jev.
В state кладите всё, что нужно для решения: текст обращения, данные клиента, при необходимости — выдержку из правил. Документация TypeSafe советует группировать связанные данные в одном объекте с понятными именами полей.
from typesafe_sdk import TypeSafeClient, Choice, Noul, Score
state = {
"ticket": {
"subject": "Списали деньги дважды",
"messages": [
"Здравствуйте! За подписку списали оплату два раза. "
"Если не вернёте до пятницы, отменю подписку."
],
},
"customer": {"plan": "business", "open_orders": 0},
}
questions = {
"intent": Choice(
instructions="Какова основная цель обращения?",
criteria={
"order_status": "Узнать статус существующего заказа",
"product_question": "Вопрос о продукте или тарифе до покупки",
"return_exchange": "Вернуть или обменять товар",
"billing": "Списания, счета, оплата подписки",
"bug": "Ошибка или сбой в работе сервиса",
"complaint": "Недоволен сервисом и хочет решения",
"other": None,
},
),
"complexity": Score(
instructions="Насколько сложно решить обращение?",
criteria=[
"простая справка или стандартная процедура",
"нужно суждение или несколько шагов",
"нестандартная ситуация, нужна эскалация",
],
),
"urgency": Score(
instructions="Насколько срочно нужно ответить?",
criteria=["может подождать", "сегодня", "немедленно"],
),
"wants_human": Noul(instructions="Клиент просит соединить его с живым оператором?"),
"churn_risk": Noul(instructions="Клиент грозит уйти, отменить подписку или пойти в суд?"),
"refund_requested": Noul(instructions="Клиент просит вернуть деньги?"),
}
with TypeSafeClient() as client:
r = client.system_one(state=state, questions=questions)
Маршрутизация по уверенности
def route(r) -> str:
intent = r.choices["intent"]
complexity = r.scores["complexity"] # от 0 до 2
if intent.confidence < 0.5 or r.nouls["wants_human"].noul >= 0.8:
return "human"
if intent.choice == "order_status":
return "code" # ответ из CRM без LLM
if intent.choice == "complaint" and (complexity.score > 1 or complexity.confidence < 0.5):
return "human"
if intent.choice == "billing" and r.nouls["refund_requested"].noul >= 0.7:
return "billing_queue" # деньги возвращает человек
return "llm"
Основа взята из примера TypeSafe: при уверенности в теме ниже 0,5 обращение уходит человеку, статус заказа обрабатывает код, а жалобу со сложностью выше 1 или с неуверенной оценкой сложности получает оператор. Остальные правила — наши дополнения. Вопрос churn_risk не меняет маршрут, а влияет на приоритет: высокое значение — повод сразу уведомить менеджера клиента.
Для обращения из примера ответ мог бы выглядеть так (числа условные): тема billing с уверенностью 0,86, refund_requested — 0,93, churn_risk — 0,81, wants_human — 0,12. Код отправит обращение в очередь биллинга, а высокий риск ухода поднимет его в начало очереди. Оценки стоит показать оператору рядом с текстом обращения.
Пороги по цене ошибки
В примере паттерна confidence-gated routing TypeSafe разбирает голосового помощника банка. Общий нижний порог — 0,6: всё, что ниже, уходит в поддержку. Действия с низкой ценой ошибки, например озвучить баланс, выполняются от 0,6. Перевод денег выполняется автоматически только от 0,85, а в промежутке 0,6–0,85 помощник просит подтверждения. В поддержке та же логика может выглядеть так:
| Действие | Цена ошибки | Стартовая политика |
|---|---|---|
| Тег и очередь | низкая — оператор перекинет | автоматически от 0,6 |
| Автоответ из базы знаний | средняя — клиент получит не тот ответ | автоматически от 0,85, ниже — черновик для оператора |
| Возврат денег, компенсация | высокая | только человек, Jev готовит карточку обращения |
Числа — отправная точка. Как подобрать их на своих данных, рассказано в статье об уверенности Jev.
Приоритет очереди считайте в коде
Не спрашивайте модель, насколько важно обращение. Спросите о составляющих — срочности, риске ухода — и сложите их с весами в коде. Автор проекта typesafe-triage-guard объясняет выгоду так: изменение веса становится правкой кода с понятными последствиями, а не правкой промпта.
urgency = r.scores["urgency"].score / 2 # приводим к шкале 0..1
churn = r.nouls["churn_risk"].noul
vip = 1.0 if state["customer"]["plan"] == "business" else 0.0
priority = 0.5 * urgency + 0.3 * churn + 0.2 * vip
Подробно этот приём описан в статье о составной оценке.
Как проверить качество
- Историческая разметка. Возьмите 300–500 закрытых обращений, где известен итоговый отдел. Учтите, что в истории есть ошибки операторов: спорные случаи пересмотрите вручную.
- Теневой режим. Одну-две недели Jev размечает обращения, но ничего не решает. Сравните её маршруты с решениями операторов по каждому классу.
- Что мерить. Точность по классам, долю обращений, ушедших к человеку, долю неверных автоматических маршрутов и время до первого ответа.
- Путаница классов. Если вероятности размазаны между «оплатой» и «возвратом», разведите описания вариантов в criteria.
- Русский язык. Основной язык обучения Jev — английский, на других языках точность ниже, поэтому мерьте на своих обращениях.
Реальные примеры
Triage Desk. Один запрос на обращение и четыре вопроса: отдел (Choice), срочность (Score), нужен ли человек (Noul) и спам (Noul). По замерам автора, в медиане это около 400 мс и примерно 540 входных токенов на обращение. Дальше обращения расходятся по трём дорожкам: срочные и эскалации — людям, обычные проблемы — LLM, которая готовит черновик ответа для оператора, простые вопросы и спам — в архив или на автоответ из FAQ.
triage-guard. Сначала обращение проходит защитную проверку. Если она блокирует сообщение, классификация не запускается и второй запрос не тратится. Приоритет считается из нескольких оценок с весами в коде, а при низкой уверенности обращение уходит человеку независимо от ответа модели. В опубликованном прогоне на реальной модели сообщение о двойном списании ушло в отдел оплаты с уверенностью 1,0.
Подводные камни
- Пересекающиеся категории. «Оплата» и «возврат» частично совпадают — опишите границу в criteria явно.
- Нет варианта «другое». Без него модель выберет ближайшую категорию даже для обращения не по адресу.
- Больше 255 категорий. В одном Choice их не уместить. Разбейте выбор на два этапа: сначала группа, потом тема внутри группы.
- Длинные переписки. Отправляйте последние сообщения и нужные поля CRM, а не всю историю: по документации TypeSafe, лишний контекст снижает точность.
- Суммы и даты. TypeSafe прямо пишет, что Jev — не калькулятор и плохо сравнивает даты. Сколько раз и когда списали деньги, проверяет код по данным биллинга.
- Ответ пишет не Jev. Для текста нужна LLM или шаблоны. Jev может выбрать шаблон из списка, и это тоже Choice. Примеры кода на Python — в руководстве по SDK.
Когда Jev в поддержке не нужна
Автор обзора на Хабре перечисляет случаи, когда Jev лучше не брать. В поддержке они выглядят так:
- тему уже выбирает сам клиент в форме обращения — тогда хватит обычных правил;
- набор тем стабилен и есть тысячи размеченных обращений — дообученный локальный классификатор может оказаться точнее и быстрее;
- переписку с клиентами нельзя отправлять во внешнее облако;
- решение сразу запускает необратимое действие, например возврат денег, без проверки человеком.
В остальных случаях основные затраты уходят на то, чтобы написать вопросы и подобрать пороги. Сама модель стоит доли цента на обращение: в Triage Desk это около 540 входных токенов, то есть примерно $0,00002.
Частые вопросы
Может ли Jev отвечать клиентам вместо оператора?
Нет, Jev не генерирует текст. Она может выбрать подходящий шаблон ответа из списка или решить, какому обработчику передать обращение, а сам ответ пишет человек, LLM или готовый шаблон.
Сколько вопросов можно задать одному обращению?
Отдельного лимита на число вопросов в документации нет, ограничивает общий размер запроса. Все вопросы считаются параллельно. В тесте TypeSafe 13 вопросов в одном запросе обошлись в 12 раз дешевле и в 10 раз быстрее, чем 13 отдельных запросов.
Как начать использовать Jev в поддержке?
С теневого режима. Пусть Jev размечает обращения параллельно с текущим процессом, а вы сравниваете её решения с решениями операторов, прежде чем доверить ей маршрутизацию.
Источники
- TypeSafe Docs — Intent routing
- TypeSafe Docs — Confidence-gated routing
- TypeSafe Docs — Speculative fan-out
- TypeSafe Docs — Parallel questions
- TypeSafe Docs — State
- TypeSafe Docs — Jev 1.13 jaggedness
- GitHub — jev-triage-desk
- GitHub — typesafe-triage-guard
- Хабр — Jev — как устроен его API решений и что на нём уже строят