Как проектировать систему на Jev: пошаговый подход
Jev — не агент и не чат-бот, а примитив, который встраивается в обычный код. Поэтому и проектировать систему на ней нужно иначе, чем на LLM. Разбираем подход TypeSafe по шагам и собираем всё в рабочую схему разбора обращений.
Главный принцип: код управляет, модель решает
TypeSafe сравнивает три архитектуры:
| Архитектура | Кто управляет процессом | Особенность |
|---|---|---|
| Обычное ПО | Код — дерево решений из надёжных примитивов | Надёжно, но само не разбирает неструктурированные данные |
| LLM-агент | Модель сама выбирает следующий шаг | Каждый цикл — ещё один шанс сбиться |
| ПО с ИИ-примитивами | Код, а модель — только там, где нужен здравый смысл | Каждое ИИ-решение узкое и ограничено схемой |
Агентная схема, по оценке TypeSafe, хорошо работает, когда за процессом следит человек. Jev рассчитана на третий вариант. Она не пишет код и не выбирает собственное следующее действие, а отвечает на узкие типизированные вопросы. Управление, правила и побочные эффекты остаются в коде.
Название класса моделей подсказывает, какие вопросы им задавать. System One отсылает к «быстрому мышлению» из книги Даниэля Канемана «Думай медленно… решай быстро»: модель рассчитана на быстрые сфокусированные суждения, а не на долгие рассуждения.
Ниже — семь шагов из руководства TypeSafe с нашими комментариями.
Шаг 1. Всё, что можно, делайте кодом
Детерминированная логика надёжна и дёшева. Если счёт просрочен больше чем на 30 дней, это if, а не вопрос к модели. То же относится к арифметике, счёту и сравнению дат: по данным TypeSafe, jev-1.13 в них ошибается. Модель извлекает, код считает.
Перед каждым новым вопросом спросите себя, нельзя ли получить ответ регулярным выражением, парсером или запросом к базе.
Шаг 2. Соберите state только из нужного
Кладите в state только то, что относится к текущим вопросам. Лишние детали отвлекают модель — TypeSafe называет это «гниением контекста» (context rot). И не полагайтесь на знания в весах модели, если актуальные данные есть в вашей базе: правила возврата лучше передать в state, чем надеяться, что модель их «знает».
Структурируйте state как JSON с понятными именами полей и указывайте в вопросах путь к нужному значению в обратных кавычках:
{
"model": "jev-latest",
"state": {
"ticket": { "message": "С меня дважды списали 4900 ₽ за заказ A-104." },
"order": {
"id": "A-104",
"charges": [
{ "amount_rub": 4900, "status": "captured" },
{ "amount_rub": 4900, "status": "captured" }
]
},
"refund_policy": "Двойные списания возвращаются полностью."
},
"questions": {
"duplicate_charge": {
"type": "noul",
"instructions": "Указывают ли `ticket.message` и `order.charges` на двойное списание?"
},
"policy_supports_refund": {
"type": "noul",
"instructions": "Подтверждает ли `refund_policy` возврат, о котором просит `ticket.message`?"
}
}
}
Подробнее о формате — в статье о state в Jev.
Шаг 3. Разбейте вопросы до атомарных
TypeSafe называет этот шаг, вероятно, самым важным. Широкий вопрос прячет несколько суждений за одним ответом, а атомарные открывают их для проверки, настройки и комбинирования.
Пример из документации — подозрительное письмо. Плохо: один вопрос «Это спам?». Хорошо: шесть узких Noul.
| Вопрос | Что проверяет |
|---|---|
requests_credentials |
просит ли письмо пароль или другие данные для входа |
offers_unexpected_reward |
обещает ли неожиданный приз или выплату |
creates_time_pressure |
торопит ли получателя |
sender_identity_mismatch |
противоречит ли имя отправителя его домену |
link_domain_mismatch |
противоречит ли домен ссылки названной организации |
disguises_link_destination |
маскирует ли текст ссылки её реальный адрес |
Так же TypeSafe разбирает проверку вызовов инструментов у агента: вместо «вызовы корректны?» — девять отдельных вопросов про выбор инструмента, соответствие аргументов схеме, совпадение даты, единиц измерения и координат.
Шаг 4. Добавьте структуру в вопросы
Кроме строк, instructions и criteria принимают объекты. Это удобно, когда к вопросу нужны данные из кода: запись из базы кладётся в отдельное поле, а вопрос ссылается на неё по имени. Похожие варианты Choice разводят описаниями с полями «что входит», «что не входит» и «примеры». Короткий однозначный вопрос оставляйте строкой. Все приёмы разобраны в статье о продвинутых вопросах.
Шаг 5. Задавайте много вопросов за раз
Независимые вопросы к одному state отправляйте одним запросом. Они считаются параллельно, поэтому декомпозиция не добавляет лишних обращений к API. По словам TypeSafe, так вы получаете максимум «интеллекта на доллар» — подробности в статье о паттерне fan-out.
Шаг 6. Собирайте ответы в коде
Независимые ответы объединяют правилами или взвешенной суммой. Если есть исторические исходы, вероятности Jev можно подать признаками в классическую ML-модель. Как объединять, не теряя отдельных суждений, — в статье о составной оценке.
Шаг 7. Маршрутизируйте по неуверенности
Код должен по-разному действовать при уверенных и неуверенных ответах, а неуверенные — отдавать человеку или более дорогой рассуждающей модели. Пороги TypeSafe советует проверять на своих данных, построив зависимость точности от уверенности. Как собрать из этого экономичную систему — в статье о каскаде Jev → LLM.
Как это выглядит целиком
Сокращённая версия примера TypeSafe — разбор входящего обращения:
from typesafe_sdk import Choice, Noul, TypeSafeClient
def triage_ticket(ticket, customer):
if ticket["status"] == "closed": # шаг 1: детерминированное решает код
return "no_action"
state = { # шаг 2: только нужный контекст
"ticket": {"message": ticket["message"], "sender": ticket["sender"]},
"customer": {"plan": customer["plan"]},
}
questions = { # шаги 3–5: атомарные вопросы одним запросом
"topic": Choice(
instructions="Какая команда должна обработать `ticket.message`?",
criteria={
"billing": "Списания, счета, возвраты",
"orders": "Статус, доставка, отмена заказа",
"account": "Вход, профиль, безопасность",
},
),
"requests_credentials": Noul(instructions="Просит ли `ticket.message` прислать пароль или код?"),
"sender_mismatch": Noul(instructions="Противоречит ли `ticket.sender.display_name` домену в `ticket.sender.email`?"),
"unexpected_reward": Noul(instructions="Сообщает ли `ticket.message` о неожиданном призе или выплате?"),
"refund_requested": Noul(instructions="Клиент прямо просит вернуть деньги?"),
}
with TypeSafeClient() as client:
r = client.system_one(state=state, questions=questions)
spam_risk = ( # шаг 6: веса в коде
0.45 * r.nouls["requests_credentials"].noul
+ 0.30 * r.nouls["sender_mismatch"].noul
+ 0.25 * r.nouls["unexpected_reward"].noul
)
topic = r.choices["topic"]
if 0.4 < spam_risk < 0.6 or topic.confidence < 0.75: # шаг 7
return route_to_human_review(ticket)
if spam_risk >= 0.6:
return quarantine_as_spam(ticket)
if topic.choice == "billing":
return route_to_billing(ticket, refund=r.nouls["refund_requested"].noul >= 0.7)
return route_to_team(topic.choice, ticket)
Веса признаков спама — 0,45, 0,30 и 0,25 — в сумме дают 1. Серая зона от 0,4 до 0,6 и неуверенная тема уходят человеку, остальное код решает сам. Вопрос о возврате спекулятивный: код читает его только в ветке оплаты.
Как тестировать
Типизированные ответы удобно проверять обычными тестами. TypeSafe заявляет, что System One даёт стабильные ответы при повторных вызовах, поэтому набор размеченных примеров работает как регрессионный тест:
- Соберите от нескольких десятков до пары сотен реальных входов с ожидаемым ответом на каждый вопрос.
- Прогоните их и сохраните ответы, вероятности и уверенность.
- После каждой правки формулировок, критериев или весов прогоняйте набор снова и сравнивайте с прошлым результатом.
- Отдельно разбирайте примеры с низкой уверенностью: они показывают, каким вопросам не хватает контекста или чётких критериев.
Быстро попробовать state и вопросы без кода можно в песочнице консоли TypeSafe — на неё ведут ссылки из примеров в документации. Собрать запрос и сразу получить код поможет наш конструктор запроса.
Чего избегать
- спрашивать модель о том, что код вычисляет точно;
- прятать несколько суждений в одном вопросе;
- ставить задачи для медленного мышления: многоходовые рассуждения и цепочки косвенных ссылок;
- класть в state больше, чем нужно вопросу;
- ждать дообучения: по данным TypeSafe, Jev не дообучают под клиентов, и свою область модель узнаёт из state, инструкций и критериев;
- подбирать пороги под алиас
jev-latest: он переезжает на новые версии, поэтому TypeSafe советует фиксировать конкретную; - проверять систему только на английских примерах: основной язык обучения Jev — английский, и для русского потока точность и пороги нужно мерить отдельно.
Частые вопросы
Можно ли дообучить Jev на своих данных?
Нет. По данным TypeSafe, Jev не дообучают и не адаптируют через LoRA под клиентов — все аккаунты работают с одними весами. Под свою область модель настраивают через state, формулировки вопросов и критерии, а также через декомпозицию.
Чем система на Jev отличается от LLM-агента?
В агенте модель сама выбирает следующий шаг, и каждый цикл — шанс сбиться. В системе на Jev порядок действий задаёт код, а модель отвечает на узкие вопросы, результаты которых код проверяет и комбинирует.
Что важнее всего при проектировании на Jev?
По словам TypeSafe, декомпозиция вопросов. Узкие атомарные вопросы открывают каждое суждение для проверки и настройки, а широкий вопрос прячет несколько суждений за одним ответом.