Ключи моделей
Набор ключей поставщиков, которыми установка может отвечать, и экран, на котором оператор их добавляет, выключает и оживляет. До него у продукта был один ключ, он жил в окружении 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 отвечает ключом из окружения, и экран говорит об этом над таблицей — потому что таблица из восьми отдыхающих ключей и таблица без ключей описывают один и тот же работающий продукт. (Снимок сделан до появления столбца Уровни.)
Что говорит строка
| столбец | |
|---|---|
| Поставщик | anthropic или openai |
| Название | как вы его назвали — рабочая область или аккаунт, откуда он |
| Уровни | какие уровни модели этот ключ обслуживает; «all levels», если не назван ни один |
| Оканчивается на | последние четыре символа: именно по ним вы сличаете его с консолью поставщика |
| Состояние | одно из четырёх ниже |
| Последнее использование, Вызовы, Токены | что через него реально прошло |
Один ключ — это одна строка LlmCredential, и обычное чтение этой таблицы показывает ровно то же, что показывает экран:
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 независимых копий факта, который обязан быть один. Но смысл в том, чтобы размазать нагрузку, а не нормировать её, и строгая очередь потребовала бы блокировки на горячем пути каждого вызова модели ради справедливости, которую никто не просил.
Проверить: завести второй ключ
- Войдите в админку администратором платформы → Ключи моделей.
- Add a key — второй ключ
anthropic, из другой рабочей области, ни одного уровня не отмечать. Строка появляется в состоянии Работает, в «Уровнях» — «all levels», «Оканчивается на» совпадает с последними четырьмя символами в консоли поставщика, Вызовы — 0. - Проведите два разных разговора с агентом, по несколько ходов каждый.
- Откройте экран снова. Вызовы выросли на обеих строках, и каждый разговор остался на одном ключе — это работает ротация по умолчанию.
- Отметьте на новом ключе только
genius. Со следующего вызова обычные ходы его не трогают, и его Вызовы стоят на месте, пока какой-нибудь ход не оценят какgenius. - Switch off на одном ключе. Состояние становится Switched off, а счётчики на месте.
- Switch on обратно; новый трафик к нему возвращается.
Единственное, чего нельзя сделать ни в одном пункте этой последовательности, — прочитать ключ обратно. Это свойство, а не недоделка.
Где код
| набор, состояния, уровни, ротация | api/src/slices/system/llmCredential |
| шесть маршрутов и их DTO | api/src/slices/admin/llmCredential |
| экран | admin/slices/platform/llmCredential |
См. также Секреты и Роль администратора.