Как подключить нейросеть к приложению: ключ, SDK, стриминг и ошибки
Пошаговая инструкция для разработчика: где хранить ключ, как вызвать модель из Python и Node.js, как правильно стримить ответ, какие ошибки обрабатывать и как считать расходы по каждому запросу.
6 мин · · разработка · API-ключи
«Как подключить нейросеть к приложению» — вопрос, с которого начинается почти любая интеграция: чат в личном кабинете, генерация описаний, разбор заявок. Сам вызов модели — десяток строк кода, а основная работа в деталях: где хранить ключ, как стримить ответ, что делать с ошибками и как не потерять контроль над расходами. Ниже — пошаговая схема на примере Zukko, с кодом на Python и Node.js.
Как подключить нейросеть к приложению: общая схема
браузер / мобильное приложение → ваш бэкенд → https://api.zukko.pro → модель
Главное правило: ключ живёт только на бэкенде. Если положить его в JavaScript на странице или в сборку мобильного приложения, его достанет любой желающий и потратит ваш баланс. Клиент обращается к вашему серверу, сервер проверяет пользователя, применяет свои ограничения и уже потом вызывает модель.
Шаг 1. Получите ключ
- Создайте ключ в кабинете на странице API-ключей. Полный ключ (
fetch_...) показывается один раз — сразу сохраните его в менеджер секретов. - Задайте ключу имя, дневной и месячный лимит в долларах и список разрешённых моделей (
allowed_models). - Заведите отдельные ключи для разработки и продакшена: утёкший или «сошедший с ума» тестовый ключ не затронет боевой.
- Пополните баланс: запрос стартует, только если на счёте есть хотя бы $0.01. Пополнение — от $1; из России доступны СБП, карта РФ и криптовалюта.
Шаг 2. Ключ в переменных окружения
export ZUKKO_API_KEY="fetch_ваш_ключ"В Docker и CI передавайте его как секрет, не как часть образа или репозитория. Если используете официальный Python SDK OpenAI, можно задать OPENAI_BASE_URL=https://api.zukko.pro/v1 и OPENAI_API_KEY, и клиент подхватит их сам.
Шаг 3. Первый запрос через SDK
Zukko работает с официальными SDK: меняются только адрес и ключ. Для OpenAI SDK адрес https://api.zukko.pro/v1, для Anthropic SDK — https://api.zukko.pro (без /v1, SDK допишет его сам). Любую модель можно вызвать в любом из двух форматов.
Python (pip install openai):
import os
from openai import OpenAI
client = OpenAI(base_url="https://api.zukko.pro/v1", api_key=os.environ["ZUKKO_API_KEY"])
resp = client.chat.completions.create(
model="gpt-6.1-sol",
messages=[
{"role": "system", "content": "Ты помощник интернет-магазина. Отвечай кратко."},
{"role": "user", "content": "Как оформить возврат?"},
],
max_tokens=500,
)
print(resp.choices[0].message.content)Node.js (npm install openai, ES-модули):
import OpenAI from "openai";
const client = new OpenAI({
baseURL: "https://api.zukko.pro/v1",
apiKey: process.env.ZUKKO_API_KEY,
});
const resp = await client.chat.completions.create({
model: "gpt-6.1-sol",
messages: [{ role: "user", content: "Как оформить возврат?" }],
max_tokens: 500,
});
console.log(resp.choices[0].message.content);Список моделей — GET https://api.zukko.pro/v1/models (ключ не нужен). Держите имя модели в конфиге, чтобы менять его без деплоя кода.
Шаг 4. Стриминг ответа
Для чатов включайте stream: true — пользователь видит текст по мере генерации. В OpenAI-формате Zukko сам добавляет stream_options.include_usage, поэтому последний фрагмент потока приходит с usage и пустым choices. Код, который читает chunk.choices[0] без проверки, на нём упадёт.
Python:
stream = client.chat.completions.create(
model="gpt-6.1-sol",
messages=[{"role": "user", "content": "Расскажи про условия доставки"}],
max_tokens=500,
stream=True,
)
for chunk in stream:
if chunk.choices:
print(chunk.choices[0].delta.content or "", end="", flush=True)
elif chunk.usage:
print(f"\n[вход {chunk.usage.prompt_tokens}, выход {chunk.usage.completion_tokens}]")Node.js:
const stream = await client.chat.completions.create({
model: "gpt-6.1-sol",
messages: [{ role: "user", content: "Расскажи про условия доставки" }],
max_tokens: 500,
stream: true,
});
for await (const chunk of stream) {
if (chunk.choices.length > 0) {
process.stdout.write(chunk.choices[0].delta?.content ?? "");
} else if (chunk.usage) {
console.log("\nтокены:", chunk.usage.prompt_tokens, chunk.usage.completion_tokens);
}
}Два факта о стриминге из документации Zukko:
- Статус 200 отправляется до начала ответа модели. Если ошибка случилась позже, она придёт внутри потока событием с полем
error— проверяйте события, а не только HTTP-статус. - Если клиент оборвал соединение, Zukko дочитывает ответ модели до конца и списывает всё сгенерированное. Ограничивайте расход через
max_tokens, а не обрывом потока.
Шаг 5. Обработка ошибок
Zukko отвечает ошибками в формате OpenAI: HTTP-статус и JSON с error.code. Разбирайте ошибку по статусу и коду, а не по тексту.
| Статус | Код | Что значит | Что делать |
|---|---|---|---|
| 401 | — | неверный или отозванный ключ | проверить ключ, не повторять |
| 402 | insufficient_balance | не хватает баланса | пополнить, предупредить себя алертом |
| 403 | model_not_allowed_for_key | модели нет в списке ключа | исправить конфиг или ключ |
| 404 | model_not_found | неизвестное имя модели | сверить с GET /v1/models |
| 413 | payload_too_large | слишком большой запрос | уменьшить вход |
| 429 | key_daily_limit_reached / key_monthly_limit_reached | исчерпан лимит ключа | ждать сброса или поднять лимит |
| 429 | код провайдера | ограничение у провайдера модели | повторить с паузой |
| 503 | model_disabled | модель выключена | взять другую из GET /v1/models |
| 503 | другой код | провайдер недоступен | повторить с паузой или сменить модель |
| 529 | код провайдера | провайдер модели перегружен, статус проброшен шлюзом | как 503: повторить с паузой или сменить модель |
Своего общего rate limit у Zukko нет: 429 от Zukko — только лимиты ключа, которые вы задали сами. Почему перегрузка провайдера может прийти и как 529, и как 503, — в статье ошибка 529 overloaded у Claude.
Официальные SDK OpenAI по умолчанию сами повторяют запрос 2 раза при ошибках соединения, 429 и 5xx (настраивается параметром max_retries в Python и maxRetries в Node.js). Повторы не помогут при исчерпанном лимите ключа и при model_disabled — такие случаи обрабатывайте в своём коде:
import openai
FALLBACK_MODEL = "deepseek-v4.1-flash"
def ask(messages, model="gpt-6.1-sol"):
try:
resp = client.chat.completions.create(model=model, messages=messages, max_tokens=500)
return resp.choices[0].message.content
except openai.APIStatusError as e:
if e.status_code == 503 and e.code == "model_disabled" and model != FALLBACK_MODEL:
return ask(messages, FALLBACK_MODEL)
if e.status_code == 402:
raise RuntimeError("Не хватает баланса Zukko") from e
if e.status_code == 429 and e.code in ("key_daily_limit_reached", "key_monthly_limit_reached"):
raise RuntimeError("Исчерпан лимит API-ключа") from e
raiseВ Node.js те же поля доступны как err.status и err.code у OpenAI.APIError. Подробная схема повторов и запасной модели — в статье обработка ошибок LLM API.
Шаг 6. Лимиты
- Лимиты ключа в Zukko мягкие: перед запросом сравнивается уже списанная сумма с потолком. Последний пропущенный запрос может немного превысить лимит, а параллельные запросы, начатые до его достижения, пройдут. Дневной расход обнуляется в 00:00 UTC, месячный — в начале месяца по UTC. Подробнее — в документации о лимитах ключей и кэше.
- Свои лимиты на пользователя делайте в приложении: число запросов в минуту, максимальная длина сообщения, дневной бюджет на аккаунт. Ключ один на всё приложение, и без этого один активный пользователь может съесть лимит за всех.
max_tokensв каждом запросе — простой способ ограничить стоимость одного ответа.
Шаг 7. Учёт расходов
Для обычных (не потоковых) ответов Zukko возвращает заголовок X-Zukko-Cost-Micros — стоимость запроса в микродолларах. Его удобно писать в базу рядом с id пользователя:
raw = client.chat.completions.with_raw_response.create(
model="gpt-6.1-sol",
messages=[{"role": "user", "content": "Привет!"}],
max_tokens=200,
)
resp = raw.parse()
cost_micros = int(raw.headers.get("x-zukko-cost-micros", "0"))
print(resp.choices[0].message.content, cost_micros / 1_000_000, "$")В Node.js заголовок доступен через .withResponse(): response.headers.get("x-zukko-cost-micros"). У потоковых ответов этого заголовка нет — считайте по usage из последнего фрагмента и ценам модели или смотрите историю в кабинете и через GET /v1/usage.
Сколько это стоит. Цены на 7 октября 2026, $ за 1M токенов. Приложение делает 10 000 запросов в месяц, в среднем 1 500 токенов входа и 400 выхода. Формула: 10 000 × (1 500 × вход + 400 × выход) / 1 000 000.
gpt-6.1-sol($0.27 / $1.32): 10 000 × (405 + 528) / 1 000 000 = $9.33 в месяц. Один такой запрос без кэша — 1 500 × 0.27 + 400 × 1.32 = 933 микродоллара; с этим числом удобно сверить заголовокX-Zukko-Cost-Micros.deepseek-v4.1-flash($0.013 / $0.038): 10 000 × (19.5 + 15.2) / 1 000 000 ≈ $0.35 в месяц.
Если в промпте длинная повторяющаяся инструкция, учитывайте кэш: у GPT чтение кэша дешевле входа и видно в prompt_tokens_details.cached_tokens, а для Claude скидка за кэш работает только в Anthropic-формате. Актуальный список и цены — GET https://api.zukko.pro/v1/models или страница «Модели» в кабинете. Больше примеров кода — в документации по Python.
Частые вопросы
Можно ли вызывать API прямо из браузера или мобильного приложения?
Не стоит: ключ окажется у пользователей. Делайте вызовы с бэкенда, а клиенту отдавайте уже готовый ответ или поток.
Какой SDK выбрать — OpenAI или Anthropic?
Для большинства приложений проще OpenAI SDK: через него доступны все модели. Anthropic SDK имеет смысл, если вы работаете с Claude и хотите скидку за кэш длинного системного промпта.
Почему стриминг падает с IndexError в конце?
Последний фрагмент потока приходит с usage и пустым choices. Проверяйте if chunk.choices: (в Node.js — chunk.choices.length > 0) перед обращением к chunk.choices[0].
Как узнать стоимость потокового ответа?
Заголовка X-Zukko-Cost-Micros у потоков нет. Возьмите usage из последнего фрагмента и умножьте на цены модели либо посмотрите запрос в истории расходов в кабинете или через GET /v1/usage.
zukko
Платите в 25×
меньше
за Claude Code & ChatGPT
Один ключ fetch_* и base URL api.zukko.pro — в Cursor, Claude Code, Codex и любом SDK.
Похожие статьи
- Свой прокси к нейросети на Node.js: не показывать API-ключ во фронтендеНебольшой сервер на Express между вашим фронтендом и API нейросети: ключ остаётся на сервере, а модели, длина запроса и частота ограничены.
- DeepSeek API на Python: запросы, стриминг, JSON и обработка ошибокГотовый код для DeepSeek V4.1 Flash и V4 Pro на OpenAI SDK: запрос, стриминг, разбор JSON и аккуратная обработка ошибок.
- Gemini API на Python: запросы через OpenAI SDK и OpenAI-форматРабочий пример Gemini API на Python без отдельной библиотеки: меняем base_url в OpenAI SDK, добавляем стриминг и JSON и сравниваем с google-genai.
- Anthropic SDK для Python: base_url, ключ и первый запрос через шлюзОфициальный пакет anthropic для Python нужно только направить на другой адрес. Разбираем base_url, ключ, стриминг, подсчёт токенов, кэш и ошибки.