Jump to content

Connect Leeroopedia MCP: Equip your AI agents to search best practices, build plans, verify code, diagnose failures, and look up hyperparameter defaults.

Implementation:CrewAIInc CrewAI Tool Usage And Hooks

From Leeroopedia

Overview

Concrete tool execution orchestrator and hook decorators for monitoring and intercepting tool calls provided by the CrewAI framework.

Source Reference

  • Repository: crewAIInc/crewAI
  • Files:
    • lib/crewai/src/crewai/tools/tool_usage.py -- Lines L73-1052 (ToolUsage class)
    • lib/crewai/src/crewai/hooks/decorators.py -- Lines L203-300 (hook decorators)

Signatures

ToolUsage

ToolUsage(
    tools_handler,
    tools,
    task,
    function_calling_llm,
    agent,
).use(calling, tool_string) -> str

The use() method is the main entry point. It receives the tool call information from the LLM output, resolves the tool, validates arguments, executes hooks, runs the tool, and returns the result.

@before_tool_call Decorator

@before_tool_call(
    func=None,
    *,
    tools: list[str] | None = None,
    agents: list[str] | None = None,
)

Registers a function as a before-tool-call hook. The decorated function receives a ToolCallHookContext and returns bool | None:

  • Return False to block the tool execution.
  • Return True or None to allow execution to proceed.

@after_tool_call Decorator

@after_tool_call(
    func=None,
    *,
    tools: list[str] | None = None,
    agents: list[str] | None = None,
)

Registers a function as an after-tool-call hook. The decorated function receives a ToolCallHookContext (which includes the tool_result) and returns str | None:

  • Return a str to replace the tool's result with the returned value.
  • Return None to keep the original result unchanged.

ToolCallHookContext

class ToolCallHookContext:
    tool_name: str           # Name of the tool being invoked
    tool_input: dict         # Mutable dict of tool arguments (can be modified in before hooks)
    tool: BaseTool           # The tool instance
    agent: BaseAgent         # The agent invoking the tool
    task: Task               # The current task
    crew: Crew               # The crew the agent belongs to
    tool_result: str | None  # The tool's output (only available in after hooks)

Fields

Context Field Available In Mutable Description
tool_name before, after No The name of the tool being called
tool_input before, after Yes (before) Dict of arguments; can be modified in before hooks to change inputs
tool before, after No The BaseTool instance being invoked
agent before, after No The agent that triggered the tool call
task before, after No The task context of the tool call
crew before, after No The crew the agent belongs to
tool_result after only No The string result returned by the tool (only populated in after hooks)

Import

from crewai.hooks import before_tool_call, after_tool_call

How It Works

ToolUsage Execution Pipeline

The ToolUsage class orchestrates the complete tool invocation lifecycle:

  1. Parse the LLM's output to extract the tool name and arguments. The parser handles multiple output formats (JSON function calls, XML-style tool use, natural language).
  2. Resolve the tool by matching the parsed name against available tools. If no match is found, an error is returned to the LLM.
  3. Validate the arguments against the tool's args_schema. Type coercion is attempted for minor mismatches (e.g., string to int).
  4. Check usage limits: If max_usage_count is set and the count has been reached, return an error.
  5. Fire before hooks: Execute all registered @before_tool_call hooks matching the tool and agent filters. If any hook returns False, abort execution.
  6. Execute the tool's _run() method with the validated (and possibly hook-modified) arguments.
  7. Fire after hooks: Execute all registered @after_tool_call hooks. If any hook returns a string, that string replaces the tool result.
  8. Return the final result string to the LLM for further reasoning.

Hook Registration

Hooks are registered globally using the @before_tool_call and @after_tool_call decorators. The optional tools and agents parameters act as filters:

  • tools=["search_web", "read_file"]: The hook only fires for tools with these names.
  • agents=["Research Analyst"]: The hook only fires when this agent role invokes a tool.
  • No filters: The hook fires for every tool invocation by every agent.

Example

@before_tool_call for Logging

from crewai.hooks import before_tool_call


@before_tool_call
def log_tool_invocation(context):
    """Log every tool call for observability."""
    print(
        f"[TOOL CALL] Agent '{context.agent.role}' "
        f"calling '{context.tool_name}' "
        f"with args: {context.tool_input}"
    )
    # Return None to allow execution to proceed
    return None

@before_tool_call for Blocking

from crewai.hooks import before_tool_call


@before_tool_call(tools=["delete_file"])
def block_dangerous_delete(context):
    """Block file deletion for system-critical paths."""
    file_path = context.tool_input.get("file_path", "")
    if file_path.startswith("/etc/") or file_path.startswith("/usr/"):
        print(f"[BLOCKED] Refusing to delete system file: {file_path}")
        return False  # Block execution
    return True  # Allow execution

@after_tool_call for Result Sanitization

from crewai.hooks import after_tool_call
import re


@after_tool_call(tools=["search_web", "scrape_website"])
def sanitize_pii(context):
    """Remove email addresses from tool results before returning to LLM."""
    if context.tool_result:
        sanitized = re.sub(
            r'[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}',
            '[EMAIL REDACTED]',
            context.tool_result,
        )
        return sanitized  # Replace result with sanitized version
    return None  # Keep original result

@after_tool_call for Result Truncation

from crewai.hooks import after_tool_call


@after_tool_call
def truncate_long_results(context):
    """Truncate tool results that exceed 10,000 characters."""
    max_length = 10_000
    if context.tool_result and len(context.tool_result) > max_length:
        truncated = context.tool_result[:max_length]
        return truncated + "\n\n[RESULT TRUNCATED]"
    return None  # Keep original result

Filtered Hook for Specific Agent

from crewai.hooks import before_tool_call


@before_tool_call(agents=["Junior Researcher"], tools=["execute_code"])
def restrict_junior_code_execution(context):
    """Prevent junior researcher agents from executing code."""
    print(f"[POLICY] Code execution not permitted for role: {context.agent.role}")
    return False  # Block execution

Modifying Tool Inputs

from crewai.hooks import before_tool_call


@before_tool_call(tools=["search_web"])
def add_safety_filter(context):
    """Add a safety filter to all web search queries."""
    original_query = context.tool_input.get("query", "")
    context.tool_input["query"] = f"{original_query} safe:active"
    return True  # Allow execution with modified input

Principle Link

Principle:CrewAIInc_CrewAI_Tool_Execution_And_Monitoring

See Also

Page Connections

Double-click a node to navigate. Hold to expand connections.
Principle
Implementation
Heuristic
Environment