fix: CrewAI-based flows with no extra openai (#4683)
* fix: CrewAI-based flows with no extra openai * [autofix.ci] apply automated fixes * Clean up the location of the crewai model processing * [autofix.ci] apply automated fixes * Properly subclass the tasks and agents method --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
This commit is contained in:
parent
31885175e5
commit
2fa258068d
6 changed files with 140 additions and 29 deletions
|
|
@ -1,9 +1,11 @@
|
|||
from collections.abc import Callable
|
||||
from typing import cast
|
||||
from typing import Any, cast
|
||||
|
||||
from crewai import Agent, Crew, Process, Task
|
||||
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 import Component
|
||||
from langflow.inputs.inputs import HandleInput, InputTypes
|
||||
|
|
@ -13,6 +15,82 @@ from langflow.schema.message import Message
|
|||
from langflow.utils.constants import MESSAGE_SENDER_AI
|
||||
|
||||
|
||||
def _find_api_key(model):
|
||||
"""Attempts to find the API key attribute for a LangChain LLM model instance using partial matching.
|
||||
|
||||
Args:
|
||||
model: LangChain LLM model instance.
|
||||
|
||||
Returns:
|
||||
The API key if found, otherwise None.
|
||||
"""
|
||||
# Define the possible API key attribute patterns
|
||||
key_patterns = ["key", "token"]
|
||||
|
||||
# Iterate over the model attributes
|
||||
for attr in dir(model):
|
||||
attr_lower = attr.lower()
|
||||
|
||||
# Check if the attribute name contains any of the key patterns
|
||||
if any(pattern in attr_lower for pattern in key_patterns):
|
||||
value = getattr(model, attr, None)
|
||||
|
||||
# Check if the value is a non-empty string
|
||||
if isinstance(value, str):
|
||||
return value
|
||||
if isinstance(value, SecretStr):
|
||||
return value.get_secret_value()
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def convert_llm(llm: Any, excluded_keys=None) -> LLM:
|
||||
"""Converts a LangChain LLM object to a CrewAI-compatible LLM object.
|
||||
|
||||
Args:
|
||||
llm: A LangChain LLM object.
|
||||
excluded_keys: A set of keys to exclude from the conversion.
|
||||
|
||||
Returns:
|
||||
A CrewAI-compatible LLM object
|
||||
"""
|
||||
if not llm:
|
||||
return None
|
||||
|
||||
# Check if this is already an LLM object
|
||||
if isinstance(llm, LLM):
|
||||
return llm
|
||||
|
||||
# Retrieve the API Key from the LLM
|
||||
if excluded_keys is None:
|
||||
excluded_keys = {"model", "model_name", "_type", "api_key"}
|
||||
|
||||
# Find the API key in the LLM
|
||||
api_key = _find_api_key(llm)
|
||||
|
||||
# Convert Langchain LLM to CrewAI-compatible LLM object
|
||||
return LLM(
|
||||
model=llm.model_name,
|
||||
api_key=api_key,
|
||||
**{k: v for k, v in llm.dict().items() if k not in excluded_keys},
|
||||
)
|
||||
|
||||
|
||||
def convert_tools(tools):
|
||||
"""Converts LangChain tools to CrewAI-compatible tools.
|
||||
|
||||
Args:
|
||||
tools: A LangChain tools list.
|
||||
|
||||
Returns:
|
||||
A CrewAI-compatible tools list.
|
||||
"""
|
||||
if not tools:
|
||||
return []
|
||||
|
||||
return [Tool.from_langchain(tool) for tool in tools]
|
||||
|
||||
|
||||
class BaseCrewComponent(Component):
|
||||
description: str = (
|
||||
"Represents a group of agents, defining how they should collaborate and the tasks they should perform."
|
||||
|
|
@ -39,12 +117,33 @@ class BaseCrewComponent(Component):
|
|||
Output(display_name="Output", name="output", method="build_output"),
|
||||
]
|
||||
|
||||
# Model properties to exclude when creating a CrewAI LLM object
|
||||
manager_llm: LLM | None
|
||||
|
||||
def task_is_valid(self, task_data: Data, crew_type: Process) -> Task:
|
||||
return "task_type" in task_data and task_data.task_type == crew_type
|
||||
|
||||
def get_tasks_and_agents(self) -> tuple[list[Task], list[Agent]]:
|
||||
def get_tasks_and_agents(self, agents_list=None) -> tuple[list[Task], list[Agent]]:
|
||||
# Allow passing a custom list of agents
|
||||
if not agents_list:
|
||||
agents_list = self.agents or []
|
||||
|
||||
# Set all the agents llm attribute to the crewai llm
|
||||
for agent in agents_list:
|
||||
# Convert Agent LLM and Tools to proper format
|
||||
agent.llm = convert_llm(agent.llm)
|
||||
agent.tools = convert_tools(agent.tools)
|
||||
|
||||
return self.tasks, self.agents
|
||||
|
||||
def get_manager_llm(self) -> LLM | None:
|
||||
if not self.manager_llm:
|
||||
return None
|
||||
|
||||
self.manager_llm = convert_llm(self.manager_llm)
|
||||
|
||||
return self.manager_llm
|
||||
|
||||
def build_crew(self) -> Crew:
|
||||
msg = "build_crew must be implemented in subclasses"
|
||||
raise NotImplementedError(msg)
|
||||
|
|
|
|||
|
|
@ -1,10 +1,23 @@
|
|||
from crewai import Agent
|
||||
|
||||
from langflow.base.agents.crewai.crew import convert_llm, convert_tools
|
||||
from langflow.custom import Component
|
||||
from langflow.io import BoolInput, DictInput, HandleInput, MultilineInput, Output
|
||||
|
||||
|
||||
class CrewAIAgentComponent(Component):
|
||||
"""Component for creating a CrewAI agent.
|
||||
|
||||
This component allows you to create a CrewAI agent with the specified role, goal, backstory, tools,
|
||||
and language model.
|
||||
|
||||
Args:
|
||||
Component (Component): Base class for all components.
|
||||
|
||||
Returns:
|
||||
Agent: CrewAI agent.
|
||||
"""
|
||||
|
||||
display_name = "CrewAI Agent"
|
||||
description = "Represents an agent of CrewAI."
|
||||
documentation: str = "https://docs.crewai.com/how-to/LLM-Connections/"
|
||||
|
|
@ -69,17 +82,21 @@ class CrewAIAgentComponent(Component):
|
|||
|
||||
def build_output(self) -> Agent:
|
||||
kwargs = self.kwargs or {}
|
||||
|
||||
# Define the Agent
|
||||
agent = Agent(
|
||||
role=self.role,
|
||||
goal=self.goal,
|
||||
backstory=self.backstory,
|
||||
llm=self.llm,
|
||||
llm=convert_llm(self.llm),
|
||||
verbose=self.verbose,
|
||||
memory=self.memory,
|
||||
tools=self.tools or [],
|
||||
tools=convert_tools(self.tools),
|
||||
allow_delegation=self.allow_delegation,
|
||||
allow_code_execution=self.allow_code_execution,
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
self.status = repr(agent)
|
||||
|
||||
return agent
|
||||
|
|
|
|||
|
|
@ -1,9 +1,7 @@
|
|||
import os
|
||||
|
||||
from crewai import Crew, Process
|
||||
|
||||
from langflow.base.agents.crewai.crew import BaseCrewComponent
|
||||
from langflow.io import HandleInput, SecretStrInput
|
||||
from langflow.io import HandleInput
|
||||
|
||||
|
||||
class HierarchicalCrewComponent(BaseCrewComponent):
|
||||
|
|
@ -20,20 +18,11 @@ class HierarchicalCrewComponent(BaseCrewComponent):
|
|||
HandleInput(name="tasks", display_name="Tasks", input_types=["HierarchicalTask"], is_list=True),
|
||||
HandleInput(name="manager_llm", display_name="Manager LLM", input_types=["LanguageModel"], required=False),
|
||||
HandleInput(name="manager_agent", display_name="Manager Agent", input_types=["Agent"], required=False),
|
||||
SecretStrInput(
|
||||
name="openai_api_key",
|
||||
display_name="OpenAI API Key",
|
||||
info="The OpenAI API Key to use for the OpenAI model.",
|
||||
value="OPENAI_API_KEY",
|
||||
),
|
||||
]
|
||||
|
||||
def build_crew(self) -> Crew:
|
||||
tasks, agents = self.get_tasks_and_agents()
|
||||
|
||||
# Set the OpenAI API Key
|
||||
if self.openai_api_key:
|
||||
os.environ["OPENAI_API_KEY"] = self.openai_api_key
|
||||
manager_llm = self.get_manager_llm()
|
||||
|
||||
return Crew(
|
||||
agents=agents,
|
||||
|
|
@ -46,7 +35,7 @@ class HierarchicalCrewComponent(BaseCrewComponent):
|
|||
share_crew=self.share_crew,
|
||||
function_calling_llm=self.function_calling_llm,
|
||||
manager_agent=self.manager_agent,
|
||||
manager_llm=self.manager_llm,
|
||||
manager_llm=manager_llm,
|
||||
step_callback=self.get_step_callback(),
|
||||
task_callback=self.get_task_callback(),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -16,11 +16,16 @@ class SequentialCrewComponent(BaseCrewComponent):
|
|||
HandleInput(name="tasks", display_name="Tasks", input_types=["SequentialTask"], is_list=True),
|
||||
]
|
||||
|
||||
def get_tasks_and_agents(self) -> tuple[list[Task], list[Agent]]:
|
||||
return self.tasks, [task.agent for task in self.tasks]
|
||||
def get_tasks_and_agents(self, agents_list=None) -> tuple[list[Task], list[Agent]]:
|
||||
if not agents_list:
|
||||
agents_list = [task.agent for task in self.tasks] or []
|
||||
|
||||
# Use the superclass implementation, passing the customized agents_list
|
||||
return super().get_tasks_and_agents(agents_list=agents_list)
|
||||
|
||||
def build_crew(self) -> Message:
|
||||
tasks, agents = self.get_tasks_and_agents()
|
||||
|
||||
return Crew(
|
||||
agents=agents,
|
||||
tasks=tasks,
|
||||
|
|
|
|||
|
|
@ -154,7 +154,7 @@ dependencies = [
|
|||
"nanoid>=2.0.0",
|
||||
"filelock>=3.15.4",
|
||||
"grandalf>=0.8.0",
|
||||
"crewai>=0.74.2",
|
||||
"crewai~=0.80.0",
|
||||
"spider-client>=0.0.27",
|
||||
"diskcache>=5.6.3",
|
||||
"clickhouse-connect==0.7.19",
|
||||
|
|
|
|||
15
uv.lock
generated
15
uv.lock
generated
|
|
@ -1094,7 +1094,7 @@ toml = [
|
|||
|
||||
[[package]]
|
||||
name = "crewai"
|
||||
version = "0.74.2"
|
||||
version = "0.80.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "appdirs" },
|
||||
|
|
@ -1115,17 +1115,18 @@ dependencies = [
|
|||
{ name = "python-dotenv" },
|
||||
{ name = "pyvis" },
|
||||
{ name = "regex" },
|
||||
{ name = "tomli" },
|
||||
{ name = "tomli-w" },
|
||||
{ name = "uv" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/27/7f/998b084d1ebfca9739f7e26e0c97eab6e4f35391689a2836c04497430dd8/crewai-0.74.2.tar.gz", hash = "sha256:de8c70bda1862ee52f52202d0f2afb53cae3e5849ecaee3c6bdcb774bac2358f", size = 5812388 }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/f7/38/584389ffb7ca7bc4719438a932d99993b8e24781e81dec33908c8bdb8954/crewai-0.80.0.tar.gz", hash = "sha256:8fc10f8a0344349f5fcc431fcdd03dcb033704d402d67f9b145a6d9d099d8e42", size = 5842314 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/a3/ef/5fae2f28d634c146cd06da118b50f3b9c7213ef9649e20f4ba1e7b6d2f06/crewai-0.74.2-py3-none-any.whl", hash = "sha256:4f0acd839ac604da1ad8efea67394166700e02ae643ee829b0f8eb22f2129ef2", size = 187638 },
|
||||
{ url = "https://files.pythonhosted.org/packages/13/55/8caa2264c59be4c11266be1aae2b57610dcd30cd1c6f0752416589126f3b/crewai-0.80.0-py3-none-any.whl", hash = "sha256:74eb67b6de2688871c831bc617de0a839667c643c8b6b3757b3c1e849bea3ea0", size = 197680 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crewai-tools"
|
||||
version = "0.13.2"
|
||||
version = "0.14.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "beautifulsoup4" },
|
||||
|
|
@ -1143,9 +1144,9 @@ dependencies = [
|
|||
{ name = "requests" },
|
||||
{ name = "selenium" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/96/02/136f42ed8a7bd706a85663714c615bdcb684e43e95e4719c892aa0ce3d53/crewai_tools-0.13.2.tar.gz", hash = "sha256:c6782f2e868c0e96b25891f1b40fb8c90c01e920bab2fd1388f89ef1d7a4b99b", size = 816250 }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/9b/6d/4fa91b481b120f83bb58f365203d8aa8564e8ced1035d79f8aedb7d71e2f/crewai_tools-0.14.0.tar.gz", hash = "sha256:510f3a194bcda4fdae4314bd775521964b5f229ddbe451e5d9e0216cae57f4e3", size = 815892 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/28/30/df215173b6193b2cfb1902a339443be73056eae89579805b853c6f359761/crewai_tools-0.13.2-py3-none-any.whl", hash = "sha256:8c7583c9559fb625f594349c6553a5251ebd7b21918735ad6fbe8bab7ec3db50", size = 463444 },
|
||||
{ url = "https://files.pythonhosted.org/packages/c8/ed/9f4e64e1507062957b0118085332d38b621c1000874baef2d1c4069bfd97/crewai_tools-0.14.0-py3-none-any.whl", hash = "sha256:0a804a828c29869c3af3253f4fc4c3967a3f80f06dab22e9bbe9526608a31564", size = 462980 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -3914,7 +3915,7 @@ requires-dist = [
|
|||
{ name = "celery", marker = "extra == 'deploy'", specifier = ">=5.3.1" },
|
||||
{ name = "chardet", specifier = ">=5.2.0" },
|
||||
{ name = "clickhouse-connect", specifier = "==0.7.19" },
|
||||
{ name = "crewai", specifier = ">=0.74.2" },
|
||||
{ name = "crewai", specifier = "~=0.80.0" },
|
||||
{ name = "cryptography", specifier = ">=42.0.5,<44.0.0" },
|
||||
{ name = "ctransformers", marker = "extra == 'all'", specifier = ">=0.2" },
|
||||
{ name = "ctransformers", marker = "extra == 'local'", specifier = ">=0.2" },
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue