From d1e094b22fd108f5dcdc998f20dc0c335a401941 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Tue, 9 Jun 2026 17:15:01 +0000 Subject: [PATCH] Fix #6089: Widen litellm dependency constraint to >=1.84.0,<2 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The litellm extra was pinned to >=1.83.7,<1.84, but all versions in that range have transitive dependency conflicts with crewAI's core requirements: - litellm 1.83.7 pins python-dotenv==1.0.1 (crewAI needs >=1.2.2) - litellm 1.83.8 pins openai==2.24.0 (crewAI needs >=2.30.0) - litellm 1.83.14 pins openai==2.24.0 litellm 1.84.0+ relaxes these to openai>=2.20.0,<3 and python-dotenv>=1.0.0,<2, which are compatible with crewAI's bounds. Adds regression tests verifying the dependency bounds stay compatible. Co-Authored-By: João --- lib/crewai/pyproject.toml | 2 +- .../tests/test_litellm_dependency_compat.py | 142 ++++++++++++++++++ uv.lock | 10 +- 3 files changed, 148 insertions(+), 6 deletions(-) create mode 100644 lib/crewai/tests/test_litellm_dependency_compat.py diff --git a/lib/crewai/pyproject.toml b/lib/crewai/pyproject.toml index 6747c60a5..577f986e5 100644 --- a/lib/crewai/pyproject.toml +++ b/lib/crewai/pyproject.toml @@ -87,7 +87,7 @@ voyageai = [ "voyageai~=0.3.5", ] litellm = [ - "litellm>=1.83.7,<1.84", + "litellm>=1.84.0,<2", ] bedrock = [ "boto3~=1.42.79", diff --git a/lib/crewai/tests/test_litellm_dependency_compat.py b/lib/crewai/tests/test_litellm_dependency_compat.py new file mode 100644 index 000000000..32cddc2c0 --- /dev/null +++ b/lib/crewai/tests/test_litellm_dependency_compat.py @@ -0,0 +1,142 @@ +"""Tests for litellm optional dependency compatibility with crewAI core deps. + +Regression tests for https://github.com/crewAIInc/crewAI/issues/6089: +the litellm extra must not pin versions whose transitive dependencies +conflict with crewAI's own requirements (openai, python-dotenv, etc.). +""" + +import sys +import tomllib +from pathlib import Path + +import pytest +from packaging.specifiers import SpecifierSet +from packaging.version import Version + +PYPROJECT_PATH = Path(__file__).resolve().parents[1] / "pyproject.toml" + + +@pytest.fixture(scope="module") +def pyproject(): + """Load lib/crewai/pyproject.toml as a dict.""" + with open(PYPROJECT_PATH, "rb") as f: + return tomllib.load(f) + + +def _parse_specifier(dep_string: str) -> tuple[str, SpecifierSet]: + """Split 'pkg>=1.2,<3' into ('pkg', SpecifierSet('>=1.2,<3')).""" + for op in (">=", "<=", "~=", "==", "!=", ">", "<"): + idx = dep_string.find(op) + if idx != -1: + name = dep_string[:idx].strip() + spec = dep_string[idx:].split(";")[0].strip() + return name, SpecifierSet(spec) + return dep_string.strip(), SpecifierSet() + + +class TestLitellmDependencyBounds: + """Verify the litellm optional-dependency range is resolvable.""" + + def test_litellm_extra_exists(self, pyproject): + """The litellm optional dependency group must be defined.""" + opt_deps = pyproject["project"]["optional-dependencies"] + assert "litellm" in opt_deps, "Missing 'litellm' optional-dependency group" + + def test_litellm_lower_bound_at_least_1_84(self, pyproject): + """litellm lower bound must be >= 1.84.0 to avoid transitive conflicts. + + Versions < 1.84 pin openai and python-dotenv to ranges that are + incompatible with crewAI's core requirements. + """ + opt_deps = pyproject["project"]["optional-dependencies"] + litellm_deps = opt_deps["litellm"] + litellm_dep = [d for d in litellm_deps if d.startswith("litellm")] + assert litellm_dep, "No litellm dependency found in litellm extras" + + _, spec = _parse_specifier(litellm_dep[0]) + + # 1.83.x versions have conflicting transitive deps; must be excluded + assert not spec.contains(Version("1.83.7")), ( + "litellm 1.83.7 must be excluded (pins python-dotenv==1.0.1)" + ) + assert not spec.contains(Version("1.83.8")), ( + "litellm 1.83.8 must be excluded (pins openai==2.24.0)" + ) + assert not spec.contains(Version("1.83.14")), ( + "litellm 1.83.14 must be excluded (pins openai==2.24.0)" + ) + + # 1.84.0+ relaxes transitive pins to compatible bounds + assert spec.contains(Version("1.84.0")), ( + "litellm 1.84.0 should be allowed (compatible transitive deps)" + ) + + def test_litellm_upper_bound_allows_recent_versions(self, pyproject): + """litellm range must accept recent 1.x releases.""" + opt_deps = pyproject["project"]["optional-dependencies"] + litellm_deps = opt_deps["litellm"] + litellm_dep = [d for d in litellm_deps if d.startswith("litellm")] + + _, spec = _parse_specifier(litellm_dep[0]) + + # Ensure reasonable recent versions are included + assert spec.contains(Version("1.87.0")), ( + "litellm 1.87.x should be allowed" + ) + assert spec.contains(Version("1.90.0")), ( + "litellm 1.90.x should be allowed" + ) + + def test_litellm_range_excludes_v2(self, pyproject): + """litellm range must not include v2 (potential breaking changes).""" + opt_deps = pyproject["project"]["optional-dependencies"] + litellm_deps = opt_deps["litellm"] + litellm_dep = [d for d in litellm_deps if d.startswith("litellm")] + + _, spec = _parse_specifier(litellm_dep[0]) + + assert not spec.contains(Version("2.0.0")), ( + "litellm 2.x should be excluded to avoid breaking changes" + ) + + def test_core_openai_dep_compatible_with_litellm_range(self, pyproject): + """crewAI's openai requirement must be satisfiable alongside litellm>=1.84. + + litellm>=1.84.0 requires openai>=2.20.0,<3.0.0, which overlaps + with crewAI's openai>=2.30.0,<3. + """ + deps = pyproject["project"]["dependencies"] + openai_deps = [d for d in deps if d.startswith("openai")] + assert openai_deps, "openai must be a core dependency" + + _, crewai_openai_spec = _parse_specifier(openai_deps[0]) + + # litellm>=1.84 allows openai>=2.20.0,<3.0.0 + # crewAI requires openai>=2.30.0,<3 + # The intersection should be non-empty + test_version = Version("2.30.0") + litellm_openai_spec = SpecifierSet(">=2.20.0,<3.0.0") + assert crewai_openai_spec.contains(test_version) and litellm_openai_spec.contains(test_version), ( + "openai 2.30.0 must satisfy both crewAI and litellm>=1.84 requirements" + ) + + def test_core_python_dotenv_dep_compatible_with_litellm_range(self, pyproject): + """crewAI's python-dotenv requirement must be satisfiable alongside litellm>=1.84. + + litellm>=1.84.0 requires python-dotenv>=1.0.0,<2.0, which overlaps + with crewAI's python-dotenv>=1.2.2,<2. + """ + deps = pyproject["project"]["dependencies"] + dotenv_deps = [d for d in deps if d.startswith("python-dotenv")] + assert dotenv_deps, "python-dotenv must be a core dependency" + + _, crewai_dotenv_spec = _parse_specifier(dotenv_deps[0]) + + # litellm>=1.84 allows python-dotenv>=1.0.0,<2.0 + # crewAI requires python-dotenv>=1.2.2,<2 + # The intersection should be non-empty + test_version = Version("1.2.2") + litellm_dotenv_spec = SpecifierSet(">=1.0.0,<2.0") + assert crewai_dotenv_spec.contains(test_version) and litellm_dotenv_spec.contains(test_version), ( + "python-dotenv 1.2.2 must satisfy both crewAI and litellm>=1.84 requirements" + ) diff --git a/uv.lock b/uv.lock index aeeed3d75..6884fb3d6 100644 --- a/uv.lock +++ b/uv.lock @@ -13,7 +13,7 @@ resolution-markers = [ ] [options] -exclude-newer = "2026-06-06T00:11:14.404922Z" +exclude-newer = "0001-01-01T00:00:00Z" # This has no effect and is included for backwards compatibility when using relative exclude-newer values. exclude-newer-span = "P3D" [manifest] @@ -1421,7 +1421,7 @@ requires-dist = [ { name = "json5", specifier = "~=0.10.0" }, { name = "jsonref", specifier = "~=1.1.0" }, { name = "lancedb", specifier = ">=0.29.2,<0.30.1" }, - { name = "litellm", marker = "extra == 'litellm'", specifier = ">=1.83.7,<1.84" }, + { name = "litellm", marker = "extra == 'litellm'", specifier = ">=1.84.0,<2" }, { name = "mcp", specifier = "~=1.26.0" }, { name = "mem0ai", marker = "extra == 'mem0'", specifier = ">=2.0.0,<3" }, { name = "openai", specifier = ">=2.30.0,<3" }, @@ -4066,7 +4066,7 @@ wheels = [ [[package]] name = "litellm" -version = "1.83.14" +version = "1.87.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "aiohttp" }, @@ -4082,9 +4082,9 @@ dependencies = [ { name = "tiktoken" }, { name = "tokenizers" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/8d/7c/c095649380adc96c8630273c1768c2ad1e74aa2ee1dd8dd05d218a60569f/litellm-1.83.14.tar.gz", hash = "sha256:24aef9b47cdc424c833e32f3727f411741c690832cd1fe4405e0077144fe09c9", size = 14836599, upload-time = "2026-04-26T03:16:10.176Z" } +sdist = { url = "https://files.pythonhosted.org/packages/5d/e5/d0ac1c8f55e2c8d8799589e831bef0d450e69e02ecb511901ffc8de054d9/litellm-1.87.1.tar.gz", hash = "sha256:70ac9d6b25f56ad30de6ff95d26fac3b3fc697a95da582b6072d25d8dc73d493", size = 15455709, upload-time = "2026-06-04T16:23:23.339Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/7f/5c/1b5691575420135e90578543b2bf219497caa33cfd0af64cb38f30288450/litellm-1.83.14-py3-none-any.whl", hash = "sha256:92b11ba2a32cf80707ddf388d18526696c7999a21b418c5e3b6eda1243d2cfdb", size = 16457054, upload-time = "2026-04-26T03:16:05.72Z" }, + { url = "https://files.pythonhosted.org/packages/ff/18/8275c95ef09e81ab0c01a162c7b780ce3fbc49066b5d532c6b6ab3dc0118/litellm-1.87.1-py3-none-any.whl", hash = "sha256:dd4e00278cdb846d52e99a09d732575a897273540b54eb044247ecbc0d98f67c", size = 17105482, upload-time = "2026-06-04T16:23:20.769Z" }, ] [[package]]