OpenAI Agents SDK on a Custom OpenAI-Compatible API: Chat Completions, Responses and the Tracing Trap
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

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.
from agents import set_default_openai_api, set_tracing_disabled set_tracing_disabled ( True ) set_default_openai_api ( " chat_completions " ) Enter fullscreen mode Exit fullscreen mode 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 ()) Enter fullscreen mode Exit fullscreen mode Runner.run_sync works the same way if you do not want asyncio at the call site.
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 ) Enter fullscreen mode Exit fullscreen mode Order matters. If you leave the default API on "responses", the same client still posts to /v1/responses and a chat-only gateway 404s.
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 ) Enter fullscreen mode Exit fullscreen mode 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 OpenAI Python client appends /chat/completions to whatever you put in base_url.
Always stop at /v1. Do not paste the full chat endpoint into base_url.
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'}} Enter fullscreen mode Exit fullscreen mode 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.
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 ) Enter fullscreen mode Exit fullscreen mode 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.
Once those six are right, the Agents SDK stops fighting the gateway and you can focus on instructions, tools and handoffs.