OpenAI Agents SDK over our /v1/responses endpoint

The Agents SDK talks to OpenAI by default. Two client settings redirect it through our gateway onto the models in our catalog.

4 min · · Updated · агенты · development

The OpenAI Agents SDK (the openai-agents package for Python, and its JS counterpart) is built on top of the Responses API — the same POST /v1/responses our gateway exposes. An "agent" inside the library is really just a loop of client.responses.create() calls with tool calls and handoffs layered on top. Swap the underlying client for a custom base_url and key, and the whole loop routes through the gateway without touching any agent logic.

Minimal Python setup

bash
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_your_key",
)
set_default_openai_client(client)

agent = Agent(
    name="Support Triage",
    instructions="Answer concisely, escalate complex cases.",
    model="gpt-5.6-sol",
)

result = Runner.run_sync(agent, "A customer says their March invoice never arrived")
print(result.final_output)

Two things matter here: base_url ends in /v1 (that's the OpenAI-compatible path), and set_default_openai_client runs before any agent is created — call it after, and part of the SDK's internal clients may already be cached against the default https://api.openai.com.

Model selection is per-agent

The SDK doesn't validate model names ahead of time — the string just goes straight into the request body. That means any model in our catalog works:

bash
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="...")

Different agents in the same pipeline can freely use different models — a handoff between them doesn't need a new client, because there's exactly one client for the whole app.

Tools and function calling

/v1/responses accepts tools in the request body — the SDK builds them automatically from decorated Python functions:

bash
from agents import function_tool

@function_tool
def get_invoice_status(invoice_id: str) -> str:
    """Returns the status of an invoice by its ID."""
    return db.lookup(invoice_id)

agent = Agent(
    name="Billing",
    model="gpt-5.6-sol",
    tools=[get_invoice_status],
    instructions="Use get_invoice_status to check invoices.",
)

The function-calling format in the Responses API is compatible with what our text models generate through the gateway regardless of vendor — GPT, GLM, and Claude alike — because /v1/responses is a single entry point independent of which provider actually serves the model.

Streaming and handoffs

bash
result = Runner.run_streamed(agent, "Draft a report on the last three invoices")
async for event in result.stream_events():
    if event.type == "raw_response_event":
        print(event.data, end="", flush=True)

Streaming behaves exactly like it does against official OpenAI, because the response protocol doesn't change — only the destination address does. Handoffs between agents (handoffs=[billing_agent, support_agent]) need no extra work either: the SDK decides who takes over the conversation based on agent instructions, not on who hosts the model.

Idempotency across multi-step chains

Agent pipelines often retry a step after a network timeout — and without protection that duplicates the charge. If you wrap calls in your own retry logic, add an Idempotency-Key through extra_headers on the specific .create() call — unique per logical request and identical across its retries. Don't put it in the client's default_headers: there's one client for the whole app, and the very next request with a different body gets 422 idempotency_conflict.

bash
import uuid
from openai import APITimeoutError

idem_key = str(uuid.uuid4())  # one per logical request; the same on retry
for attempt in range(3):
    try:
        resp = await client.responses.create(
            model="gpt-5.6-sol",
            input="Draft a report on the last three invoices",
            extra_headers={"Idempotency-Key": idem_key},
        )
        break
    except APITimeoutError:
        continue

A repeat request with the same key within 24 hours returns the same response without charging again — useful when a step times out client-side after the model has already produced an answer.

Tracing and balance headers

The Agents SDK's built-in tracing tries to send traces to platform.openai.com by default — a separate service unrelated to the gateway, and it won't work without an official OpenAI key. Disable it explicitly if you don't need it:

bash
from agents import set_tracing_disabled
set_tracing_disabled(True)

Otherwise your logs fill with constant 401s from the tracer — unrelated to whether /v1/responses itself is working, and easy to mistake for a gateway problem.

For cost debugging, watch the response headers X-Zukko-Cost-Micros (the cost of that call in micro-dollars) and X-Fetch-Balance-Cents (balance remaining after the charge — despite the name, also in micro-dollars, 1,000,000 = $1) — they arrive on every non-streaming responses.create() call, including intermediate steps inside a single Runner.run(). Streamed responses (Runner.run_streamed, stream: true) don't carry them — check spend there in the dashboard, GET /v1/usage or GET /v1/balance.

Common mistakes

Every call returns 404 — the base_url is missing /v1, or has an extra one (the Agents SDK doesn't append the path itself, so it must be exactly https://api.zukko.pro/v1).

The model answers but the handoff never fires — that's not a gateway issue, it's SDK logic: a handoff depends on how the model phrases its tool call, not on which provider serves that model.

402 mid-chain across several agents — balance is checked on every individual /v1/responses call, so an agent can complete three of five steps and fail on the fourth. Check the remaining balance ahead of time with GET /v1/balance using the same key.

The model list and current limits are available without a key at GET /v1/models/info.

zukko

Pay 25×
less

for Claude Code & ChatGPT

One fetch_* key and base URL api.zukko.pro — for Cursor, Claude Code, Codex, and any SDK.

Get API keyDocumentation

api.zukko.pro · fetch_*

OpenAI Agents SDK setup with /v1/responses · Zukko