Files
crewAI/docs/edge/en/learn/create-custom-tools.mdx
Vinicius Brasil 9b8ecc7df5 Use custom format_output_for_agent override for tool output
When a `BaseTool` subclass overrides `format_output_for_agent`, route
the agent-facing text through it instead of the default JSON/`str()`
serialization. The structured tool wrapper now delegates to the original
tool via `_original_tool`, so a tool can present Markdown or any custom
representation to the agent while `tool.run(...)` still returns the raw
Python value.
2026-06-18 21:41:34 -07:00

265 lines
9.7 KiB
Plaintext

---
title: Create Custom Tools
description: Comprehensive guide on crafting, using, and managing custom tools within the CrewAI framework, including new functionalities and error handling.
icon: hammer
mode: "wide"
---
## Creating and Utilizing Tools in CrewAI
This guide provides detailed instructions on creating custom tools for the CrewAI framework and how to efficiently manage and utilize these tools,
incorporating the latest functionalities such as tool delegation, error handling, and dynamic tool calling. It also highlights the importance of collaboration tools,
enabling agents to perform a wide range of actions.
<Tip>
**Want to publish your tool for the community?** If you're building a tool that others could benefit from, check out the [Publish Custom Tools](/en/guides/tools/publish-custom-tools) guide to learn how to package and distribute your tool on PyPI.
</Tip>
### Subclassing `BaseTool`
To create a personalized tool, inherit from `BaseTool` and define the necessary attributes, including the `args_schema` for input validation, and the `_run` method.
```python Code
from typing import Type
from crewai.tools import BaseTool
from pydantic import BaseModel, Field
class MyToolInput(BaseModel):
"""Input schema for MyCustomTool."""
argument: str = Field(..., description="Description of the argument.")
class MyCustomTool(BaseTool):
name: str = "Name of my tool"
description: str = "What this tool does. It's vital for effective utilization."
args_schema: Type[BaseModel] = MyToolInput
def _run(self, argument: str) -> str:
# Your tool's logic here
return "Tool's result"
```
### Using the `tool` Decorator
Alternatively, you can use the tool decorator `@tool`. This approach allows you to define the tool's attributes and functionality directly within a function,
offering a concise and efficient way to create specialized tools tailored to your needs.
```python Code
from crewai.tools import tool
@tool("Tool Name")
def my_simple_tool(question: str) -> str:
"""Tool description for clarity."""
# Tool logic here
return "Tool output"
```
### Best Practice: Define Typed Outputs
When a tool returns structured data, define the output as a Pydantic model. This is optional, but recommended because it gives CrewAI a clear contract for the data your tool returns.
Typed outputs create a clear split between direct Python usage and agent execution:
- `tool.run(...)` returns the raw Python value from your tool.
- Agent execution validates the raw value with the tool's output schema and sends the agent an LLM-safe string.
- Valid Pydantic outputs are serialized to JSON for the agent.
- If validation or serialization fails, CrewAI emits a runtime warning and falls back to `str(raw_result)` only for the agent-facing text.
#### Return a Pydantic Model
CrewAI infers the output schema when your `BaseTool` or `@tool` function has a Pydantic return annotation.
```python Code
from crewai.tools import BaseTool
from pydantic import BaseModel, Field
class SentimentResult(BaseModel):
label: str = Field(description="The sentiment label, such as positive, neutral, or negative.")
confidence: float = Field(description="Confidence score from 0 to 1.")
class SentimentTool(BaseTool):
name: str = "Sentiment Analyzer"
description: str = "Analyze the sentiment of a short text passage."
def _run(self, text: str) -> SentimentResult:
# Replace this with your model, API call, or business logic.
return SentimentResult(label="positive", confidence=0.92)
tool = SentimentTool()
result = tool.run(text="CrewAI makes multi-agent workflows easier.")
# Direct Python calls receive the raw Pydantic object.
print(result.label)
print(result.confidence)
```
When an agent calls `SentimentTool`, it receives JSON like this:
```json
{"label":"positive","confidence":0.92}
```
This is easier for the agent to reason over than a Python object representation.
#### Use `output_schema` for Dictionary Results
If your implementation naturally returns a dictionary, set `output_schema` explicitly. CrewAI validates the dictionary and serializes the validated result to JSON for the agent.
```python Code
from crewai.tools import BaseTool
from pydantic import BaseModel, Field
class ProductLookupResult(BaseModel):
sku: str = Field(description="The product SKU.")
name: str = Field(description="The product name.")
in_stock: bool = Field(description="Whether the product is available.")
class ProductLookupTool(BaseTool):
name: str = "Product Lookup"
description: str = "Look up product availability by SKU."
output_schema: type[BaseModel] = ProductLookupResult
def _run(self, sku: str) -> dict[str, object]:
return {
"sku": sku,
"name": "CrewAI Enterprise License",
"in_stock": True,
}
```
You can use the same pattern with the `@tool` decorator:
```python Code
from crewai.tools import tool
from pydantic import BaseModel, Field
class ProductLookupResult(BaseModel):
sku: str = Field(description="The product SKU.")
name: str = Field(description="The product name.")
in_stock: bool = Field(description="Whether the product is available.")
@tool("Product Lookup", output_schema=ProductLookupResult)
def product_lookup(sku: str) -> dict[str, object]:
"""Look up product availability by SKU."""
return {
"sku": sku,
"name": "CrewAI Enterprise License",
"in_stock": True,
}
```
#### Customize the Text Sent to the Agent
By default, typed tool outputs are sent to the agent as JSON. If your agent should receive Markdown, XML, or a compact human-readable summary instead, subclass `BaseTool` and override `format_output_for_agent`.
This only changes the agent-facing text. Direct calls to `tool.run(...)` still return the raw Python value from `_run`.
```python Code
from crewai.tools import BaseTool
from pydantic import BaseModel, Field
class ProductLookupResult(BaseModel):
sku: str = Field(description="The product SKU.")
name: str = Field(description="The product name.")
in_stock: bool = Field(description="Whether the product is available.")
class ProductLookupTool(BaseTool):
name: str = "Product Lookup"
description: str = "Look up product availability by SKU."
def _run(self, sku: str) -> ProductLookupResult:
return ProductLookupResult(
sku=sku,
name="CrewAI Enterprise License",
in_stock=True,
)
def format_output_for_agent(self, raw_result: object) -> str:
result = ProductLookupResult.model_validate(raw_result)
status = "in stock" if result.in_stock else "out of stock"
return (
f"### {result.name}\n\n"
f"- SKU: `{result.sku}`\n"
f"- Status: **{status}**"
)
tool = ProductLookupTool()
result = tool.run(sku="CREW-ENT")
# Direct Python calls receive the raw Pydantic object.
print(result.name)
```
When an agent calls `ProductLookupTool`, it receives the Markdown returned by `format_output_for_agent`. When you do not override this method, CrewAI uses the default behavior: validate typed outputs and serialize them to JSON, or use `str(raw_result)` for untyped outputs.
Use typed outputs for tool results that have stable fields, nested data, lists, IDs, status values, scores, or any structure the agent should interpret precisely. Plain strings are still fine for simple prose results.
### Defining a Cache Function for the Tool
To optimize tool performance with caching, define custom caching strategies using the `cache_function` attribute.
```python Code
@tool("Tool with Caching")
def cached_tool(argument: str) -> str:
"""Tool functionality description."""
return "Cacheable result"
def my_cache_strategy(arguments: dict, result: str) -> bool:
# Define custom caching logic
return True if some_condition else False
cached_tool.cache_function = my_cache_strategy
```
### Creating Async Tools
CrewAI supports async tools for non-blocking I/O operations. This is useful when your tool needs to make HTTP requests, database queries, or other I/O-bound operations.
#### Using the `@tool` Decorator with Async Functions
The simplest way to create an async tool is using the `@tool` decorator with an async function:
```python Code
import aiohttp
from crewai.tools import tool
@tool("Async Web Fetcher")
async def fetch_webpage(url: str) -> str:
"""Fetch content from a webpage asynchronously."""
async with aiohttp.ClientSession() as session:
async with session.get(url) as response:
return await response.text()
```
#### Subclassing `BaseTool` with Async Support
For more control, subclass `BaseTool` and implement both `_run` (sync) and `_arun` (async) methods:
```python Code
import requests
import aiohttp
from crewai.tools import BaseTool
from pydantic import BaseModel, Field
class WebFetcherInput(BaseModel):
"""Input schema for WebFetcher."""
url: str = Field(..., description="The URL to fetch")
class WebFetcherTool(BaseTool):
name: str = "Web Fetcher"
description: str = "Fetches content from a URL"
args_schema: type[BaseModel] = WebFetcherInput
def _run(self, url: str) -> str:
"""Synchronous implementation."""
return requests.get(url).text
async def _arun(self, url: str) -> str:
"""Asynchronous implementation for non-blocking I/O."""
async with aiohttp.ClientSession() as session:
async with session.get(url) as response:
return await response.text()
```
By adhering to these guidelines and incorporating new functionalities and collaboration tools into your tool creation and management processes,
you can leverage the full capabilities of the CrewAI framework, enhancing both the development experience and the efficiency of your AI agents.