fix: re-read Azure env vars in lazy client build

Azure's `_normalize_azure_fields` captures env vars at construction
time. When `LLM(model="azure/...")` is instantiated before deployment
env vars are set, `self.api_key` / `self.endpoint` freeze as `None`
and the lazy client builder then always raises — defeating the point
of deferred init for Azure.

Re-read `AZURE_API_KEY` / `AZURE_ENDPOINT` (and friends) inside
`_make_client_kwargs` when the fields are still unset, matching
OpenAI's `_get_client_params` pattern. Runs the endpoint validator
on any env-provided value so the same normalization applies.

Add a regression test that constructs the LLM with no env vars set,
then patches them in afterwards and asserts `_get_sync_client()`
successfully builds a client and writes the resolved values back
onto the LLM instance.
This commit is contained in:
Greyson LaLonde
2026-04-12 04:46:39 +08:00
parent 77e66647b0
commit ad6d7fa198
2 changed files with 42 additions and 0 deletions

View File

@@ -155,6 +155,22 @@ class AzureCompletion(BaseLLM):
return AsyncChatCompletionsClient(**self._make_client_kwargs())
def _make_client_kwargs(self) -> dict[str, Any]:
# Re-read env vars so that a deferred build can pick up credentials
# that weren't set at instantiation time (e.g. LLM constructed at
# module import before deployment env vars were injected).
if not self.api_key:
self.api_key = os.getenv("AZURE_API_KEY")
if not self.endpoint:
endpoint = (
os.getenv("AZURE_ENDPOINT")
or os.getenv("AZURE_OPENAI_ENDPOINT")
or os.getenv("AZURE_API_BASE")
)
if endpoint:
self.endpoint = AzureCompletion._validate_and_fix_endpoint(
endpoint, self.model
)
if not self.api_key:
raise ValueError(
"Azure API key is required. Set AZURE_API_KEY environment "

View File

@@ -416,6 +416,32 @@ async def test_azure_aclose_is_noop_when_uninitialized():
pass
def test_azure_lazy_build_reads_env_vars_set_after_construction():
"""When `LLM(model="azure/...")` is constructed before env vars are set,
the lazy client builder must re-read `AZURE_API_KEY` / `AZURE_ENDPOINT`
so the LLM actually works once credentials become available."""
from crewai.llms.providers.azure.completion import AzureCompletion
with patch.dict(os.environ, {}, clear=True):
llm = AzureCompletion(model="gpt-4")
assert llm.api_key is None
assert llm.endpoint is None
with patch.dict(
os.environ,
{
"AZURE_API_KEY": "late-key",
"AZURE_ENDPOINT": "https://test.openai.azure.com/openai/deployments/gpt-4",
},
clear=True,
):
client = llm._get_sync_client()
assert client is not None
assert llm.api_key == "late-key"
assert llm.endpoint is not None
assert "test.openai.azure.com" in llm.endpoint
def test_azure_endpoint_configuration():
"""
Test that Azure endpoint configuration works with multiple environment variable names