fix: keep Azure aclose a no-op when the async client was never built

The lazy-init refactor rewrote `aclose` to access the async client via
`_get_async_client()`, which forces lazy construction. When an
`AzureCompletion` is instantiated without credentials (the whole point
of deferred init), that call raises `ValueError: "Azure API key is
required"` during cleanup — including via `async with` / `__aexit__`.

Access the cached `_async_client` attribute directly so cleanup on an
uninitialized LLM is a harmless no-op. Add a regression test that
enters and exits an `async with` block against a credentials-less
`AzureCompletion`.
This commit is contained in:
Greyson LaLonde
2026-04-12 04:31:04 +08:00
parent 96031fa358
commit 8312e1caad
2 changed files with 21 additions and 3 deletions

View File

@@ -1132,10 +1132,13 @@ class AzureCompletion(BaseLLM):
"""Close the async client and clean up resources.
This ensures proper cleanup of the underlying aiohttp session
to avoid unclosed connector warnings.
to avoid unclosed connector warnings. Accesses the cached client
directly rather than going through `_get_async_client` so a
cleanup on an uninitialized LLM is a harmless no-op rather than
a credential-required error.
"""
if hasattr(self._get_async_client(), "close"):
await self._get_async_client().close()
if self._async_client is not None and hasattr(self._async_client, "close"):
await self._async_client.close()
async def __aenter__(self) -> Self:
"""Async context manager entry."""

View File

@@ -401,6 +401,21 @@ def test_azure_raises_error_when_api_key_missing():
llm._get_sync_client()
@pytest.mark.asyncio
async def test_azure_aclose_is_noop_when_uninitialized():
"""`aclose` (and `async with`) on an uninstantiated-client LLM must be
a harmless no-op, not force lazy construction that then raises for
missing credentials."""
from crewai.llms.providers.azure.completion import AzureCompletion
with patch.dict(os.environ, {}, clear=True):
llm = AzureCompletion(model="gpt-4")
assert llm._async_client is None
await llm.aclose()
async with llm:
pass
def test_azure_endpoint_configuration():
"""
Test that Azure endpoint configuration works with multiple environment variable names