Repository navigation
fix(client): fall back to default base_url when OPENAI_BASE_URL is empty - #3884
Sehastrajit-S wants to merge 1 commit into
Conversation
Deployment tooling (Docker, Kubernetes, CI templates) commonly sets
unset env vars to an empty string rather than omitting them, e.g.
OPENAI_BASE_URL=${SOME_VAR:-}. Previously an empty OPENAI_BASE_URL
was treated as an explicit value, producing an invalid base URL and
a confusing APIConnectionError instead of falling back to the
default OpenAI endpoint.
Fixes openai#2927
|
Independent offline verification (AI-assisted): I checked eight constructor/copy precedence cases for both Python 3.14.7, shared existing dependencies, isolated source trees and network-denied runs. A fake key and rejecting mock transport were used; no request occurred. This does not test X.509, TLS, provider routing, a historical dependency matrix or full SDK compatibility. Runnable separately against each source tree: import asyncio,itertools,json,os
import httpx2
from openai import OpenAI,AsyncOpenAI
import importlib
source=importlib.import_module('openai._client')
cases=[('unset',None,{},'https://api.openai.com/v1/',True),('empty','',{},'https://api.openai.com/v1/',True),('custom_env','https://example.invalid/env',{},'https://example.invalid/env/',False),('explicit_custom_empty_env','',{'base_url':'https://example.invalid/explicit'},'https://example.invalid/explicit/',False),('explicit_custom_custom_env','https://example.invalid/env',{'base_url':'https://example.invalid/explicit'},'https://example.invalid/explicit/',False),('eu_empty_env','',{'data_residency':'eu'},'https://eu.api.openai.com/v1/',False),('eu_custom_env','https://example.invalid/env',{'data_residency':'eu'},'https://eu.api.openai.com/v1/',False),('explicit_empty','https://example.invalid/env',{'base_url':''},'',False)]
rows=[]
def handler(request):raise AssertionError('No request expected')
async def main():
for is_async in [False,True]:
for name,env,kwargs,expected,default in cases:
if env is None:os.environ.pop('OPENAI_BASE_URL',None)
else:os.environ['OPENAI_BASE_URL']=env
http=(httpx2.AsyncClient if is_async else httpx2.Client)(transport=httpx2.MockTransport(handler),trust_env=False)
client=(AsyncOpenAI if is_async else OpenAI)(api_key='fictional-key',http_client=http,**kwargs)
copied=client.with_options(timeout=2)
actual=str(client.base_url);copied_url=str(copied.base_url)
rows.append({'case':name,'async':is_async,'actual':actual,'expected':expected,'default_flag':client._base_url_was_default,'copied_url':copied_url,'copied_default_flag':copied._base_url_was_default,'pass':actual==expected and copied_url==expected and client._base_url_was_default==default and copied._base_url_was_default==default})
if is_async:await client.close()
else:client.close()
print(json.dumps({'source_module':source.__file__,'cases':rows,'passed':sum(r['pass'] for r in rows),'failed':sum(not r['pass'] for r in rows),'scope':'Constructor/copy precedence and default flags; fake key and transport that rejects calls; no X.509/provider/TLS test.'}))
asyncio.run(main()) |
Summary
Fixes #2927.
OpenAI()andAsyncOpenAI()readOPENAI_BASE_URLfrom the environment when nobase_urlis passed explicitly, and fall back tohttps://api.openai.com/v1only when that value isNone. If the env var is set but empty (OPENAI_BASE_URL=""), it is treated as an explicit value, so the fallback is skipped and the client ends up with an empty base URL, producing a confusingAPIConnectionErrorinstead of just using the default endpoint.This is a common failure mode in deployment tooling: Docker Compose, Kubernetes manifests, and CI templates frequently interpolate optional variables as empty strings rather than omitting them entirely (for example
OPENAI_BASE_URL=${SOME_VAR:-}), so this can surface in production without any obviously wrong configuration on the user's part.Change
Treat an empty
OPENAI_BASE_URLthe same as an unset one in both the sync and async client constructors, so it correctly falls back to the default endpoint (or the mTLS default, when applicable).Test plan
test_base_url_env_empty_string_falls_back_to_defaultfor bothTestOpenAIandTestAsyncOpenAIintests/test_client.py, verifyingOPENAI_BASE_URL=""results in the defaulthttps://api.openai.com/v1/base URL.uv run pytest tests/test_client.py -k base_url_env -vpasses.test_client.py, data residency, x509, mTLS, Bedrock provider tests) to confirm no regressions; the only failures observed (test_proxy_environment_variables) are pre-existing onmainand unrelated to this change.uv run ruff check/uv run ruff format --diff/uv run mypy src/openai/_client.pyall pass on the changed files.