OpenAI Agents SDK через наш /v1/responses
Agents SDK по умолчанию ходит в OpenAI. Меняем два параметра клиента — и агенты работают через наш шлюз на моделях из каталога.
4 мин · · Обновлено · агенты · разработка
OpenAI Agents SDK (пакет openai-agents для Python и его JS-аналог) построен поверх Responses API — того самого POST /v1/responses, который есть и у нас. Агент внутри библиотеки — это просто цикл вызовов client.responses.create() с добавлением tool calls и хендоффов между агентами. Если подменить клиента на кастомный base_url и ключ, весь этот цикл поедет через шлюз без переписывания логики агентов.
Минимальная настройка на Python
from agents import Agent, Runner, set_default_openai_client
from openai import AsyncOpenAI
client = AsyncOpenAI(
base_url="https://api.zukko.pro/v1",
api_key="fetch_ваш_ключ",
)
set_default_openai_client(client)
agent = Agent(
name="Support Triage",
instructions="Отвечай кратко, по-русски, эскалируй сложные случаи.",
model="gpt-5.6-sol",
)
result = Runner.run_sync(agent, "Клиент пишет, что не приходит счёт за март")
print(result.final_output)Два момента здесь решают всё: base_url заканчивается на /v1 (это OpenAI-совместимый путь), и set_default_openai_client подставляется до создания агента — если вызвать после, часть внутренних клиентов SDK успеет закэшироваться на дефолтный https://api.openai.com.
Модель можно назначать per-agent
SDK не проверяет имя модели заранее — оно летит в тело запроса как строка. Значит любая модель из нашего каталога подходит:
triage_agent = Agent(name="Triage", model="glm-5.3", instructions="...")
writer_agent = Agent(name="Writer", model="claude-sonnet-5", instructions="...")
vision_agent = Agent(name="Vision", model="claude-opus-5-5", instructions="...")Разные агенты в одном пайплайне спокойно используют разные модели — хендофф между ними не требует пересоздания клиента, потому что клиент один на всё приложение.
Tools и function calling
/v1/responses принимает tools в теле — SDK формирует их сам из декорированных Python-функций:
from agents import function_tool
@function_tool
def get_invoice_status(invoice_id: str) -> str:
"""Возвращает статус счёта по его ID."""
return db.lookup(invoice_id)
agent = Agent(
name="Billing",
model="gpt-5.6-sol",
tools=[get_invoice_status],
instructions="Используй get_invoice_status для проверки счетов.",
)Формат вызова функций в Responses API совместим с тем, что генерируют текстовые модели через шлюз — не только GPT-линейка, но и GLM, и Claude, поскольку у нас /v1/responses — это единая точка входа независимо от провайдера модели.
Streaming и хендоффы
result = Runner.run_streamed(agent, "Составь отчёт по трём последним счетам")
async for event in result.stream_events():
if event.type == "raw_response_event":
print(event.data, end="", flush=True)Стриминг работает так же, как с официальным OpenAI, потому что протокол ответа не меняется — меняется только адрес, куда летит запрос. Хендоффы между агентами (handoffs=[billing_agent, support_agent]) тоже не требуют доработок: SDK сам решает, кому передать разговор, основываясь на инструкциях агента, а не на том, кто хостит модель.
Guardrails и контекст между агентами
SDK поддерживает input_guardrails и output_guardrails — функции-проверки, которые запускаются до или после основного вызова модели и могут прервать выполнение раньше, чем запрос вообще уйдёт на /v1/responses:
from agents import input_guardrail, GuardrailFunctionOutput
@input_guardrail
async def block_pii(ctx, agent, input_text):
contains_card = "4111 1111" in input_text
return GuardrailFunctionOutput(
output_info={"blocked": contains_card},
tripwire_triggered=contains_card,
)
agent = Agent(
name="Billing",
model="claude-sonnet-5",
input_guardrails=[block_pii],
)Это полезно не только для безопасности, но и для экономии: guardrail, сработавший на входе, останавливает цепочку до того, как модель что-либо посчитает, а значит и до списания. Контекст между шагами одного Runner.run() SDK хранит сам, передавать историю сообщений вручную не нужно — она автоматически попадает в следующий вызов /v1/responses внутри той же сессии.
Идемпотентность в многошаговых цепочках
Агентные пайплайны часто ретраят шаг при таймауте сети — и без защиты это дублирует списание. Если оборачиваете вызовы своей обвязкой поверх retry-логики, добавьте Idempotency-Key через extra_headers в конкретном вызове .create() — уникальный для каждого логического запроса и одинаковый при его повторах. Не кладите его в default_headers клиента: клиент один на всё приложение, и уже второй запрос с другим телом получит 422 idempotency_conflict.
import uuid
from openai import APITimeoutError
idem_key = str(uuid.uuid4()) # один на логический запрос; при повторе — тот же
for attempt in range(3):
try:
resp = await client.responses.create(
model="gpt-5.6-sol",
input="Составь отчёт по трём последним счетам",
extra_headers={"Idempotency-Key": idem_key},
)
break
except APITimeoutError:
continueПовтор с тем же ключом в течение 24 часов вернёт тот же ответ без повторного списания — полезно, если шаг агента упал по таймауту уже после того, как модель отработала.
Трассировка и балансовые заголовки
Встроенный трейсинг Agents SDK по умолчанию пытается отправить трассы на platform.openai.com — это отдельный, не связанный со шлюзом сервис, и он не заработает без официального OpenAI-ключа. Если он не нужен, отключите его явно:
from agents import set_tracing_disabled
set_tracing_disabled(True)Иначе в логах будут постоянные 401 от трейсинга — они не имеют отношения к работе самих агентов через /v1/responses, и это легко перепутать с реальной проблемой шлюза.
Для отладки стоимости смотрите заголовки ответа X-Zukko-Cost-Micros (цена конкретного вызова в микродолларах) и X-Fetch-Balance-Cents (остаток после списания — несмотря на название, тоже в микродолларах, 1 000 000 = $1) — они приходят на каждый нестриминговый вызов responses.create(), включая промежуточные шаги внутри одного Runner.run(). У потоковых ответов (Runner.run_streamed, stream: true) этих заголовков нет — расход там смотрите в кабинете, GET /v1/usage или GET /v1/balance.
Частые ошибки
404 на каждом вызове — забыт /v1 в base_url, или наоборот, добавлен лишний (Agents SDK сам не подставляет путь, значит указывать нужно ровно https://api.zukko.pro/v1).
Модель отвечает, но хендофф не срабатывает — это не проблема шлюза, а логики самого SDK: хендофф зависит от того, как модель формулирует tool call, а не от того, какой провайдер модель обслуживает.
402 посреди цепочки из нескольких агентов — баланс проверяется на каждом отдельном вызове /v1/responses, значит агент может успеть отработать три шага из пяти и упасть на четвёртом. Проверить остаток заранее: GET /v1/balance с тем же ключом.
Список моделей и их актуальные лимиты — без ключа, на GET /v1/models/info.
zukko
Платите в 25×
меньше
за Claude Code & ChatGPT
Один ключ fetch_* и base URL api.zukko.pro — в Cursor, Claude Code, Codex и любом SDK.
Похожие статьи
- Промпт-инъекция: что это и как защитить бота и агентаОбъясняем, как работает промпт-инъекция через пользовательский ввод и чужие документы, и какие меры снижают ущерб: изоляция данных, права инструментов, лимиты ключа.
- Claude Code YOLO: режимы разрешений и работа без подтвержденийОтдельного режима YOLO в Claude Code нет, зато есть шесть режимов разрешений и гибкие правила. Разбираем, как перестать нажимать «Да» и не лишиться файлов.
- Claude Code: чек-лист лучших практик для ежедневной работыПривычки, которые делают работу с Claude Code предсказуемой: короткий CLAUDE.md, план перед правками, мелкие задачи, проверка результата и контроль расхода.
- MCP-сервер на Java: минимальный пример с одним инструментомПишем минимальный MCP-сервер на Java с одним инструментом, регистрируем его в Claude Code и учимся находить причину, если агент его не видит.