From 8312e1caadf1c3f48e81619bd374752de1b8451b Mon Sep 17 00:00:00 2001 From: Greyson LaLonde Date: Sun, 12 Apr 2026 04:31:04 +0800 Subject: [PATCH] fix: keep Azure aclose a no-op when the async client was never built MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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`. --- .../src/crewai/llms/providers/azure/completion.py | 9 ++++++--- lib/crewai/tests/llms/azure/test_azure.py | 15 +++++++++++++++ 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/lib/crewai/src/crewai/llms/providers/azure/completion.py b/lib/crewai/src/crewai/llms/providers/azure/completion.py index 249c22550..156fa8984 100644 --- a/lib/crewai/src/crewai/llms/providers/azure/completion.py +++ b/lib/crewai/src/crewai/llms/providers/azure/completion.py @@ -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.""" diff --git a/lib/crewai/tests/llms/azure/test_azure.py b/lib/crewai/tests/llms/azure/test_azure.py index a9dbfb3ee..bee767157 100644 --- a/lib/crewai/tests/llms/azure/test_azure.py +++ b/lib/crewai/tests/llms/azure/test_azure.py @@ -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