fix: relax openai version constraint to allow 2.x for litellm[proxy] compatibility

Fixes #4079

The openai version constraint was pinned to ~=1.83.0 (>=1.83.0,<1.84.0),
which prevented installation alongside litellm[proxy]>=1.74.9 that requires
openai>=2.8.0.

Changes:
- Updated openai constraint from ~=1.83.0 to >=1.83.0,<3
- Added tests to verify the constraint allows openai 2.8.0
- Added import smoke tests for OpenAI provider compatibility

The new constraint:
- Maintains backward compatibility with openai 1.83.x
- Allows openai 2.x for users who need litellm[proxy]
- Caps at <3 to prevent future breaking changes

Verified that all OpenAI SDK imports used by crewAI's OpenAI provider
work correctly with openai 2.8.0.

Co-Authored-By: João <joao@crewai.com>
This commit is contained in:
Devin AI
2025-12-13 10:01:34 +00:00
parent 38b0b125d3
commit 480ad5bbb2
3 changed files with 4365 additions and 4152 deletions

View File

@@ -10,7 +10,7 @@ requires-python = ">=3.10, <3.14"
dependencies = [
# Core Dependencies
"pydantic~=2.11.9",
"openai~=1.83.0",
"openai>=1.83.0,<3",
"instructor>=1.3.3",
# Text Processing
"pdfplumber~=0.11.4",

View File

@@ -0,0 +1,154 @@
"""Tests for OpenAI SDK version compatibility.
These tests verify that crewAI's openai dependency constraint allows
installation alongside packages that require openai 2.x (like litellm[proxy]).
Related to GitHub issue #4079: CrewAI dependency conflict with litellm[proxy]
"""
import sys
from pathlib import Path
import pytest
from packaging.specifiers import SpecifierSet
def test_openai_version_constraint_allows_2x():
"""Test that the openai version constraint in pyproject.toml allows openai 2.x.
This test verifies the fix for issue #4079 where crewAI could not be installed
alongside litellm[proxy]>=1.74.9 due to conflicting openai version requirements.
The constraint should allow openai>=1.83.0,<3 to support both:
- Existing users on openai 1.83.x
- Users who need openai 2.x for litellm[proxy] compatibility
"""
try:
import tomllib
except ImportError:
import tomli as tomllib
# Find the pyproject.toml file
tests_dir = Path(__file__).parent
pyproject_path = tests_dir.parent / "pyproject.toml"
with open(pyproject_path, "rb") as f:
pyproject = tomllib.load(f)
dependencies = pyproject.get("project", {}).get("dependencies", [])
# Find the openai dependency
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"
# Extract the version specifier from the dependency string
# e.g., "openai>=1.83.0,<3" -> ">=1.83.0,<3"
version_spec = openai_dep.replace("openai", "").strip()
specifier = SpecifierSet(version_spec)
# Test that the specifier allows openai 2.8.0 (required by litellm[proxy])
assert "2.8.0" in specifier, (
f"openai constraint '{openai_dep}' does not allow version 2.8.0 "
"which is required by litellm[proxy]>=1.74.9"
)
# Test that the specifier still allows openai 1.83.0 (backward compatibility)
assert "1.83.0" in specifier, (
f"openai constraint '{openai_dep}' does not allow version 1.83.0 "
"which breaks backward compatibility"
)
# Test that the specifier has an upper bound (to prevent future breaks)
assert "<3" in version_spec or "<3.0" in version_spec, (
f"openai constraint '{openai_dep}' should have an upper bound <3 "
"to prevent future breaking changes"
)
def test_openai_provider_imports_are_valid():
"""Test that all imports used by the OpenAI provider are valid.
This test verifies that the OpenAI SDK exports all the classes and types
that crewAI's OpenAI provider depends on. This ensures compatibility
across different openai SDK versions.
"""
# Test core client imports
from openai import APIConnectionError, AsyncOpenAI, NotFoundError, OpenAI, Stream
assert OpenAI is not None
assert AsyncOpenAI is not None
assert Stream is not None
assert APIConnectionError is not None
assert NotFoundError is not None
# Test streaming imports
from openai.lib.streaming.chat import ChatCompletionStream
assert ChatCompletionStream is not None
# Test type imports
from openai.types.chat import ChatCompletion, ChatCompletionChunk
from openai.types.chat.chat_completion import Choice
from openai.types.chat.chat_completion_chunk import ChoiceDelta
assert ChatCompletion is not None
assert ChatCompletionChunk is not None
assert Choice is not None
assert ChoiceDelta is not None
def test_openai_client_instantiation():
"""Test that OpenAI clients can be instantiated with a test API key.
This verifies that the OpenAI SDK client initialization is compatible
with crewAI's usage patterns.
"""
from openai import AsyncOpenAI, OpenAI
# Test sync client instantiation
client = OpenAI(api_key="test-key-for-instantiation-test")
assert client is not None
assert hasattr(client, "chat")
assert hasattr(client.chat, "completions")
# Test async client instantiation
async_client = AsyncOpenAI(api_key="test-key-for-instantiation-test")
assert async_client is not None
assert hasattr(async_client, "chat")
assert hasattr(async_client.chat, "completions")
def test_openai_completion_provider_can_be_imported():
"""Test that crewAI's OpenAI completion provider can be imported.
This verifies that the OpenAI provider module loads correctly with
the installed openai SDK version.
"""
from crewai.llms.providers.openai.completion import OpenAICompletion
assert OpenAICompletion is not None
def test_openai_completion_provider_instantiation():
"""Test that OpenAICompletion can be instantiated.
This verifies that crewAI's OpenAI provider works correctly with
the installed openai SDK version.
"""
from crewai.llms.providers.openai.completion import OpenAICompletion
# Instantiate with a test API key
completion = OpenAICompletion(
model="gpt-4o",
api_key="test-key-for-instantiation-test",
)
assert completion is not None
assert completion.model == "gpt-4o"
assert completion.client is not None
assert completion.async_client is not None

8361
uv.lock generated

File diff suppressed because it is too large Load Diff