Jev и LangChain: обвязка (harness) для агентов
LangChain выпустила интеграцию с Jev через два дня после релиза модели. Идея такая: агентом по-прежнему управляет LLM, а мелкие решения внутри цикла принимает Jev. Например, какую модель взять для задачи и не опасен ли вызов инструмента.
Зачем Jev в цикле агента
Агент работает циклом: LLM решает, что делать, инструмент выполняет действие, модель оценивает результат, и всё повторяется до конца задачи. В блоге LangChain объясняют, где здесь узкое место. Вызов инструментов и структурированный вывод упростили встраивание LLM в программы, но каждое решение в цикле по-прежнему стоит отдельного вызова модели. Это медленно и дорого.
Jev закрывает часть этих решений: выбрать ветку, оценить риск, классифицировать запрос. По данным TypeSafe, которые приводит LangChain, на задачах классификации Jev работает до 200 раз быстрее и до 400 раз дешевле сопоставимых LLM. Это заявление компании, на своих данных его стоит проверить.
LangChain прямо пишет, что Jev не заменяет LLM. Модель не пишет текст, поэтому схема получается связкой: LLM рассуждает и генерирует, Jev быстро принимает структурные решения по ходу работы. Как устроены вопросы, на которые отвечает Jev, разобрано в статье о примитивах Noul, Choice и Score.
Установка
pip install langchain-typesafe
export TYPESAFE_API_KEY="ваш_ключ"
Пакет требует Python 3.10 или новее. На 20 сентября 2026 года на PyPI лежит версия 0.0.1a3, то есть альфа. Для middleware нужен дополнительный набор зависимостей:
pip install "langchain-typesafe[experimental]"
Если вы работаете через шлюз, задайте его адрес в TYPESAFE_BASE_URL. По умолчанию используется https://api.typesafe.ai.
TypeSafeClassifier: первый вызов
Интеграция отдаёт Jev как TypeSafeClassifier. Это Runnable, поэтому его можно вызывать через invoke, ainvoke и batch и собирать в цепочки с другими компонентами LangChain.
from langchain_typesafe import Choice, Noul, Score, TypeSafeClassifier
classifier = TypeSafeClassifier()
response = classifier.invoke(
{
"state": "Деплой упал дважды, у клиентов ошибки 500. Посмотрите срочно?",
"questions": {
"urgent": Noul(instructions="Это нужно решать прямо сейчас?"),
"team": Choice(
instructions="Какая команда должна взять задачу?",
criteria={
"infra": "Деплой, доступность, дежурства",
"billing": "Платежи, счета, подписки",
},
),
"severity": Score(
instructions="Насколько серьёзно влияние на пользователей?",
criteria=["Косметика", "Страдает часть пользователей", "Полный отказ"],
),
},
}
)
print(response.nouls["urgent"].noul)
print(response.choices["team"].choice, response.choices["team"].confidence)
print(response.scores["severity"].score)
state и questions передаются вместе при каждом вызове. В ранних версиях вопросы задавались в конструкторе классификатора, так что старые примеры из сети могут не работать. Кроме ответов, в объекте есть модель, которая ответила, расход токенов в usage и request_id. Вызовы попадают в трассировку LangSmith вместе с остальными шагами агента.
Сообщения LangChain как state
В state можно передать строку, JSON или сообщения LangChain. Объекты BaseMessage и списки сообщений превращаются в JSON с полями role и content, даже если лежат внутри большого объекта:
from langchain_core.messages import HumanMessage, SystemMessage
response = classifier.invoke(
{
"state": {
"conversation": [
SystemMessage("Ты проверяешь переписку службы поддержки."),
HumanMessage("Третий день не проходят выплаты. Помогите!"),
],
"account_tier": "enterprise",
},
"questions": {"urgent": Noul(instructions="Клиенту нужна срочная помощь?")},
}
)
Поэтому Jev удобно вызывать из узла графа или хука middleware: контекст агента уже лежит в нужном виде.
Готовые middleware
В пакете две экспериментальные middleware для create_agent. Импортируются они из langchain_typesafe.experimental.middleware.
| Middleware | Хуки | Что решает |
|---|---|---|
ModelRouterMiddleware |
before_agent, wrap_model_call |
Какая модель обработает запуск |
AutoModeMiddleware |
wrap_tool_call |
Не слишком ли рискован вызов инструмента |
Роутер моделей
Простой запрос не требует той же модели, что сложная отладка. Роутер задаёт Jev вопрос типа Choice по последнему сообщению пользователя и выбирает модель по вашим критериям:
from langchain.agents import create_agent
from langchain_typesafe.experimental.middleware import (
ModelChoice,
ModelRouterMiddleware,
)
router = ModelRouterMiddleware(
choices={
"fast": ModelChoice(
model="openai:gpt-5.6-terra", # подставьте свои модели
criteria="Простые запросы, извлечение данных, точечные правки.",
),
"powerful": ModelChoice(
model="openai:gpt-6-astra",
criteria="Архитектура, поиск причин сбоев, решения с высокой ценой ошибки.",
),
},
instructions="Выбери самую дешёвую модель, которая надёжно справится с задачей.",
)
agent = create_agent("openai:gpt-5.6-terra", middleware=[router])
Классификация идёт один раз за запуск, выбранная модель обслуживает все вызовы до конца. Полный ответ с вероятностями и confidence сохраняется в состоянии агента, в result["model_route"]. Его стоит логировать: так видно, где роутер сомневался. Подробнее о подходе — в статье о маршрутизации между моделями.
Auto Mode: проверка вызовов инструментов
Агента можно уговорить на нежелательное действие: плохой инструкцией или атакой через входные данные. LangChain замечает, что в агентах для программирования вроде Claude Code, Codex и Cursor уже есть классификаторы опасных действий, но они закрыты. Дешёвая модель-классификатор позволяет повторить этот приём в любом агенте.
from langchain.agents import create_agent
from langchain_typesafe import NoulCriteria
from langchain_typesafe.experimental.middleware import AutoModeMiddleware
# model, read_file и delete_file определены в вашем коде
auto_mode = AutoModeMiddleware(
tools=[delete_file],
criteria=NoulCriteria(
true="Вызов пишет, удаляет, публикует данные или меняет права доступа.",
false="Вызов только читает публичные данные или данные пользователя.",
),
)
agent = create_agent(model, tools=[read_file, delete_file], middleware=[auto_mode])
Проверяются только перечисленные инструменты: их можно передать объектами или по именам. Если вероятность риска достигает порога, вместо запуска инструмента агент получает ToolMessage с ошибкой. У этой middleware две оговорки из документации:
- она отклоняет рискованные вызовы, но не запрашивает подтверждения. Если нужен человек, добавьте middleware human-in-the-loop;
- аргументы инструментов и состояние диалога уходят в TypeSafe. Не кладите туда секреты, если их нельзя передавать внешнему сервису.
Другие схемы защиты описаны в статье о безопасности агентов.
Своя обвязка на любом шаге
Готовые middleware закрывают два сценария. Остальное удобно собрать самому: классификатор принимает сообщения LangChain напрямую, так что вызов Jev умещается в один хук. Пример из документации LangChain, сокращённый, с русскими критериями:
from langchain.agents.middleware import AgentMiddleware, AgentState, Runtime
from langchain_typesafe import Choice, ChoiceAnswer, TypeSafeClassifier
from typing_extensions import NotRequired
class TriageState(AgentState):
triage: NotRequired[ChoiceAnswer]
class TriageMiddleware(AgentMiddleware[TriageState]):
state_schema = TriageState
def __init__(self) -> None:
self.classifier = TypeSafeClassifier()
def before_agent(self, state: TriageState, runtime: Runtime) -> dict[str, ChoiceAnswer]:
response = self.classifier.invoke(
{
"state": state["messages"],
"questions": {
"triage": Choice(
instructions="Какая команда должна вести этот диалог?",
criteria={
"billing": "Платежи, счета, подписки",
"infra": "Деплой, доступность, инциденты",
"other": "Запросы для других команд",
},
)
},
}
)
return {"triage": response.choices["triage"]}
Хук выбирайте по моменту решения. before_agent срабатывает один раз за запуск, before_model — перед каждым вызовом модели, то есть и после каждого результата инструмента, а wrap_tool_call оборачивает предложенное действие.
Ошибки и сеть
Ошибки провайдера наследуются от стандартной иерархии LangChain. Можно ловить специфичное исключение TypeSafe, если нужны его поля, или общее, если в приложении несколько провайдеров:
from langchain_core.exceptions import ModelAuthenticationError, ModelRateLimitError
from langchain_typesafe import TypeSafeRateLimitError
try:
response = classifier.invoke(request)
except TypeSafeRateLimitError as error:
print(error.request_id, error.retry_after_ms)
except (ModelAuthenticationError, ModelRateLimitError):
handle_model_error()
Здесь request — словарь со state и questions, а handle_model_error — ваш обработчик. Для прокси и общих пулов соединений в классификатор можно передать свои HTTP-клиенты через параметры client и async_client. Что означают коды 401, 422, 429 и 529, разобрано в статье об ошибках API Jev.
Что учесть перед продакшеном
- Версия. Пакет в альфе, API middleware может измениться. Зафиксируйте версию в зависимостях и проверяйте изменения перед обновлением.
- Язык. По документации TypeSafe, основной язык обучения Jev — английский, на нём точность лучше всего. Прогоните русские данные до запуска.
- Пороги. Порог риска в Auto Mode и выбор модели в роутере проверьте на размеченных примерах. Как читать вероятности и
confidence, объясняем в статье об уверенности Jev.
Частые вопросы
Как подключить Jev к LangChain?
Установите пакет langchain-typesafe, задайте переменную TYPESAFE_API_KEY и вызывайте TypeSafeClassifier().invoke() со словарём, где есть state и questions. Ответы приходят в nouls, choices и scores.
Можно ли использовать Jev как чат-модель в LangChain?
Нет. Jev не генерирует текст, поэтому интеграция устроена как Runnable-классификатор, а не как ChatModel. Отвечать пользователю по-прежнему должна LLM.
Насколько стабильна интеграция langchain-typesafe?
Это ранняя версия: на 20 сентября 2026 года на PyPI лежит 0.0.1a3, а middleware находятся в модуле experimental и могут меняться без предупреждения.
Можно ли работать через шлюз вместо API TypeSafe?
Да. Документация LangChain разрешает сменить адрес через переменную TYPESAFE_BASE_URL: на совместимый шлюз, тестовый сервер или частное развёртывание.