Как подключить нейросеть к приложению: ключ, SDK, стриминг и ошибки

Пошаговая инструкция для разработчика: где хранить ключ, как вызвать модель из Python и Node.js, как правильно стримить ответ, какие ошибки обрабатывать и как считать расходы по каждому запросу.

6 мин · · разработка · API-ключи

«Как подключить нейросеть к приложению» — вопрос, с которого начинается почти любая интеграция: чат в личном кабинете, генерация описаний, разбор заявок. Сам вызов модели — десяток строк кода, а основная работа в деталях: где хранить ключ, как стримить ответ, что делать с ошибками и как не потерять контроль над расходами. Ниже — пошаговая схема на примере Zukko, с кодом на Python и Node.js.

Как подключить нейросеть к приложению: общая схема

браузер / мобильное приложение → ваш бэкенд → https://api.zukko.pro → модель

Главное правило: ключ живёт только на бэкенде. Если положить его в JavaScript на странице или в сборку мобильного приложения, его достанет любой желающий и потратит ваш баланс. Клиент обращается к вашему серверу, сервер проверяет пользователя, применяет свои ограничения и уже потом вызывает модель.

Шаг 1. Получите ключ

  1. Создайте ключ в кабинете на странице API-ключей. Полный ключ (fetch_...) показывается один раз — сразу сохраните его в менеджер секретов.
  2. Задайте ключу имя, дневной и месячный лимит в долларах и список разрешённых моделей (allowed_models).
  3. Заведите отдельные ключи для разработки и продакшена: утёкший или «сошедший с ума» тестовый ключ не затронет боевой.
  4. Пополните баланс: запрос стартует, только если на счёте есть хотя бы $0.01. Пополнение — от $1; из России доступны СБП, карта РФ и криптовалюта.

Шаг 2. Ключ в переменных окружения

bash
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):

bash
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-модули):

bash
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:

bash
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:

bash
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—неверный или отозванный ключпроверить ключ, не повторять
402insufficient_balanceне хватает балансапополнить, предупредить себя алертом
403model_not_allowed_for_keyмодели нет в списке ключаисправить конфиг или ключ
404model_not_foundнеизвестное имя моделисверить с GET /v1/models
413payload_too_largeслишком большой запросуменьшить вход
429key_daily_limit_reached / key_monthly_limit_reachedисчерпан лимит ключаждать сброса или поднять лимит
429код провайдераограничение у провайдера моделиповторить с паузой
503model_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 — такие случаи обрабатывайте в своём коде:

bash
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 пользователя:

bash
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.