Как это устроено

Работа на открытых моделях

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

Агенты здесь не пишутся под конкретную модель. Всё, что нужно слою агентов — чат, векторизация, вызовы инструментов, — идёт через шлюз, который говорит на OpenAI-совместимом HTTP, поэтому за ним может стоять облачный фронтир-API, открытая модель на вашем железе или сразу несколько.

Эта страница — инженерная сторона такого заявления. Она существует потому, что «поддерживаем открытые модели» легко сказать и трудно проверить, а тот, кто выбирает платформу, вправе спросить.

Шлюз

Бэкенд — это строка в таблице, привязанная к арендатору. В ней базовый URL, API-ключ, имя модели, вид (chat или embedding) и вес. Добавить или заменить бэкенд — изменение конфигурации: без деплоя и без переписывания агентов.

Ни одного вендорского SDK во всём дереве зависимостей нет. Шлюз делает обычные HTTP-запросы к OpenAI-совместимой поверхности — поэтому всё, что её реализует (vLLM, Ollama, серверы на llama.cpp, большинство коммерческих API), работает без кода-адаптера.

Возможности опрашиваются, а не предполагаются

При старте шлюз вызывает /v1/models на включённом бэкенде и считывает то, что модель действительно даёт: контекстное окно, размерность векторов, заявленные возможности.

Ничего про модель не зашито в код. Это важнее, чем звучит: платформа, которая рассчитывает на фронтирное контекстное окно, молча переполнит открытую модель с окном в 32K, и сбой проявится не ошибкой, а обрезанным или бессмысленным ответом.

Если бэкенд не отвечает на опрос, шлюз записывает окно как неизвестное и отказывается подрезать контекст вместо того, чтобы подставить наугад маленькое число. Неверная догадка молча удаляет контекст, которым модель могла бы воспользоваться; попытка и явный отказ хотя бы видны.

Четыре способа сломаться у небольших открытых моделей

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

1. Вызовы инструментов приходят текстом

Многие открытые модели игнорируют структурное поле tool_calls и вписывают вызов прямо в тело ответа. Распространены две формы:

<tool_call>
  <function=search_docs>
    <parameter=query>refund policy</parameter>
  </function>
</tool_call>
<tool_call>
{"name": "search_docs", "arguments": {"query": "refund policy"}}
</tool_call>

Если это не разобрать, не выполняется ничего, а пользователь видит в чате сырую разметку. Цикл инструментов разбирает обе формы, вызывает инструмент как обычно и — на всякий случай — вычищает из текста остатки разметки <tool_call> перед отправкой дальше. Сырой синтаксис вызова инструмента не должен доходить до конечного пользователя никогда, даже если разбор не удался.

2. Потоковые вызовы приходят по кускам

OpenAI-совместимые серверы расходятся в том, как разрезать вызов инструмента по чанкам потока. Один вызов может прийти несколькими частичными дельтами, иногда вперемешку, если модель запросила больше одного инструмента. Фрагменты накапливаются по индексу и собираются в целые вызовы прежде, чем что-либо будет выполнено.

3. Бюджет контекста считается от настоящего окна

Полезный бюджет входа не настраивается, а выводится:

input_budget =
    probed_context_window − output_reservation − safety_margin

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

4. Рассуждения съедают ответ

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

Маршрутизация и переключение при отказе

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

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

Чего это не делает

Честно сказать о границах полезнее, чем удлинить список возможностей:

  • Это не делает маленькую модель такой же способной, как большая. Выбор инструмента — решить, какой именно вызвать и с какими аргументами в запутанной многошаговой ситуации — это как раз то, где качество модели видно сильнее всего. Прогоните собственные задачи, прежде чем переносить на модель поменьше работу с высокой ценой ошибки.
  • Это не управляет вашей инфраструктурой инференса. Если вы хостите модель сами, мощности GPU, сервер модели и его доступность остаются вашими. Шлюз обойдёт лежащий бэкенд, но не сделает его быстрее.
  • Это не сертифицирует конкретные модели. Работа над совместимостью касается тех форм, которые модели выдают, а не проверенного списка. Ожидается, что заработает любой OpenAI-совместимый эндпоинт; какая модель подходит вашей нагрузке — это оценка, которую стоит провести вам.

Смотрите также