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, потому что читаются они из базы на каждый вызов. Ключ из окружения обслуживает все уровни.

Что в ключе можно поменять после добавления ​

С AGNT2-415, по каждому полю:

полепочему
НазваниеRename, прямо в строкеподпись, которую вы пишете себе. Две строки с тем же поставщиком, названием и уровнями — список, который не различает собственные строки
Уровниотметить в строкевыше
Поставщикотказ, с объяснениемэто факт о ключе, а не настройка: ключ Anthropic не становится ключом OpenAI от правки поля. Добавьте ключ другого поставщика, а этот выключите
Сам ключReplace key — без чтениядля ключа, который истёк или утёк. Строка сохраняет название, уровни, счётчики и историю; меняется только значение. Старое перезаписывается, и ни старое, ни новое не показываются
Состояниекнопки ниже

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

AGNT2-367 записал, что название «не стоит отдельной дороги». Это было верно, пока каждый ключ приходил из своей рабочей области и названия различались сами собой; перестало быть верным в тот день, когда две строки стали одинаковыми.

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

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

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

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

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

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

Кнопки ​

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

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

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

GET   /admin/llm-credentials              все ключи: поставщик, название, уровни, последние четыре, состояние, трафик
POST  /admin/llm-credentials              добавить
PATCH /v1/admin/llm-credentials/:id          переименовать; vendor или secret в запросе — отказ с объяснением
PUT   /admin/llm-credentials/:id/secret   новое значение в ту же строку; значение не возвращается
PATCH /v1/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 обратно; новый трафик к нему возвращается.
  8. Rename на одном ключе — название меняется на месте, остальное в строке не двигается.
  9. Replace key в той же строке, со свежим ключом от поставщика. «Оканчивается на» меняется на новые четыре символа; Вызовы, Токены, последнее использование и Уровни остаются.

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

Где код ​

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

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