Although the tool interface is straightforward and simple to use, there are actually quite a few practices and tricks that you can use to get significantly better results.

Use Chain-of-Thought Prompting for Complex Tools

Use chain-of-thought prompting to allow the agent to think and plan before executing a complex tool.

from agency_swarm.tools import BaseTool
from pydantic import Field

class ComplexAnalysisTool(BaseTool):
    """
    Performs complex analysis after planning the approach.
    """
    chain_of_thought: str = Field(
        ...,
        description="Think-step-by-step about how to perform the analysis."
    )
    data: str = Field(..., description="Data to analyze.")

    def run(self):
        # Analysis logic
        return "Analysis complete."

Provide Hints for the Agent

Based on your tool’s logic, you can provide hints for the agent in tool output on what to do next.

class QueryDatabase(BaseTool):
    question: str = Field(...)

    def run(self):
        # query your database here
        context = self.query_database(self.question)

        # context not found
        if context is None:
            # tell agent what to do next
            raise ValueError("No context found. Please propose to the user to change the topic.")
        else:
            # return the context to the agent
            return context

Use Shared State to Control the Tool Flow

Use shared_state to validate previous actions taken by this or other agents, before allowing it to proceed with the next action.

class Action2(BaseTool):
    input: str = Field(...)

    def run(self):
        if self._shared_state.get("action_1_result", None) is "failure":
            raise ValueError("Please proceed with the Action1 tool first.")
        else:
            return "Success. The action has been taken."

Use Special Types

Restrict the agent to only use specific values for a field, instead of letting it wander by itself.

from typing import Literal

class RunCommand(BaseTool):
    """
    Execute predefined system commands.
    """
    command: Literal["start", "stop"] = Field(..., description="Command to execute: 'start' or 'stop'.")

    def run(self):
        if self.command == "start":
            # Start command logic
            pass
        elif self.command == "stop":
            # Stop command logic
            pass
        else:
            raise ValueError("Invalid command")

or use special Pydantic types like EmailStr.

from pydantic import EmailStr

class EmailSender(BaseTool):
    recipient: EmailStr = Field(..., description="Email recipient's address.")

Combine Multiple Methods

Combine multiple methods to make your execution flow more readable.

class CompositeTool(BaseTool):
    """
    A tool that combines several methods to perform a series of actions.
    """
    input_data: str = Field(..., description="Input data for the composite operation.")

    def run(self):
        # Step 1: Process data
        processed_data = self.process_data(self.input_data)
        # Step 2: Analyze results
        analysis = self.analyze_results(processed_data)
        # Step 3: Format output
        output = self.format_output(analysis)
        return output

    def process_data(self, data):
        # Implement data processing logic
        pass

    def analyze_results(self, data):
        # Implement analysis logic
        pass

    def format_output(self, data):
        # Implement output formatting
        pass

Include a Test Case

Include test cases at the bottom of each tool file.

if __name__ == "__main__":
    # Test the EmailSender tool
    email_sender = EmailSender(
        chain_of_thought="Plan to inform the team about the update.",
        recipient="user@example.com",
        subject="Project Update",
        body="The project is on track."
    )
    assert email_sender.run() == "Email sent successfully."

Next Steps

We highly recommend you explore the resources provided in the Pydantic is all you need section.