mirror of
https://github.com/crewAIInc/crewAI.git
synced 2026-07-01 13:18:10 +00:00
Fix #6089: Widen litellm dependency constraint to >=1.84.0,<2
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 <joao@crewai.com>
This commit is contained in:
@@ -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",
|
||||
|
||||
142
lib/crewai/tests/test_litellm_dependency_compat.py
Normal file
142
lib/crewai/tests/test_litellm_dependency_compat.py
Normal file
@@ -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"
|
||||
)
|
||||
10
uv.lock
generated
10
uv.lock
generated
@@ -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]]
|
||||
|
||||
Reference in New Issue
Block a user