crewAIInc / crewAI

Framework for orchestrating role-playing, autonomous AI agents. By fostering collaborative intelligence, CrewAI empowers agents to work together seamlessly, tackling complex tasks.
https://crewai.com
MIT License
18.62k stars 2.57k forks source link

Adding arbitrary agents into the framework? #228

Closed ZmeiGorynych closed 1 day ago

ZmeiGorynych commented 6 months ago

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

joaomdmoura commented 6 months 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 :)

ZmeiGorynych commented 6 months ago

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.

ZmeiGorynych commented 6 months ago

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
sreecodeslayer commented 4 months ago

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

ricfaith commented 3 months ago

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?

theCyberTech commented 3 months ago

CrewAI is built on langchain, why would they not work?

sameermahajan commented 3 months ago

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?

github-actions[bot] commented 1 week ago

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.

github-actions[bot] commented 1 day ago

This issue was closed because it has been stalled for 5 days with no activity.