Closed ZmeiGorynych closed 1 day ago
Curious to hear more on how you see that working, is that idea that an eng would wrap it's external agent into this class so it can be integrate within a crew? If that is the case it could be straightforward to implement but I'm curious to hear more and also any examples you might have :)
That's exactly the idea, so one would only need to provide a minimal wrapper (and for the major APIs, such as LangChain agents, we could just include one out of the box) that translates between a CrewAI agent API and that particular agent's (eg other crew members who can be delegated to could eg be wrapped as tools to that agent). Just thinking that it makes no sense to reinvent the wheel, and the individual agent implementations out there are many and fun, so combining them with crewai's task/comms infrastructure would give the best of both worlds. Will spend tomorrow morning reading crewai code and playing around, to hopefully get more specific.
Something like this perhaps?
from typing import Any, Optional, List
from abc import ABC, abstractmethod
from pydantic import BaseModel, Field, PrivateAttr
from langchain.agents import AgentExecutor, create_openai_tools_agent
from langchain_core.messages import AIMessage, HumanMessage
from langchain_community.chat_models import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
from crewai.utilities import I18N
from crewai.agent import Agent
class AgentWrapperParent(ABC, BaseModel):
_i18n: I18N = PrivateAttr(default=I18N())
@property
def i18n(self) -> I18N:
if hasattr(self, "_agent") and hasattr(self._agent, "i18n"):
return self._agent.i18n
else:
return self._i18n
@i18n.setter
def i18n(self, value: I18N) -> None:
if hasattr(self, "_agent") and hasattr(self._agent, "i18n"):
self._agent.i18n = value
else:
self._i18n = value
@abstractmethod
def execute_task(
self,
task: str,
context: Optional[str] = None,
tools: Optional[List[Any]] = None,
) -> str:
pass
@property
@abstractmethod
def allow_delegation(self) -> bool:
pass
@property
@abstractmethod
def role(self) -> str:
pass
def set_cache_handler(self, cache_handler: Any) -> None:
pass
def set_rpm_controller(self, rpm_controller: Any) -> None:
pass
# Implemented this one just to figure out the interface
# The example at https://github.com/joaomdmoura/crewAI runs fine when substituting
# AgentWrapper(researcher) for researcher, after fixing the types in Task and Crew
class AgentWrapper(AgentWrapperParent):
_agent: Agent = PrivateAttr()
def __init__(self, agent: Any, **data: Any):
super().__init__(**data)
self._agent = agent
def execute_task(
self,
task: str,
context: Optional[str] = None,
tools: Optional[List[Any]] = None,
) -> str:
return self._agent.execute_task(task, context, tools)
@property
def allow_delegation(self) -> bool:
return self._agent.allow_delegation
@property
def role(self) -> str:
return self._agent.role
def set_cache_handler(self, cache_handler: Any) -> None:
return self._agent.set_cache_handler(cache_handler)
def set_rpm_controller(self, rpm_controller: Any) -> None:
return self._agent.set_rpm_controller(rpm_controller)
class OpenAIToolsAgent(AgentWrapperParent):
_llm: ChatOpenAI = PrivateAttr()
_prompt: ChatPromptTemplate = PrivateAttr()
_tools: List[Any] = PrivateAttr(default=[])
_role: str = PrivateAttr(default="")
_allow_delegation: bool = PrivateAttr(default=False)
_agent_executor: Any = PrivateAttr(default=None)
def __init__(
self,
llm: ChatOpenAI,
prompt: ChatPromptTemplate,
tools: List[Any],
role: str,
allow_delegation: bool = False,
**data: Any
):
super().__init__(**data)
self._llm = llm
self._prompt = prompt
self._role = role
self._allow_delegation = allow_delegation
self.init(tools)
def execute_task(
self,
task: str,
context: Optional[str] = None,
tools: Optional[List[Any]] = None,
) -> str:
# Most agents require their tools list to be known at creation time,
# so might need to re-create the agent if there are new tools added
# TODO: compare whether they're actually the same tools!
if tools is not None and len(tools) != len(self._tools):
self.init(tools)
# TODO: better wrap the context as a sequence of messages
return self._agent_executor.invoke(
{"input": task, "chat_history": [HumanMessage(content=context)]}
)["output"]
def init(self, tools: List[Any]) -> None:
self._tools = tools
agent = create_openai_tools_agent(self._llm, tools, self._prompt)
# Create an agent executor by passing in the agent and tools
self._agent_executor = AgentExecutor(agent=agent, tools=tools, verbose=True)
@property
def allow_delegation(self) -> bool:
return self._allow_delegation
@property
def role(self) -> str:
return self._role
if __name__ == "__main__":
# this is how you would use the OpenAIToolsAgent
from langchain_community.tools import DuckDuckGoSearchRun
from langchain_openai import ChatOpenAI
from langchain import hub
search_tool = DuckDuckGoSearchRun()
prompt = hub.pull("hwchase17/openai-tools-agent")
llm = ChatOpenAI(model="gpt-4-0125-preview", temperature=0)
researcher = OpenAIToolsAgent(
llm=llm, prompt=prompt, tools=[search_tool], role="Senior Research Analyst"
)
# This should then be substituted for crewAI agents
This would be a super helpful addition, given I had a similar usecase with langchain sql agent, https://github.com/joaomdmoura/crewAI/issues/341#issuecomment-2032616661
Unfortunately this doesn't work well as of now.
I'll try to test this with PR https://github.com/joaomdmoura/crewAI/pull/246
It's been a few months since OP, but I agree with the @sreecodeslayer on the value prop of allowing external agents, or at least native langchain agents to work within the CrewAI framework.
Is there any interest in seeing this through @joaomdmoura?
CrewAI is built on langchain, why would they not work?
This would be a super helpful addition, given I had a similar usecase with langchain sql agent, #341 (comment)
Unfortunately this doesn't work well as of now.
I'll try to test this with PR #246
@sreecodeslayer did the PR #246 work for you?
This issue is stale because it has been open for 30 days with no activity. Remove stale label or comment or this will be closed in 5 days.
This issue was closed because it has been stalled for 5 days with no activity.
As far as I can see, CrewAI's main strengths are the concepts of tasks (which can reference each other) and of delegation between agents, while the agent implementation itself looks reasonable but fairly traditional.
How hard would it be to provide a way to plug in any kind of agents (Tree of Thought, AutoGPT, you name it) as individual agents, while taking advantage of the task and delegation infrastructure of CrewAI? You'd really just need to expose an agent interface (abstract class?) that a new agent plugin has to implement, and that's it, right?
Happy to help implement and test if you're interested ;)