AI proxy и LLM gateway: в чём разница для запроса к модели

14 мин чтения

Как отличить сетевую пересылку, reverse-прокси и шлюз моделей, чтобы выбрать слой под задачу — без подмены API и без обещаний про доступ.

Сетевой прокси, reverse-прокси и LLM gateway решают разные задачи. Эта статья помогает отличить пересылку трафика от маршрутизации модели и выбрать слой под задачу. Мы не выдаём ключи провайдеров и не обещаем, что выходной IP меняет доступ к API. Слово «AI proxy» у разных продуктов означает разное — ниже это разбирается на ограниченных примерах, а не как единый стандарт.

01Чем HTTP-прокси отличается от «AI proxy»

В разговорной речи «прокси для нейросети» часто смешивает три разных роли. Первая — обычный forward proxy: клиент сам выбирает посредника, и тот пересылает запрос к абсолютному URI. Вторая — reverse proxy или API gateway: клиент ходит на имя шлюза, а шлюз выглядит как origin. Третья — прикладной LLM gateway: посредник читает тело запроса, выбирает модель или провайдера, проверяет свой ключ, пишет логи и иногда подменяет формат ответа.

RFC 9110 (июнь 2022, STD 97) проводит эту границу прямо. Proxy — агент пересылки, которого выбирает клиент, обычно локальной конфигурацией. Gateway, он же reverse proxy, для исходящего соединения ведёт себя как origin, а внутрь переводит запрос на другой сервер. Tunnel — слепой релей двух соединений: после установления туннель уже не считается участником HTTP-общения. Если продукт называет себя «AI proxy», это ещё не говорит, к какой из трёх ролей он ближе.

Практический признак простой. Сетевой HTTP-прокси интересует куда соединиться: хост, порт, иногда метод CONNECT. Он может требовать Proxy-Authorization, но не обязан понимать поле model в JSON. Прикладной шлюз интересует какой маршрут выбрать внутри приложения: имя модели, виртуальный ключ команды, лимит токенов, иногда отдельный URL вида /v1/chat/completions. Оба слоя могут стоять в одной цепочке. Тогда ошибка на одном уровне не объясняет состояние другого.

Путаница усиливается тем, что документация вендоров переиспользует слово proxy. Страница LiteLLM AI Gateway (просмотрена 15 октября 2026) описывает self-hosted сервер с одним OpenAI-совместимым эндпоинтом для многих провайдеров. Cloudflare AI Gateway (страница с пометкой Last updated Apr 20, 2026) говорит про аналитику, кэш, лимиты, ретраи и fallback моделей. Это прикладные шлюзы. Они не заменяют определение HTTP-прокси из RFC и не делают сетевой выход «ключом от модели».

Если нужно только сменить исходящий адрес клиента к уже известному HTTPS API, смотрите слой пересылки. Если нужно раздать команде один внутренний URL, виртуальные ключи и маршруты fixture-alpha / fixture-beta — это уже шлюз приложения. Смешивать их в одном чеклисте «просто включите прокси» — частая причина, почему TCP проходит, а модель отвечает 401 или «unknown model».

02CONNECT и SOCKS5: туннель без разбора модели

Для HTTPS клиент по спецификации не отдаёт прокси расшифрованное тело. RFC 9110 §9.3.6 задаёт метод CONNECT: получатель должен установить туннель к указанному origin и дальше слепо пересылать байты в обе стороны, пока туннель не закроют. Цель — виртуальное соединение, которое затем закрывают TLS. Успешный CONNECT — это любой 2xx, после заголовков ответа посредник переходит в режим туннеля. Он не становится стороной TLS и не читает JSON модели, если только вы отдельно не поставили TLS-терминацию (это уже другая архитектура, её нельзя молча предполагать). Непрозрачность тела к TLS-origin — нормативное следствие этой схемы, а не измерение нашего стенда.

Записанный семикейсовый CONNECT в исходной лаборатории — plaintext-релей после 200 Connection Established: внутренний HTTP шёл открытым текстом, TLS не поднимался. Флаг json_parsed: false значит лишь, что обработчик не вызывал json.loads, и не доказывает конфиденциальность TLS.

SOCKS5 устроен ещё ниже HTTP. RFC 1928 (март 1996) описывает протокол как «shim-layer» между приложением и транспортом: клиент открывает TCP к SOCKS-серверу, обычно порт 1080, договаривается о методе аутентификации и шлёт команду. CONNECT здесь — код X'01', рядом BIND и UDP ASSOCIATE. SOCKS не разбирает HTTP-семантику и не знает про chat completions. Он умеет IPv4, доменное имя и IPv6 как тип адреса. Успех SOCKS означает, что реле разрешил соединение с DST.ADDR:DST.PORT, а не что провайдер модели принял ключ.

Документация NGINX Plus отдельно подчёркивает разницу reverse и forward. Страница HTTP CONNECT forward proxy (просмотрена 15 октября 2026) говорит, что NGINX Plus R36 и новее можно настроить как forward proxy через CONNECT; reverse proxy защищает серверы, а forward обслуживает клиентские запросы во внешнюю сеть. Это не доказательство, что любой Open Source NGINX из коробки — CONNECT-прокси, и не руководство по обходу фильтров. Это только вендорская фиксация: CONNECT — про туннель клиента, proxy_pass — про входящий запрос к вашему серверу.

Что из этого следует для вызова модели. Если HTTP-клиент ходит через CONNECT или SOCKS5, прокси может успешно установить туннель, а API всё равно вернуть 401: ключ провайдера проверяет origin, не прокси. Если вы смотрите только на «прокси живой» в прокси-чекере, вы проверяете сетевой выход, а не право вызывать конкретную модель. Чекер не подтверждает пригодность IP для LLM API и не заменяет заголовок Authorization провайдера. Для самопроверки исходящего адреса есть страница «Мой IP»: она показывает, что видно снаружи, и тоже не говорит, примет ли модель запрос.

03Обратный прокси и API-шлюз

Reverse proxy стоит перед вашим сервисом. Клиент резолвит имя шлюза, TLS чаще всего заканчивается на нём, а дальше proxy_pass отправляет запрос на upstream. Руководство NGINX Reverse Proxy (просмотрено 15 октября 2026) описывает базовый цикл: отправить запрос указанному серверу, получить ответ, вернуть клиенту. По умолчанию NGINX переписывает Host и Connection и отбрасывает пустые заголовки. Адрес в proxy_pass может подменять часть URI. Это маршрутизация пути и апстрима, не поля model.

API gateway обычно добавляет к reverse proxy прикладные правила: ключ клиента шлюза, квоты, OpenAPI-проверку, канарейку по заголовку. Он всё ещё может ничего не знать про конкретные модели. Типичный пример — один вход /v1/* на несколько микросервисов. Пока правило смотрит на путь, заголовок и JWT, это HTTP-шлюз приложения. Когда правило читает JSON и выбирает fixture-alpha вместо fixture-beta, вы пересекаете границу LLM gateway.

Полезно держать в голове таблицу решений, а не искать «правильный AI proxy» как категорию товара.

СлойЧто выбирает клиентЧто видит посредникТипичная задачаЧего слой не обещает
HTTP forward (http://)Адрес проксиМетод, путь и тело (JSON, включая model)Учебная или внутренняя пересылка без TLSНепрозрачность тела, ключ модели, «доступность чата»
CONNECT / SOCKS к TLS-originАдрес проксиХост:порт и, при наличии, прокси-аутентификация; тело origin непрозрачно, пока нет инспекцииВыход к HTTPS API через периметрКлюч модели; непрозрачность не измерена нашим plaintext CONNECT-релеем
Reverse proxy / HTTP API gatewayИмя вашего входаПуть, Host, заголовки после TLS на шлюзеСвести много сервисов к одному origin, балансировкаОдинаковые ретраи, стриминг и fallback у всех продуктов
LLM / model gatewayИмя шлюза и виртуальный ключТело запроса, имя модели, маршруты, лимиты, логиМаршрутизация моделей, бюджеты команды, единый SDK-входПолную эквивалентность API провайдера и сетевой «чистоты» IP

Для каталога прокси как сетевого слоя, а не как шлюза моделей, на сайте есть живые разделы резидентских и мобильных прокси. Это про тип выхода, не про маршрутизацию gpt-* внутри JSON. Сетевой выход может понадобиться клиенту, который уже ходит на официальный API, но сам по себе он не выдаёт модель и не заменяет виртуальный ключ шлюза.

04Что обычно умеет LLM gateway — и чего нельзя предполагать

У прикладного шлюза моделей повторяется один и тот же набор обещаний, но набор не обязан быть полным. LiteLLM на просмотренной странице перечисляет контроль доступа виртуальными ключами, учёт трат, балансировку, fallback и ретраи, фильтры, логи и админку. Cloudflare AI Gateway на обзорной странице — аналитику, логи, кэш, rate limit, ретраи и model fallback. Страница Dynamic routing Cloudflare (Last updated Oct 2, 2026) добавляет именованный маршрут вместо имени модели, узлы условий, проценты, лимиты бюджета и откат версии. Это описание конкретных продуктов на дату просмотра, не свойства любого «LLM gateway».

Безопасное обобщение такое. Шлюз может:

  • принять локальный/виртуальный ключ, который не равен ключу провайдера;
  • выбрать апстрим по имени модели, тегу или отдельному route id;
  • посчитать токены и обрезать бюджет;
  • записать request id и статус;
  • при наличии функции — повторить запрос или уйти на запасную модель.

Шлюз может не уметь стриминг так же, как origin. Может не повторять запросы. Может не понимать Responses API, если клиент ждёт chat completions. Может объявлять «OpenAI-compatible», имея в виду несколько путей /v1/chat/completions и /v1/embeddings, но не полный набор заголовков, событий стрима и ошибок. Фраза LiteLLM «если клиент умеет OpenAI API, он умеет и шлюз» — маркетинговое сокращение на их странице, а не формальное равенство спецификаций. OpenAI в обзоре API (финальный URL после https://platform.openai.com/docs/api-reference, 200 на 15 октября 2026) отдельно фиксирует Bearer-секрет, лимиты размера заголовков и то, что поведение промпта между снимками модели может меняться. Совместимый путь не отменяет этих правил origin.

Поэтому в лабораторном стенде ниже ретраи, стриминг и fallback не исполнялись. Если функция не прогнана, её нельзя приписывать ни фикстуре, ни «типичному шлюзу». Для выбора продукта читайте текущую страницу вендора по каждой функции отдельно: кэш, retry, fallback, streaming — разные переключатели.

Ещё одна частая ловушка — два ключа. Ключ шлюза открывает вход в вашу политику. Ключ провайдера, который шлюз хранит у себя (у Cloudflare в документации это связка authentication + BYOK), открывает апстрим. Клиент, который подставил только сетевой прокси и origin-ключ, не получает политику шлюза. Клиент, который подставил только ключ шлюза и забыл провайдерский секрет на стороне шлюза, получит прикладную ошибку, хотя TCP и HTTP до шлюза уже успешны.

05Где заканчивается TLS и что видит посредник

Схема «кто обрывает TLS» определяет, какой слой вообще может маршрутизировать модель.

  1. Клиент → CONNECT/SOCKS → TLS-origin. По RFC 9110 прокси видит целевой хост:порт (в CONNECT — authority без пути) и слепо релеит байты; тело chat-запроса для него — шифротекст, пока нет отдельной инспекции. Маршрутизация по model без разрыва TLS невозможна. Этот пункт — норма источника, не результат нашего CONNECT-прогона.
  2. Клиент → HTTP forward на http:// origin. Прокси видит метод, путь и тело. Этот режим используют и копируемый рецепт ниже, и внутренний POST после лабораторного CONNECT: оба — plaintext HTTP, не TLS.
  3. Клиент → reverse proxy / LLM gateway с терминацией TLS. Шлюз видит HTTP после расшифровки. Здесь появляется прикладная маршрутизация, локальная аутентификация и логи. Дальше шлюз сам устанавливает новое TLS-соединение к провайдеру или к внутреннему фикстурному origin.

Путать (1) и (3) опасно. В (1) «прокси работает» значит: туннель установлен. В (3) «шлюз работает» значит: запрос принят политикой входа. Ни то ни другое не равно «модель сгенерировала текст». Даже HTTP 200 от origin может быть фикстурой или прикладным объектом с error внутри тела — поэтому ниже лабораторный origin явно помечает ответы строкой CONTROLLED_FIXTURE_NOT_A_PROVIDER.

Если вы всё же ставите TLS-инспекцию на корпоративном периметре, это уже MITM-политика организации, а не свойство SOCKS или CONNECT. RFC описывает туннель как слепой релей. Любое чтение содержимого нужно объявлять отдельно и проверять сертификаты. В этой статье такой инспектор не собирался и не тестировался.

06Совместимость эндпоинта не равна полному API

«OpenAI-compatible» обычно означает: клиентский SDK можно направить на другой base_url, оставив тот же метод create chat completion. Это удобно и этого часто достаточно. Этого недостаточно, чтобы считать API равным.

Различия, которые ломают прод:

  • Другой набор моделей и другие имена. Шлюз может принимать fixture-alpha, тогда как origin ждёт внутренний id.
  • Другие коды ошибок. 401 шлюза — нет локального токена. 401 origin — нет секрета провайдера. Оба выглядят как «не авторизован», чинятся в разных местах.
  • Другие заголовки лимитов и request id.
  • Частичная поддержка стрима, tool calling, multimodal, Responses vs Chat Completions.
  • Поведение при неизвестной модели: один шлюз отвечает 404 сразу, другой проксирует 400 origin, третий молча мапит имя.

Лабораторный шлюз ниже нарочно отвечает 404 на fixture-unknown до origin. Это прикладной отказ маршрута, при том что TCP до шлюза успешен. Сетевой forward на тот же JSON напротив доставляет тело как есть: origin получает model: fixture-alpha и отвечает с того же пути /v1/chat/completions, без выбора /alpha или /beta. Если ваш продукт обещает совместимость, проверяйте именно те методы, которыми ходит клиент, а не таблицу «100+ провайдеров» на главной.

07Три уровня ошибок и чеклист выбора слоя

Различайте три успеха.

  1. Транспорт. TCP/SOCKS/CONNECT установились. Для записанного CONNECT это строка HTTP/1.1 200 Connection Established, после которой в той лаборатории шёл plaintext HTTP, а не TLS. Туннель ещё не говорит про JSON и не измеряет непрозрачность.
  2. HTTP. Статус ответа посредника или origin: 200, 401, 403, 404, 502. 200 CONNECT и 200 JSON — разные 200.
  3. Приложение / модель. Тело без прикладной ошибки, ожидаемый origin_route, не unsupported_model. В реальном провайдере сюда же относятся finish_reason, отказ политики и лимит токенов. В лаборатории origin всегда фикстура: даже 200 не является вызовом платного API.

Чеклист, которого хватает перед выбором слоя:

  • Нужно сменить только исходящий IP или пройти корпоративный выход? Берёте forward HTTP, CONNECT или SOCKS5. Проверяете выход чекером и «Мой IP», не ожидая, что это заменит ключ модели.
  • Нужно спрятать несколько HTTP-сервисов за одним именем? Reverse proxy / API gateway, правила по пути и заголовкам.
  • Нужны виртуальные ключи команды, маршрут по имени модели, сводные логи? LLM gateway. Смотрите, какие из функций (retry, fallback, stream, кэш) реально включены, и проверяете их отдельно.
  • Клиент уже шлёт Bearer провайдера. Сетевой прокси этот заголовок не подменит сам. Шлюз может потребовать другой Bearer.
  • Неизвестный model должен отсекаться на входе — это правило шлюза, не прокси.
  • Не переносите лимиты и географию аккаунта на тип прокси. Сетевой слой не выдаёт право на модель.
Результат лабораторного прогона: пересылка, CONNECT, маршруты шлюза и отказы авторизации

Ниже копируемый loopback-рецепт на стандартной библиотеке Python 3: фикстурный origin, HTTP-пересылка абсолютным URI и шлюз, который выбирает маршрут по полю model. Все сокеты только 127.0.0.1, порты эфемерные, таймаут 2 с, без ключей провайдера и без внешних API. Этот рецепт не исполняет TLS, SOCKS, CONNECT, ретраи, стриминг и fallback — их нельзя считать проверенными здесь. Рисунок выше — отдельный записанный прогон исходной лаборатории; он тоже не доказывает TLS/SOCKS и не заменяет копируемый код.

Сохраните блок как файл forward_vs_gateway.py в текущем каталоге (любая локальная папка; репозиторий не нужен). Зависимости: только python3. Затем:

python3 forward_vs_gateway.py
#!/usr/bin/env python3
"""Loopback: HTTP forwarding vs application model routing.

Python 3 stdlib only. Binds 127.0.0.1, ephemeral ports, 2s timeouts.
Labeled fixture origin. No provider keys, paid APIs, TLS, or SOCKS.
"""
from __future__ import annotations

import json
from http.client import HTTPConnection
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
from threading import Thread

BIND = "127.0.0.1"
TOKEN = "***"
FIXTURE = "CONTROLLED_FIXTURE_NOT_A_PROVIDER"


class Origin(BaseHTTPRequestHandler):
    def log_message(self, *args):
        return

    def do_POST(self):
        n = int(self.headers.get("Content-Length", "0") or 0)
        payload = json.loads(self.rfile.read(n) or b"{}")
        if self.path.endswith("/alpha"):
            route = "alpha"
        elif self.path.endswith("/beta"):
            route = "beta"
        else:
            route = "forwarded-as-is"
        body = json.dumps(
            {"fixture": FIXTURE, "origin_route": route, "model": payload.get("model")}
        ).encode()
        self.send_response(200)
        self.send_header("Content-Type", "application/json")
        self.send_header("Content-Length", str(len(body)))
        self.end_headers()
        self.wfile.write(body)


class Forward(BaseHTTPRequestHandler):
    def log_message(self, *args):
        return

    def do_POST(self):
        n = int(self.headers.get("Content-Length", "0") or 0)
        raw = self.rfile.read(n)
        without = self.path[len("http://") :]
        hostport, _, path = without.partition("/")
        host, port_s = hostport.rsplit(":", 1)
        if host != BIND:
            self.send_error(403)
            return
        up = HTTPConnection(host, int(port_s), timeout=2)
        try:
            up.request(
                "POST",
                "/" + path,
                body=raw,
                headers={"Content-Type": "application/json"},
            )
            resp = up.getresponse()
            data = resp.read()
        finally:
            up.close()
        self.send_response(resp.status)
        self.send_header("Content-Type", "application/json")
        self.send_header("Content-Length", str(len(data)))
        self.send_header("X-Layer", "forward")
        self.end_headers()
        self.wfile.write(data)


class Gateway(BaseHTTPRequestHandler):
    origin_port = 0

    def log_message(self, *args):
        return

    def _send(self, status, obj, route=""):
        data = json.dumps(obj).encode()
        self.send_response(status)
        self.send_header("Content-Type", "application/json")
        self.send_header("Content-Length", str(len(data)))
        if route:
            self.send_header("X-Gateway-Route", route)
        self.end_headers()
        self.wfile.write(data)

    def do_POST(self):
        n = int(self.headers.get("Content-Length", "0") or 0)
        raw = self.rfile.read(n)
        auth = self.headers.get("Authorization", "")
        if not auth:
            return self._send(
                401, {"error": "missing local gateway token", "fixture": FIXTURE}
            )
        if auth != f"Bearer {TOKEN}":
            return self._send(
                403, {"error": "invalid local gateway token", "fixture": FIXTURE}
            )
        model = json.loads(raw or b"{}").get("model")
        routes = {"fixture-alpha": "/alpha", "fixture-beta": "/beta"}
        if model not in routes:
            return self._send(
                404, {"error": "unsupported_model", "fixture": FIXTURE}, "unsupported"
            )
        up = HTTPConnection(BIND, self.origin_port, timeout=2)
        try:
            up.request(
                "POST",
                routes[model],
                body=raw,
                headers={"Content-Type": "application/json"},
            )
            resp = up.getresponse()
            data = resp.read()
        finally:
            up.close()
        self.send_response(resp.status)
        self.send_header("Content-Type", "application/json")
        self.send_header("Content-Length", str(len(data)))
        self.send_header("X-Gateway-Route", model)
        self.end_headers()
        self.wfile.write(data)


def serve(handler):
    httpd = ThreadingHTTPServer((BIND, 0), handler)
    t = Thread(target=httpd.serve_forever, daemon=False)
    t.start()
    return httpd, t, httpd.server_address[1]


def post(port, path, body, headers=None):
    raw = json.dumps(body).encode()
    h = {"Content-Type": "application/json"}
    if headers:
        h.update(headers)
    c = HTTPConnection(BIND, port, timeout=2)
    try:
        c.request("POST", path, body=raw, headers=h)
        r = c.getresponse()
        return r.status, json.loads(r.read()), dict(r.getheaders())
    finally:
        c.close()


def main():
    origin, ot, op = serve(Origin)
    forward, ft, fp = serve(Forward)
    Gateway.origin_port = op
    gateway, gt, gp = serve(Gateway)
    try:
        s1, b1, _ = post(
            fp,
            f"http://{BIND}:{op}/v1/chat/completions",
            {"model": "fixture-alpha"},
        )
        s2, b2, h2 = post(
            gp,
            "/v1/chat/completions",
            {"model": "fixture-alpha"},
            {"Authorization": f"Bearer {TOKEN}"},
        )
        s3, b3, h3 = post(
            gp,
            "/v1/chat/completions",
            {"model": "fixture-beta"},
            {"Authorization": f"Bearer {TOKEN}"},
        )
        s4, b4, _ = post(gp, "/v1/chat/completions", {"model": "fixture-alpha"})
        s5, b5, _ = post(
            gp,
            "/v1/chat/completions",
            {"model": "fixture-alpha"},
            {"Authorization": "***"},
        )
        s6, b6, _ = post(
            gp,
            "/v1/chat/completions",
            {"model": "no-such-model"},
            {"Authorization": f"Bearer {TOKEN}"},
        )
        rows = [
            ("forward", s1, b1.get("origin_route"), b1.get("fixture")),
            ("gw-alpha", s2, b2.get("origin_route"), h2.get("X-Gateway-Route")),
            ("gw-beta", s3, b3.get("origin_route"), h3.get("X-Gateway-Route")),
            ("gw-no-auth", s4, b4.get("error"), None),
            ("gw-bad-auth", s5, b5.get("error"), None),
            ("gw-unknown", s6, b6.get("error"), None),
        ]
        for row in rows:
            print(row)
        assert s1 == 200 and b1["fixture"] == FIXTURE
        assert s2 == 200 and b2["origin_route"] == "alpha"
        assert s3 == 200 and b3["origin_route"] == "beta"
        assert s4 == 401 and s5 == 403 and s6 == 404
        print("public recipe assertions ok")
    finally:
        for httpd in (origin, forward, gateway):
            httpd.shutdown()
            httpd.server_close()
        for t in (ot, ft, gt):
            t.join(timeout=2)


if __name__ == "__main__":
    main()

Ожидаемый вывод контрольного прогона рецепта (Python 3.9.6, 15 октября 2026, exit 0):

('forward', 200, 'forwarded-as-is', 'CONTROLLED_FIXTURE_NOT_A_PROVIDER')
('gw-alpha', 200, 'alpha', 'fixture-alpha')
('gw-beta', 200, 'beta', 'fixture-beta')
('gw-no-auth', 401, 'missing local gateway token', None)
('gw-bad-auth', 403, 'invalid local gateway token', None)
('gw-unknown', 404, 'unsupported_model', None)
public recipe assertions ok

Скрипт в finally вызывает shutdown, server_close и join. Forward оставляет origin_route=forwarded-as-is, шлюз разводит alpha и beta, нет токена — 401, неверный токен — 403, неизвестная модель — 404. Это демонстратор слоёв на фикстуре, не бенчмарк, не коммерческий прокси и не вызов модели провайдера.

Если после этого чеклиста остаётся только сетевой слой, имеет смысл смотреть тип выхода — резидентский или мобильный — как свойство IP, а не как «AI gateway». Если остаётся прикладной слой, читайте текущую документацию выбранного шлюза по ключам, маршрутам и ошибкам и не переносите на него свойства CONNECT.

Меняет ли сетевой прокси авторизацию API модели?

Нет. Forward HTTP, CONNECT и SOCKS5 переносят или туннелируют соединение. Заголовок Authorization для origin остаётся секретом провайдера. Шлюз моделей может требовать отдельный локальный ключ; это другая проверка, её сетевой прокси не выполняет.

Видит ли CONNECT содержимое TLS и поле model?

По RFC 9110 успешный CONNECT к TLS-origin — слепой туннель: посредник не сторона TLS и не видит JSON, пока нет отдельной инспекции. Это норма спецификации. Наш записанный CONNECT после 200 пересылал plaintext HTTP; флаг json_parsed false не измеряет непрозрачность TLS и SOCKS мы не исполняли.

Чем ключ LLM gateway отличается от ключа провайдера?

Ключ шлюза открывает вашу политику входа, лимиты и маршруты. Ключ провайдера открывает апстрим. Оба могут быть Bearer, поэтому 401 нужно читать по месту отказа. В стенде отсутствие локального токена дало 401, неверный — 403, до origin запрос не дошёл.

Что означает OpenAI-compatible у шлюза?

Обычно — совместимый вход chat completions на другом base_url. Это не полное равенство API, стрима, ошибок и набора моделей. Неизвестный маршрут в лаборатории отвергнут шлюзом как 404 unsupported_model, хотя транспорт до шлюза уже был успешен.

Проверяет ли прокси-чекер, что IP годится для LLM API?

Нет. Живой /ru/proxy-checker проверяет сетевой выход списка прокси. Он не подтверждает право вызывать модель, географию аккаунта и совместимость SDK. Для исходящего адреса используйте /ru/my-ip как сетевую самопроверку, не как допуск к API.

Когда достаточно SOCKS5, а когда нужен LLM gateway?

SOCKS5, если задача — установить TCP/UDP через периметр к уже известному origin. Шлюз моделей, если нужны виртуальные ключи, выбор модели, бюджеты и единый внутренний URL. Оба слоя можно поставить подряд, но ошибки у них разные и чинятся отдельно.

Можно ли считать HTTP 200 успехом модели?

Нет. 200 CONNECT — транспорт. 200 JSON — HTTP origin или шлюза. Прикладной успех смотрят в теле и в том, какой слой ответил. Лабораторный 200 всегда помечен как CONTROLLED_FIXTURE_NOT_A_PROVIDER и не является вызовом провайдера.