Перейти к основному содержимому

Справочник Gateway API

Шлюз - это внецепочечный сервис, который превращает HTTP-запрос в работу в сети AETRON. Он аутентифицирует вызывающего, сверяет мощность Нейронета в цепи, выбирает майнера, отправляет задание по libp2p, возвращает ответ и записывает обслуженный запрос для расчётов. В самой цепи вызовов на выдачу заданий нет, поэтому эта HTTP-поверхность и есть точка входа для всех, кто пользуется Нейронетом.

Поднять шлюз может кто угодно, поэтому хост зависит от того, кто обслуживает ваш Нейронет. В тестнете managed-шлюз публично доступен по адресу https://gateway-testnet.aetron.ai; в мейннете managed-хост пока не опубликован. Маршруты ниже одинаковы, где бы шлюз ни работал. Актуальные адреса приведены в статье Сети и эндпоинты.

Базовый путь и маршрутизация​

Каждый маршрут слоя данных привязан к идентификатору Нейронета:

POST /n/{id}/v1/{task}/infer
POST /n/{id}/v1/{task}/infer/stream
GET /n/{id}/v1/requests/{req_id}
POST /n/{id}/v1/training
GET /n/{id}/v1/training/{tid}
GET /healthz
GET /metrics

{id} - идентификатор Нейронета (беззнаковое 32-битное), {task} - идентификатор задачи внутри него (беззнаковое 16-битное). CORS разрешительный, поэтому браузерные клиенты с другого источника могут обращаться к шлюзу напрямую.

GET /healthz не требует аутентификации и возвращает обычную строку ok. GET /metrics отдаёт счётчики Prometheus того же шлюза.

Аутентификация​

Принимаются два пути идентификации, определяемые по заголовкам запроса.

Челлендж холодным ключом (владелец)​

Владелец подписывает каноническую полезную нагрузку холодным ключом, которому принадлежит Нейронет, по sr25519 со стандартным контекстом подписи Substrate.

ЗаголовокЧто означает
x-aetron-accountSS58-адрес подписанта
x-aetron-signatureПодпись sr25519 в hex, 64 байта, префикс 0x опционален
x-aetron-timestampUnix-секунды, использованные в полезной нагрузке

Подписывается ASCII-строка AETRON-owner-auth:v1, за ней идентификатор Нейронета как little-endian u32, затем метка времени как little-endian u64, всего 32 байта. Привязка к идентификатору Нейронета означает, что подпись нельзя переиграть против другого Нейронета.

Метка времени обязана попадать в 300 секунд от часов шлюза в любую сторону. Браузерные кошельки, которые оборачивают данные в <Bytes> перед подписью, поддерживаются: шлюз проверяет подпись и против сырой полезной нагрузки, и против обёрнутой формы.

Ключ API (конечный пользователь)​

Конечный пользователь обращается с заголовком x-api-key: ak_live_.... Ключ выпускается control plane под одного арендатора и один Нейронет, поэтому проверку владения в цепи шлюз к нему не применяет. У ключа есть дневной бюджет запросов. Если заголовка x-api-key нет, запрос проваливается на путь холодного ключа.

Ключи API принимаются только на эндпоинтах инференса. Обучение требует холодного ключа владельца, потому что запуск обучения тратит мощность владельца.

POST /n/{id}/v1/{task}/infer​

Выполняет одно задание инференса и синхронно возвращает результат.

Тело запроса:

{
"input": "string",
"params": {}
}

input - промпт или полезная нагрузка, её схему решает задача Нейронета. params - необязательный произвольный JSON-объект, который пробрасывается майнеру. Задание уходит майнеру с дедлайном 30000 мс.

Ответ, HTTP 200:

{
"id": "64-hex job id",
"output": "text answer",
"verification": { "status": "pending", "receipt": "vrc_..." }
}

id - идентификатор задания, шестнадцатеричная строка на 64 символа, которую вы передаёте эндпоинту статуса запроса. receipt - это vrc_ и первые 16 шестнадцатеричных символов идентификатора задания.

Когда майнер возвращает байты, не являющиеся корректным UTF-8, например картинку от диффузионной задачи, output будет пустой строкой, а появятся два дополнительных поля: output_b64 с сырыми байтами в base64 и content_type. Тип определяется по первым байтам и равен image/png, image/jpeg или application/octet-stream. На текстовом пути ни одного из этих полей нет, поэтому старые клиенты видят неизменившийся ответ.

Что происходит за запросом​

Обработчик идёт фиксированной последовательностью, и у каждой стадии свой код отказа: проверка приостановки, аутентификация, лимит на аккаунт, дневная квота ключа, контроль допуска, проверка активности Нейронета, проверка владения, гейт мощности, выбор майнера, отправка и запись для расчётов. Проверка стартует уже после и ответ никогда не задерживает.

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

Как только ответ принят, шлюз разыгрывает задание против доли перепроверок задачи и решает, запускать ли проверку. Значение по умолчанию в пальете - 70000 миллионных долей, то есть 7 процентов запросов.

POST /n/{id}/v1/{task}/infer/stream​

Тот же вызов, что и infer, но токен-дельты приходят по мере генерации. То же тело, та же аутентификация, те же гейты. Ответ приходит потоком Server-Sent Events с HTTP 200, и в нём встречаются три типа событий:

СобытиеПолезная нагрузка
deltaКусок сгенерированного вывода. Пока майнер работает, их приходит несколько, по порядку.
resultФинальный JSON, байт в байт тот же объект, который возвращает обычный маршрут infer.
errorЗадание упало уже после того, как поток открылся.

Поскольку SSE обязан ответить 200 до того, как что-то будет записано, отказ по аутентификации или мощности, который на обычном маршруте был бы 4xx, здесь сообщается внутри потока. Клиентам, которым нужен код статуса, и клиентам, которым не нужен частичный вывод, стоит звать infer.

Идентификатор задания выводится из идентификатора Нейронета, идентификатора задачи и хэша входа, поэтому он известен до завершения задания. Именно это позволяет потоку опрашивать майнера на дельты, пока авторитетный запрос ещё в работе, и по этой же причине повторённый запрос сохраняет тот же идентификатор.

GET /n/{id}/v1/requests/{req_id}​

Смотрит состояние проверки одного более раннего запроса на инференс. req_id - полный 64-символьный шестнадцатеричный идентификатор задания, который эндпоинт infer вернул в поле id, с префиксом 0x или без него.

Ответ, HTTP 200:

{
"id": "64-hex job id",
"verification": { "status": "pending", "votes": 3, "quorum": 20 }
}

votes и quorum появляются только пока голоса ещё собираются. quorum - число голосов проверяющих, которое требует протокол; если цепь не ответила, шлюз подставляет 20. Значения статуса:

СтатусЧто означает
pendingПроверяющие назначены, голоса копятся, кворум не финализирован
verifiedКворум финализировал задание как честное
flaggedКворум финализировал задание как мошенничество
not_sampledЗадание не попало в выборку на пересчёт либо вердикт оказался воздержанием

Битый req_id возвращает HTTP 500 с ошибкой internal.

POST /n/{id}/v1/training​

Запускает обучение. Только по холодному ключу владельца; ключ API отклоняется с not_owner.

Тело запроса:

{
"task_id": 0,
"dataset_hash": "64-hex",
"n_steps": 1000,
"round_nonce": 0,
"method": "lora",
"lr": 0.0001,
"seed": 42
}

Обязательны task_id, dataset_hash и n_steps. round_nonce по умолчанию 0, method по умолчанию lora, а lr и seed необязательны. Принимаются методы full, lora, sft, dpo, orpo и qlora. Выбор qlora сужает пул майнеров до железа NVIDIA, потому что его 4-битный путь работает только там. Отбор майнеров для обучения фильтрует не по задержке, а по уровню Pulse, требуя уровень 2 и выше, и дальше берёт самого мощного: обратные проходы и состояние оптимизатора не вытягивают более низкие уровни.

Ответ, HTTP 202:

{
"training_id": "64-hex",
"miner": "SS58 hotkey",
"checkpoint_root": "64-hex",
"n_steps": 1000,
"status": "dispatched"
}

Статус dispatched означает, что майнер подтвердил приём задания по сети. Он не означает, что обязательство уже есть в цепи.

GET /n/{id}/v1/training/{tid}​

Возвращает живой статус обучения, взятый из состояния цепи, а не из собственной памяти шлюза. tid - 64-символьный шестнадцатеричный идентификатор обучения.

{
"training_id": "64-hex",
"verification": { "status": "committed" },
"checkpoint_root": "64-hex"
}

checkpoint_root появляется только после того, как майнер закоммитил траекторию в цепь, потому что именно за это значение отвечает его залог.

СтатусЧто означает
dispatchedОтправлено майнеру, обязательства в цепи ещё нет
committedОбязательство по траектории записано в цепь
trainingИдёт, несёт поле pct
challengedШаг попал на проверку, собираются вердикты свидетелей
verifiedКворум свидетелей сошёлся
flaggedТраектория не сошлась либо майнер пропустил дедлайн челленджа, и тогда в reason будет miner_timeout

Неизвестный tid возвращает HTTP 500 с ошибкой internal.

Коды ошибок​

Ошибки возвращают JSON-тело с полем error, называющим ситуацию.

HTTPerrorПричина
401unauthorizedПодпись отсутствует, битая, просроченная или неверная
402tenant_suspendedManaged-арендатор приостановлен, проверяется раньше всего остального
403not_ownerВызывающий аутентифицирован, но не владеет Нейронетом
409neuronet_frozenНейронет неактивен в цепи
422pulse_floor_unmetПорог Pulse для задачи не взят
429capacity_exceededМощность в цепи на эпоху исчерпана
429rate_limitedПревышена частота запросов на аккаунт
429quota_exceededДневной бюджет ключа API израсходован
500internalБитый идентификатор или неклассифицированный сбой
502miner_errorМайнер ответил ошибкой, её текст в detail
503no_miner_availableВ пуле задачи нет подходящего майнера
503overloadedДостигнут лимит одновременных запросов шлюза
504miner_timeoutНи один майнер не ответил за отведённые попытки

capacity_exceeded добавляет capacity, used и reset_epoch, чтобы клиент видел, насколько заполнена эпоха и когда бюджет обновится. quota_exceeded добавляет limit, used и reset_at и не несёт Retry-After, потому что дневной бюджет возвращается в полночь по UTC, а не через секунду. overloaded и rate_limited добавляют retry_after в тело и заголовок Retry-After: 1.

Лимиты​

Ниже встроенные значения по умолчанию. Конкретная инсталляция может их переопределить.

  • Лимит на аккаунт: 20 запросов в секунду устойчиво, всплеск до 40.
  • Допуск: 64 запроса в работе на весь шлюз, 16 на один Нейронет. Сверх этого запросы сбрасываются с overloaded, а не встают в очередь.
  • Окно свежести челленджа: 300 секунд.
  • Попыток отправки на запрос: 3, каждая на другом майнере.
  • Дедлайн майнера на задание инференса: 30000 мс.

Мощность считается в слотах. Запрос на инференс по умолчанию стоит 1 слот, обучение дороже, и сколько именно, задаёт задача. Шлюз считает слоты, отправленные им в текущей эпохе, поверх числа из цепи, потому что счётчик в цепи двигается только на расчёте. Как стейк задаёт мощность Нейронета, описано в разделе Стейкинг, а упомянутые здесь настройки задачи - в Конфигурации задачи.