Python
Отдельная библиотека для шлюза не нужна: он совместим по протоколу, поэтому работают официальные openai и anthropic. Меняется один параметр конструктора.
Установка и клиент#
pip install openai
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)Стрим#
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. Подробнее о порядке кусков — в стриминге.
Инструменты#
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),
})Цикл, а не одна итерация: модель может позвать инструмент несколько раз подряд. Разбор формата — в инструментах.
Картинка во входе#
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}"}},
],
}],
)Ограничения и форматы — в картинках.
Ошибки#
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#
pip install anthropic
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.
Асинхронно и пачками#
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#
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 — он проверку не делает.