Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding arbitrary agents into the framework? #228

Open
ZmeiGorynych opened this issue Feb 7, 2024 · 7 comments
Open

Adding arbitrary agents into the framework? #228

ZmeiGorynych opened this issue Feb 7, 2024 · 7 comments

Comments

@ZmeiGorynych
Copy link

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
Copy link
Owner

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
Copy link
Author

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
Copy link
Author

ZmeiGorynych commented Feb 9, 2024

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
Copy link

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

@ricfaith
Copy link

ricfaith commented May 7, 2024

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
Copy link
Contributor

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

@sameermahajan
Copy link

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?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

6 participants