fix: remove crewai dependency and add import guards in crewai components (#8923)
This commit is contained in:
parent
8fdd23a9b6
commit
24db0cdc73
14 changed files with 716 additions and 951 deletions
|
|
@ -104,11 +104,10 @@ dependencies = [
|
|||
"sseclient-py==1.8.0",
|
||||
"arize-phoenix-otel>=0.6.1",
|
||||
"openinference-instrumentation-langchain>=0.1.29",
|
||||
"crewai==0.102.0",
|
||||
# "crewai>=0.126.0",
|
||||
"mcp>=1.10.1",
|
||||
"uv>=0.5.7",
|
||||
"scipy>=1.14.1",
|
||||
"ag2>=0.1.0",
|
||||
"scrapegraph-py>=1.12.0",
|
||||
"pydantic-ai>=0.0.19",
|
||||
"smolagents>=1.8.0",
|
||||
|
|
|
|||
|
|
@ -2,10 +2,6 @@ from collections.abc import Callable
|
|||
from typing import Any, cast
|
||||
|
||||
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 langflow.custom.custom_component.component import Component
|
||||
|
|
@ -45,7 +41,7 @@ def _find_api_key(model):
|
|||
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.
|
||||
|
||||
Args:
|
||||
|
|
@ -55,6 +51,12 @@ def convert_llm(llm: Any, excluded_keys=None) -> LLM:
|
|||
Returns:
|
||||
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:
|
||||
return None
|
||||
|
||||
|
|
@ -109,6 +111,12 @@ def convert_tools(tools):
|
|||
Returns:
|
||||
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:
|
||||
return []
|
||||
|
||||
|
|
@ -142,12 +150,12 @@ class BaseCrewComponent(Component):
|
|||
]
|
||||
|
||||
# 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
|
||||
|
||||
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
|
||||
if not agents_list:
|
||||
agents_list = self.agents or []
|
||||
|
|
@ -160,7 +168,7 @@ class BaseCrewComponent(Component):
|
|||
|
||||
return self.tasks, agents_list
|
||||
|
||||
def get_manager_llm(self) -> LLM | None:
|
||||
def get_manager_llm(self):
|
||||
if not self.manager_llm:
|
||||
return None
|
||||
|
||||
|
|
@ -168,13 +176,19 @@ class BaseCrewComponent(Component):
|
|||
|
||||
return self.manager_llm
|
||||
|
||||
def build_crew(self) -> Crew:
|
||||
def build_crew(self):
|
||||
msg = "build_crew must be implemented in subclasses"
|
||||
raise NotImplementedError(msg)
|
||||
|
||||
def get_task_callback(
|
||||
self,
|
||||
) -> 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:
|
||||
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}")
|
||||
|
|
@ -184,7 +198,13 @@ class BaseCrewComponent(Component):
|
|||
def get_step_callback(
|
||||
self,
|
||||
) -> 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
|
||||
if isinstance(agent_output, AgentFinish):
|
||||
messages = agent_output.messages
|
||||
|
|
|
|||
|
|
@ -1,4 +1,7 @@
|
|||
from crewai import Task
|
||||
try:
|
||||
from crewai import Task
|
||||
except ImportError:
|
||||
Task = object
|
||||
|
||||
|
||||
class SequentialTask(Task):
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
from crewai import Agent
|
||||
|
||||
from langflow.base.agents.crewai.crew import convert_llm, convert_tools
|
||||
from langflow.custom.custom_component.component import Component
|
||||
from langflow.io import BoolInput, DictInput, HandleInput, MultilineInput, Output
|
||||
|
|
@ -22,6 +20,7 @@ class CrewAIAgentComponent(Component):
|
|||
description = "Represents an agent of CrewAI."
|
||||
documentation: str = "https://docs.crewai.com/how-to/LLM-Connections/"
|
||||
icon = "CrewAI"
|
||||
legacy = True
|
||||
|
||||
inputs = [
|
||||
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"),
|
||||
]
|
||||
|
||||
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 {}
|
||||
|
||||
# Define the Agent
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
from crewai import Crew, Process
|
||||
|
||||
from langflow.base.agents.crewai.crew import BaseCrewComponent
|
||||
from langflow.io import HandleInput
|
||||
|
||||
|
|
@ -11,6 +9,7 @@ class HierarchicalCrewComponent(BaseCrewComponent):
|
|||
)
|
||||
documentation: str = "https://docs.crewai.com/how-to/Hierarchical/"
|
||||
icon = "CrewAI"
|
||||
legacy = True
|
||||
|
||||
inputs = [
|
||||
*BaseCrewComponent._base_inputs,
|
||||
|
|
@ -20,7 +19,13 @@ class HierarchicalCrewComponent(BaseCrewComponent):
|
|||
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()
|
||||
manager_llm = self.get_manager_llm()
|
||||
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ class HierarchicalTaskComponent(Component):
|
|||
display_name: str = "Hierarchical Task"
|
||||
description: str = "Each task must have a description, an expected output and an agent responsible for execution."
|
||||
icon = "CrewAI"
|
||||
legacy = True
|
||||
inputs = [
|
||||
MultilineInput(
|
||||
name="task_description",
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
from crewai import Agent, Crew, Process, Task
|
||||
|
||||
from langflow.base.agents.crewai.crew import BaseCrewComponent
|
||||
from langflow.io import HandleInput
|
||||
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."
|
||||
documentation: str = "https://docs.crewai.com/how-to/Sequential/"
|
||||
icon = "CrewAI"
|
||||
legacy = True
|
||||
|
||||
inputs = [
|
||||
*BaseCrewComponent._base_inputs,
|
||||
|
|
@ -17,11 +16,11 @@ class SequentialCrewComponent(BaseCrewComponent):
|
|||
]
|
||||
|
||||
@property
|
||||
def agents(self: "SequentialCrewComponent") -> list[Agent]:
|
||||
def agents(self: "SequentialCrewComponent") -> list:
|
||||
# Derive agents directly from linked tasks
|
||||
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
|
||||
if not agents_list:
|
||||
existing_agents = self.agents
|
||||
|
|
@ -30,6 +29,12 @@ class SequentialCrewComponent(BaseCrewComponent):
|
|||
return super().get_tasks_and_agents(agents_list=agents_list)
|
||||
|
||||
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()
|
||||
|
||||
return Crew(
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ class SequentialTaskComponent(Component):
|
|||
display_name: str = "Sequential Task"
|
||||
description: str = "Each task must have a description, an expected output and an agent responsible for execution."
|
||||
icon = "CrewAI"
|
||||
legacy = True
|
||||
inputs = [
|
||||
MultilineInput(
|
||||
name="task_description",
|
||||
|
|
@ -65,7 +66,7 @@ class SequentialTaskComponent(Component):
|
|||
tasks.append(task)
|
||||
self.status = 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
|
||||
elif isinstance(self.task, SequentialTask):
|
||||
tasks = [self.task, *tasks]
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
from crewai import Agent, Task
|
||||
|
||||
from langflow.base.agents.crewai.tasks import SequentialTask
|
||||
from langflow.custom.custom_component.component import Component
|
||||
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."
|
||||
documentation = "https://docs.crewai.com/how-to/LLM-Connections/"
|
||||
icon = "CrewAI"
|
||||
legacy = True
|
||||
|
||||
inputs = [
|
||||
# Agent inputs
|
||||
|
|
@ -105,6 +104,12 @@ class SequentialTaskAgentComponent(Component):
|
|||
]
|
||||
|
||||
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
|
||||
agent_kwargs = self.agent_kwargs or {}
|
||||
agent = Agent(
|
||||
|
|
|
|||
|
|
@ -900,7 +900,9 @@ class Component(CustomComponent):
|
|||
|
||||
def _get_method_return_type(self, method_name: str) -> list[str]:
|
||||
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)
|
||||
return [format_type(extracted_return_type) for extracted_return_type in extracted_return_types]
|
||||
|
||||
|
|
|
|||
|
|
@ -2,9 +2,7 @@ from .starter_projects import (
|
|||
basic_prompting_graph,
|
||||
blog_writer_graph,
|
||||
document_qa_graph,
|
||||
hierarchical_tasks_agent_graph,
|
||||
memory_chatbot_graph,
|
||||
sequential_tasks_agent_graph,
|
||||
vector_store_rag_graph,
|
||||
)
|
||||
|
||||
|
|
@ -16,8 +14,6 @@ def get_starter_projects_graphs():
|
|||
document_qa_graph(),
|
||||
memory_chatbot_graph(),
|
||||
vector_store_rag_graph(),
|
||||
sequential_tasks_agent_graph(),
|
||||
hierarchical_tasks_agent_graph(),
|
||||
]
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
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"
|
||||
|
|
|
|||
|
|
@ -9,6 +9,14 @@ from langflow.schema import dotdict
|
|||
from langflow.template import Output
|
||||
from typing_extensions import override
|
||||
|
||||
crewai_available = False
|
||||
try:
|
||||
import crewai # noqa: F401
|
||||
|
||||
crewai_available = True
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
|
||||
def test_set_invalid_output():
|
||||
chatinput = ChatInput()
|
||||
|
|
@ -17,6 +25,7 @@ def test_set_invalid_output():
|
|||
chatoutput.set(input_value=chatinput.build_config)
|
||||
|
||||
|
||||
@pytest.mark.skipif(not crewai_available, reason="CrewAI is not installed")
|
||||
def test_set_component():
|
||||
crewai_agent = CrewAIAgentComponent()
|
||||
task = SequentialTaskComponent()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue