-
Notifications
You must be signed in to change notification settings - Fork 4.3k
Open
Labels
bugSomething isn't workingSomething isn't working
Description
Confirm this is an issue with the Python library and not an underlying OpenAI API
- This is an issue with the Python library
Describe the bug
The callable api_key
feature seems to be broken for Azure OpenAI clients. Please let me know if I am missing something. Happy to contribute a PR if needed.
How It's Supposed to Work
-
OpenAI Client (Working)
- When you pass a callable as
api_key
, the client stores it in_api_key_provider
and setsapi_key
to empty string. - During request preparation,
_prepare_options()
calls_refresh_api_key()
(src/openai/_client.py:310). _refresh_api_key()
invokes the callable and updatesself.api_key
with the fresh value.- The fresh API key is then used in the
auth_headers
property.
- When you pass a callable as
-
Azure OpenAI Client (Broken)
- Inherits from
OpenAI
, so it gets the_api_key_provider
storage mechanism. - However, Azure overrides
_prepare_options()
without calling the parent implementation. - Azure's
_prepare_options()
(src/openai/lib/azure.py:322-339) directly usesself.api_key
without refreshing it. - Since
api_key
is initialized to an empty string when using a callable, Azure sends an empty API key.
- Inherits from
The Bug Location
- File:
src/openai/lib/azure.py
- Lines: 322-339 (sync) and 605-622 (async)
The Azure implementation checks self.api_key
directly:
elif self.api_key is not API_KEY_SENTINEL:
if headers.get("api-key") is None:
headers["api-key"] = self.api_key # ← Uses stale/empty value!
It should either:
- Call
super()._prepare_options()
first to trigger the refresh, OR - Manually call
self._refresh_api_key()
before usingself.api_key
.
Impact
Anyone trying to use dynamic API key generation (e.g., from a secrets manager, key rotation, etc.) with Azure OpenAI will find it doesn't work — the client will always use an empty API key.
Additional Context
- This callable
api_key
feature was introduced in PR feat(client): support callable api_key #2588.
To Reproduce
Try this out:
from openai import OpenAI
from openai.lib.azure import AzureOpenAI
def get_api_key():
print("Getting API key...")
return "sk-..."
# Works: callable is invoked
client = OpenAI(api_key=get_api_key)
client.models.list() # Prints "Getting API key..."
# Broken: callable is never invoked
azure_client = AzureOpenAI(
api_key=get_api_key,
azure_endpoint="https://...",
api_version="2024-02-01"
)
azure_client.models.list() # Callable never invoked, auth fails
Code snippets
#!/usr/bin/env python3
"""Test script to verify callable api_key functionality in OpenAI and Azure OpenAI clients"""
import os
from openai import OpenAI, AsyncOpenAI
from openai.lib.azure import AzureOpenAI, AsyncAzureOpenAI
def test_openai_callable():
"""Test callable api_key with regular OpenAI client"""
call_count = 0
def get_api_key():
nonlocal call_count
call_count += 1
print(f"OpenAI: get_api_key called (call #{call_count})")
return os.environ.get("OPENAI_API_KEY", "test-key")
client = OpenAI(api_key=get_api_key)
# Check that callable is stored properly
print(f"OpenAI client api_key: '{client.api_key}'")
print(f"OpenAI client _api_key_provider: {client._api_key_provider}")
# Try to make a request (will fail without real key, but we can see if callable is invoked)
try:
# This should trigger the callable
client.models.list()
except Exception as e:
print(f"OpenAI request failed (expected): {e}")
print(f"OpenAI: Total calls to get_api_key: {call_count}")
print()
def test_azure_callable():
"""Test callable api_key with Azure OpenAI client"""
call_count = 0
def get_api_key():
nonlocal call_count
call_count += 1
print(f"Azure: get_api_key called (call #{call_count})")
return os.environ.get("AZURE_OPENAI_API_KEY", "test-key")
client = AzureOpenAI(
api_key=get_api_key,
api_version="2024-02-01",
azure_endpoint="https://test.openai.azure.com"
)
# Check that callable is stored properly
print(f"Azure client api_key: '{client.api_key}'")
# Azure client inherits from OpenAI, so it should have _api_key_provider
print(f"Azure client _api_key_provider: {getattr(client, '_api_key_provider', 'NOT FOUND')}")
# Try to make a request (will fail without real key, but we can see if callable is invoked)
try:
# This should trigger the callable
client.models.list()
except Exception as e:
print(f"Azure request failed (expected): {e}")
print(f"Azure: Total calls to get_api_key: {call_count}")
print()
def test_azure_prepare_options():
"""Test to see what happens in _prepare_options for Azure"""
def get_api_key():
print("Azure _prepare_options test: get_api_key called")
return "dynamic-key-12345"
client = AzureOpenAI(
api_key=get_api_key,
api_version="2024-02-01",
azure_endpoint="https://test.openai.azure.com"
)
print(f"Initial api_key value: '{client.api_key}'")
# Simulate what happens when a request is made
# The OpenAI parent class should call _refresh_api_key in its _prepare_options
# But Azure overrides _prepare_options and doesn't call the parent
from openai._models import FinalRequestOptions
options = FinalRequestOptions(
method="GET",
url="/models",
)
# This should call Azure's _prepare_options
prepared = client._prepare_options(options)
print(f"After _prepare_options, api_key value: '{client.api_key}'")
print()
if __name__ == "__main__":
print("Testing Callable API Key Support\n")
print("=" * 50)
test_openai_callable()
test_azure_callable()
test_azure_prepare_options()
OS
macOS
Python version
Python v3.13.7
Library version
v1.107.1
Metadata
Metadata
Assignees
Labels
bugSomething isn't workingSomething isn't working