Skip to content

Building Custom Tools

While CrewAI comes with a robust toolkit, real-world applications often require unique integrations—proprietary internal APIs, specific database queries, or complex data transformations. This is where Custom Tools shine.

CrewAI offers two primary methods for creating custom tools:

  • The @tool Decorator: Quick, function-based, ideal for simple logic.
  • Subclassing BaseTool: Object-oriented, supports complex arguments and validation, ideal for production.

For simple functions, you can decorate a Python function. The docstring is critical here—it becomes the description the Agent reads to understand when to use the tool.

from crewai.tools import tool
@tool("Calculate Length")
def calculate_length(text: str) -> int:
"""Returns the length of the text provided."""
return len(text)
# Usage in Agent
agent = Agent(..., tools=[calculate_length])
Section titled “Method 2: Subclassing BaseTool (Recommended)”

For robust applications, we subclass BaseTool. This allows us to define a strict schema for the inputs using Pydantic. If the LLM tries to call your tool with missing or wrong data types, CrewAI will catch the error and ask the LLM to correct itself automatically.

First, we define what inputs our tool expects using a Pydantic model.

from pydantic import BaseModel, Field
class CustomAPIToolInput(BaseModel):
query: str = Field(..., description="The search query to send to the API")
limit: int = Field(5, description="Max number of results to return")

Next, we implement the tool class, linking the schema and defining the _run method.

from crewai.tools import BaseTool
from typing import Type
import requests
class CustomSearchTool(BaseTool):
name: str = "Internal Knowledge Search"
description: str = (
"Useful for searching the company's internal documentation. "
"Always use this before asking the user."
)
args_schema: Type[BaseModel] = CustomAPIToolInput
def _run(self, query: str, limit: int = 5) -> str:
# Your custom logic here
response = requests.get(
"https://api.internal.company/search",
params={"q": query, "limit": limit}
)
if response.status_code == 200:
return response.text
else:
return f"Error fetching data: {response.status_code}"
# Instantiate and use
custom_tool = CustomSearchTool()
  • Descriptive Names: The name and description are part of the prompt. Be specific.
  • Type Hinting: Always type hint your _run method arguments.
  • Error Handling: Return clear error strings rather than raising exceptions directly if you want the Agent to try again or handle the failure gracefully.