Skip to content

Tools as class methods #1109

@pieroit

Description

@pieroit

Intro

Help needed on designing and implementing a truly flexible tool system for Cat v2 agents.

Background

In v2 tools will come from three different places:

1 - MCP servers
2 - global tools (defined in plugins) with the @tool decorator
3 - agent specific tools (defined in plugins, directly in the agent class)

1 and 2 already work, this issue is on 3.

Agent specific tools

Tools available to an agent come from the agent.list_tools() method. The method gives out a list of CatTool objects to be passed to agent.llm(...) alongside messages and other params. Example of the typical agentic loop is found in the default agent:

        while True:
            llm_mex: Message = await self.llm(
                # delegate prompt construction to plugins
                await self.get_system_prompt(),
                # pass conversation messages
                messages=self.chat_request.messages + self.chat_response.messages,
                # pass tools (both internal and MCP)
                tools=await self.list_tools(),
                # whether to stream or not
                stream=self.chat_request.stream,
            )

            # do stuff with the message

I'll probably wrap this loop in a method like agent.inner_loop() so it can be reused or overridden at your pleasure. The issue here regards list_tools.

How list_tools work

The method is included in BaseAgent:

    async def list_tools(self) -> List[CatTool]:
        """Get both plugins' tools and MCP tools in CatTool format."""

        mcp_tools = await self.mcp.list_tools()
        mcp_tools = [
            CatTool.from_fastmcp(t, self.mcp.call_tool)
            for t in mcp_tools
        ]

        tools = await self.execute_hook(
            "agent_allowed_tools",
            mcp_tools + self.mad_hatter.tools
        )

        return tools

What is missing here are tools defined in the agent itself, in the most simple way possible.
Something like (in a plugin):

from cat import tool, BaseAgent

class MyAgent(BaseAgent):

    async def execute(self):
        # ...
    
    @tool
    async def get_weather(self, city: str):
        # possibility here to use `self.xxx`
        return x

Note cat is not needed here, as the BaseAgent has CatMixin allowing it to access user, execute hooks, and do everything that was previously possible to do with cat in the tool arguments.

So in your own agent, it will be possible to:

  • ignore list_tools and pass CatTool objects manually, or none
  • call list_tools to give your agent all the tools available (eventually filtered by agent_allowed_tools hook that was already active in v1)
  • override list_tools in your agent to define a custom logic

What to do

Method list_tools should add to the list also class based tools.
I don't know if CatTool.from_decorated_function will work or requires adjustments or another constructor is necessary. The self argument there must be taken into account, also in the decorator function.

Comments are welcome if you get ideas on the design. Most agent libraries assume you give to the agent the list of tools at construction and you let it run. This is an amateurish approach, and we're gonna make school on this as we already made school on many other primitives.

If available to implement the feature, wait for direct assignation.
I'll open many issues on v2 in the next days :)

Thanks

P.S.: instructions on how to install v2 are available in V2_DEV_NOTES.md.
P.S.: tests are broken in branch v2 and many bugs around. Will be fixed and available later on.

Metadata

Metadata

Assignees

No one assigned

    Labels

    V2agentRelated to cat agent (reasoner / prompt engine)enhancementNew feature or request

    Projects

    Status

    🔖 Ready

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions