fix: remove crewai dependency and add import guards in crewai components (#8923)

This commit is contained in:
Gabriel Luiz Freitas Almeida 2025-07-08 08:41:53 -03:00
commit 24db0cdc73
14 changed files with 716 additions and 951 deletions

View file

@ -104,11 +104,10 @@ dependencies = [
"sseclient-py==1.8.0", "sseclient-py==1.8.0",
"arize-phoenix-otel>=0.6.1", "arize-phoenix-otel>=0.6.1",
"openinference-instrumentation-langchain>=0.1.29", "openinference-instrumentation-langchain>=0.1.29",
"crewai==0.102.0", # "crewai>=0.126.0",
"mcp>=1.10.1", "mcp>=1.10.1",
"uv>=0.5.7", "uv>=0.5.7",
"scipy>=1.14.1", "scipy>=1.14.1",
"ag2>=0.1.0",
"scrapegraph-py>=1.12.0", "scrapegraph-py>=1.12.0",
"pydantic-ai>=0.0.19", "pydantic-ai>=0.0.19",
"smolagents>=1.8.0", "smolagents>=1.8.0",

View file

@ -2,10 +2,6 @@ from collections.abc import Callable
from typing import Any, cast from typing import Any, cast
import litellm import litellm
from crewai import LLM, Agent, Crew, Process, Task
from crewai.task import TaskOutput
from crewai.tools.base_tool import Tool
from langchain_core.agents import AgentAction, AgentFinish
from pydantic import SecretStr from pydantic import SecretStr
from langflow.custom.custom_component.component import Component from langflow.custom.custom_component.component import Component
@ -45,7 +41,7 @@ def _find_api_key(model):
return None return None
def convert_llm(llm: Any, excluded_keys=None) -> LLM: def convert_llm(llm: Any, excluded_keys=None):
"""Converts a LangChain LLM object to a CrewAI-compatible LLM object. """Converts a LangChain LLM object to a CrewAI-compatible LLM object.
Args: Args:
@ -55,6 +51,12 @@ def convert_llm(llm: Any, excluded_keys=None) -> LLM:
Returns: Returns:
A CrewAI-compatible LLM object A CrewAI-compatible LLM object
""" """
try:
from crewai import LLM
except ImportError as e:
msg = "CrewAI is not installed. Please install it with `uv pip install crewai`."
raise ImportError(msg) from e
if not llm: if not llm:
return None return None
@ -109,6 +111,12 @@ def convert_tools(tools):
Returns: Returns:
A CrewAI-compatible tools list. A CrewAI-compatible tools list.
""" """
try:
from crewai.tools.base_tool import Tool
except ImportError as e:
msg = "CrewAI is not installed. Please install it with `uv pip install crewai`."
raise ImportError(msg) from e
if not tools: if not tools:
return [] return []
@ -142,12 +150,12 @@ class BaseCrewComponent(Component):
] ]
# Model properties to exclude when creating a CrewAI LLM object # Model properties to exclude when creating a CrewAI LLM object
manager_llm: LLM | None manager_llm = None
def task_is_valid(self, task_data: Data, crew_type: Process) -> Task: def task_is_valid(self, task_data: Data, crew_type) -> bool:
return "task_type" in task_data and task_data.task_type == crew_type return "task_type" in task_data and task_data.task_type == crew_type
def get_tasks_and_agents(self, agents_list=None) -> tuple[list[Task], list[Agent]]: def get_tasks_and_agents(self, agents_list=None) -> tuple[list, list]:
# Allow passing a custom list of agents # Allow passing a custom list of agents
if not agents_list: if not agents_list:
agents_list = self.agents or [] agents_list = self.agents or []
@ -160,7 +168,7 @@ class BaseCrewComponent(Component):
return self.tasks, agents_list return self.tasks, agents_list
def get_manager_llm(self) -> LLM | None: def get_manager_llm(self):
if not self.manager_llm: if not self.manager_llm:
return None return None
@ -168,13 +176,19 @@ class BaseCrewComponent(Component):
return self.manager_llm return self.manager_llm
def build_crew(self) -> Crew: def build_crew(self):
msg = "build_crew must be implemented in subclasses" msg = "build_crew must be implemented in subclasses"
raise NotImplementedError(msg) raise NotImplementedError(msg)
def get_task_callback( def get_task_callback(
self, self,
) -> Callable: ) -> Callable:
try:
from crewai.task import TaskOutput
except ImportError as e:
msg = "CrewAI is not installed. Please install it with `uv pip install crewai`."
raise ImportError(msg) from e
def task_callback(task_output: TaskOutput) -> None: def task_callback(task_output: TaskOutput) -> None:
vertex_id = self._vertex.id if self._vertex else self.display_name or self.__class__.__name__ vertex_id = self._vertex.id if self._vertex else self.display_name or self.__class__.__name__
self.log(task_output.model_dump(), name=f"Task (Agent: {task_output.agent}) - {vertex_id}") self.log(task_output.model_dump(), name=f"Task (Agent: {task_output.agent}) - {vertex_id}")
@ -184,7 +198,13 @@ class BaseCrewComponent(Component):
def get_step_callback( def get_step_callback(
self, self,
) -> Callable: ) -> Callable:
def step_callback(agent_output: AgentFinish | list[tuple[AgentAction, str]]) -> None: try:
from langchain_core.agents import AgentFinish
except ImportError as e:
msg = "langchain_core is not installed. Please install it with `uv pip install langchain-core`."
raise ImportError(msg) from e
def step_callback(agent_output) -> None:
id_ = self._vertex.id if self._vertex else self.display_name id_ = self._vertex.id if self._vertex else self.display_name
if isinstance(agent_output, AgentFinish): if isinstance(agent_output, AgentFinish):
messages = agent_output.messages messages = agent_output.messages

View file

@ -1,4 +1,7 @@
from crewai import Task try:
from crewai import Task
except ImportError:
Task = object
class SequentialTask(Task): class SequentialTask(Task):

View file

@ -1,5 +1,3 @@
from crewai import Agent
from langflow.base.agents.crewai.crew import convert_llm, convert_tools from langflow.base.agents.crewai.crew import convert_llm, convert_tools
from langflow.custom.custom_component.component import Component from langflow.custom.custom_component.component import Component
from langflow.io import BoolInput, DictInput, HandleInput, MultilineInput, Output from langflow.io import BoolInput, DictInput, HandleInput, MultilineInput, Output
@ -22,6 +20,7 @@ class CrewAIAgentComponent(Component):
description = "Represents an agent of CrewAI." description = "Represents an agent of CrewAI."
documentation: str = "https://docs.crewai.com/how-to/LLM-Connections/" documentation: str = "https://docs.crewai.com/how-to/LLM-Connections/"
icon = "CrewAI" icon = "CrewAI"
legacy = True
inputs = [ inputs = [
MultilineInput(name="role", display_name="Role", info="The role of the agent."), MultilineInput(name="role", display_name="Role", info="The role of the agent."),
@ -80,7 +79,13 @@ class CrewAIAgentComponent(Component):
Output(display_name="Agent", name="output", method="build_output"), Output(display_name="Agent", name="output", method="build_output"),
] ]
def build_output(self) -> Agent: def build_output(self):
try:
from crewai import Agent
except ImportError as e:
msg = "CrewAI is not installed. Please install it with `uv pip install crewai`."
raise ImportError(msg) from e
kwargs = self.kwargs or {} kwargs = self.kwargs or {}
# Define the Agent # Define the Agent

View file

@ -1,5 +1,3 @@
from crewai import Crew, Process
from langflow.base.agents.crewai.crew import BaseCrewComponent from langflow.base.agents.crewai.crew import BaseCrewComponent
from langflow.io import HandleInput from langflow.io import HandleInput
@ -11,6 +9,7 @@ class HierarchicalCrewComponent(BaseCrewComponent):
) )
documentation: str = "https://docs.crewai.com/how-to/Hierarchical/" documentation: str = "https://docs.crewai.com/how-to/Hierarchical/"
icon = "CrewAI" icon = "CrewAI"
legacy = True
inputs = [ inputs = [
*BaseCrewComponent._base_inputs, *BaseCrewComponent._base_inputs,
@ -20,7 +19,13 @@ class HierarchicalCrewComponent(BaseCrewComponent):
HandleInput(name="manager_agent", display_name="Manager Agent", input_types=["Agent"], required=False), HandleInput(name="manager_agent", display_name="Manager Agent", input_types=["Agent"], required=False),
] ]
def build_crew(self) -> Crew: def build_crew(self):
try:
from crewai import Crew, Process
except ImportError as e:
msg = "CrewAI is not installed. Please install it with `uv pip install crewai`."
raise ImportError(msg) from e
tasks, agents = self.get_tasks_and_agents() tasks, agents = self.get_tasks_and_agents()
manager_llm = self.get_manager_llm() manager_llm = self.get_manager_llm()

View file

@ -7,6 +7,7 @@ class HierarchicalTaskComponent(Component):
display_name: str = "Hierarchical Task" display_name: str = "Hierarchical Task"
description: str = "Each task must have a description, an expected output and an agent responsible for execution." description: str = "Each task must have a description, an expected output and an agent responsible for execution."
icon = "CrewAI" icon = "CrewAI"
legacy = True
inputs = [ inputs = [
MultilineInput( MultilineInput(
name="task_description", name="task_description",

View file

@ -1,5 +1,3 @@
from crewai import Agent, Crew, Process, Task
from langflow.base.agents.crewai.crew import BaseCrewComponent from langflow.base.agents.crewai.crew import BaseCrewComponent
from langflow.io import HandleInput from langflow.io import HandleInput
from langflow.schema.message import Message from langflow.schema.message import Message
@ -10,6 +8,7 @@ class SequentialCrewComponent(BaseCrewComponent):
description: str = "Represents a group of agents with tasks that are executed sequentially." description: str = "Represents a group of agents with tasks that are executed sequentially."
documentation: str = "https://docs.crewai.com/how-to/Sequential/" documentation: str = "https://docs.crewai.com/how-to/Sequential/"
icon = "CrewAI" icon = "CrewAI"
legacy = True
inputs = [ inputs = [
*BaseCrewComponent._base_inputs, *BaseCrewComponent._base_inputs,
@ -17,11 +16,11 @@ class SequentialCrewComponent(BaseCrewComponent):
] ]
@property @property
def agents(self: "SequentialCrewComponent") -> list[Agent]: def agents(self: "SequentialCrewComponent") -> list:
# Derive agents directly from linked tasks # Derive agents directly from linked tasks
return [task.agent for task in self.tasks if hasattr(task, "agent")] return [task.agent for task in self.tasks if hasattr(task, "agent")]
def get_tasks_and_agents(self, agents_list=None) -> tuple[list[Task], list[Agent]]: def get_tasks_and_agents(self, agents_list=None) -> tuple[list, list]:
# Use the agents property to derive agents # Use the agents property to derive agents
if not agents_list: if not agents_list:
existing_agents = self.agents existing_agents = self.agents
@ -30,6 +29,12 @@ class SequentialCrewComponent(BaseCrewComponent):
return super().get_tasks_and_agents(agents_list=agents_list) return super().get_tasks_and_agents(agents_list=agents_list)
def build_crew(self) -> Message: def build_crew(self) -> Message:
try:
from crewai import Crew, Process
except ImportError as e:
msg = "CrewAI is not installed. Please install it with `uv pip install crewai`."
raise ImportError(msg) from e
tasks, agents = self.get_tasks_and_agents() tasks, agents = self.get_tasks_and_agents()
return Crew( return Crew(

View file

@ -7,6 +7,7 @@ class SequentialTaskComponent(Component):
display_name: str = "Sequential Task" display_name: str = "Sequential Task"
description: str = "Each task must have a description, an expected output and an agent responsible for execution." description: str = "Each task must have a description, an expected output and an agent responsible for execution."
icon = "CrewAI" icon = "CrewAI"
legacy = True
inputs = [ inputs = [
MultilineInput( MultilineInput(
name="task_description", name="task_description",
@ -65,7 +66,7 @@ class SequentialTaskComponent(Component):
tasks.append(task) tasks.append(task)
self.status = task self.status = task
if self.task: if self.task:
if isinstance(self.task, list) and all(isinstance(task, SequentialTask) for task in self.task): if isinstance(self.task, list) and all(isinstance(task_item, SequentialTask) for task_item in self.task):
tasks = self.task + tasks tasks = self.task + tasks
elif isinstance(self.task, SequentialTask): elif isinstance(self.task, SequentialTask):
tasks = [self.task, *tasks] tasks = [self.task, *tasks]

View file

@ -1,5 +1,3 @@
from crewai import Agent, Task
from langflow.base.agents.crewai.tasks import SequentialTask from langflow.base.agents.crewai.tasks import SequentialTask
from langflow.custom.custom_component.component import Component from langflow.custom.custom_component.component import Component
from langflow.io import BoolInput, DictInput, HandleInput, MultilineInput, Output from langflow.io import BoolInput, DictInput, HandleInput, MultilineInput, Output
@ -10,6 +8,7 @@ class SequentialTaskAgentComponent(Component):
description = "Creates a CrewAI Task and its associated Agent." description = "Creates a CrewAI Task and its associated Agent."
documentation = "https://docs.crewai.com/how-to/LLM-Connections/" documentation = "https://docs.crewai.com/how-to/LLM-Connections/"
icon = "CrewAI" icon = "CrewAI"
legacy = True
inputs = [ inputs = [
# Agent inputs # Agent inputs
@ -105,6 +104,12 @@ class SequentialTaskAgentComponent(Component):
] ]
def build_agent_and_task(self) -> list[SequentialTask]: def build_agent_and_task(self) -> list[SequentialTask]:
try:
from crewai import Agent, Task
except ImportError as e:
msg = "CrewAI is not installed. Please install it with `uv pip install crewai`."
raise ImportError(msg) from e
# Build the agent # Build the agent
agent_kwargs = self.agent_kwargs or {} agent_kwargs = self.agent_kwargs or {}
agent = Agent( agent = Agent(

View file

@ -900,7 +900,9 @@ class Component(CustomComponent):
def _get_method_return_type(self, method_name: str) -> list[str]: def _get_method_return_type(self, method_name: str) -> list[str]:
method = getattr(self, method_name) method = getattr(self, method_name)
return_type = get_type_hints(method)["return"] return_type = get_type_hints(method).get("return")
if return_type is None:
return []
extracted_return_types = self._extract_return_type(return_type) extracted_return_types = self._extract_return_type(return_type)
return [format_type(extracted_return_type) for extracted_return_type in extracted_return_types] return [format_type(extracted_return_type) for extracted_return_type in extracted_return_types]

View file

@ -2,9 +2,7 @@ from .starter_projects import (
basic_prompting_graph, basic_prompting_graph,
blog_writer_graph, blog_writer_graph,
document_qa_graph, document_qa_graph,
hierarchical_tasks_agent_graph,
memory_chatbot_graph, memory_chatbot_graph,
sequential_tasks_agent_graph,
vector_store_rag_graph, vector_store_rag_graph,
) )
@ -16,8 +14,6 @@ def get_starter_projects_graphs():
document_qa_graph(), document_qa_graph(),
memory_chatbot_graph(), memory_chatbot_graph(),
vector_store_rag_graph(), vector_store_rag_graph(),
sequential_tasks_agent_graph(),
hierarchical_tasks_agent_graph(),
] ]

View file

@ -6,5 +6,5 @@ async def test_get_starter_projects(client: AsyncClient, logged_in_headers):
response = await client.get("api/v1/starter-projects/", headers=logged_in_headers) response = await client.get("api/v1/starter-projects/", headers=logged_in_headers)
result = response.json() result = response.json()
assert response.status_code == status.HTTP_200_OK assert response.status_code == status.HTTP_200_OK, response.text
assert isinstance(result, list), "The result must be a list" assert isinstance(result, list), "The result must be a list"

View file

@ -9,6 +9,14 @@ from langflow.schema import dotdict
from langflow.template import Output from langflow.template import Output
from typing_extensions import override from typing_extensions import override
crewai_available = False
try:
import crewai # noqa: F401
crewai_available = True
except ImportError:
pass
def test_set_invalid_output(): def test_set_invalid_output():
chatinput = ChatInput() chatinput = ChatInput()
@ -17,6 +25,7 @@ def test_set_invalid_output():
chatoutput.set(input_value=chatinput.build_config) chatoutput.set(input_value=chatinput.build_config)
@pytest.mark.skipif(not crewai_available, reason="CrewAI is not installed")
def test_set_component(): def test_set_component():
crewai_agent = CrewAIAgentComponent() crewai_agent = CrewAIAgentComponent()
task = SequentialTaskComponent() task = SequentialTaskComponent()

1550
uv.lock generated

File diff suppressed because it is too large Load diff