What is BaseTool._run()?
Every tool the LLM can invoke eventually lands in a BaseTool subclass: the framework validates arguments against args_schema (typically a Pydantic model), maps JSON-ish tool calls into Python kwargs, and routes the invocation to _run for synchronous work. What you return becomes the tool observation string the model reads on the next turn, so optimize for clarity — summarize long payloads, include stable ids, and surface actionable errors as plain text rather than opaque stack traces unless you truly need them.
Keep _run side-effecting only where necessary. Idempotent reads (search, fetch) are ideal; writes should either be guarded with explicit confirmation tools or designed so a mistaken duplicate call cannot corrupt data. When latency climbs, move network I/O to _arun or wrap async clients with asyncio.to_thread so you do not block the executor that also serves other agents.
The @tool decorator generates a BaseTool for simple functions, but hand-written subclasses win when you need shared setup, custom names, or richer descriptions pulled from configuration.
Use Cases
- • Custom integrations
- • Wrapping APIs
Key Features
- ✓ Validated input via args_schema
- ✓ Returns serializable result
When NOT to Use
Async-only operations — implement _arun() instead.
Notes
Return shape is part of your prompt contract
Agents reason over the literal tool string. If you return raw dict reprs or huge HTML dumps, subsequent calls waste tokens and invite hallucinated structure. Prefer tight JSON or markdown summaries.
Exceptions vs error strings
Raising stops the tool call with a hard failure — sometimes desirable for invariant violations. Returning an error description keeps the agent in the loop for self-correction when the mistake is recoverable.
Thread safety
A single tool instance may be invoked concurrently under parallel tasks. Store mutable shared state on self only when guarded, or allocate per-call clients inside _run.
Pair with _arun for hybrid tools
Implement both when callers might use kickoff() (sync) and kickoff_async(). Keep argument validation identical so the model sees one schema regardless of execution mode.
Parameters
| Parameter | Type | Required | Purpose |
|---|---|---|---|
| **kwargs | Any | No | Fields from args_schema. |
Code Examples
Minimal BaseTool with typed args
class Adder(BaseTool):
name = 'add'
description = 'Add two integers and return the decimal string.'
def _run(self, a: int, b: int) -> str:
return str(a + b)
Return JSON for structured downstream parsing
import json
from crewai.tools import BaseTool
class Lookup(BaseTool):
name = 'lookup_customer'
description = 'Fetch a customer record by id.'
def _run(self, customer_id: str) -> str:
row = db.get(customer_id)
return json.dumps(row, default=str)
Recoverable failure as a string (not a bare exception)
from crewai.tools import BaseTool
class SafeDelete(BaseTool):
name = 'safe_delete'
description = 'Delete a row when allowed; otherwise explain why not.'
def _run(self, row_id: str, force: bool = False) -> str:
if not force and not policy.allows(row_id):
return 'BLOCKED: policy denied delete'
db.delete(row_id)
return 'ok'
When to Use
Every BaseTool subclass.
Common Mistakes
❌ Returning non-stringable objects
✅ Return str(obj) or json.dumps(obj).
Related: @tool decorator reference, Agent class reference, and the first Crew tutorial.
BaseTool._run() FAQ
What is BaseTool._run() in CrewAI?
The synchronous body of a tool — the function the agent's runtime calls. Every tool the LLM can invoke eventually lands in a BaseTool subclass: the framework validates arguments against args_schema (typically a Pydantic model), maps JSON-ish tool calls into Python kwargs, and routes the invocation to _run for synchronous work. What you return becomes the tool observation string the model reads on the next turn, so optimize for clarity — summarize long payloads, include stable ids, and surface actionable errors as plain text rather than opaque stac…
Which CrewAI types expose the method BaseTool._run()?
DevShelfHub documents BaseTool._run() on Custom Tools. The reference maps it to Python module crewai.tools.BaseTool — pin your installed crewai version and match imports to the snippet on this page.
When should I use BaseTool._run()?
Every BaseTool subclass.
When should I avoid BaseTool._run()?
Async-only operations — implement _arun() instead.
How do I call BaseTool._run() from Python?
def _run(self, **kwargs): return do_work(**kwargs)
Where can I explore more CrewAI API reference pages?
Open the CrewAI API reference index on DevShelfHub to search classes, methods, and decorators, each with runnable examples, parameters, common mistakes, and cross-links.