Краткий ответ
Создайте клиент с двумя аргументами, base_url и api_key, и продолжайте использовать те же методы, которые вы уже знаете. Базовый URL для этого API — https://api.apicompatible.com/v1, идентификатор модели — uncensored, а аутентификация представляет собой стандартный Bearer-токен, который SDK формирует из вашего ключа.
from openai import OpenAI
client = OpenAI(
base_url="https://api.apicompatible.com/v1",
api_key="YOUR_API_KEY",
)
resp = client.chat.completions.create(
model="uncensored",
messages=[
{"role": "system", "content": "You are a concise assistant."},
{"role": "user", "content": "Name three uses for a hash map."},
],
)
print(resp.choices[0].message.content)
print(resp.usage)
Это всё. SDK добавляет к базовому URL /chat/completions, поэтому запрос попадает на POST /v1/chat/completions. Если у вас ещё нет ключа, на странице API-ключ описаны регистрация и пробный баланс.
Установка базового URL с помощью переменных окружения
Официальный SDK Python считывает OPENAI_API_KEY и OPENAI_BASE_URL из окружения, если вы не передаёте их в коде. Это полезно, когда одно и то же приложение работает с разными эндпоинтами в среде разработки и на продакшене, или когда сторонний инструмент создаёт клиент за вас и не даёт возможности редактировать аргументы конструктора.
export OPENAI_BASE_URL="https://api.apicompatible.com/v1"
export OPENAI_API_KEY="YOUR_API_KEY"
python - <<'EOF'
from openai import OpenAI
client = OpenAI()
r = client.chat.completions.create(
model="uncensored",
messages=[{"role": "user", "content": "Say hello in five words."}],
)
print(r.choices[0].message.content)
EOF
Явные аргументы имеют приоритет над переменными окружения. Если запрос идёт на неверный хост, проверьте, не создаёт ли какая-то другая часть вашего кода клиент со своим base_url, и загружается ли файл .env до того, как ваш скрипт прочитает переменные.
Храните ключ вне системы контроля версий. Ключ в переменной окружения или менеджере секретов легко заменить; ключ в закоммиченном файле — нет.
То же изменение в Node
Пакет Node использует верблюжий регистр для той же опции. Всё остальное, включая имена методов и структуру ответа, совпадает с тем, что вы написали бы для любого эндпоинта формата OpenAI.
import OpenAI from "openai";
const client = new OpenAI({
baseURL: "https://api.apicompatible.com/v1",
apiKey: process.env.API_KEY,
});
const completion = await client.chat.completions.create({
model: "uncensored",
messages: [{ role: "user", content: "Give me a one-line commit message for a typo fix." }],
});
console.log(completion.choices[0].message.content);
Для проверки через чистый HTTP без SDK этот вызов curl отправляет тот же запрос и удобен для разделения проблем SDK и проблем с ключом:
curl https://api.apicompatible.com/v1/chat/completions \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"model":"uncensored","messages":[{"role":"user","content":"ping"}]}'
Потоковая передача, асинхронность и повторные попытки
Потоковая передача использует серверные события, а SDK преобразует их в итератор. Передайте stream=True и считывайте дельты по мере их поступления. Данные об использовании поступают в конце потока, поэтому учитывайте чанки, не содержащие текста.
from openai import OpenAI
client = OpenAI(
base_url="https://api.apicompatible.com/v1",
api_key="YOUR_API_KEY",
)
stream = client.chat.completions.create(
model="uncensored",
messages=[{"role": "user", "content": "Explain idempotency in two sentences."}],
stream=True,
)
for chunk in stream:
if chunk.choices and chunk.choices[0].delta.content:
print(chunk.choices[0].delta.content, end="", flush=True)
print()
Асинхронный клиент принимает те же два аргумента. Замените OpenAI на AsyncOpenAI, добавьте await и используйте async for для потоков.
Важны два лимита, когда вы выполняете более одного запроса одновременно. Полный ключ позволяет 8 параллельных запросов и 300 запросов в минуту, а пробный ключ — 2 и 60 соответственно. Потоковый запрос удерживает слот параллельности до завершения потока, поэтому застрявший потребитель, который не считывает поток, тратит слот. При превышении лимита вы получаете HTTP 429 с кодом ошибки rate_limited или concurrency. SDK по умолчанию повторяет неудачные запросы несколько раз, и это можно изменить с помощью параметра max_retries в клиенте. Заголовок Retry-After: 5 при ответе 429 означает временную перегрузку, поэтому правильный ответ — подождать пять секунд.
Таймауты, повторное использование клиента и отладка отправленных данных
Создайте клиент один раз и используйте его повторно. Каждый клиент хранит свой собственный пул соединений, поэтому создание нового для каждого запроса тратит время и усложняет понимание кода потоковой передачи. Оба параметра ниже задаются в конструкторе и применяются ко всем вызовам, сделанным через этот клиент.
from openai import OpenAI
client = OpenAI(
base_url="https://api.apicompatible.com/v1",
api_key="YOUR_API_KEY",
timeout=60.0,
max_retries=2,
)
Длинные генерации могут занимать много времени, поэтому очень короткий таймаут приводит к ошибкам, похожим на сбои сервиса. Выберите таймаут, соответствующий самому долгому ожидаемому ответу, и увеличьте его для больших объёмов вывода.
Когда вы не можете определить, какой URL или ключ использовался для запроса, включите отладочный лог SDK. В последних версиях Python-пакета читается переменная окружения OPENAI_LOG, а её установка в значение debug выводит строку запроса и статус ответа. Проверьте текущую документацию SDK для точного поведения в вашей версии и удалите переменную после этого, так как логи могут содержать конфиденциальные данные. Выведенный хост должен быть api.apicompatible.com. Если отображается что-то другое, другой экземпляр клиента или переменная окружения переопределяет вашу настройку.
Последняя привычка, экономящая время: храните базовый URL, ключ и идентификатор модели в одном модуле конфигурации. Когда все три параметра находятся в одном месте, переключение эндпоинтов становится одним редактированием и одной проверкой.
Ошибки, ломающие первый вызов
Большинство неудачных первых вызовов вызваны коротким списком причин. Пройдитесь по нему по порядку, прежде чем подозревать сервис.
В базовом URL отсутствует /v1 или есть лишний путь
SDK добавляет только имя эндпоинта, поэтому базовый URL должен заканчиваться на /v1. Использование https://api.apicompatible.com без сегмента версии или вставка полного пути /v1/chat/completions обычно приводит к ошибке «не найдено», так как конечный URL получается неверным. Выведите client.base_url, если не уверены, какой URL разрешил SDK.
Идентификатор модели всё ещё — имя от другого провайдера
Это API обслуживает одну модель, и её идентификатор — uncensored. Оставшееся имя модели от предыдущей настройки — частая причина ошибок, и его легко пропустить, когда имя находится в файле конфигурации, а не в коде. Ищите в проекте старую строку.
Ключ получен из неправильного места
Ключ от другого провайдера здесь не сработает, и наоборот. Если вы установите и OPENAI_API_KEY, и явный api_key, будет использовано явное значение. Проверьте, какой именно ключ дошёл до клиента.
Вы вызываете эндпоинт, который этот API не предлагает
API работает только с текстом. Он предоставляет /v1/chat/completions, /v1/responses и /v1/models. Нет эндпоинтов для изображений, аудио или эмбеддингов, нет дообучения, поэтому пути кода, использующие их, должны оставаться у оригинального провайдера. Анализ стоимости и компромиссов охватывает, когда такое разделение оправдано.
Тело запроса слишком велико
Максимальный размер тела запроса — 8 МБ. Длинные контексты в порядке, до 100 000 токенов всего для промпта и завершения, но base64-блоки или большие вставленные файлы могут превысить лимит тела раньше.
Проверка соединения и вашего баланса
Перед отладкой кода приложения вызовите GET /v1/models. Чистый список означает, что ключ и URL верны. Каждый ответ также содержит заголовок X-Balance-USD, который позволяет отслеживать расходы без отдельного вызова. В Python SDK используйте with_raw_response для чтения заголовков:
from openai import OpenAI
client = OpenAI(
base_url="https://api.apicompatible.com/v1",
api_key="YOUR_API_KEY",
)
raw = client.chat.completions.with_raw_response.create(
model="uncensored",
messages=[{"role": "user", "content": "ping"}],
)
print(raw.headers.get("X-Balance-USD"))
print(raw.headers.get("X-Request-Id"))
completion = raw.parse()
print(completion.choices[0].message.content)
Сохраняйте значение X-Request-Id при обращении в поддержку, так как оно идентифицирует точный запрос. Неудачные или отклонённые запросы не тарифицируются, поэтому плохой вызов ничего не стоит. Тарификация — $0.15 за 1M входных токенов и $0.50 за 1M выходных токенов из предоплаченного баланса; на странице цен есть детали, а в документации перечислены все параметры.
