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: на совместимый шлюз, тестовый сервер или частное развёртывание.

Источники