Skip to content

Ключи моделей

Набор ключей поставщиков, которыми установка может отвечать, и экран, на котором оператор их добавляет, выключает и оживляет. До него у продукта был один ключ, он жил в окружении api, и замена сгоревшего была деплоем. Появилось в AGNT2-366; с AGNT2-367 ключ может ещё и сказать, какие уровни модели он обслуживает.

Кто это видит: администратор платформы. Экран — /model-keys в админке.

Админка на английском

У неё нет перевода, поэтому названия столбцов и состояний ниже даны по-русски для чтения, а на экране вы увидите английские: Vendor, Name, Levels, Ends in, State, Last used, Calls, Tokens; состояния — Working, Resting, Needs attention, Switched off.

Пустой набор ничего не меняет

Это добавляет источник. Когда в наборе нечем обслужить вызов, оба поставщика возвращаются к ключу из окружения, которым пользовались всегда: ANTHROPIC_API_KEY для ответов, OPENAI_API_KEY для эмбеддингов. Значит:

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

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

Что говорит строка

столбец
Поставщикanthropic или openai
Названиекак вы его назвали — рабочая область или аккаунт, откуда он
Уровникакие уровни модели этот ключ обслуживает; «all levels», если не назван ни один
Оканчивается напоследние четыре символа: именно по ним вы сличаете его с консолью поставщика
Состояниеодно из четырёх ниже
Последнее использование, Вызовы, Токенычто через него реально прошло

Один ключ — это одна строка LlmCredential, и обычное чтение этой таблицы показывает ровно то же, что показывает экран:

sql
SELECT vendor, label, tiers, last4, "restingUntil", "attentionAt", "disabledAt", "requestCount"
FROM "LlmCredential";

Значения в этом списке нет, потому что оно и не читается: оно лежит конвертом AES-256-GCM, запечатанным на идентификатор самой строки, — так что блоб, скопированный в другую строку, не откроется. Шифр тот же EnvelopeCipher, что хранит собственные секреты агента: AGNT2-366 спустил его в setup/core именно затем, чтобы второго способа шифровать не появилось.

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

Какие уровни обслуживает ключ

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

  • Не назвать ни одного — значит обслуживать все. Так хранится каждый ключ, добавленный до появления уровней, поэтому установка, никогда не привязывавшая ключ к уровню, ведёт себя ровно как раньше — то же правило, что и для пустого набора.
  • Один ключ на несколько уровней — обычный случай: simple и smart у одного поставщика, как правило, сидят на одном ключе.
  • Уровень может обслуживаться ключами другого поставщика, чем уровень ниже. Сегодня ответы даёт только Anthropic, так что дорога открыта в данных, но ещё не пройдена.

Уровни — единственное, что в ключе меняют после добавления: отметьте их прямо в строке, и изменение вступает в силу со следующего вызова модели в каждой реплике api, потому что читаются они из базы на каждый вызов. Ключ из окружения обслуживает все уровни.

Четыре состояния

состояниечто значитвернётся
Работаетв наборе, через него идут вызовы
Отдыхаетпоставщик ответил 429 — квота исчерпанасам, в момент, названный в retry-after поставщика, — или через LLM_CREDENTIAL_REST_MS (сутки), если тот ничего не назвал
Требует вниманияпоставщик ответил 401 / 403 — сам ключ отвергнут: отозван, опечатка, не из того аккаунтаникогда сам. Только человек
Выключенкто-то вывел его из строя. История сохраненакогда кто-то вернёт

Средние два нарисованы по-разному нарочно. Отдыхающему ключу никто не нужен; отвергнутому нужны вы. Покрасить оба янтарным — значит увести ваш взгляд на строку, которой ничего не нужно. Слить их — тот самый дефект, который стоит назвать: отозванный ключ, «выздоравливающий» через сутки, ломает каждый ответ всё время, пока существует, и делает это молча.

А третий случай не помечает ничего. Таймаут, оборванный сокет, 5xx или наш собственный кривой запрос — не вина ключа. Пометки на них опустошили бы набор на сутки из-за плохой минуты у поставщика.

Отдых длится столько, сколько сказал поставщик, и не дольше (AGNT2-381). Настроенные сутки раньше были не только умолчанием, но и нижней границей, поэтому всплесковый лимит, который Anthropic снимает за двадцать секунд, укладывал ключ спать на двадцать четыре часа — а на установке с одним ключом это вся модель, спящая сутки. Теперь слово поставщика побеждает всегда, когда оно есть; сутки — догадка на случай, когда он промолчал.

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

Три действия

кнопка
Add a keyпоставщик, название, ключ и, по желанию, уровни, которые он обслуживает. Значение хранится зашифрованным и наружу не выходит
Switch off / Switch onвывести из строя или вернуть
Put back in serviceпрекратить отдых досрочно или снять отказ, с которым вы разобрались

Удаления нет, и это намеренно. Выключение сохраняет строку, потому что к строке привязан прошлый расход, — а удалённая делает вопрос «во что нам обошёлся этот ключ» безответным ровно для того ключа, который убрали именно потому, что он был дорогим.

За экраном — шесть маршрутов, все за префиксом admin/: страж смотрит на адрес, а не на декоратор, который кто-то должен не забыть поставить.

GET   /admin/llm-credentials              все ключи: поставщик, название, уровни, последние четыре, состояние, трафик
POST  /admin/llm-credentials              добавить
PATCH /admin/llm-credentials/:id/tiers    сменить обслуживаемые уровни; пустой список — все
POST  /admin/llm-credentials/:id/disable  вывести из строя — это и есть здешнее «удалить»
POST  /admin/llm-credentials/:id/enable   вернуть в строй
POST  /admin/llm-credentials/:id/revive   прервать отдых досрочно или снять отказ, с которым человек разобрался

Какой ключ возьмёт следующий вызов

Задаётся LLM_CREDENTIAL_ROTATION, и умолчание здесь важнее, чем кажется:

sticky — на разговор (по умолчанию)разговор остаётся на одном ключе, выбранном rendezvous-хешированием по пригодному множеству. Две реплики api приходят к одному ответу, ничем не обмениваясь, а уход ключа на отдых сдвигает только те разговоры, которые были на нём
request — на запросключ, которым дольше всего не пользовались, на каждый вызов; очередь — по lastUsedAt в базе

По умолчанию — на разговор, потому что кэш промпта у поставщика принадлежит той рабочей области, которой принадлежит ключ. Восемь ключей из восьми рабочих областей — это восемь разных кэшей, и смена ключа внутри разговора выбрасывает сохранённое начало и платит за запись заново. Измерено на настоящем api: ротация на каждый запрос стоит в 3,4 раза дороже за ход против тёплого кэша, а ротация на разговор стоит столько же, сколько один ключ, и при этом по-прежнему размазывает нагрузку.

Что это значит на практике — на странице Уровни модели и кэш начала запроса.

3,4× — из отчёта AGNT2-366, который лежит на той карточке, а не в репозитории; исследование, из которого вырос сам набор ключей, — specs/AGNT2-369-openrouter-or-own-keys/research.md.

Исчерпание должно приходить быстро, иначе карусель не даёт ничего

Набор, который уходит с исчерпанного ключа, бесполезен, если «исчерпан» приходит не ошибкой, а часом ожидания — человек уже ушёл. Поэтому собственный неостановимый повтор внутри SDK поставщика выключен: он исполняет присланное «подожди столько-то» буквально, а на исчерпанной квоте это указание называет время в часах вперёд, и прервать его нечем. Вместо него у api собственный ограниченный повтор (AGNT2-377): пауза между попытками не длиннее ANTHROPIC_RETRY_WAIT_MAX_MS, а весь блокирующий вызов — не длиннее ANTHROPIC_CALL_BUDGET_MS. Все четыре числа — на странице Уровни модели и кэш начала запроса.

Поставщик, просящий долго ждать, не просит нас повторить. Он сообщает, что этот ключ на сегодня всё, — а это факт, на который надо реагировать, а не просыпать его.

Две реплики могут взять один ключ

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

Проверить: завести второй ключ

  1. Войдите в админку администратором платформы → Ключи моделей.
  2. Add a key — второй ключ anthropic, из другой рабочей области, ни одного уровня не отмечать. Строка появляется в состоянии Работает, в «Уровнях» — «all levels», «Оканчивается на» совпадает с последними четырьмя символами в консоли поставщика, Вызовы — 0.
  3. Проведите два разных разговора с агентом, по несколько ходов каждый.
  4. Откройте экран снова. Вызовы выросли на обеих строках, и каждый разговор остался на одном ключе — это работает ротация по умолчанию.
  5. Отметьте на новом ключе только genius. Со следующего вызова обычные ходы его не трогают, и его Вызовы стоят на месте, пока какой-нибудь ход не оценят как genius.
  6. Switch off на одном ключе. Состояние становится Switched off, а счётчики на месте.
  7. Switch on обратно; новый трафик к нему возвращается.

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

Где код

набор, состояния, уровни, ротацияapi/src/slices/system/llmCredential
шесть маршрутов и их DTOapi/src/slices/admin/llmCredential
экранadmin/slices/platform/llmCredential

См. также Секреты и Роль администратора.