CrewAI Tutorial (Python): building custom tools for intermediate developers

By Cyprian AaronsUpdated 2026-04-21
crewaibuilding-custom-tools-for-intermediate-developerspython

This tutorial shows you how to build a CrewAI agent that uses a custom Python tool, wire it into a task, and test it end-to-end. You need this when the built-in tools are not enough and your agent must call internal logic, hit a private API, or wrap business rules in a controlled way.

What You'll Need

  • Python 3.10+
  • crewai
  • crewai-tools
  • python-dotenv
  • An LLM API key, such as:
    • OPENAI_API_KEY
  • Basic familiarity with:
    • CrewAI agents, tasks, and crews
    • Python functions and classes
  • A terminal and a virtual environment

Step-by-Step

  1. Install the packages and set up your environment.

    Keep this isolated in a virtualenv so your tool code and agent code stay reproducible. If you are building for production, this is also where you pin versions before handing it to another engineer.

    python -m venv .venv
    source .venv/bin/activate
    
    pip install crewai crewai-tools python-dotenv
    
  2. Create a custom tool by subclassing BaseTool.

    The key point is that CrewAI tools are just Python classes with a _run() method. Here we build a simple lookup tool that simulates pulling policy details from an internal system.

    # tools.py
    from typing import Type
    from pydantic import BaseModel, Field
    from crewai_tools import BaseTool
    
    class PolicyLookupInput(BaseModel):
        policy_id: str = Field(..., description="Policy identifier like POL123")
    
    class PolicyLookupTool(BaseTool):
        name: str = "policy_lookup"
        description: str = "Look up policy status and premium by policy ID."
        args_schema: Type[BaseModel] = PolicyLookupInput
    
        def _run(self, policy_id: str) -> str:
            mock_db = {
                "POL123": {"status": "active", "premium": 120.50},
                "POL456": {"status": "lapsed", "premium": 89.99},
            }
    
            record = mock_db.get(policy_id)
            if not record:
                return f"No policy found for {policy_id}"
    
            return (
                f"Policy {policy_id}: status={record['status']}, "
                f"premium=${record['premium']}"
            )
    
  3. Load your API key and define the agent, task, and crew.

    The agent gets the tool injected directly into its tools list. That keeps the capability explicit instead of hiding business logic inside prompts.

    # main.py
    import os
    from dotenv import load_dotenv
    from crewai import Agent, Task, Crew, Process
    from tools import PolicyLookupTool
    
    load_dotenv()
    
    llm_model = os.getenv("OPENAI_MODEL", "gpt-4o-mini")
    tool = PolicyLookupTool()
    
    agent = Agent(
        role="Insurance Operations Analyst",
        goal="Answer policy questions using approved internal tools.",
        backstory="You work with policy servicing data and must use tools for facts.",
        tools=[tool],
        verbose=True,
        llm=llm_model,
    )
    
    task = Task(
        description=(
            "Use the policy_lookup tool to answer the customer question: "
            "'What is the status of policy POL123?'"
        ),
        expected_output="A concise answer with the policy status and premium.",
        agent=agent,
    )
    
    crew = Crew(
        agents=[agent],
        tasks=[task],
        process=Process.sequential,
        verbose=True,
    )
    
  4. Run the crew and print the result.

    This is where you confirm the model can actually call your custom tool instead of hallucinating an answer. In production, this same pattern works for database lookups, document retrieval, validation rules, or internal service wrappers.

    # main.py continued
    if __name__ == "__main__":
        result = crew.kickoff()
        print("\n=== FINAL RESULT ===")
        print(result)
    
  5. Make the tool more production-friendly with validation and failure handling.

    A real tool should fail cleanly when input is bad or upstream data is missing. Returning structured errors or clear messages makes debugging easier when an agent chains multiple steps.

    # tools.py continued
    class PolicyLookupTool(BaseTool):
        name: str = "policy_lookup"
        description: str = "Look up policy status and premium by policy ID."
        args_schema: Type[BaseModel] = PolicyLookupInput
    
        def _run(self, policy_id: str) -> str:
            if not policy_id.startswith("POL"):
                return f"Invalid policy ID format: {policy_id}"
    
            mock_db = {
                "POL123": {"status": "active", "premium": 120.50},
                "POL456": {"status": "lapsed", "premium": 89.99},
            }
    
            record = mock_db.get(policy_id)
            if record is None:
                return f"No policy found for {policy_id}"
    
            return (
                f"Policy {policy_id}: status={record['status']}, "
                f"premium=${record['premium']}"
            )
    

Testing It

Run python main.py after exporting your OpenAI key in the shell or placing it in a .env file. You should see CrewAI logs showing the agent reasoning about the task and invoking policy_lookup. The final output should mention POL123, its status, and its premium.

If the agent does not call the tool, check three things first:

  • The tool is included in tools=[tool]
  • The task explicitly requires information that only the tool can provide
  • Your model has access to function/tool calling through the configured LLM

For local debugging, set verbose=True on both the agent and crew so you can inspect whether the model selected the tool or answered directly from memory.

Next Steps

  • Wrap real APIs in tools instead of mock dictionaries:
    • CRM systems
    • claims platforms
    • document stores
  • Add structured outputs with Pydantic models so downstream tasks can consume clean data.
  • Learn how to chain multiple agents where one agent validates tool output before another one summarizes it.

Keep learning

By Cyprian Aarons, AI Consultant at Topiax.

Want the complete 8-step roadmap?

Grab the free AI Agent Starter Kit — architecture templates, compliance checklists, and a 7-email deep-dive course.

Get the Starter Kit

Related Guides