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",
|
"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",
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,7 @@
|
||||||
|
try:
|
||||||
from crewai import Task
|
from crewai import Task
|
||||||
|
except ImportError:
|
||||||
|
Task = object
|
||||||
|
|
||||||
|
|
||||||
class SequentialTask(Task):
|
class SequentialTask(Task):
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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",
|
||||||
|
|
|
||||||
|
|
@ -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(
|
||||||
|
|
|
||||||
|
|
@ -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]
|
||||||
|
|
|
||||||
|
|
@ -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(
|
||||||
|
|
|
||||||
|
|
@ -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]
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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(),
|
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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"
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue