Справочник 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-account | SS58-адрес подписанта |
x-aetron-signature | Подпись sr25519 в hex, 64 байта, префикс 0x опционален |
x-aetron-timestamp | Unix-секунды, использованные в полезной нагрузке |
Подписывается 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, называющим ситуацию.
| HTTP | error | Причина |
|---|---|---|
| 401 | unauthorized | Подпись отсутствует, битая, просроченная или неверная |
| 402 | tenant_suspended | Managed-арендатор приостановлен, проверяется раньше всего остального |
| 403 | not_owner | Вызывающий аутентифицирован, но не владеет Нейронетом |
| 409 | neuronet_frozen | Нейронет неактивен в цепи |
| 422 | pulse_floor_unmet | Порог Pulse для задачи не взят |
| 429 | capacity_exceeded | Мощность в цепи на эпоху исчерпана |
| 429 | rate_limited | Превышена частота запросов на аккаунт |
| 429 | quota_exceeded | Дневной бюджет ключа API израсходован |
| 500 | internal | Битый идентификатор или неклассифицированный сбой |
| 502 | miner_error | Майнер ответил ошибкой, её текст в detail |
| 503 | no_miner_available | В пуле задачи нет подходящего майнера |
| 503 | overloaded | Достигнут лимит одновременных запросов шлюза |
| 504 | miner_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 слот, обучение дороже, и сколько именно, задаёт задача. Шлюз считает слоты, отправленные им в текущей эпохе, поверх числа из цепи, потому что счётчик в цепи двигается только на расчёте. Как стейк задаёт мощность Нейронета, описано в разделе Стейкинг, а упомянутые здесь настройки задачи - в Конфигурации задачи.