How to Fix 'tool calling failure' in CrewAI (Python)
What the error means
tool calling failure in CrewAI usually means the agent tried to invoke a tool, but the tool signature, return shape, or runtime setup did not match what CrewAI expected. You typically hit it when an agent is allowed to use tools, but the tool function is malformed, missing required metadata, or returns something the framework cannot serialize.
In practice, this shows up during task execution after the LLM decides to call a tool. The stack trace often includes something like crewai.tools.tool_usage.ToolUsageError, Tool calling failure, or a lower-level exception from your own function.
The Most Common Cause
The #1 cause is a bad tool definition: wrong signature, missing docstring/description, or returning an unsupported value. CrewAI tools need to be explicit about input and output, and the agent needs a clean contract to call.
Here’s the broken pattern versus the fixed pattern.
| Broken | Fixed |
|---|---|
| Tool has no proper schema/metadata | Tool is declared with @tool and clear description |
| Function accepts arbitrary args | Function accepts one typed input or a structured model |
| Returns raw complex objects | Returns plain string / JSON-serializable dict |
# BROKEN
from crewai import Agent, Task, Crew
from crewai_tools import tool
@tool
def lookup_policy(policy_id, extra):
# extra is not what the LLM will reliably provide
return {"policy": policy_id, "status": "active"} # may fail serialization in some flows
agent = Agent(
role="Insurance Assistant",
goal="Look up policy details",
backstory="You help customers.",
tools=[lookup_policy],
)
task = Task(
description="Find policy 12345",
expected_output="Policy details",
agent=agent,
)
crew = Crew(agents=[agent], tasks=[task])
crew.kickoff()
# FIXED
from crewai import Agent, Task, Crew
from crewai_tools import tool
@tool("lookup_policy")
def lookup_policy(policy_id: str) -> str:
"""Look up a policy by ID and return a short text summary."""
# Replace with real DB/API call
return f"Policy {policy_id}: active"
agent = Agent(
role="Insurance Assistant",
goal="Look up policy details",
backstory="You help customers.",
tools=[lookup_policy],
)
task = Task(
description="Find policy 12345",
expected_output="Policy details",
agent=agent,
)
crew = Crew(agents=[agent], tasks=[task])
crew.kickoff()
If you’re using custom tools with Pydantic schemas or subclassing BaseTool, keep the input contract strict. CrewAI’s tool router does much better when it can infer exactly one input shape.
Other Possible Causes
1) The agent is not actually allowed to use tools
This happens when tools=[], allow_delegation is misread as tool access, or you attached the tool to the wrong agent.
agent = Agent(
role="Support Agent",
goal="Answer account questions",
backstory="...",
tools=[], # no tools attached
)
Fix:
agent = Agent(
role="Support Agent",
goal="Answer account questions",
backstory="...",
tools=[lookup_policy],
)
2) The tool returns an unserializable object
CrewAI expects outputs that can be passed through its internal message flow. Returning a DB cursor, datetime object without conversion, open file handle, or custom class often breaks tool execution.
# Bad
return session.query(Policy).filter_by(id=policy_id).first()
# Good
policy = session.query(Policy).filter_by(id=policy_id).first()
return f"{policy.id} | {policy.status} | {policy.updated_at.isoformat()}"
3) Your prompt encourages malformed tool arguments
If the model invents extra fields or passes natural language where your tool expects a strict ID, you’ll see failures like argument validation errors or Tool calling failure.
task = Task(
description="""
Use lookup_policy with whatever info you need.
""",
expected_output="...",
agent=agent,
)
Fix by making the call format explicit:
task = Task(
description="""
Call lookup_policy with exactly one argument:
- policy_id: string like "12345"
""",
expected_output="...",
agent=agent,
)
4) Version mismatch between CrewAI and crewai-tools
A lot of these failures are just incompatible package versions. If crewai and crewai-tools are out of sync, tool decorators and invocation internals can break.
pip show crewai crewai-tools
pip install -U crewai crewai-tools
If you pinned versions in requirements.txt, check both together:
crewai==0.x.x
crewai-tools==0.x.x
How to Debug It
- •
Inspect the full traceback
- •Look for
ToolUsageError,ValidationError, or your own exception underneathtool calling failure. - •If you see Pydantic validation errors, your function signature or schema is wrong.
- •Look for
- •
Print the raw input your tool receives
- •Add logging at the top of the function.
- •Confirm whether CrewAI is passing one string argument or a structured payload.
@tool("lookup_policy")
def lookup_policy(policy_id: str) -> str:
print(f"lookup_policy called with: {policy_id!r}")
return f"Policy {policy_id}: active"
- •Run the tool outside CrewAI
- •Call it directly in Python.
- •If it fails standalone, CrewAI is not the problem.
print(lookup_policy.func("12345"))
- •Reduce to one agent and one tool
- •Remove memory, delegation, multiple tasks, and extra tools.
- •Reintroduce complexity only after basic invocation works.
Prevention
- •Keep tools narrow: one responsibility, one clear input shape, one serializable output.
- •Return plain strings or JSON-safe dicts from tools; avoid ORM objects and custom classes.
- •Pin compatible versions of
crewaiandcrewai-toolsin every project. - •Write a quick unit test that calls each tool directly before wiring it into an
Agent.
If you’re seeing tool calling failure, start with the tool signature first. In most CrewAI projects I’ve debugged, that’s where the bug actually lives.
Keep learning
- •The complete AI Agents Roadmap — my full 8-step breakdown
- •Free: The AI Agent Starter Kit — PDF checklist + starter code
- •Work with me — I build AI for banks and insurance companies
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