advanced45 minLesson 4 of 5

Custom Tools, Tool Integration and Shared Context

Build custom tools with the BaseTool class, integrate built-in tools from crewai_tools, and share context between tasks for advanced workflows.

Custom Tools, Tool Integration and Shared Context

Tools give agents the ability to interact with external systems: search the web, query databases, run calculations, or call APIs. CrewAI provides a rich tool ecosystem and a simple API for creating your own. Without tools, agents can only use their training data; with tools, they can access real-time information and take actions in the world.


The BaseTool Class

Every tool in CrewAI extends BaseTool. At minimum, you define a name, description, and _run() method:

python
from crewai.tools import BaseTool from pydantic import Field class CalculatorTool(BaseTool): name: str = "Calculator" description: str = "Performs basic arithmetic operations (add, subtract, multiply, divide)." # Optional: define input schema with Pydantic args_schema: type = Field # uses the tool's own fields def _run(self, expression: str) -> str: """Evaluate a simple arithmetic expression.""" try: result = eval(expression) # simplified — use safe eval in production return f"Result: {result}" except Exception as e: return f"Error: {str(e)}"
📌Important

The name and description fields are critical — they are what the agent's LLM reads to decide when to use a tool. A clear, descriptive name and a detailed description of when and how to use the tool dramatically improve correct tool selection by the agent.


Tool Class Hierarchy

100%

Creating a Custom Tool with Parameters

You can define structured input parameters using Pydantic:

python
from crewai.tools import BaseTool from pydantic import BaseModel, Field class SearchInput(BaseModel): query: str = Field(description="The search query string") max_results: int = Field(default=5, description="Maximum number of results to return") class WebSearchTool(BaseTool): name: str = "Web Search" description: str = "Search the web for current information on a given topic." args_schema: type = SearchInput def _run(self, query: str, max_results: int = 5) -> str: # Mock implementation — replace with real API call results = [ f"Result {i}: Mock result for '{query}'" for i in range(1, max_results + 1) ] return "\n".join(results)
python
# Using the tool tool = WebSearchTool() result = tool._run(query="CrewAI agents", max_results=3) print(result) # Result 1: Mock result for 'CrewAI agents' # Result 2: Mock result for 'CrewAI agents' # Result 3: Mock result for 'CrewAI agents'

Custom Tool Execution Flow

100%

Real-World Tool: Web Search with Serper

python
import requests from crewai.tools import BaseTool from pydantic import BaseModel, Field class SearchInput(BaseModel): query: str = Field(description="The search query to look up") class RealSearchTool(BaseTool): name: str = "Web Search" description: str = "Search Google for current information using the Serper API." args_schema: type = SearchInput def _run(self, query: str) -> str: """Execute a real web search via Serper.dev API.""" url = "https://google.serper.dev/search" headers = { "X-API-KEY": "your-api-key-here", # replace with real key "Content-Type": "application/json", } payload = {"q": query, "num": 5} try: response = requests.post(url, json=payload, headers=headers) response.raise_for_status() data = response.json() # Format results results = [] for item in data.get("organic", []): title = item.get("title", "") snippet = item.get("snippet", "") link = item.get("link", "") results.append(f"- [{title}]({link})\n {snippet}") return "\n".join(results) if results else "No results found." except Exception as e: return f"Search failed: {str(e)}"

Tool Caching

CrewAI automatically caches tool outputs when the same input is used again within a crew run:

python
class ExpensiveAPITool(BaseTool): name: str = "Expensive API" description: str = "Calls an external API with expensive rate limits." cache_function: callable = lambda args: True # enable caching def _run(self, query: str) -> str: # This result will be cached after the first call return f"Data for: {query}"
Cache SettingBehavior
No cache functionResults are never cached
lambda args: TrueAlways cache results
lambda args: len(args) > 10Cache only for long queries
⚠️Warning

Caching is in-memory and scoped to one crew.kickoff() call. If you need persistent caching across runs, implement your own cache layer using Redis or a database.

💡Tip

Use caching aggressively for tools that call rate-limited APIs (e.g., GPT-4 API calls, external search APIs). A cache_function that always returns True for deterministic tools prevents redundant API calls and speeds up execution significantly.

python
from datetime import datetime, timedelta # Time-based caching — only cache for 5 minutes class TimeBasedCacheTool(BaseTool): name: str = "Time Check" description: str = "Checks current time." cache_function: callable = lambda args: False # never cache time data # Conditional caching — cache based on input characteristics class SmartCacheTool(BaseTool): name: str = "Stock Price" description: str = "Fetches current stock prices." cache_function: callable = lambda args: len(str(args)) < 20 # short queries

Built-in Tools (crewai_tools)

The crewai_tools package provides many ready-to-use tools:

python
from crewai_tools import ( SerperDevTool, # Google Search via Serper ScrapeWebsiteTool, # Extract text from a URL FileReadTool, # Read local files FileWriterTool, # Write to local files MDXSearchTool, # Search within .mdx documentation DirectoryReadTool, # List directory contents CodeInterpreterTool, # Execute Python code PDFSearchTool, # Search PDF documents ) # Attach tools to an agent researcher = Agent( role="Researcher", goal="Find and summarize online information", backstory="You are a research specialist.", tools=[SerperDevTool(), ScrapeWebsiteTool()], )

Built-in Tool Comparison

ToolPurposeInputOutputCategory
SerperDevToolGoogle SearchQuery stringSearch result snippetsSearch
ScrapeWebsiteToolWeb scrapingURLPage text contentWeb
FileReadToolRead filesFile pathFile contentsFile
FileWriterToolWrite filesPath + contentConfirmation messageFile
MDXSearchToolMDX doc searchQueryRelevant sectionsSearch
DirectoryReadToolList directoryDirectory pathFile/folder listFile
CodeInterpreterToolRun PythonCode stringExecution outputCode
PDFSearchToolPDF searchPDF path + queryMatching text chunksSearch

Tool Categories

CategoryToolsUse Case
SearchSerperDevTool, MDXSearchTool, PDFSearchToolFinding information
WebScrapeWebsiteToolExtracting web content
FileFileReadTool, FileWriterTool, DirectoryReadToolLocal file operations
CodeCodeInterpreterToolRunning code

Sharing Context Between Tasks

Tasks can share context explicitly, enabling multi-step research workflows:

python
from crewai import Agent, Task, Crew researcher = Agent( role="Research Analyst", goal="Find comprehensive information", backstory="You are a skilled researcher.", ) writer = Agent( role="Technical Writer", goal="Create documentation from research notes", backstory="You write clear developer documentation.", ) # Task 1: research research_task = Task( description="Research CrewAI custom tool development.", expected_output="Detailed research notes with API references.", agent=researcher, ) # Task 2: write — receives research output as context write_task = Task( description="""Write a step-by-step guide based on this research: {context}""", expected_output="A complete guide in markdown format.", agent=writer, context=[research_task], # explicit context from prior task ) crew = Crew( agents=[researcher, writer], tasks=[research_task, write_task], verbose=True, ) result = crew.kickoff()

Context Flow Diagram

100%

Full Example: Custom Tool + Shared Context

python
from crewai.tools import BaseTool from crewai import Agent, Task, Crew from pydantic import BaseModel, Field # --- Custom tool --- class DocumentQueryInput(BaseModel): doc_id: str = Field(description="Document identifier") question: str = Field(description="Question about the document") class DocumentQATool(BaseTool): name: str = "Document QA" description: str = "Answer questions about internal documents." args_schema: type = DocumentQueryInput def _run(self, doc_id: str, question: str) -> str: # Mock QA — replace with a real retrieval pipeline return f"Answer to '{question}' on doc {doc_id}: [mock answer]" # --- Agents & Tasks --- qa_agent = Agent( role="QA Specialist", goal="Answer document questions accurately", backstory="You are an expert at document analysis.", tools=[DocumentQATool()], ) summarizer = Agent( role="Summarizer", goal="Summarize Q&A into key insights", backstory="You distill complex Q&A into clear summaries.", ) qa_task = Task( description="Answer questions about doc-42 using the Document QA tool.", expected_output="Answers to all questions.", agent=qa_agent, ) summary_task = Task( description="Summarize the Q&A results.\n\n{context}", expected_output="3 key insight bullet points.", agent=summarizer, context=[qa_task], ) crew = Crew( agents=[qa_agent, summarizer], tasks=[qa_task, summary_task], verbose=True, ) crew.kickoff()

Interactive Questions

Practice Question

You build a custom tool that queries a weather API. The agent never uses it. What is the most likely cause?

Practice Question

Your custom tool calls an API with rate limits. The same query is made 10 times in one crew run. How can you optimize this?

Practice Question

A task needs input from two different upstream tasks. How do you provide both contexts?

Practice Question

You deploy a CrewAI system to production. Tool caching no longer works across user sessions. Why?

Practice Question

An agent has both SerperDevTool (web search) and CalculatorTool. The task is 'Calculate 15% of 340'. Which tool should the agent use?


5 Practice Questions

1. Which base class should you extend to create a custom CrewAI tool?

  • A) Agent
  • B) BaseTool
  • C) ToolBase
  • D) Crew

2. What is the purpose of args_schema in a custom tool?

  • A) It enables caching
  • B) It defines the tool's input parameters using Pydantic ✅
  • C) It sets the tool's description
  • D) It registers the tool with the crew

3. Which tool from crewai_tools would you use to search the web?

  • A) FileReadTool
  • B) CodeInterpreterTool
  • C) SerperDevTool
  • D) DirectoryReadTool

4. How does tool caching work in CrewAI?

  • A) Results are stored in a SQLite database
  • B) Results are cached in memory per kickoff() call ✅
  • C) Results are never cached
  • D) Caching requires a Redis instance

5. Which parameter on a Task allows you to pass output from another task?

  • A) context
  • B) depends_on
  • C) inputs
  • D) shared_context

Success

Key Takeaways

  • Extend BaseTool and implement _run() to create custom tools.
  • Use Pydantic models as args_schema for structured tool inputs.
  • Tool caching is in-memory per crew run and reduces redundant API calls.
  • The crewai_tools package provides ready-to-use tools for search, scraping, files, and code execution.
  • The context parameter on a Task enables explicit data flow between tasks.
  • Custom tools can encapsulate any external API or computation.
  • Always provide a clear name and description for each tool so agents use them correctly.
  • Tool descriptions are what LLMs use to decide tool selection — invest in them.
  • Agent-level tools can be overridden by task-level tools for specific needs.
Progress80%