Anthropic SDK для Python: base_url, ключ и первый запрос через шлюз

Официальный пакет anthropic для Python нужно только направить на другой адрес. Разбираем base_url, ключ, стриминг, подсчёт токенов, кэш и ошибки.

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

Пакет anthropic — официальный клиент для Claude на Python, и чтобы работать с ним через шлюз, не нужно ничего переписывать: достаточно сменить адрес и ключ. Главная ловушка здесь одна: у Anthropic SDK base_url пишется без /v1, а у OpenAI SDK с /v1. На ней спотыкаются чаще всего. Ниже — рабочий код для Zukko: клиент, обычный и потоковый запрос, подсчёт токенов, кэш, обработка ошибок и таблица отличий от OpenAI SDK. Сам пакет и его возможности описаны в репозитории anthropics/anthropic-sdk-python и в документации Anthropic, а этот текст про подключение.

Anthropic python base url: установка и клиент

Установка стандартная. Документация SDK указывает, что нужен Python 3.10 или новее:

bash
pip install anthropic

Если вы обновляетесь с версии 0.x на 1.x, в репозитории есть руководство по миграции с перечнем несовместимых изменений. Сверьтесь с ним, прежде чем править старый код.

Создайте клиент и задайте адрес шлюза:

bash
import os
from anthropic import Anthropic

client = Anthropic(
    base_url="https://api.zukko.pro",
    api_key=os.environ["ZUKKO_API_KEY"],
)

message = client.messages.create(
    model="claude-sonnet-5-5",
    max_tokens=1024,
    messages=[{"role": "user", "content": "Объясни, что делает декоратор в Python"}],
)

for block in message.content:
    if block.type == "text":
        print(block.text)

Ключ fetch_... берётся в кабинете и кладётся в переменную ZUKKO_API_KEY. Передавайте его в api_key явно: тогда SDK не подставит случайно переменную окружения со старым официальным ключом. SDK шлёт ключ в заголовке x-api-key, шлюз принимает его наравне с Authorization: Bearer.

Почему без /v1

SDK сам достраивает путь /v1/messages. Если написать base_url="https://api.zukko.pro/v1", запрос уйдёт на /v1/v1/messages и вернётся 404. Это зеркальная ошибка к OpenAI SDK, где адрес должен заканчиваться на /v1. Общее правило для клиентов описано в справке по Python.

Адрес через переменные окружения

В документации SDK указано, что адрес можно задать переменной ANTHROPIC_BASE_URL. Удобно, когда один и тот же код работает с разными окружениями:

bash
export ANTHROPIC_BASE_URL=https://api.zukko.pro
export ZUKKO_API_KEY=fetch_ваш_ключ

Если вы вместо api_key предпочитаете схему с заголовком Authorization: Bearer, у SDK есть параметр auth_token и одноимённая переменная окружения. Точные имена проверьте в README вашей версии пакета.

Чем Anthropic SDK отличается от OpenAI SDK

anthropicopenai
base_urlhttps://api.zukko.prohttps://api.zukko.pro/v1
Вызовclient.messages.create(...)client.chat.completions.create(...)
max_tokensобязательныйнеобязательный
Системная инструкцияпараметр systemреплика с ролью system
Текст ответаmessage.content[0].textresponse.choices[0].message.content
Токеныusage.input_tokens, usage.output_tokensusage.prompt_tokens, usage.completion_tokens
Кэш Claudeвиден, есть скидкавесь вход по цене входа

Последняя строка таблицы существенна для бюджета: скидку за кэш у Claude шлюз применяет только в Anthropic-формате, потому что именно там видны чтение и запись кэша. Если ваш код ходит к Claude через openai, перенос на anthropic может заметно снизить счёт на повторяющихся контекстах. Подробнее в статье о кэше.

Стриминг

Для потока в Python удобнее помощник messages.stream, он собирает текст и итоговое сообщение:

bash
with client.messages.stream(
    model="claude-sonnet-5-5",
    max_tokens=1024,
    messages=[{"role": "user", "content": "Напиши функцию слияния двух отсортированных списков"}],
) as stream:
    for text in stream.text_stream:
        print(text, end="", flush=True)
    final = stream.get_final_message()

print()
print(final.usage)

Есть и вариант messages.create(..., stream=True): он отдаёт сырые события, а сборкой текста вам придётся заниматься самим.

Подсчёт токенов до запроса

bash
count = client.messages.count_tokens(
    model="claude-sonnet-5-5",
    messages=[{"role": "user", "content": "Привет, мир"}],
)
print(count.input_tokens)

Метод ходит на POST /v1/messages/count_tokens, подробности есть в справке по count tokens в документации Zukko. Он полезен, чтобы оценить стоимость длинного промпта заранее.

Кэш промптов с проверкой по usage

Если в каждом запросе повторяется большой префикс (инструкция, документация, правила), пометьте его cache_control:

bash
LONG_TEXT = open("rules.md", encoding="utf-8").read()

resp = client.messages.create(
    model="claude-sonnet-5-5",
    max_tokens=500,
    system=[
        {"type": "text", "text": LONG_TEXT, "cache_control": {"type": "ephemeral"}},
    ],
    messages=[{"role": "user", "content": "Проверь, не нарушает ли этот абзац правила"}],
)

u = resp.usage
print(u.input_tokens, u.cache_creation_input_tokens, u.cache_read_input_tokens, u.output_tokens)

В первом запросе заполнится cache_creation_input_tokens, в последующих cache_read_input_tokens. Расчёт по формуле из справки: свежий вход × цена входа + чтение кэша × цена чтения + запись кэша × цена записи + выход × цена выхода. Пример на claude-sonnet-5-5 (вход 0.4, чтение 0.05, запись 0.5, $ за 1M токенов, на 18 октября 2026) для префикса в 20 000 токенов и 50 запросов, если пауза между ними короче срока жизни кэша:

  • без кэша: 50 × 20 000 × 0.4 / 1 000 000 = $0.40;
  • с кэшем: запись 20 000 × 0.5 / 1 000 000 = $0.01, плюс 49 чтений 49 × 20 000 × 0.05 / 1 000 000 = $0.049, итого $0.059.

Учтены только токены префикса, реплики пользователя и ответы считаются отдельно. У Anthropic есть минимальный размер префикса, короче которого кэш не включается, и срок жизни кэша: уточняйте их в документации Anthropic, а фактический результат проверяйте по полям usage и списаниям в кабинете.

Ошибки и повторы

Исключения в SDK соответствуют статус-кодам:

bash
import anthropic

try:
    client.messages.create(
        model="claude-sonnet-5-5",
        max_tokens=100,
        messages=[{"role": "user", "content": "Привет"}],
    )
except anthropic.AuthenticationError:
    print("401: проверьте ключ")
except anthropic.NotFoundError:
    print("404: неизвестная модель или лишний /v1 в base_url")
except anthropic.RateLimitError:
    print("429: лимит ключа или ограничение провайдера")
except anthropic.APIStatusError as e:
    print(e.status_code, e.response.text)

В SDK 400 даёт BadRequestError, 401 AuthenticationError, 403 PermissionDeniedError, 404 NotFoundError, 413 RequestTooLargeError, 429 RateLimitError, 500 и выше InternalServerError, а ошибка соединения APIConnectionError. Код 402 (не хватает баланса) отдельного класса не имеет и ловится через общий APIStatusError. Ответ 503 с model_disabled (модель выключена) придёт как InternalServerError, и SDK по умолчанию повторит его: повторы тут не помогут, смените модель на одну из списка GET /v1/models.

По умолчанию SDK повторяет запрос дважды на ошибках соединения, 408, 409, 429 и 5xx. Параметр max_retries меняет это. Для 429 из-за дневного лимита ключа повторы не помогут: лимит нужно поднять в кабинете. Для отладки у каждого ответа есть _request_id, а заголовки доступны через client.messages.with_raw_response.create(...).

Асинхронный клиент

Для пакетной обработки есть AsyncAnthropic с тем же интерфейсом:

bash
import asyncio
from anthropic import AsyncAnthropic

client = AsyncAnthropic(base_url="https://api.zukko.pro", api_key="fetch_ваш_ключ")

async def main():
    msg = await client.messages.create(
        model="claude-sonnet-5-5",
        max_tokens=200,
        messages=[{"role": "user", "content": "Скажи привет"}],
    )
    print(msg.content[0].text)

asyncio.run(main())

Если запросов тысячи, посмотрите разбор асинхронной обработки. Создайте ключ в кабинете, пополните баланс от $1 (СБП с комиссией платёжной страницы 8,5% сверху, MasterCard или криптовалюта) и запустите первый пример.

Частые вопросы

Почему я получаю 404 от шлюза?

Чаще всего в base_url указан /v1. Для пакета anthropic адрес должен быть https://api.zukko.pro. Вторая причина — опечатка в имени модели; список имён даёт GET /v1/models.

Можно ли вызывать через anthropic не только Claude?

Шлюз позволяет звать любую модель из своего списка в любом формате, но поведение конкретных полей запроса (например, инструментов или рассуждений) у других поставщиков может отличаться. Сначала проверьте на простом запросе.

Нужен ли заголовок anthropic-version?

SDK ставит его сам. Если вы пишете запросы вручную через requests, добавьте anthropic-version: 2023-06-01.

Как переключиться с OpenAI SDK, не меняя весь код?

Перенесите вызов на messages.create, вынесите системную реплику в system, добавьте max_tokens и поправьте разбор ответа. Остальное в приложении можно оставить как есть.

zukko

Платите в 25×
меньше

за Claude Code & ChatGPT

Один ключ fetch_* и base URL api.zukko.pro — в Cursor, Claude Code, Codex и любом SDK.