Skip to content

Commit 73629e2

Browse files
fix(crewai): support crewai 1.x LLM factory in convert_llm (#14397)
* fix(crewai): support crewai 1.x LLM factory * test(crewai): cover BaseLLM conversion without dependency --------- Co-authored-by: Eric Hare <ericrhare@gmail.com>
1 parent f69be28 commit 73629e2

3 files changed

Lines changed: 108 additions & 3 deletions

File tree

src/lfx/src/lfx/base/agents/crewai/crew.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -52,16 +52,16 @@ def convert_llm(llm: Any, excluded_keys=None):
5252
A CrewAI-compatible LLM object
5353
"""
5454
try:
55-
from crewai import LLM
55+
from crewai import LLM, BaseLLM
5656
except ImportError as e:
5757
msg = "CrewAI is not installed. Please install it with `uv pip install crewai`."
5858
raise ImportError(msg) from e
5959

6060
if not llm:
6161
return None
6262

63-
# Check if this is already an LLM object
64-
if isinstance(llm, LLM):
63+
# From crewai 1.0 `LLM(...)` returns provider-specific BaseLLM subclasses, not LLM instances.
64+
if isinstance(llm, BaseLLM):
6565
return llm
6666

6767
# Check if we should use model_name model, or something else
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
"""Tests for CrewAI LLM conversion across supported crewai versions.
2+
3+
Regression coverage for LE-2092: with crewai >= 1.0, ``crewai.LLM(...)`` is a
4+
factory that returns provider-specific subclasses of ``BaseLLM`` (for example
5+
``OpenAICompletion``) which are *not* instances of ``crewai.LLM``. A guard that
6+
tests ``isinstance(llm, LLM)`` therefore stops being idempotent, and the second
7+
conversion pass performed by ``BaseCrewComponent.get_tasks_and_agents`` falls
8+
through to the LangChain branch and raises
9+
``AttributeError: 'OpenAICompletion' object has no attribute 'get_lc_namespace'``.
10+
11+
crewai is an optional dependency that is not installable in the default
12+
workspace (documented httpx conflict), so these tests skip when it is absent.
13+
They were run against crewai 0.126.0, 0.134.0 and 1.15.10.
14+
"""
15+
16+
import pytest
17+
18+
pytest.importorskip("crewai", reason="crewai is an optional dependency")
19+
20+
from crewai import BaseLLM
21+
from lfx.base.agents.crewai.crew import convert_llm
22+
23+
API_KEY = "sk-component-level-key"
24+
MODEL_NAME = "gpt-4o-mini"
25+
26+
27+
@pytest.fixture
28+
def langchain_model():
29+
langchain_openai = pytest.importorskip("langchain_openai")
30+
return langchain_openai.ChatOpenAI(model=MODEL_NAME, api_key=API_KEY)
31+
32+
33+
def test_should_build_crewai_llm_when_given_langchain_model(langchain_model):
34+
# Act
35+
converted = convert_llm(langchain_model)
36+
37+
# Assert
38+
assert isinstance(converted, BaseLLM)
39+
assert converted.api_key == API_KEY
40+
41+
42+
def test_should_return_same_object_when_llm_is_already_crewai_native(langchain_model):
43+
"""The crew re-converts every agent LLM, so conversion must be idempotent."""
44+
# Arrange
45+
converted = convert_llm(langchain_model)
46+
47+
# Act
48+
reconverted = convert_llm(converted)
49+
50+
# Assert
51+
assert reconverted is converted
52+
assert reconverted.api_key == API_KEY
53+
54+
55+
def test_should_preserve_agent_llm_when_crew_reconverts_it(langchain_model):
56+
"""Reproduces the Sequential Task Agent -> Sequential Crew failure path."""
57+
# Arrange
58+
from crewai import Agent
59+
from lfx.components.crewai.sequential_crew import SequentialCrewComponent
60+
61+
agent = Agent(
62+
role="Researcher",
63+
goal="Research the topic",
64+
backstory="An experienced researcher",
65+
llm=convert_llm(langchain_model),
66+
)
67+
crew_component = SequentialCrewComponent()
68+
69+
# Act
70+
_tasks, agents = crew_component.get_tasks_and_agents(agents_list=[agent])
71+
72+
# Assert
73+
assert isinstance(agents[0].llm, BaseLLM)
74+
assert agents[0].llm.api_key == API_KEY
75+
76+
77+
def test_should_return_none_when_llm_is_missing():
78+
assert convert_llm(None) is None
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
"""Dependency-free regression tests for CrewAI LLM conversion."""
2+
3+
import sys
4+
from types import ModuleType
5+
6+
from lfx.base.agents.crewai.crew import convert_llm
7+
8+
9+
def test_should_return_same_object_for_crewai_base_llm(monkeypatch):
10+
class FakeBaseLLM:
11+
pass
12+
13+
class FakeLLM(FakeBaseLLM):
14+
pass
15+
16+
class FakeProviderLLM(FakeBaseLLM):
17+
pass
18+
19+
fake_crewai = ModuleType("crewai")
20+
fake_crewai.BaseLLM = FakeBaseLLM
21+
fake_crewai.LLM = FakeLLM
22+
monkeypatch.setitem(sys.modules, "crewai", fake_crewai)
23+
24+
provider_llm = FakeProviderLLM()
25+
26+
assert not isinstance(provider_llm, FakeLLM)
27+
assert convert_llm(provider_llm) is provider_llm

0 commit comments

Comments
 (0)