OpenAI Agents SDK 如何接入自訂 OpenAI 相容 API:Chat Completions、Responses 與追蹤陷阱
OpenAI Agents SDK on a Custom OpenAI-Compatible API: Chat Completions, Responses and the Tracing Trap
Aman Kumar 示範以 OpenAI Agents SDK 接入只支援 `/v1/chat/completions` 的自訂 OpenAI 相容 API。
I'm Aman Kumar. The OpenAI Agents SDK (openai-agents on PyPI) is a clean way to run tool-calling agents in Python. Out of the box it talks to api.openai.com over the Responses API and ships traces back to OpenAI. Point it at another OpenAI-compatible server without changing those defaults and you get 404s that look like "the model is wrong" or mysterious network errors from the tracer.
I wrote this against openai-agents 0.23.1 and verified the three working setups below on a local mock that only implements /v1/chat/completions. The examples use APIClaw, a gateway I build, so weigh that accordingly. Base URL is https://apiclaw.biz/v1. Copy the model ID from /v1/models or the model hub; I use YOUR_MODEL_ID below. On that hub you will see IDs like deepseek-v4-flash, deepseek-v4-pro or claude-sonnet-5-5. Crypto is on the site; other payment methods go through support. Swap host, key and model for whatever you run — vLLM, LiteLLM, Ollama's /v1 endpoint, or any server that speaks Chat Completions.
What the SDK defaults to
Two defaults bite people:
-
API shape.
set_default_openai_apidefaults to"responses". Most third-party gateways implement/v1/chat/completions, not/v1/responses. Hitting Responses on a chat-only gateway returnsNotFoundError(404). -
Tracing. Unless you call
set_tracing_disabled(True), the SDK tries to export traces using an OpenAI API key. On a custom gateway that key is either absent or not valid for OpenAI's trace endpoint, so you get noise even when chat works.
Fix both before you debug model IDs.
from agents import set_default_openai_api, set_tracing_disabled
set_tracing_disabled(True)
set_default_openai_api("chat_completions")
Setup A: OpenAIChatCompletionsModel (most explicit)
Pass an AsyncOpenAI client with your base URL into OpenAIChatCompletionsModel. The agent never touches the Responses path.
import asyncio
from openai import AsyncOpenAI
from agents import Agent, Runner, OpenAIChatCompletionsModel, set_tracing_disabled
set_tracing_disabled(True)
client = AsyncOpenAI(
base_url="https://apiclaw.biz/v1", # include /v1
api_key="YOUR_API_KEY",
)
model = OpenAIChatCompletionsModel(
model="YOUR_MODEL_ID",
openai_client=client,
)
agent = Agent(
name="assistant",
instructions="Reply in one short paragraph.",
model=model,
)
async def main():
result = await Runner.run(agent, "What is a base URL in an OpenAI client?")
print(result.final_output)
asyncio.run(main())
Runner.run_sync(...) works the same way if you do not want asyncio at the call site.
Setup B: set_default_openai_client + chat_completions
When every agent in the process should use the same gateway, set the process defaults once.
from openai import AsyncOpenAI
from agents import (
Agent,
Runner,
set_default_openai_client,
set_default_openai_api,
set_tracing_disabled,
)
set_tracing_disabled(True)
set_default_openai_client(
AsyncOpenAI(base_url="https://apiclaw.biz/v1", api_key="YOUR_API_KEY")
)
set_default_openai_api("chat_completions")
agent = Agent(
name="assistant",
instructions="Be concise.",
model="YOUR_MODEL_ID", # plain string is fine now
)
result = Runner.run_sync(agent, "List three reasons to pin a base URL.")
print(result.final_output)
Order matters. If you leave the default API on "responses", the same client still posts to /v1/responses and a chat-only gateway 404s.
Setup C: OpenAIProvider with use_responses=False
OpenAIProvider is what RunConfig(model_provider=...) expects when you want a provider object instead of globals.
from openai import AsyncOpenAI
from agents import Agent, Runner, OpenAIProvider, RunConfig, set_tracing_disabled
set_tracing_disabled(True)
client = AsyncOpenAI(base_url="https://apiclaw.biz/v1", api_key="YOUR_API_KEY")
provider = OpenAIProvider(openai_client=client, use_responses=False)
agent = Agent(
name="assistant",
instructions="Be concise.",
model="YOUR_MODEL_ID",
)
result = Runner.run_sync(
agent,
"Explain chat completions vs responses in one sentence.",
run_config=RunConfig(model_provider=provider),
)
print(result.final_output)
use_responses=False makes get_model return OpenAIChatCompletionsModel. You can also pass base_url= and api_key= directly into OpenAIProvider instead of a prebuilt client.
The /v1 path rule
The OpenAI Python client appends /chat/completions to whatever you put in base_url.
base_url |
Request path | Typical result on an OpenAI-layout gateway |
|---|---|---|
https://apiclaw.biz/v1 |
/v1/chat/completions |
Works |
https://apiclaw.biz |
/chat/completions |
404 |
https://apiclaw.biz/v1/chat/completions |
/v1/chat/completions/chat/completions |
404 |
Always stop at /v1. Do not paste the full chat endpoint into base_url.
What breaks if you keep Responses
On my chat-only mock, leaving the default API on "responses" raised:
openai.NotFoundError: Error code: 404 -
{'error': {'message': 'Responses API not available on this gateway',
'type': 'invalid_request_error'}}
That is not a bad model ID. The SDK is calling a different route. Switch to chat completions (any of A/B/C above) and retry with the same model string.
Tools still work on Chat Completions
Function tools go through the same model wrapper. Minimal example:
from agents import Agent, Runner, OpenAIChatCompletionsModel, function_tool, set_tracing_disabled
from openai import AsyncOpenAI
set_tracing_disabled(True)
client = AsyncOpenAI(base_url="https://apiclaw.biz/v1", api_key="YOUR_API_KEY")
@function_tool
def add(a: int, b: int) -> int:
"""Add two integers."""
return a + b
agent = Agent(
name="math",
instructions="Use the add tool for arithmetic.",
model=OpenAIChatCompletionsModel(model="YOUR_MODEL_ID", openai_client=client),
tools=[add],
)
print(Runner.run_sync(agent, "What is 7 + 5?").final_output)
If a gateway strips tool_calls from chat responses, the agent will loop or answer without tools — that is a server capability issue, not an Agents SDK base-URL bug. Check one raw /v1/chat/completions request with "tools" in the body before you dig into the runner.
Checklist I use
-
pip install openai-agents(I tested 0.23.1). -
set_tracing_disabled(True)unless you intentionally send traces to OpenAI. - Force Chat Completions:
OpenAIChatCompletionsModel, orset_default_openai_api("chat_completions"), orOpenAIProvider(..., use_responses=False). -
base_urlends with/v1, not/v1/chat/completions. - Model ID is the full string from the gateway's model list, not a short nickname.
- Confirm a plain
client.chat.completions.create(...)works before wrapping it inAgent.
Once those six are right, the Agents SDK stops fighting the gateway and you can focus on instructions, tools and handoffs.
來源:DEV Community · dev.to