YouMind
Войти

Создание кастомного харнеса с Pi и Jev

@omarsar0
АНГЛИЙСКИЙ23 сент. 2026 г.
102K
1.1K
115
41
2.2K

Суть

В этом руководстве разработчики узнают, как создать кастомный харнес для ИИ-агентов с помощью SDK Pi и специализированной модели принятия решений Jev. Показано внедрение шлюзов безопасности, динамической маршрутизации моделей и верификации ответов для оптимизации производительности и затрат.

AI-агент — это языковая модель, работающая в цикле. Она читает задачу, использует инструмент вроде «прочитай этот файл» или «удали тот файл», смотрит на результат и продолжает работу, пока дело не будет сделано. Каждый раз, когда модель просит использовать инструмент, такой запрос называется вызовом инструмента (tool call).

Код, который управляет этим циклом, называется обвязкой (harness). Модель решает, что она хочет сделать. Обвязка выполняет это решение, а заодно определяет, что модели вообще разрешено делать.

Хорошая обвязка по ходу дела принимает множество мелких решений. Какая модель должна обработать этот запрос? Безопасно ли выполнять этот вызов инструмента? Достаточно ли хорош ответ, чтобы вернуть его пользователю? Большинство обвязок отвечают на эти вопросы, обращаясь к чат-модели и читая её ответ. Но каждый такой запрос — это полноценный вызов модели, поэтому на практике большинство проверок просто пропускают.

Jev от TypeSafe AI — это небольшая модель, созданная исключительно для таких решений. Вы описываете ситуацию и задаёте несколько вопросов, а она отвечает на каждый числом. Никакого текста она не генерирует.

Особенно это важно, когда вы создаёте собственную обвязку — свой цикл агента вместо готового решения из коробки. Своя обвязка позволяет выбирать, какие модели работают, к чему агент имеет доступ и что считается выполненной задачей. Jev делает проверки, стоящие за этими выборами, настолько дешёвыми, что их можно запускать на каждом шаге.

В этом руководстве мы соберём обвязку с помощью Pi SDK — набора инструментов на TypeScript для создания агентов — и используем Jev в трёх местах. В конце вы запустите готовую обвязку в живой песочнице и сможете сами менять её настройки.

Полный интерактивный туториал и песочница доступны здесь:

https://academy.dair.ai/resources/jev-decisions-in-a-pi-sdk-harness

Это руководство вдохновлено статьёй Сидни Ранкл Building a Harness with Jev в блоге LangChain, где маршрутизация моделей и контроль доступа к инструментам показаны как готовые middleware для LangChain. Здесь вы реализуете те же идеи самостоятельно на Pi SDK, а затем добавите ещё два паттерна: обработку сбоев и проверку ответов.

Что мы будем создавать

Обвязка состоит из трёх частей. Каждая задаёт Jev вопрос в определённый момент, и дальше в руководстве мы будем называть их именно так.

elvis - inline image

Сквозной пример — агент, работающий в папке с заметками о походах по тропам (по одному файлу на выход). Он может читать, писать и реально удалять эти заметки, поэтому контроль доступа здесь критически важен.

Что такое Jev

TypeSafe называет Jev моделью Системы 1. Название отсылает к психологу Даниэлю Канеману, описавшему два режима мышления. Система 1 быстрая и автоматическая — например, когда вы сразу понимаете, что сковорода горячая. Система 2 медленная и осознанная — как деление в столбик.

В нашей обвязке обычная языковая модель выполняет медленную работу: читает файлы и пишет ответы. А Jev принимает быстрые решения вокруг неё. Jev достаточно дешёвая и быстрая, чтобы спрашивать её о каждом вызове инструмента, а не только о тех, которые кажутся вам опасными.

elvis - inline image

Каждое число — это вероятность от 0 до 1. Значение 0.83 означает, что Jev довольно уверена в ответе «да». А 0.03 — что она почти наверняка говорит «нет».

Три типа вопросов

Каждый запрос к Jev состоит из двух частей. Состояние (state) — это ситуация, которую нужно оценить, например вызов инструмента или запрос пользователя. Вопросы (questions) — то, что вы хотите о ней узнать. Jev отвечает на все вопросы за один вызов одновременно, поэтому три вопроса занимают примерно столько же времени, сколько один.

Jev поддерживает три вида вопросов. Первый, который в Jev называется noul, — это вопрос с ответом «да» или «нет».

elvis - inline image

Jev знает только то, что вы ей скажете, поэтому описывайте каждый вариант и каждый уровень простыми словами. Эти описания и есть промпт.

Подготовка

Jev доступна через OpenRouter — сервис, дающий доступ ко множеству AI-моделей по одному API-ключу. Этого ключа хватит и для языковой модели, и для Jev. Установите два пакета Pi и задайте ключ.

bash
1npm install @earendil-works/pi-agent-core @earendil-works/pi-ai
bash
1OPENROUTER_API_KEY=...

У Jev свой адрес, отдельный от обычного чат-API. Всегда указывайте точную версию, например typesafe/jev-1.13. Весь клиент Jev — это один вызов fetch.

javascript
1const JEV = "typesafe/jev-1.13";
2
3async function ask(state: unknown, questions: unknown) {
4 const response = await fetch("https://openrouter.ai/api/alpha/decisions", {
5 method: "POST",
6 headers: {
7 "Content-Type": "application/json",
8 Authorization: `Bearer ${process.env.OPENROUTER_API_KEY}`,
9 },
10 body: JSON.stringify({ model: JEV, state, questions }),
11 signal: AbortSignal.timeout(2_000),
12 });
13 if (!response.ok) throw new Error(await response.text());
14 return response.json();
15}

Агент ждёт ответа Jev, поэтому вызов прерывается через две секунды. Обычный ответ занимает от 200 до 400 миллисекунд.

Где используется Jev

Класс Agent в Pi запускает цикл за вас. Он позволяет выполнять ваш код в определённые моменты этого цикла. Такие точки называются хуками. Наша обвязка использует по одному хуку для каждой из трёх частей.

elvis - inline image

Первый барьер

Начнём с минимально полезной версии барьера. Перед каждым вызовом инструмента задаём Jev один вопрос «да/нет» и блокируем вызов, если ответ похож на «да».

javascript
1import { Agent } from "@earendil-works/pi-agent-core";
2
3const agent = new Agent({
4 initialState: { systemPrompt, model, tools },
5 streamFn: models.streamSimple.bind(models),
6
7 beforeToolCall: async ({ toolCall, args }) => {
8 const { answers } = await ask(
9 { tool: toolCall.name, arguments: args },
10 {
11 destructive: {
12 type: "noul",
13 instructions: "Этот вызов инструмента уничтожает или перезаписывает данные, созданные не в текущем запуске.",
14 criteria: {
15 true: "Удаляет файлы, обрезает или перезаписывает существующий контент, сбрасывает данные или делает force-push поверх истории.",
16 false: "Читает, выводит список, создаёт новый файл или дописывает в файл, уже созданный в этом запуске.",
17 },
18 },
19 },
20 );
21
22 if (answers.destructive.noul >= 0.65) {
23 return { block: true, reason: "Заблокировано: выглядит разрушительным.", terminate: true };
24 }
25 },
26});

Значение 0.65 — это порог, граница, после которой обвязка перестаёт доверять вызову. Всё, что Jev оценивает на эту отметку или выше, блокируется.

Теперь агент, пытающийся удалить ваши заметки, остановится до того, как удаление произойдёт. Удаление заметки даёт около 0.83, а чтение — 0.01. Правда, барьер грубоват. Создание совершенно нового файла оценивается примерно в 0.70, поэтому оно тоже будет заблокировано.

Запросы стоят копейки. Jev берёт $0.042 за миллион входных токенов — меньше трети от стоимости входа GLM 5.3 Flash, более дешёвой из двух моделей, которые использует эта обвязка.

От первого барьера к полноценной обвязке

Первый барьер работает, но оставляет четыре пробела.

  1. Порог зашит прямо в хук, поэтому его сложно менять или тестировать.
  2. Все запросы идут через одну и ту же модель, будь они простые или сложные.
  3. Нигде не сказано, что делать, если Jev недоступна.
  4. Никто не проверяет, насколько хорош итоговый ответ.

Каждый пронумерованный раздел ниже закрывает один из этих пробелов.

1. Храните пороги в одном месте

Этот раздел улучшает барьер.

Пороги определяют, что агенту разрешено делать, и вы будете часто их подкручивать, когда увидите реальные результаты. Поэтому вынесем их из хука в обычную функцию decideGate(). Она принимает числа от Jev и возвращает вердикт: окончательное решение обвязки по вызову. Сбор таких правил в одном месте называется политикой.

Политика добавляет и промежуточный вариант. Один порог может сказать только «разрешить» или «заблокировать». Два порога дают три вердикта.

  • На уровне blockAt или выше вызов блокируется.
  • Ниже askAt вызов выполняется.
  • Между ними вызов ждёт одобрения человека.

Этот средний диапазон ловит вызовы, в которых Jev сомневается и где единственный порог неизбежно ошибся бы в ту или иную сторону.

elvis - inline image
javascript
1export function decideGate(signals: GateSignals, policy = DEFAULT_GATE_POLICY): GateVerdict {
2 const worst = Math.max(
3 signals.destructive.noul,
4 signals.irreversible.noul,
5 signals.outsideWorkspace.noul,
6 );
7 if (worst >= policy.blockAt) return { action: "block", ... };
8 if (worst >= policy.askAt) return { action: "ask", ... };
9 return { action: "allow", ... };
10}

Поскольку это обычная функция, её можно тестировать, передавая числа вроде тех, что выше, без подключения живой модели.

2. Выбирайте модель под каждый запрос

Этот раздел добавляет маршрутизатор.

Некоторые запросы простые, например прочитать один файл. Некоторые сложные — разобраться, почему всё сломалось. Прогонять всё через самую мощную модель — транжирить деньги, а гнать всё через дешёвую — получать слабые ответы на сложные задачи. Маршрутизатор подбирает каждому запросу подходящий уровень (tier): быструю дешёвую модель или мощную дорогую.

Перед началом запроса маршрутизатор задаёт Jev два вопроса в одном вызове. choice выбирает уровень, а score оценивает сложность запроса.

elvis - inline image
javascript
1const ROUTER_QUESTIONS = {
2 tier: choice("Какой уровень модели должен обработать этот запрос?", {
3 fast: "Чтение одного файла, извлечение факта из него или небольшая правка в одном месте.",
4 powerful: "Работа, затрагивающая несколько файлов, или сбой без очевидной причины.",
5 }),
6 complexity: score("Сколько рассуждений требует этот запрос?", [
7 "Механическая. Один шаг, без оценки ситуации.",
8 "Локальная. Несколько шагов в одной области.",
9 "Архитектурная. Много движущихся частей или неизвестная корневая причина.",
10 ]),
11};

Политика маршрутизатора использует оба ответа так:

  • Если оценка сложности высокая, берём мощную модель, даже если Jev выбрала fast.
  • Если Jev не уверена в своём выборе, на всякий случай берём мощную модель.
  • Иначе используем модель, которую выбрала Jev.
javascript
1if (complexity.score >= policy.escalateAtComplexity) return powerful;
2if (tierAnswer.confidence < policy.minConfidence) return policy.fallbackTier;
3return tierAnswer.choice;

Это лишь пример политики. Ваша может взвешивать ответы иначе, под вашу предметную область: например, экономить при массовой пакетной обработке или всегда эскалировать всё, что касается продакшена. Jev только даёт ответы, а ваш код решает, что с ними делать.

Почему выбор делается один раз

Маршрутизатор выбирает модель один раз, в начале запроса, и держит её до конца. Причина — кэширование промптов.

На каждом шаге агента модель заново читает всю историю диалога. Провайдеры AI хранят недавно прочитанные диалоги, чтобы повторное чтение стоило дёшево. Но у каждой модели своё хранилище. Смените модель на полпути — и новой придётся читать всё заново по полной цене.

Основатель Jev разбирает цифры в документе о дизайне агентов для программирования. В одной длинной сессии переключение с Claude Opus на более дешёвый Sonnet и обратно обошлось примерно на 50% дороже, чем работа на Opus всё время. Так что выбирайте модель в самом начале, пока диалог короткий, и не меняйте её.

3. Будьте готовы к падению Jev

Этот раздел меняет и барьер, и маршрутизатор.

Как только обвязка начинает спрашивать Jev о каждом вызове инструмента, агент становится от неё зависим. Как любой онлайн-сервис, Jev может тормозить или лежать. Заранее решите, что будет делать каждая часть, если ответа нет. Для разных частей правильный выбор разный.

Барьер блокирует вызов. Если барьер не может спросить Jev, он понятия не имеет, безопасен ли вызов. Пропустить его — риск потерять файлы, поэтому барьер отказывает. Инженеры называют это fail-closed (безопасный отказ) — как дверь, которая запирается при отключении электричества.

Маршрутизатор берёт мощную модель. Если маршрутизатор не может спросить Jev, он не знает, насколько сложен запрос. Мощная модель справится с чем угодно, так что запрос всё равно получит хороший ответ, просто вы заплатите чуть больше. Это fail-open — позволяем работе продолжаться.

elvis - inline image

4. Проверяйте ответ

Этот раздел добавляет верификатор. В обвязках агентов верификатор — это этап, который проверяет работу агента перед тем, как признать её выполненной.

Агент может закончить работу ответом, в котором чего-то не хватает, или заявить то, чего он на самом деле не проверял в файлах. Человек, читающий ответ, часто этого не заметит. Проверка ответа перед возвратом ловит такие ошибки, пока агент ещё может попробовать снова.

Верификатор отправляет Jev готовый ответ вместе с файлами и результатами работы инструментов, на которые тот опирался. Jev оценивает качество ответа и говорит, подкреплены (grounded) ли его утверждения — то есть подтверждены ли они тем, что агент действительно прочитал.

elvis - inline image
javascript
1const VERIFY_QUESTIONS = {
2 quality: score("Насколько хорошо ответ удовлетворяет запросу?", [
3 "Не отвечает на запрос.",
4 "Отвечает частично, с пробелом, который читатель заметит.",
5 "Полностью отвечает на запрос.",
6 ]),
7 grounded: noul("Каждое фактическое утверждение подтверждается файлами или результатами инструментов из лога."),
8};

Два правила не дают агенту бесконечно пытаться снова. Всего ему даётся максимум две попытки. А если Jev не уверена в собственной оценке, обвязка принимает ответ, вместо того чтобы платить за новую попытку.

Безопасность и логирование

Jev выдаёт вероятность, а вероятность может быть ошибочной. Поэтому всё, что обычный код может проверить наверняка, нужно проверять кодом. В этой обвязке любой файловый инструмент отвергает пути за пределами папки проекта, что бы ни сказала Jev. Оставьте Jev для тех решений, которые код принять не способен.

Барьер смотрит только на сам вызов инструмента — его имя и входные данные. Это помогает против промпт-инъекций, когда спрятанный в файле или на странице текст заставляет модель сделать что-то вредоносное. Барьер не видит саму уловку, но видит вредоносный вызов, к которому она ведёт.

Логируйте каждое решение вместе с числами, которые за ним стоят. Лог объясняет, почему что-то было заблокировано, и даёт реальные цифры для настройки порогов. Обвязка пишет по одной строке на решение в файл decisions.jsonl. Барьер видит всё, что пытается сделать агент, поэтому лог скрывает email-адреса и ключи, а длинные входные данные обрезает.

Попробуйте сами

Песочница ниже запускает готовую обвязку из этого руководства, подключённую к настоящей Jev. Она работает с папкой заметок о походах, и её инструмент delete_path действительно может их удалять.

Попробовать: https://academy.dair.ai/resources/jev-decisions-in-a-pi-sdk-harness

Зачем собирать свою обвязку

Готовые агенты принимают эти решения по встроенным правилам. Собственная обвязка переносит их в ваш код. Вы выбираете модели, настраиваете пороги, решаете, когда вмешивается человек, и видите в логах, почему был сделан каждый вызов.

Jev делает всё это практически применимым. Каждое решение занимает несколько сотен миллисекунд и стоит доли цента, так что вы можете добавить проверку везде, где она нужна, а не только там, где можете позволить себе полный вызов модели. Три части из этого руководства — лишь отправная точка. Обвязка под вашу предметную область может задавать Jev любые важные для вас вопросы.

Другие применения

Те же три части работают и за пределами агентов для программирования.

  • Боты для код-ревью. Оценивайте каждое предложенное изменение и показывайте человеку только те, которые стоит читать.
  • Очистка записей. Спрашивайте, описывают ли две записи одно и то же, прежде чем объединять их, а спорные пары оставляйте человеку.
  • Конвейеры обработки документов. Оценивайте каждую извлечённую страницу и перезапускайте обработку только для тех, что получили низкий балл.
  • Очереди на согласование. До живого ревьюера доходят только вызовы из диапазона «спросить человека».

Во всех случаях языковая модель делает основную творческую работу, а Jev отвечает на мелкие вопросы вокруг неё.

Вопросы, пороги и политики в этом руководстве — учебные примеры, а не настроенные боевые параметры. Мы сейчас замеряем, как эти изменения обвязки влияют на стоимость и качество ответов, и скоро выпустим продолжение с результатами.

Я потратил вечер с Opus 5.5, собирая это руководство и песочницу. Если столкнётесь с проблемами, пишите мне в личку. Смело копируйте статью и скармливайте своим агентам, чтобы продолжить эксперименты с этими идеями.

Сохранение в один клик

Используйте YouMind для глубокого чтения вирусных статей с помощью ИИ

Сохраняйте источники, задавайте точные вопросы, обобщайте аргументы и превращайте вирусные статьи в полезные заметки в одном рабочем пространстве ИИ.

Исследовать YouMind
Для авторов

Превратите ваш Markdown в аккуратную статью для 𝕏

Когда вы публикуете длинные тексты, изображения, таблицы и блоки кода, форматирование в 𝕏 становится мучением. YouMind превращает полный черновик в Markdown в чистую статью, готовую к публикации в 𝕏.

Попробовать Markdown для 𝕏

Другие паттерны для анализа

Недавние виральные статьи

Смотреть другие виральные статьи