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",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue