SDK · Python

Python

Отдельная библиотека для шлюза не нужна: он совместим по протоколу, поэтому работают официальные openai и anthropic. Меняется один параметр конструктора.

Установка и клиент#

bash
pip install openai
python
import os
from openai import OpenAI

client = OpenAI(
    base_url="https://nirastudio.org/v1",
    api_key=os.environ["NIRA_API_KEY"],
)

resp = client.chat.completions.create(
    model="anthropic/claude-sonnet-4.6",
    messages=[{"role": "user", "content": "Привет"}],
)
print(resp.choices[0].message.content)
Ключ читайте из окружения. Строка в исходнике рано или поздно уезжает в репозиторий — и заметно это становится по чужим списаниям, а не по коду.

Стрим#

python
stream = client.chat.completions.create(
    model="anthropic/claude-sonnet-4.6",
    messages=[{"role": "user", "content": "Расскажи про кэш процессора"}],
    stream=True,
    stream_options={"include_usage": True},
)

usage = None
for chunk in stream:
    if chunk.usage:
        usage = chunk.usage
    if not chunk.choices:        # у куска с usage choices пустой
        continue
    piece = chunk.choices[0].delta.content
    if piece:
        print(piece, end="", flush=True)

print(f"\n\nтокенов: {usage.total_tokens if usage else '—'}")

Проверка if not chunk.choices обязательна — иначе последний кусок уронит цикл по IndexError. Подробнее о порядке кусков — в стриминге.

Инструменты#

python
import json

TOOLS = [{
    "type": "function",
    "function": {
        "name": "get_weather",
        "description": "Текущая погода в городе",
        "parameters": {
            "type": "object",
            "properties": {"city": {"type": "string"}},
            "required": ["city"],
        },
    },
}]

def get_weather(city: str) -> str:
    return f"{city}: -3 °C, снег"

messages = [{"role": "user", "content": "Погода в Москве?"}]

while True:
    resp = client.chat.completions.create(
        model="anthropic/claude-sonnet-4.6",
        messages=messages,
        tools=TOOLS,
    )
    msg = resp.choices[0].message
    messages.append(msg)                     # вызов должен остаться в истории

    if not msg.tool_calls:
        print(msg.content)
        break

    for call in msg.tool_calls:
        args = json.loads(call.function.arguments)
        messages.append({
            "role": "tool",
            "tool_call_id": call.id,
            "content": get_weather(**args),
        })

Цикл, а не одна итерация: модель может позвать инструмент несколько раз подряд. Разбор формата — в инструментах.

Картинка во входе#

python
import base64

b64 = base64.b64encode(open("screenshot.png", "rb").read()).decode()

resp = client.chat.completions.create(
    model="anthropic/claude-sonnet-4.6",
    messages=[{
        "role": "user",
        "content": [
            {"type": "text", "text": "Что за ошибка на скриншоте?"},
            {"type": "image_url",
             "image_url": {"url": f"data:image/png;base64,{b64}"}},
        ],
    }],
)

Ограничения и форматы — в картинках.

Ошибки#

python
from openai import APIStatusError, APIConnectionError

try:
    resp = client.chat.completions.create(...)
except APIStatusError as e:
    if e.status_code == 402:
        print("баланс кончился")
    elif e.status_code == 429:
        print("дневной лимит ключа")
    else:
        print(e.status_code, e.response.text)
except APIConnectionError:
    print("сеть недоступна — можно повторить")

Повторять стоит 429, 502 и сетевые обрывы. Библиотека делает это сама: OpenAI(max_retries=3). Полный список кодов — в ошибках.

Библиотека anthropic#

bash
pip install anthropic
python
import os
from anthropic import Anthropic

client = Anthropic(base_url="https://nirastudio.org", api_key=os.environ["NIRA_API_KEY"])

with client.messages.stream(
    model="anthropic/claude-sonnet-4.6",
    max_tokens=1024,
    messages=[{"role": "user", "content": "Привет"}],
) as stream:
    for text in stream.text_stream:
        print(text, end="", flush=True)

Адрес здесь без /v1: библиотека дописывает путь сама. Поля этого протокола — в Anthropic Messages.

Асинхронно и пачками#

python
import asyncio
from openai import AsyncOpenAI

client = AsyncOpenAI(base_url="https://nirastudio.org/v1", api_key=os.environ["NIRA_API_KEY"])

async def classify(text: str) -> str:
    resp = await client.chat.completions.create(
        model="anthropic/claude-haiku-4.5",
        messages=[
            {"role": "system", "content": "Ответь одним словом: жалоба, вопрос или отзыв."},
            {"role": "user", "content": text},
        ],
    )
    return resp.choices[0].message.content.strip()

async def main(items: list[str]) -> list[str]:
    sem = asyncio.Semaphore(8)               # без предела упрётесь в лимит ключа

    async def one(t: str) -> str:
        async with sem:
            return await classify(t)

    return await asyncio.gather(*(one(t) for t in items))
  • Семафор нужен: сотня одновременных запросов быстро выберет дневной лимит и вернёт 429.
  • На массовых задачах берите быструю модель — разница в цене со старшей на порядок, а на классификации качество почти то же.

LangChain и LlamaIndex#

python
from langchain_openai import ChatOpenAI

llm = ChatOpenAI(
    base_url="https://nirastudio.org/v1",
    api_key=os.environ["NIRA_API_KEY"],
    model="anthropic/claude-sonnet-4.6",
)

Любой каркас, который принимает base_url для OpenAI, работает со шлюзом. У LlamaIndex это OpenAILike с теми же аргументами.

Часть каркасов сама проверяет имя модели по своему списку и отказывается от незнакомого. В таких случаях ищите класс с приставкой Like или Custom — он проверку не делает.