fix: recompute is_azure_openai_endpoint after lazy endpoint resolve

`_prepare_completion_params` uses `is_azure_openai_endpoint` to decide
whether to include the `model` parameter in requests — Azure OpenAI
endpoints embed the deployment name in the URL and reject a `model`
field. When the endpoint was resolved lazily from env vars, the flag
stayed at its pre-resolve `False` value, causing every lazily-inited
Azure OpenAI request to include `model` and fail.

Factor the classification into `_is_azure_openai_endpoint` and call
it from both `_normalize_azure_fields` and `_make_client_kwargs`.
Extend the lazy-build regression test to assert the flag flips to
`True` once the endpoint is resolved.
This commit is contained in:
Greyson LaLonde
2026-04-12 04:59:18 +08:00
parent ad6d7fa198
commit 60e8a4a364
2 changed files with 24 additions and 8 deletions

View File

@@ -123,18 +123,23 @@ class AzureCompletion(BaseLLM):
data["endpoint"] = AzureCompletion._validate_and_fix_endpoint(
data["endpoint"], model
)
parsed = urlparse(data["endpoint"])
hostname = parsed.hostname or ""
data["is_azure_openai_endpoint"] = (
hostname == "openai.azure.com" or hostname.endswith(".openai.azure.com")
) and "/openai/deployments/" in data["endpoint"]
else:
data["is_azure_openai_endpoint"] = False
data["is_azure_openai_endpoint"] = AzureCompletion._is_azure_openai_endpoint(
data["endpoint"]
)
data["is_openai_model"] = any(
prefix in model.lower() for prefix in ["gpt-", "o1-", "text-"]
)
return data
@staticmethod
def _is_azure_openai_endpoint(endpoint: str | None) -> bool:
if not endpoint:
return False
hostname = urlparse(endpoint).hostname or ""
return (
hostname == "openai.azure.com" or hostname.endswith(".openai.azure.com")
) and "/openai/deployments/" in endpoint
@model_validator(mode="after")
def _init_clients(self) -> AzureCompletion:
"""Eagerly build clients when credentials are available, otherwise
@@ -170,6 +175,13 @@ class AzureCompletion(BaseLLM):
self.endpoint = AzureCompletion._validate_and_fix_endpoint(
endpoint, self.model
)
# Recompute the routing flag now that the endpoint is known —
# _prepare_completion_params uses it to decide whether to
# include `model` in the request body (Azure OpenAI endpoints
# embed the deployment name in the URL and reject it).
self.is_azure_openai_endpoint = (
AzureCompletion._is_azure_openai_endpoint(self.endpoint)
)
if not self.api_key:
raise ValueError(

View File

@@ -419,13 +419,16 @@ async def test_azure_aclose_is_noop_when_uninitialized():
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."""
so the LLM actually works once credentials become available, and the
`is_azure_openai_endpoint` routing flag must be recomputed off the
newly-resolved endpoint."""
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
assert llm.is_azure_openai_endpoint is False
with patch.dict(
os.environ,
@@ -440,6 +443,7 @@ def test_azure_lazy_build_reads_env_vars_set_after_construction():
assert llm.api_key == "late-key"
assert llm.endpoint is not None
assert "test.openai.azure.com" in llm.endpoint
assert llm.is_azure_openai_endpoint is True
def test_azure_endpoint_configuration():