Обновить

Я отдал языковой модели доступ к продовой панели администрирования. Вот что пришлось построить, чтобы она её не сожгла

Уровень сложностиСложный
Время на прочтение18 мин
Охват и читатели7.9K
Всего голосов 4: ↑3 и ↓1+4
Комментарии2

Комментарии 2

Про «инструмент != эндпоинт» подпишусь под каждым словом - оборачивать API в tools один к одному - дорогой способ получить неработающего агента. Каждый эндпоинт в списке - это токены, которые платятся в каждом диалоге, и лишний шанс для модели позвать не то. Добавлю критерий нарезки, который нам помог: формулировка живого пользователя. Провалы агентов концентрируются там, где инструмент надо подобрать по размытой человеческой фразе вроде «почему упал платёж?». Поэтому резать стоит не по HTTP-глаголам и даже не по «намерениям» из головы разработчика, а по реальным репликам. Мы делали похожую обёртку над платёжным API: из полного контракта получилось восемь инструментов, полный tools/list - около 4 000 токенов, набор «только чтение» - около 1 100. Размер описаний закреплён отдельным тестом: бюджет падает в CI так же, как сломанная арифметика, иначе описания незаметно отрастают обратно.

Двухшаговое подтверждение с одноразовым токеном по хешу канонизированных аргументов - мы независимо пришли почти к той же схеме (у нас TTL - 15 минут), похоже, это уже устойчивый паттерн. Два дополнения из платёжной специфики, которые лягут и на панель. Выше порога подтверждающее значение должен ввести человек: значение, подставленное той же стороной, которая собрала операцию, подтверждением не считается. И в текст describeConsequences нельзя пускать сырые строки из внешних данных - у нас сводка собирается только из известных полей с жёсткой очисткой, потому что из внешнего реестра может приехать текст, адресованный модели.

Про отложенную элиситацию. В редакции протокола от 28.07.2026 она превратилась в возврат «нужны данные» с повторным вызовом - ваша токенная механика ложится на это почти без изменений. Единственное, что стоит зафиксировать заранее: закрытый диалог - отказ, молчание согласием не считается.

Мне как читателю не хватило раздела про поведение при обрыве связи. Агент при таймауте не «проверит глазами» (несмотря на то, что коллега с франзуским акцентом любит всё проверять глазами и править руками), а повторит вызов - для деструктивных операций это второй restart core. У нас на этом месте детерминированный ключ операции (повтор возвращает прежний результат) и честный исход «неизвестно» вместо угадывания.

И раз уж вы довели конвейер до OIDC с provenance - проверил из любопытства - у marzban-sdk и marzban-mcp аттестации на месте, npm audit signatures проходит. Для пакета, который ставят одной командой рядом с продовым токеном, это заметная часть доверия, и большинство MCP-серверов в реестре этим до сих пор пренебрегает.

Если интересно сравнить решения на смежном домене - деньги вместо инфраструктуры, - мы недавно разложили те же грабли по полочкам: механика подтверждений и идемпотентности (https://www.invoicebox.ru/ru/blog/mcp-server-money) и границы доверия с расходом контекста (https://www.invoicebox.ru/ru/blog/mcp-server-trust). Совпадений с вашим списком неприлично много - хорошая новость для всех, кто такое строит.

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

Про нарезку. Критерий у нас один, просто вы назвали его точнее: мои «намерения» и выросли из живых реплик — из вопросов, которые я полгода закрывал пятнадцатью строчками скрипта. А вот теста на бюджет описаний у меня нет. 100% покрытия в CI держу жёстко, но покрытие кода никак не мешает описаниям тихо отрастать. Заведу, с отдельными порогами на full и readonly.

Про сырые строки в describeConsequences. Этот кейс учтён, просто в статье не раскрыт. В сводку идут только аргументы вызова и типизированные поля: status, used_traffic через форматтер, expire, ключи верхнего уровня из диффа. Свободнотекстовый note пользователя туда не попадает, hosts_update показывает только имена тегов. Плюс строки подключения по умолчанию замаскированы — и в инструкциях сервера отдельно сказано, что это учётные данные, а не данные для показа. Но правило нигде не записано как инвариант, держится на моей памяти. Запишу в conventions.

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

Хуже: разбираясь после вашего комментария, нашёл рядом настоящий баг — в режиме confirm=auto доверие запоминается по имени инструмента, а не по аргументам. Подтвердили удаление alice — bob в той же сессии уйдёт без вопроса. Чиню первым.

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

Про обрыв связи. Промахнулся. И в SDK хуже, чем я думал: был уверен, что axios-retry не повторяет POST, полез в исходник — isNetworkError не смотрит на метод вообще, исключены только таймаут и отмена. То есть оборванное соединение на restartCore (это POST) молча повторяется до трёх раз. Дыра выходит двухэтажная: внизу ретрай в SDK на необратимом методе, наверху агент, который после таймаута зовёт заново. Беру вашу схему для верхнего этажа — ключ операции от канонизированных аргументов, повтор возвращает прежний результат, недоигранная операция даёт «неизвестно». Для нижнего сужу retryCondition.

Про элиситацию. Вы правы, спасибо за дату — я на 2026-07-28 смотрел с другой стороны и не связал одно с другим. Механика, кстати, наполовину сошлась сама: токен подписывается через createRequestStateCodec, то есть тем же request-state примитивом, который MRTR гоняет туда-обратно, а tools/list уже отдаёт ttlMs/cacheScope. Переход выходит скорее переносом существующей двухшаговости, чем переписыванием. «Закрытый диалог — отказ, таймаут тоже» фиксирую в ADR отдельным пунктом.

Ссылки посмотрю. Спасибо — по итогам разговора четыре задачи и один баг, которого я бы сам не нашёл

Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации