From bda22b0aa45abdc77875ee7b14f05d9ea004f1f3 Mon Sep 17 00:00:00 2001 From: Pierre-Yves Le Borgne Date: Wed, 28 Oct 2026 10:59:55 +0000 Subject: [PATCH] fix(llm): keep the litellm fallback on litellm releases before 1.97 _ensure_litellm() imported ChatCompletionDeltaCustomToolCall, which litellm added in 1.97.0. With an older litellm that import raised ImportError, so crewAI reported LiteLLM as not installed and every model that needs the fallback failed to build. The name is only used in a local variable annotation, which Python never evaluates, so import it for type checking only. Co-Authored-By: Claude Sonnet 5.5 Claude-Session: https://claude.ai/code/session_01GwUvhmzgfkiZ6vo4uGyesN --- lib/crewai/src/crewai/llm.py | 5 +---- lib/crewai/tests/test_llm.py | 19 +++++++++++++++++++ 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/lib/crewai/src/crewai/llm.py b/lib/crewai/src/crewai/llm.py index 76b0415184..9f547dfa99 100644 --- a/lib/crewai/src/crewai/llm.py +++ b/lib/crewai/src/crewai/llm.py @@ -122,7 +122,6 @@ LiteLLMStreamingChoices = None get_supported_openai_params = None ChatCompletionDeltaToolCall = None - ChatCompletionDeltaCustomToolCall = None Function = None ModelResponse = None supports_response_schema = None @@ -133,7 +132,7 @@ def _ensure_litellm() -> bool: global _litellm_loaded, LITELLM_AVAILABLE global litellm, Choices, LiteLLMDelta, Message, ModelResponseBase global ModelResponseStream, LiteLLMStreamingChoices, get_supported_openai_params - global ChatCompletionDeltaCustomToolCall, ChatCompletionDeltaToolCall, Function + global ChatCompletionDeltaToolCall, Function global ModelResponse, supports_response_schema if _litellm_loaded: @@ -146,7 +145,6 @@ def _ensure_litellm() -> bool: get_supported_openai_params as _get_supported_openai_params, ) from litellm.types.utils import ( - ChatCompletionDeltaCustomToolCall as _ChatCompletionDeltaCustomToolCall, ChatCompletionDeltaToolCall as _ChatCompletionDeltaToolCall, Choices as _Choices, Delta as _LiteLLMDelta, @@ -167,7 +165,6 @@ def _ensure_litellm() -> bool: ModelResponseStream = _ModelResponseStream # type: ignore[misc] LiteLLMStreamingChoices = _LiteLLMStreamingChoices # type: ignore[misc] get_supported_openai_params = _get_supported_openai_params - ChatCompletionDeltaCustomToolCall = _ChatCompletionDeltaCustomToolCall # type: ignore[misc] ChatCompletionDeltaToolCall = _ChatCompletionDeltaToolCall # type: ignore[misc] Function = _Function # type: ignore[misc] ModelResponse = _ModelResponse # type: ignore[misc] diff --git a/lib/crewai/tests/test_llm.py b/lib/crewai/tests/test_llm.py index 0e7c1b501a..9f32de310d 100644 --- a/lib/crewai/tests/test_llm.py +++ b/lib/crewai/tests/test_llm.py @@ -1141,6 +1141,25 @@ def test_litellm_path_preserves_provider_from_model_prefix(model, expected_provi assert llm.model == model +@requires_litellm_python_311 +def test_litellm_fallback_loads_without_custom_tool_call_delta_type( + monkeypatch: pytest.MonkeyPatch, +) -> None: + """LiteLLM releases before 1.97.0 lack ChatCompletionDeltaCustomToolCall. + + The fallback only needs that type for type checking, so it must still load. + """ + import litellm.types.utils + + monkeypatch.delattr(litellm.types.utils, "ChatCompletionDeltaCustomToolCall") + monkeypatch.setattr("crewai.llm._litellm_loaded", False) + monkeypatch.setattr("crewai.llm.LITELLM_AVAILABLE", False) + + llm = LLM(model="openai/custom-finetune-model", is_litellm=False) + + assert llm.is_litellm is True + + def test_litellm_keeps_provider_but_formats_multimodal_as_openai_schema(): llm = LLM(model="anthropic/claude-3-5-haiku-20241022", is_litellm=True) assert llm.provider == "anthropic"