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

OpenAI base_url в SDK и Cursor: приоритет, путь /v1 и проверка

Аргумент base_url в коде сильнее OPENAI_BASE_URL. В base пишут весь префикс до имени метода: /v1, /openai/v1 или /v1beta/openai, но не полный endpoint.

LaoZhang AI TeamОпубликовано9 мин чтения
Содержание
Приоритет base URL в OpenAI SDK: аргумент в коде сильнее OPENAI_BASE_URL, а она сильнее адреса api.openai.com/v1 по умолчанию

Если код или инструмент работает в формате OpenAI API, адрес назначения меняется одной настройкой — base URL. Но запрос приходит туда, куда вы рассчитывали, только при трёх условиях: SDK взял именно ваше значение; в строке есть весь префикс пути, который ждёт сервис; сервис реализует тот API, который вы вызываете.

Для официальных SDK — openai-python 3.19.2 и openai для Node 7.23.0, последних версий по состоянию на 26 сентября 2026 года — ответ такой:

  • аргумент конструктора (base_url в Python, baseURL в Node) сильнее переменной окружения OPENAI_BASE_URL, а она сильнее значения по умолчанию https://api.openai.com/v1;
  • SDK дописывает к base путь метода (chat/completions, responses), поэтому в base пишут всё до имени метода — …/v1, …/openai/v1/, …/v1beta/openai/, — но не полный endpoint;
  • HTTP-прокси задаёт сетевой маршрут, а не адрес API, и настраивается отдельно от base URL;
  • после настройки фактический адрес нужно увидеть: вывести client.base_url, прогнать echo-проверку или найти запрос в логах провайдера.

Приоритет настроек и склейка путей ниже проверены запуском обоих SDK против echo-сервера на том же компьютере с фиктивным ключом. Такая проверка показывает, какой URL строит SDK, но ничего не говорит о том, как ответит конкретный провайдер.

Какое значение возьмёт SDK

Что заданоopenai-python 3.19.2openai (Node) 7.23.0
Ничегоhttps://api.openai.com/v1https://api.openai.com/v1
Только OPENAI_BASE_URLзначение переменнойзначение переменной
OPENAI_BASE_URL и аргумент в кодеаргументаргумент
OPENAI_BASE_URL="" (задана, но пустая)base остаётся пустым, запрос падает с APIConnectionError: Connection error.пустое значение считается незаданным, запрос уходит на https://api.openai.com/v1
Только старая OPENAI_API_BASEигнорируется, остаётся https://api.openai.com/v1игнорируется, остаётся https://api.openai.com/v1
OPENAI_BASE_URL и data_residency="eu" / dataResidency: 'eu'https://eu.api.openai.com/v1https://eu.api.openai.com/v1
OPENAI_BASE_URL и baseURL: null—переменная не читается, https://api.openai.com/v1

Три строки этой таблицы требуют пояснений.

Пустая переменная. В Python она превращается в ошибку соединения, которую легко принять за сетевую проблему. В Node всё тише: клиент молча возвращается на OpenAI и отправляет туда ключ другого провайдера, так что ждать стоит отказа авторизации от OpenAI. Проверьте, что реально лежит в окружении процесса, например printenv OPENAI_BASE_URL, а в контейнерах и CI — в описании переменных самой среды.

Старое имя OPENAI_API_BASE. Так переменная называлась в openai-python до версии 1 (вместе с openai.api_base), и многие старые руководства до сих пор её показывают. Текущие SDK читают только OPENAI_BASE_URL. Некоторые сторонние фреймворки читают собственные переменные, поэтому для них имя сверяйте с документацией фреймворка, а не SDK.

Регион OpenAI. Параметр data_residency (us, eu, ae) выбирает https://us.api.openai.com/v1, https://eu.api.openai.com/v1 или https://ae.api.openai.com/v1 и перекрывает OPENAI_BASE_URL. Передать его вместе с явным base_url Python не даст: конструктор выбросит ValueError: The data_residency and base_url arguments are mutually exclusive. Параметр provider= тоже сам задаёт адрес и с base_url не сочетается ни в одном из двух SDK. SDK подставляет адрес из своего списка, а условия доступа к региональным endpoint'ам задаёт OpenAI, не SDK.

Минимальная явная настройка выглядит так. Ключ передаётся явно: без этого SDK возьмёт OPENAI_API_KEY, и ключ может оказаться от одного сервиса, а адрес — от другого.

python
import os
from openai import OpenAI

client = OpenAI(
    base_url="https://api.example.com/v1",       # сильнее OPENAI_BASE_URL
    api_key=os.environ["PROVIDER_API_KEY"],
)
print(client.base_url)  # адрес, который SDK реально будет использовать
js
import OpenAI from 'openai';

const client = new OpenAI({
  baseURL: 'https://api.example.com/v1',
  apiKey: process.env.PROVIDER_API_KEY,
});
console.log(client.baseURL);

Если другой адрес нужен только одному вызову, используйте client.with_options(base_url=...) в Python или client.withOptions({ baseURL }) в Node. Этот вызов уйдёт на новый адрес, а сам клиент сохранит прежний base.

Что писать в base URL

SDK не угадывает структуру чужого API: он берёт base и приписывает к нему относительный путь метода. Python дополнительно добавляет в конец base косую черту, поэтому …/v1 и …/v1/ дают одинаковый результат. Вот какие пути получил echo-сервер от обоих SDK при вызове chat.completions.create:

Значение base URLПуть запроса на сервере
https://host/v1/v1/chat/completions
https://host/v1//v1/chat/completions, без двойной косой черты
https://host/chat/completions
https://host/v1/chat/completions/v1/chat/completions/chat/completions

Вызов responses.create точно так же уходит на {base}/responses. Отсюда правило: base URL — это всё, что стоит перед именем метода. Если сервис публикует https://host/openai/v1/chat/completions, в base пишут https://host/openai/v1. Полный адрес метода в base — самая частая причина 404 с «удвоенным» путём.

Как SDK склеивает base URL с путём метода: https://host/v1 даёт /v1/chat/completions, вставленный полный endpoint удваивает путь; префиксы /v1 у OpenAI, /openai/v1/ у Azure и /v1beta/openai/ у Gemini

Для распространённых целей это выглядит так:

Куда отправлятьbase URLКлюч и modelЧто учесть
OpenAI, глобальный endpointhttps://api.openai.com/v1, задавать не нужноключ OpenAI, ID моделей OpenAI—
OpenAI, региональный endpointчерез data_residency, не через base_urlключ OpenAIсм. раздел выше
Azure OpenAI, v1 APIhttps://ИМЯ-РЕСУРСА.openai.azure.com/openai/v1/ или https://ИМЯ-РЕСУРСА.services.ai.azure.com/openai/v1/ключ Azure или токен Entra ID; model — имя deploymentapi-version больше не нужен; в v1 GA поддержана только часть возможностей
Gemini APIhttps://generativelanguage.googleapis.com/v1beta/openai/ключ Gemini API, ID моделей Geminiподдержка библиотек OpenAI в бете; Chat Completions описан, Responses нет
Шлюз laozhang.aihttps://api.laozhang.ai/v1ключ шлюза, ID из его каталогаResponses заявлен для GPT-6 Astra, Sol и Luna, а не для всех моделей
Свой или локальный серверпрефикс, под которым сервер отдаёт chat/completionsкак настроено на серверепуть сверьте echo-проверкой или логом сервера

Строка Azure взята из описания v1 API в документации Microsoft: там показан обычный клиент OpenAI() с таким base_url, а переменные OPENAI_BASE_URL и OPENAI_API_KEY работают с ним без параметров. Старый клиент AzureOpenAI(azure_endpoint=..., api_version=...) в SDK остался, но это другая схема адреса. Ошибка 429 после переключения на Azure значит, что адрес уже верный, а упираетесь вы в лимиты deployment, — см. Лимиты TPM в Azure OpenAI: как найти причину ошибки 429.

Строка Gemini соответствует странице совместимости с OpenAI, обновлённой 2 сентября 2026 года. Для laozhang.ai адрес и ограничение по Responses указаны в документации шлюза и объявлении о запуске GPT-6.

Прокси и base URL — разные настройки

По-русски «прокси» называют и сервис-посредник, и сетевой прокси, а для SDK это разные вещи. Если посредник принимает запросы API по собственному адресу и сам пересылает их провайдеру, это шлюз, и его адрес идёт в base URL. Если же трафик к api.openai.com просто должен пройти через HTTP-прокси, base URL не меняется, а прокси задаётся в HTTP-клиенте.

В openai-python 3.19.2:

python
from openai import OpenAI, DefaultHttpx2Client

client = OpenAI(
    http_client=DefaultHttpx2Client(proxy="http://proxy.example.com:3128"),
)

В более старых версиях Python SDK этот класс назывался DefaultHttpxClient, поэтому пример из свежего README может не импортироваться в старой установке. В Node прокси передают через fetchOptions:

ts
import OpenAI from 'openai';
import { fetch, ProxyAgent } from 'undici';

const client = new OpenAI({
  fetch,
  fetchOptions: { dispatcher: new ProxyAgent('http://proxy.example.com:3128') },
});

Адрес HTTP-прокси, записанный в base URL, превращает прокси в получателя запросов API, которых он не понимает. Если в итоге ответа нет вовсе, разбирайте сетевую часть: Ошибка подключения OpenAI API: сначала проверьте маршрут APIConnectionError.

Реализует ли сервис вызываемый API

«OpenAI-совместимый» обычно означает прежде всего совместимость с Chat Completions (POST {base}/chat/completions). Responses API (POST {base}/responses) — отдельный набор endpoint'ов, и правильный base URL не поможет, если сервис его не реализует.

Типичный пример — ветка на форуме сообщества OpenAI от 6 ноября 2025 года: client.responses.create с base URL совместимости Gemini вернул 404. Ответ дал участник сообщества, а не сотрудник: другие компании, как правило, поддерживают совместимость только с Chat Completions. Документация Gemini по состоянию на 2 сентября 2026 года описывает chat completions, embeddings, изображения, аудио, пакетную обработку и список моделей, но не Responses. Отсутствие в документации не доказывает, что endpoint никогда не заработает, но строить на нём рабочий код не стоит.

Решение зависит от того, какой API вызывает ваш код:

  • Chat Completions. Подойдёт любой сервис, который документирует chat/completions. Потоковую выдачу, function calling и structured outputs проверяйте отдельно: у каждого провайдера свой набор.
  • Responses (Agents SDK или код после миграции с OpenAI Assistants API на Responses API). Нужен сервис, который документирует /responses для вашей модели: OpenAI, Azure v1 API (в документации Microsoft есть пример …/openai/v1/responses) или шлюз, заявивший Responses для конкретных моделей. Иначе переводите вызов на Chat Completions или ставьте прослойку-адаптер вроде LiteLLM.

Как доказать, куда уходит запрос

Фраза «я же задал переменную» ничего не доказывает, пока вы не увидели адрес. Проверяйте по нарастающей, пока не найдёте расхождение.

  1. Выведите итоговый base. print(client.base_url) или console.log(client.baseURL) в том же процессе и с тем же окружением, где работает приложение. Здесь сразу видны проигранный приоритет, пустая переменная и уход на значение по умолчанию.
  2. Прогоните echo-проверку. Скрипт ниже поднимает на вашем компьютере сервер на loopback-адресе IPv6 ::1, направляет на него SDK и печатает путь, который пришёл на сервер. Наружу ничего не отправляется, ключ фиктивный.
  3. Включите журнал SDK. В Python — OPENAI_LOG=debug (или info); переменная настраивает логгер openai, а логгеры HTTP-транспорта настраиваются отдельно через logging. В Node — та же переменная или опция клиента logLevel: 'debug': на этом уровне пишутся метаданные и заголовки запросов и ответов, заголовки авторизации скрыты.
  4. Посмотрите на стороне провайдера. Если в его логах или аналитике запроса нет, он туда не дошёл, и ошибку возвращал кто-то раньше — SDK, прокси или промежуточный сервис.
python
# echo_check.py — запросы не покидают компьютер
import json, socket, sys, threading
from http.server import BaseHTTPRequestHandler, HTTPServer
from openai import OpenAI

class LoopbackV6(HTTPServer):
    address_family = socket.AF_INET6

class Echo(BaseHTTPRequestHandler):
    def do_POST(self):
        self.rfile.read(int(self.headers.get("Content-Length", 0)))
        print("сервер получил:", self.command, self.path)
        body = json.dumps({
            "id": "echo", "object": "chat.completion", "created": 0, "model": "echo",
            "choices": [{"index": 0, "finish_reason": "stop",
                         "message": {"role": "assistant", "content": "ok"}}],
        }).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)

    def log_message(self, *args):
        pass

server = LoopbackV6(("::1", 0), Echo)          # ::1 — loopback IPv6, порт выбирает ОС
threading.Thread(target=server.serve_forever, daemon=True).start()

prefix = sys.argv[1] if len(sys.argv) > 1 else "/v1"   # проверяемая часть пути
client = OpenAI(base_url=f"http://[::1]:{server.server_address[1]}{prefix}",
                api_key="sk-fake", max_retries=0)
print("client.base_url:", client.base_url)
client.chat.completions.create(model="echo", messages=[{"role": "user", "content": "ping"}])
server.shutdown()

Передайте ту часть пути, которую собираетесь записать после хоста:

bash
python echo_check.py /openai/v1/
# client.base_url: http://[::1]:49663/openai/v1/
# сервер получил: POST /openai/v1/chat/completions

python echo_check.py /v1/chat/completions
# client.base_url: http://[::1]:49669/v1/chat/completions/
# сервер получил: POST /v1/chat/completions/chat/completions

Порт в выводе каждый раз другой. Если в вашей среде отключён IPv6 (так бывает в некоторых контейнерах), замените ::1 и [::1] на IPv4-адрес loopback, а AF_INET6 — на AF_INET.

Что чинить по симптому

Что видитеГде причинаЧто сделать
404 на любом методе, в логе путь с /chat/completions/chat/completions или без нужного префиксастрока base URLоставить в base всё до имени метода, сверить echo-проверкой
404 только на responses.create, Chat Completions работаетсервис не реализует Responsesперейти на Chat Completions или на сервис с документированным /responses
401 или Incorrect API key provided с ключом не от OpenAIзапрос ушёл на api.openai.com: пустая переменная в Node, старое OPENAI_API_BASE, сброс настройки в инструментевывести client.base_url, исправить источник адреса
401 от нужного хостаключ не от этого сервиса или SDK взял OPENAI_API_KEYпередать ключ явно; какие идентификаторы вообще нужны OpenAI — в статье OpenAI API Key и Organization ID: что реально нужно в 2026 году
model not foundID модели из чужого каталогадля Azure — имя deployment, для Gemini — ID Gemini, для шлюза — ID из его каталога
APIConnectionError, ответа нетсеть, прокси или пустой base в Pythonпроверить переменную, затем маршрут

Cursor: Override OpenAI Base URL

В Cursor ту же роль играет поле Override OpenAI Base URL в Cursor Settings > Models. Справка Cursor по собственным ключам его не описывает. Она сообщает другое: свои ключи действуют только для моделей чата, Tab по-прежнему работает на моделях Cursor, а все запросы идут через серверы Cursor, где собирается итоговый промпт. Всё остальное ниже — ответы сотрудников Cursor на форуме за 2026 год, даты указаны у каждого факта. Это не документация, и поведение может меняться между версиями.

Порядок настройки, при котором запрос действительно доходит до вашего endpoint:

  1. Вставьте ключ провайдера в поле OpenAI API Key и включите переключатель Use OpenAI API key. Это два отдельных шага. Override работает, только пока ключ активен; без него запросы приходят в Cursor без ключа и отклоняются с ERROR_BAD_MODEL_NAME (ответ от 25 августа 2026 года, Cursor 3.17.19).
  2. Включите Override OpenAI Base URL. Поле само заполнится адресом https://api.openai.com/v1 — замените его на base URL провайдера, например https://api.fireworks.ai/inference/v1, нажмите Enter или щёлкните вне поля, закройте и снова откройте настройки и убедитесь, что адрес сохранился. После выключения и повторного включения переключатель снова подставит адрес OpenAI, и ключ провайдера уйдёт в OpenAI с ответом Incorrect API key provided (ответ от 10 сентября 2026 года).
  3. Добавьте свою модель под настоящим ID провайдера, который не совпадает со встроенными именами Cursor. В августе 2026 года, например, kimi-k2.6, kimi-k3 и kimi-latest считались зарезервированными: Cursor обрабатывал их как свои модели и не отправлял на Override. ID моделей, которые Cursor хостит сам (в сентябрьском ответе — kimi-k3 и glm-5p2 на Fireworks), он маршрутизирует независимо от base URL, поэтому как контрольная проверка они не годятся.
  4. Начните новый чат.

Перед включением стоит учесть охват настройки. По ответу от 23 сентября 2026 года OpenAI API key и Override OpenAI Base URL действуют на все модели, кроме Claude и Gemini, — включая Composer и Grok. Эти модели работают на инфраструктуре Cursor и с вашим ключом отклоняются: This model does not support custom API keys. Встроенные модели семейства OpenAI из списка выбора при включённом Override тоже уходят на ваш endpoint. Одновременно держать свои модели на Override, а модели Cursor на Cursor пока нельзя. Маршрутизацию по отдельным моделям в Cursor отслеживают, но сроков не называют. Обходной путь — выключать Override (или OpenAI key) на время работы с моделями Cursor.

Куда Cursor направляет запросы при включённых ключе и Override: свои модели и модели OpenAI идут на ваш endpoint, Composer и Grok отклоняются, встроенные ID Cursor обходят Override, на Claude и Gemini настройка не действует

Локальные адреса и адреса внутренней сети Cursor недоступны: запросы идут через его серверы, поэтому нужен публичный HTTPS-адрес. В ответах с февраля по май 2026 года сотрудники Cursor предлагают туннель (ngrok, Cloudflare Tunnel). Туннель открывает сервер модели всему интернету, так что включите на нём проверку ключа — это вопрос безопасности, а не условие Cursor.

Если что-то не работает, сохраните Request ID из Cursor и посмотрите аналитику провайдера. В разобранном на форуме случае с Fireworks в аналитике не было ни одной ошибки 401, и это прямо показало, что запросы до Fireworks не доходили: они уходили на адрес OpenAI по умолчанию.

У Codex своя схема настройки — через openai_base_url или отдельный provider в config.toml. Её разбор — в статье Custom provider в Codex: API-ключ, Base URL и config.toml.

Обложка «GPT Image 2.5: Sunburst или Flare?»: слева наброски и чек-лист повседневных задач, справа настольная лампа с увеличенными деталями под надписью «Точность правок»
Руководства по API

GPT Image 2.5 Flare или Sunburst: скорость, правки и цена

Начинайте с Flare: в сторонних замерах он на 25–45 % быстрее. Sunburst — для правок, где Flare не справился. При равных quality и size попытка стоит одинаково.

12 мин
Иллюстрация ChatGPT Images 2.5: создание картинки в диалоге, выбор модели в API и готовый файл
Руководства по API

ChatGPT Images 2.5: как пользоваться и где выбрать Sunburst или Flare

В ChatGPT Images 2.5 можно создавать и редактировать изображения прямо в диалоге. Объясняем, как начать работу, где выбрать Flare или Sunburst в API и почему по картинке в чате нельзя достоверно определить модель.

6 мин
Схема диагностики 429: приложение, шлюз и развёртывание Azure OpenAI с отдельными ограничениями TPM, RPM и выделенной квотой
Руководства по API

Лимиты TPM в Azure OpenAI: как найти причину ошибки 429

Ошибка 429 в Azure OpenAI возможна даже при небольшом расходе токенов. Разбираем, какие данные собрать, как отличить TPM от RPM и квоты шлюза и как проверить результат после изменения настроек.

8 мин