Octolib 0.39.0: к стеку присоединилась модель, которая не может написать ни предложения

С того дня, как мы выделили его из Octocode, каждый вызов модели в нашем стеке был одним из трёх. Сгенерировать текст. Превратить текст в вектор. Отранжировать векторы относительно запроса. Octolib — Rust-библиотека под Octomind, OctoHub, Octocode и Octobrain — содержала по модулю на каждый: llm, embeddings, reranker. Потом у неё появился media.

Сегодня появляется четвёртый, и этот не похож на остальные. octolib::evaluation обращается к модели, которая не может сгенерировать ни одного токена. Вы передаёте ей состояние и набор типизированных вопросов, а она возвращает вероятности. Да или нет, одно из N, позиция на шкале. За 70–500 миллисекунд. По $0.042 за миллион входных токенов, причём вывод бесплатный — потому что выводить нечего, биллинга нет.

Модель — Jev от TypeSafe AI, которая вышла из стелс-режима 15 сентября. TypeSafe называет эту категорию моделями System One. Мы считаем её самым интересным новым примитивом, появившимся в AI-стеке в этом году, и Octolib 0.39.0 — наша ставка на неё: одна строка provider:model, три способа до неё добраться и тот же учёт стоимости, что мы применяем ко всему остальному.

Это главная новость. Релиз также превращает Cloudflare Workers AI в полноценного медиапровайдера, добавляет локальный ONNX-бэкенд эмбеддингов, подключает двух новых LLM-провайдеров и наконец заставляет структурированный вывод валидироваться ровно один раз. Всё, что случилось после 0.36.1, — ниже.

Зачем агенту модель, которая только решает

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

У каждого серьёзного агента для кодинга есть этот второй слой. У нас это супервизор Octomind. У Anthropic — классификатор авторежима в Claude Code. И в обоих случаях модель общего назначения выносит эти суждения, когда её определяющая способность — писать развёрнутое объяснение — выключена именно для этих вызовов. Запрос туда-обратно к frontier-модели, повторяемый на каждое решение, чтобы получить одно слово.

Jev создан для таких решений и больше ни для чего. Три примитива:

  • Noul — вопрос «да/нет», на который модель отвечает вероятностью того, что ответ «да».
  • Choice — один вариант из заданного вами набора, с вероятностью для каждого варианта и уверенностью, показывающей, насколько «острое» распределение.
  • Score — позиция на упорядоченной шкале уровней, описанных словами; может попасть и между уровнями.

Он принимает состояние один раз и оценивает все вопросы по нему за один параллельный проход, так что десятый вопрос почти бесплатен. Его обучали на калибровку, а не на предпочтения, поэтому ответ «90%» должен оказываться верным примерно в 90% случаев на большой выборке предсказаний. И пространство ответов замкнуто по построению: модель может выбрать неверный вариант, но не может выдумать новый — значит, нечего чинить в JSON и нечего нечётко сопоставлять с enum.

Длинную версию — что это, где ломается, что люди построили с ним за первые 72 часа и каждое решение управляющего слоя внутри агента, которое он мог бы взять на себя, с живыми цифрами — мы написали в блоге Octomind: Jev Explained: модель System One от TypeSafe и решения внутри каждого AI-агента. Этот пост — про библиотеку.

API

Модуль называется evaluation, он включён по умолчанию и компилируется сам по себе, если вам нужно только это:

octolib = { version = "0.39", default-features = false, features = ["evaluation"] }

Форма следует остальному Octolib. Один тип запроса, один высокоуровневый хелпер, одна строка provider:model:

use octolib::{evaluate, Answer, EvaluationRequest, Question};

async fn triage() -> octolib::EvaluationResult<()> {
    // Требует TYPESAFE_API_KEY; используйте "cloudflare:typesafe/jev", чтобы списывать кредиты AI Gateway.
    let request = EvaluationRequest::new("Help! My payouts have been failing for 3 days.")
        .with_question("is_urgent", Question::noul("Does this convey urgency?"))
        .with_question(
            "department",
            Question::choice(
                "Which team should handle this?",
                [("billing", "Payments, refunds"), ("technical", "Bugs, outages")],
            ),
        )
        .with_question(
            "frustration",
            Question::score("How frustrated is the customer?", ["Calm", "Frustrated", "Very angry"]),
        );
    let response = evaluate("typesafe:jev-latest", request).await?;
    if let Answer::Noul { noul } = response.answers["is_urgent"] {
        println!("urgent with p={noul:.2}, cost {:?}", response.usage.cost);
    }
    Ok(())
}

Состоянием может быть строка, JSON-объект или массив. Ответы возвращаются типизированными под теми же id, что и вопросы — Answer::Noul, Answer::Choice с картой вероятностей и уверенностью, Answer::Score с легендой, — так что в Rust вы ветвитесь по числам, а не по распарсенному тексту.

Три маршрута к одной модели:

Строка модели Через что доходит до Jev Ключи
typesafe:jev-latest API TypeSafe напрямую (ранний доступ) TYPESAFE_API_KEY
cloudflare:typesafe/jev Cloudflare AI, биллинг через кредиты AI Gateway CLOUDFLARE_API_KEY, CLOUDFLARE_ACCOUNT_ID
octohub:jev Ваш собственный прокси OctoHub, стоимость сообщает хаб OCTOHUB_API_KEY, OCTOHUB_API_URL

Несколько деталей, которые важны только под серьёзной нагрузкой, — а это как раз та часть, которая нам важна:

  • Здесь ретраи безопасны. У оценки нет побочных эффектов, поэтому повтор не может задвоить работу. Клиент повторяет запрос на 429, 529 и 5xx с бэкоффом и уважает Retry-After с потолком в 30 секунд. Генерационные POST-запросы в других местах Octolib намеренно не повторяются; этот — повторяется.
  • Стоимость считается по опубликованному тарифу, $0.042 за миллион входных токенов, и попадает в usage.cost, как и любой другой вызов. Через OctoHub побеждает стоимость, сообщённая хабом.
  • Поле model в ответе сообщает версионированную модель, которая ответила. jev-latest сдвигается, когда TypeSafe выпускает новую версию; если вы настраиваете пороги — логируйте её и фиксируйте.
  • Id моделей валидируются до отправки запроса. openai:jev-latest — неподдерживаемый провайдер; cloudflare:@cf/zai-org/glm-5.3 — чат-модель, а не модель оценки. Оба падают при конструировании, а не при отправке запроса.

У Jev контекст 32k, и собственная документация TypeSafe говорит, что точность падает по мере того, как состояние заполняется материалом, который вопросу не нужен. Урезайте состояние. Задавайте много вопросов; отправляйте мало.

Куда это встраивается: Octomind и OctoHub

Мы не добавляем модули в Octolib ради забавы. Каждый модуль существует потому, что он понадобился продукту, и этот понадобился дважды.

Супервизор Octomind уже работает на инварианте: бесплатные сигналы отсекают лишнее до модели, а вызовы модели редки. Сегодня в main [supervisor.evaluate] добавляет калиброванный третий уровень между «бесплатным» и «frontier» на трёх стыках: фильтр релевантности после извлечения памяти, Choice по реестру, когда все правила активации навыков воздержались, и предварительная проверка, решающая, нужно ли пакету инструментов вообще будить модель-авторизатор. Каждый стык — один переключатель, по умолчанию выключен. Одна попытка на вызов, пятисекундный таймаут, без ретраев; любой сбой сохраняет прежнее поведение для этого хода. И одно правило, которое мы поставили бы во главу любой интеграции с Jev: состояние никогда не несёт результатов инструментов или сообщений ассистента, потому что ревьюер никогда не должен читать то, что, возможно, его уговаривает.

OctoHub получает POST /v1/evaluations. Настройте алиас один раз, в своём прокси, со своими ключами:

[evaluation_models]
"jev" = ["typesafe:jev-latest", "cloudflare:typesafe/jev"]

и каждый клиент на каждом языке — Python-хук, TypeScript-воркер, shell-скрипт — вызывает octohub:jev и получает те же типизированные ответы, ту же строку стоимости на запрос и ту же атрибуцию, что и его чат-комплишены. Поэтому octohub — третий провайдер оценки в Octolib: библиотека вызывает прокси, прокси вызывает Octolib.

Оба попадут в следующие релизы Octomind и OctoHub; библиотека вышла первой, потому что библиотека всегда выходит первой.

Cloudflare Workers AI теперь медиапровайдер

В прошлый раз стек media в Octolib покрывал изображения, видео, речь и транскрипцию через OpenRouter, Replicate, fal, ElevenLabs и Runway. 0.39.0 добавляет Cloudflare Workers AI для изображений, речи и транскрипции через тот же синхронный эндпоинт /ai/run, который использует чат-провайдер.

Зарегистрированные модели с возможностями, опциями и ориентировочными ценами:

  • Изображения: FLUX.1 schnell, FLUX.2 dev, Leonardo Lucid Origin и Phoenix 1.0. Цена за выходной тайл и шаг.
  • Речь: Deepgram Aura 1 и Aura 2 (английский и испанский), MeloTTS. Цена за символ.
  • Транскрипция: Whisper, Whisper large-v3-turbo, Deepgram Nova 3. Цена за минуту аудио; сегменты, слова, языки и метаданные парсятся в типизированную транскрипцию.

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

Чат-сторона провайдера Cloudflare получила ту же доработку: выбирает и нормализует уровни рассуждения по моделям, применяет схемы только к моделям в формате OpenAI, которые их действительно соблюдают, разрешает перекрывающиеся id моделей по самому длинному совпадению и для неизвестных моделей откатывается к ориентировочным возможностям вместо того, чтобы падать.

Всё остальное после 0.36.1

Десять релизов за двенадцать дней. Сжатая версия.

Два новых LLM-провайдера. Inception Labs предоставляет семейство диффузионных LLM Mercury: inception:mercury-2.5, рядом Mercury 2, вывод по JSON-схеме и вызов инструментов. Tinker от Thinking Machines предоставляет своё семейство Inkling плюс модели с открытыми весами (Nemotron, GLM, Kimi, Qwen, GPT-OSS, DeepSeek) и чекпоинты сэмплеров; id моделей с двоеточиями обрабатываются, а короткие имена вроде tinker:inkling разрешаются в id serverless-инференса. Оба идут с примерами, которые берут спецификацию модели из командной строки.

ONNX-эмбеддинги, локально. Новый провайдер эмбеддингов onnx загружает графы и токенизаторы HuggingFace в ONNX Runtime — onnx:<org>/<repo>, с необязательным #path-to.onnx для конкретного графа, — предпочитает квантованные графы, учитывает метаданные пулинга и статические размерности, группирует запросы в батчи с отслеживанием использования токенов. Он встаёт рядом с FastEmbed и HuggingFace-бэкендом на Candle как третий способ делать эмбеддинги без ключа API. Это единственное нарушение совместимости в раунде: у EmbeddingProviderType появился вариант Onnx, так что исчерпывающим match нужна новая ветка.

Структурированный вывод валидируется один раз. Прежний сценарий мог повторять провалившуюся валидацию схемы и агрегировать usage между попытками. Это было благонамеренно и неправильно: прятало поведение провайдера за циклом. Теперь ровно один вызов схемы к апстриму, один централизованный шаг извлечения и валидации, а ошибка парсинга или валидации возвращается с логированием проблемного ответа и без повтора. Клиентские вызовы инструментов сохраняются после валидации финального вывода. Два провайдера в том же изменении перестали обещать лишнее: Ollama и Cloudflare больше не заявляют гарантированное соблюдение схем, которое не могут обеспечить, а OpenAI падает с ошибкой (fails closed), когда указания схемы игнорируются, вместо того чтобы вернуть что-то, что лишь выглядит правильным.

Уровни усилия рассуждения маппятся по провайдерам. Модели GLM у Alibaba и Z.ai сопоставлены с поддерживаемыми ими уровнями усилия; Fireworks сохраняет максимальный уровень для GLM 5.2. Z.ai теперь принимает сообщения с сохранённым мышлением и оставляет ходы ассистента «только мышление» вместо того, чтобы их выбрасывать.

Список моделей изменился. Снятые модели Cerebras заменены на qwen-3.8-27b с поддержкой видео и лимитом входа 128K. Снятые маршруты V4 у DeepSeek заменены на deepseek-flash, трактуемый как мультимодальный; цены V4.1 Flash исправлены и добавлены на Alibaba. Отозванные модели Hetzner заменены на Qwen. Кеширование BytePlus и сторонние цены обновлены. OctoHub теперь определяет размерности эмбеддингов при конструировании и кеширует их по эндпоинту и модели, из-за чего конструирование провайдера OctoHub стало асинхронным — второе небольшое изменение API, о котором стоит знать. Маппер провайдеров валидирует алиасы моделей, сохраняет квантованные и отклоняет несопоставленные модели при конструировании, а не при первом запросе.

Мелочи, но важные. Аудиоформаты fal маппятся на значения, которые fal принимает (MP3 и PCM; неподдерживаемые форматы используют значение по умолчанию для модели). Пустое содержимое сообщений снова разрешено. rustls на 0.23.45, dirs на 7.0.

Обновление

  • cargo add [email protected] или поднимите версию. Все фичи включены по умолчанию; новая фича evaluation не тянет ничего тяжёлого.
  • Если вы делаете match по EmbeddingProviderType, добавьте ветку Onnx.
  • Если вы конструируете провайдер эмбеддингов OctoHub напрямую, он теперь async.
  • Если вы полагались на то, что структурированный вывод повторяет провалившуюся валидацию, — больше не повторяет. Ошибка несёт то, что вернул провайдер; решайте в своём коде.
  • Установите TYPESAFE_API_KEY или CLOUDFLARE_API_KEY плюс CLOUDFLARE_ACCOUNT_ID и попробуйте пример выше. Он съедает пару сотен входных токенов: меньше двух тысячных цента.

FAQ

Что такое модель оценки? Модель, которая отвечает на типизированные вопросы о состоянии калиброванными вероятностями вместо генерации текста. Jev от TypeSafe — первая такая модель — возвращает вероятность «да/нет» (Noul), один вариант из набора (Choice) или позицию на шкале (Score) за 70–500 мс. Она не умеет писать код или прозу. Её работа — решения вокруг вашего LLM: маршрутизация, допуск, фильтрация релевантности, выбор навыка.

Нужен ли аккаунт TypeSafe, чтобы ей пользоваться? Нет. Прямой доступ — по листу ожидания, но cloudflare:typesafe/jev работает уже сегодня с аккаунтом Cloudflare и загруженными кредитами AI Gateway, а octohub:jev работает через ваш собственный OctoHub с любым из ключей за ним.

Octolib 0.39.0 что-нибудь ломает? Один вариант enum (EmbeddingProviderType::Onnx), один конструктор стал async (эмбеддинги OctoHub), и структурированный вывод больше не ретраит провалы валидации. Чат, медиа, реранжирование и каждая строка модели, которой вы уже пользуетесь, не изменились.

Jev уже внутри Octomind? Гейты оценки уже в main Octomind, за [supervisor.evaluate], по умолчанию выключены и выйдут в следующем релизе. Этот релиз Octolib — то, на чём они построены.

Какие продукты работают на Octolib? Octomind (каждый чат-вызов, эмбеддинг и гейт оценки), OctoHub (чат, эмбеддинги, медиа и новый маршрут /v1/evaluations), Octocode (чат, эмбеддинги, реранжирование) и Octobrain (эмбеддинги, реранжирование). Когда провайдер меняет API или цены, правка живёт в одном месте и достаётся каждому продукту.

Суть

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

Модели оценки вписываются как четвёртый модуль, потому что это другая функция: не «напиши мне ответ», а «вот состояние, вот мои вопросы — скажи, что ты думаешь и насколько сильно». Агентам этот примитив был нужен с первого pre-tool-хука. Мы имитировали его одним токеном от рассуждающей модели. Теперь это типизированный вызов, с честной ценой, в одной строке от вас.

github.com/muvon/octolib · Apache-2.0 · crates.io · подробный разбор Jev в блоге Octomind.