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:
Eric Hare 2024-11-18 15:04:07 -08:00 committed by GitHub
commit 2fa258068d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 140 additions and 29 deletions

View file

@ -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)

View file

@ -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

View file

@ -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(),
)

View file

@ -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,

View file

@ -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
View file

@ -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" },