Skip to content

Commit 19798bb

Browse files
committed
fix(sdk-python): support both old and new crewai import paths (CopilotKit#3268)
1 parent 4bf09a1 commit 19798bb

3 files changed

Lines changed: 134 additions & 8 deletions

File tree

sdk-python/copilotkit/crewai/crewai_sdk.py

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,22 @@
1616
)
1717
from litellm.litellm_core_utils.streaming_handler import CustomStreamWrapper
1818
from crewai.flow.flow import FlowState, Flow
19-
from crewai.utilities.events.flow_events import (
20-
FlowEvent as CrewAIFlowEvent,
21-
FlowStartedEvent,
22-
MethodExecutionStartedEvent,
23-
MethodExecutionFinishedEvent,
24-
FlowFinishedEvent,
25-
)
19+
try:
20+
from crewai.utilities.events.flow_events import (
21+
FlowEvent as CrewAIFlowEvent,
22+
FlowStartedEvent,
23+
MethodExecutionStartedEvent,
24+
MethodExecutionFinishedEvent,
25+
FlowFinishedEvent,
26+
)
27+
except ImportError:
28+
from crewai.events.types.flow_events import ( # type: ignore[no-redef]
29+
FlowEvent as CrewAIFlowEvent,
30+
FlowStartedEvent,
31+
MethodExecutionStartedEvent,
32+
MethodExecutionFinishedEvent,
33+
FlowFinishedEvent,
34+
)
2635
from crewai.utilities.events import crewai_event_bus as _crewai_event_bus
2736

2837
from copilotkit.types import Message

sdk-python/pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ include = ["copilotkit/*.json"]
2323
python = ">=3.10,<3.13"
2424
langgraph = { version = ">=0.3.25,<2" }
2525
langchain = { version = ">=0.3.0" }
26-
crewai = { version = "0.118.0", optional = true }
26+
crewai = { version = ">=0.118.0", optional = true, python = ">=3.10,<3.13" }
2727
ag-ui-langgraph = { version = ">=0.0.29,<0.0.32", extras = ["fastapi"] }
2828
ag-ui-protocol = ">=0.1.11"
2929
fastapi = ">=0.115.0,<1.0.0"
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
"""Tests for CrewAI import compatibility (#3268).
2+
3+
When crewai>=0.177.0 is installed, the import path for flow events changed
4+
from crewai.utilities.events.flow_events to crewai.events.types.flow_events.
5+
The fix uses try/except to support both import paths.
6+
"""
7+
8+
import sys
9+
import types
10+
import pytest
11+
from unittest.mock import patch
12+
13+
14+
def _make_fake_module(attrs: dict) -> types.ModuleType:
15+
"""Create a fake module with given attributes."""
16+
mod = types.ModuleType("fake")
17+
for k, v in attrs.items():
18+
setattr(mod, k, v)
19+
return mod
20+
21+
22+
class TestCrewAIImportCompat:
23+
"""Test that crewai_sdk.py can import flow events from either path."""
24+
25+
def test_old_import_path_works(self):
26+
"""When old crewai (<0.177.0) is installed, old import path should work."""
27+
# The old path: crewai.utilities.events.flow_events
28+
# If it exists, the import should succeed
29+
fake_events = _make_fake_module({
30+
"FlowEvent": type("FlowEvent", (), {}),
31+
"FlowStartedEvent": type("FlowStartedEvent", (), {}),
32+
"MethodExecutionStartedEvent": type("MethodExecutionStartedEvent", (), {}),
33+
"MethodExecutionFinishedEvent": type("MethodExecutionFinishedEvent", (), {}),
34+
"FlowFinishedEvent": type("FlowFinishedEvent", (), {}),
35+
})
36+
37+
with patch.dict(sys.modules, {
38+
"crewai.utilities.events.flow_events": fake_events,
39+
}):
40+
# Simulate what our try/except does
41+
try:
42+
from crewai.utilities.events.flow_events import FlowEvent
43+
old_path_works = True
44+
except ImportError:
45+
old_path_works = False
46+
47+
assert old_path_works, "Old import path should work with old crewai"
48+
49+
def test_new_import_path_fallback(self):
50+
"""When new crewai (>=0.177.0) is installed, new import path should work."""
51+
# The new path: crewai.events.types.flow_events
52+
fake_events = _make_fake_module({
53+
"FlowEvent": type("FlowEvent", (), {}),
54+
"FlowStartedEvent": type("FlowStartedEvent", (), {}),
55+
"MethodExecutionStartedEvent": type("MethodExecutionStartedEvent", (), {}),
56+
"MethodExecutionFinishedEvent": type("MethodExecutionFinishedEvent", (), {}),
57+
"FlowFinishedEvent": type("FlowFinishedEvent", (), {}),
58+
})
59+
60+
# Remove old path, add new path
61+
old_mod_key = "crewai.utilities.events.flow_events"
62+
saved = sys.modules.get(old_mod_key)
63+
64+
try:
65+
# Make old path fail
66+
sys.modules[old_mod_key] = None # type: ignore
67+
68+
with patch.dict(sys.modules, {
69+
"crewai.events.types.flow_events": fake_events,
70+
}, clear=False):
71+
# Simulate fallback logic
72+
try:
73+
from crewai.utilities.events.flow_events import FlowEvent # type: ignore
74+
new_path_needed = False
75+
except ImportError:
76+
new_path_needed = True
77+
78+
assert new_path_needed, "Old path should fail, triggering fallback"
79+
80+
from crewai.events.types.flow_events import FlowEvent # type: ignore
81+
assert FlowEvent is not None, "New path should work"
82+
finally:
83+
if saved is not None:
84+
sys.modules[old_mod_key] = saved
85+
elif old_mod_key in sys.modules:
86+
del sys.modules[old_mod_key]
87+
88+
def test_both_paths_fail_raises_import_error(self):
89+
"""When neither path works, ImportError should propagate."""
90+
# This simulates crewai not having flow events at all
91+
old_key = "crewai.utilities.events.flow_events"
92+
new_key = "crewai.events.types.flow_events"
93+
94+
saved_old = sys.modules.get(old_key)
95+
saved_new = sys.modules.get(new_key)
96+
97+
try:
98+
sys.modules[old_key] = None # type: ignore
99+
sys.modules[new_key] = None # type: ignore
100+
101+
with pytest.raises(ImportError):
102+
# Old path
103+
try:
104+
from crewai.utilities.events.flow_events import FlowEvent # type: ignore
105+
except ImportError:
106+
pass
107+
# New path
108+
from crewai.events.types.flow_events import FlowEvent # type: ignore
109+
finally:
110+
if saved_old is not None:
111+
sys.modules[old_key] = saved_old
112+
elif old_key in sys.modules:
113+
del sys.modules[old_key]
114+
if saved_new is not None:
115+
sys.modules[new_key] = saved_new
116+
elif new_key in sys.modules:
117+
del sys.modules[new_key]

0 commit comments

Comments
 (0)