Files
crewAI/lib/crewai/tests/test_dependency_compatibility.py
Devin AI d255f1908a fix: loosen dependency constraints to fix OPIK integration conflict
This commit addresses issue #4201 where crewAI's overly restrictive
dependency constraints (using ~= operator) caused conflicts when
installing alongside packages like opik.

Changes:
- Changed dependency constraints from ~= (compatible release) to >=
  (minimum version) for core dependencies in crewai and crewai-tools
- Key dependencies loosened: openai, pydantic, pydantic-settings,
  opentelemetry-*, and others
- Added tests to verify dependency constraints remain flexible

The ~= operator was too restrictive as it only allows patch version
updates (e.g., openai~=1.83.0 means >=1.83.0,<1.84.0). This caused
dependency resolution failures when other packages needed different
versions of shared dependencies.

Fixes #4201

Co-Authored-By: João <joao@crewai.com>
2026-01-08 04:43:45 +00:00

172 lines
5.6 KiB
Python

"""Test that crewai dependencies are compatible with common integrations.
This test module verifies that crewai's dependency constraints are flexible enough
to allow installation alongside common third-party packages like opik for monitoring.
Related issue: https://github.com/crewAIInc/crewAI/issues/4201
"""
import re
from pathlib import Path
import tomli
def get_pyproject_path() -> Path:
"""Get the path to the crewai pyproject.toml file."""
return Path(__file__).parent.parent / "pyproject.toml"
def parse_pyproject() -> dict:
"""Parse the pyproject.toml file."""
pyproject_path = get_pyproject_path()
with open(pyproject_path, "rb") as f:
return tomli.load(f)
def test_openai_dependency_is_flexible():
"""Test that openai dependency uses >= instead of ~= to allow version flexibility.
The ~= operator (compatible release) is too restrictive and can cause dependency
conflicts with packages like opik that also depend on openai.
For example, openai~=1.83.0 means >=1.83.0,<1.84.0 which is very restrictive.
Using openai>=1.13.3 allows any version >= 1.13.3 which is more flexible.
"""
pyproject = parse_pyproject()
dependencies = pyproject["project"]["dependencies"]
openai_dep = None
for dep in dependencies:
if dep.startswith("openai"):
openai_dep = dep
break
assert openai_dep is not None, "openai dependency not found in pyproject.toml"
# Check that it uses >= instead of ~=
assert "~=" not in openai_dep, (
f"openai dependency should use >= instead of ~= for flexibility. "
f"Found: {openai_dep}"
)
assert ">=" in openai_dep, (
f"openai dependency should use >= for minimum version. Found: {openai_dep}"
)
def test_pydantic_dependency_allows_minor_updates():
"""Test that pydantic dependency allows minor version updates within v2.
Using pydantic>=2.x.x,<3.0.0 allows minor updates while staying within v2.
"""
pyproject = parse_pyproject()
dependencies = pyproject["project"]["dependencies"]
pydantic_dep = None
for dep in dependencies:
if dep.startswith("pydantic") and not dep.startswith("pydantic-settings"):
pydantic_dep = dep
break
assert pydantic_dep is not None, "pydantic dependency not found in pyproject.toml"
# Check that it uses >= and <3.0.0 instead of ~=
assert "~=" not in pydantic_dep, (
f"pydantic dependency should use >= instead of ~= for flexibility. "
f"Found: {pydantic_dep}"
)
assert ">=" in pydantic_dep, (
f"pydantic dependency should use >= for minimum version. Found: {pydantic_dep}"
)
assert "<3.0.0" in pydantic_dep, (
f"pydantic dependency should have <3.0.0 upper bound. Found: {pydantic_dep}"
)
def test_pydantic_settings_dependency_allows_minor_updates():
"""Test that pydantic-settings dependency allows minor version updates within v2."""
pyproject = parse_pyproject()
dependencies = pyproject["project"]["dependencies"]
pydantic_settings_dep = None
for dep in dependencies:
if dep.startswith("pydantic-settings"):
pydantic_settings_dep = dep
break
assert (
pydantic_settings_dep is not None
), "pydantic-settings dependency not found in pyproject.toml"
# Check that it uses >= and <3.0.0 instead of ~=
assert "~=" not in pydantic_settings_dep, (
f"pydantic-settings dependency should use >= instead of ~= for flexibility. "
f"Found: {pydantic_settings_dep}"
)
assert ">=" in pydantic_settings_dep, (
f"pydantic-settings dependency should use >= for minimum version. "
f"Found: {pydantic_settings_dep}"
)
def test_core_dependencies_use_flexible_constraints():
"""Test that core dependencies use >= instead of ~= for flexibility.
The ~= operator is too restrictive for most dependencies and can cause
conflicts with third-party packages.
"""
pyproject = parse_pyproject()
dependencies = pyproject["project"]["dependencies"]
# These are core dependencies that should use flexible constraints
core_deps = [
"openai",
"pydantic",
"opentelemetry-api",
"opentelemetry-sdk",
"click",
]
for core_dep in core_deps:
matching_dep = None
for dep in dependencies:
if dep.startswith(core_dep):
matching_dep = dep
break
if matching_dep:
assert "~=" not in matching_dep, (
f"{core_dep} dependency should use >= instead of ~= for flexibility. "
f"Found: {matching_dep}"
)
def test_no_overly_restrictive_pinning():
"""Test that dependencies don't use overly restrictive pinning.
Dependencies should not use == (exact version) or ~= (compatible release)
unless there's a specific reason documented.
"""
pyproject = parse_pyproject()
dependencies = pyproject["project"]["dependencies"]
for dep in dependencies:
# Skip comments
if dep.strip().startswith("#"):
continue
# Check for exact version pinning (==)
# Allow == only if there's a known reason
if "==" in dep:
# Currently no dependencies should use ==
assert False, (
f"Dependency uses exact version pinning (==) which is too restrictive: {dep}"
)
# Check for compatible release (~=)
if "~=" in dep:
assert False, (
f"Dependency uses compatible release (~=) which can be too restrictive: {dep}. "
f"Consider using >= instead."
)