InkdownInkdown
Start writing

Study

59 filesยท8 subfolders

Shared Workspace

Study
core

03_TOOL_SYSTEM

Shared from "Study" on Inkdown

Tool System - Comprehensive Deep Dive

Overview

The Tool system is one of the most powerful features of the OpenAI Agents SDK. Tools are functions or capabilities that agents can call to perform actions beyond just generating text. Think of tools as "skills" or "abilities" that you give to your agents - they can search the web, read files, execute code, call APIs, interact with databases, and much more.

Core Concepts

What is a Tool?

A tool is a callable function that an agent can invoke. When an agent needs to perform an action (like searching the web or reading a file), it can call a tool instead of trying to do everything through text generation.

Why Tools Matter:

  1. Action Capability - Agents can actually do things, not just talk about them
  2. Accuracy - Tools can provide precise, factual information
  3. Integration - Connect agents to external systems (databases, APIs, etc.)
  4. Efficiency - Delegating to specialized tools is often faster/better
programming-language-concepts.md
zero-language-explanation.md
DB
01-introduction.md
02-relational-databases.md
03-database-design.md
04-indexing.md
05-transactions-acid.md
06-nosql-databases.md
07-query-optimization.md
08-replication-ha.md
09-sharding-partitioning.md
10-caching-strategies.md
11-cap-theorem.md
12-connection-pooling.md
13-backup-recovery.md
14-monitoring.md
15-database-selection.md
README.md
JS
Event loop
Merlin Backend
01-Orchestration.md
02-DeepResearch.md
03-Search.md
04-Scraping.md
05-Streaming.md
06-MultiProviderLLM.md
07-MemoryAndContext.md
08-ErrorHandling.md
09-RateLimiting.md
10-TaskQueue.md
11-SecurityAndAuth.md
Orchestration-2nd-draft
OpenAI Agents Python
00_OVERVIEW.md
01_AGENT_SYSTEM.md
02_RUNNER_SYSTEM.md
03_TOOL_SYSTEM.md
04_ITEMS_SYSTEM.md
05_GUARDRAILS.md
06_HANDOFFS.md
07_MEMORY_SESSIONS.md
08_MODEL_PROVIDERS.md
09_SANDBOX_SYSTEM.md
10_TRACING.md
11_RUN_STATE.md
12_CONTEXT.md
13_LIFECYCLE_HOOKS.md
14_CONFIGURATION.md
15_ERROR_HANDLING.md
16_STREAMING.md
17_EXTENSIONS.md
18_MCP_INTEGRATION.md
19_BEST_PRACTICES.md
20_ARCHITECTURE_PATTERNS.md
opencode-study
context-handling
core
Python
Alembic
Basics
sqlalchemy - fastapi
SQLAlchemy overview
tweets
system_design_for_agentic_apps.md
  • Reliability - Tools can have error handling and validation
  • Tool Types

    The SDK supports several types of tools:

    1. Function Tools - Python functions decorated with @function_tool
    2. Hosted Tools - OpenAI-hosted tools (file search, web search, code interpreter)
    3. MCP Tools - Tools from Model Context Protocol servers
    4. Agent Tools - Other agents exposed as tools
    5. Shell Tools - Command execution tools
    6. Computer Tools - Computer interaction tools
    7. Custom Tools - Custom tool implementations

    Function Tools

    Basic Function Tools

    The most common type is the function tool - a Python function decorated with @function_tool:

    Python
    from agents import function_tool
    
    @function_tool
    def calculate_sum(a: int, b: int) -> int:
        """Calculate the sum of two numbers."""
        return a + b
    
    agent = Agent(
        name="calculator",
        instructions="Help with math calculations",
        tools=[calculate_sum],
    )

    How it works:

    1. The decorator converts the function into a FunctionTool instance
    2. The function's signature is analyzed to create a JSON schema
    3. The schema is exposed to the LLM so it knows how to call the tool
    4. When the LLM calls the tool, the function is executed with the provided arguments
    5. The return value is converted to a string and sent back to the LLM
    Function Tool Parameters

    Function tools can have various parameter types:

    Python
    from typing import Optional, List
    from datetime import datetime
    from pydantic import BaseModel
    
    @function_tool
    def search_database(
        query: str,
        limit: int = 10,
        filters: Optional[List[str]] = None,
        date: Optional[datetime] = None,
    ) -> str:
        """Search the database with optional filters."""
        # Implementation
        return f"Results for {query}"

    Supported Parameter Types:

    • Basic types: str, int, float, bool
    • Optional types: Optional[T] or T | None
    • Collections: List[T], Dict[str, T]
    • Enums: Enum subclasses
    • Pydantic models: BaseModel subclasses
    • Dataclasses: @dataclass classes
    Context-Aware Function Tools

    Function tools can access the run context:

    Python
    from agents import function_tool, RunContextWrapper
    
    @function_tool
    def get_user_data(context: RunContextWrapper[MyContext], user_id: str) -> str:
        """Get user data from the context."""
        # Access context
        current_user = context.context.user_id
        return f"Data for user {user_id}"

    Context provides:

    • context.context - Your custom context object
    • context.usage - Token usage tracking
    • context.approve_tool() / context.reject_tool() - Approval management
    • context.tool_name, context.tool_call_id - Tool metadata
    Tool Context

    For more detailed tool metadata, use ToolContext:

    Python
    from agents import function_tool, ToolContext
    
    @function_tool
    def detailed_tool(context: ToolContext, param: str) -> str:
        """Tool with detailed context."""
        print(f"Tool name: {context.tool_name}")
        print(f"Tool call ID: {context.tool_call_id}")
        print(f"Tool arguments: {context.tool_arguments}")
        print(f"Tool call: {context.tool_call}")
        return f"Processed {param}"

    ToolContext provides:

    • All of RunContextWrapper
    • tool_name - Name of the tool being called
    • tool_call_id - Unique ID for this tool call
    • tool_arguments - Raw JSON arguments
    • tool_call - The full tool call object
    • tool_namespace - Namespace if configured
    • run_config - Run configuration
    • agent - The agent calling the tool
    Async Function Tools

    Tools can be async:

    Python
    import asyncio
    
    @function_tool
    async def async_operation(query: str) -> str:
        """Perform an async operation."""
        await asyncio.sleep(1)  # Simulate async work
        return f"Async result for {query}"
    Tool Namespaces

    Organize tools into namespaces to avoid name collisions:

    Python
    from agents import function_tool, tool_namespace
    
    @tool_namespace("math")
    @function_tool
    def add(a: int, b: int) -> int:
        """Add two numbers."""
        return a + b
    
    @tool_namespace("math")
    @function_tool
    def multiply(a: int, b: int) -> int:
        """Multiply two numbers."""
        return a * b
    
    @tool_namespace("string")
    @function_tool
    def concatenate(a: str, b: str) -> str:
        """Concatenate two strings."""
        return a + b

    Why use namespaces?

    • Avoid name collisions when tools have similar names
    • Organize tools logically
    • Make it clear which "domain" a tool belongs to
    • The LLM sees names like math.add and string.concatenate
    Dynamic Tool Enablement

    Tools can be dynamically enabled or disabled:

    Python
    @function_tool
    def admin_function(context: RunContextWrapper[MyContext]) -> str:
        """Admin-only function."""
        return "Admin operation"
    
    # Set enablement condition
    admin_function.is_enabled = lambda ctx, agent: ctx.context.user_role == "admin"

    Use cases:

    • Role-based access control
    • Feature flags
    • Context-dependent availability
    • A/B testing
    Tool Descriptions

    The function's docstring becomes the tool's description:

    Python
    @function_tool
    def search_web(query: str) -> str:
        """
        Search the web for information about the given query.
        
        Returns a summary of the top 5 search results.
        Use this when you need current information or facts not in your training data.
        """
        # Implementation
        return "Search results"

    Good descriptions:

    • Explain what the tool does
    • Explain when to use it
    • Explain what it returns
    • Keep it concise but informative
    Tool Error Handling

    Tools can raise errors:

    Python
    @function_tool
    def risky_operation(value: int) -> str:
        """Perform a risky operation."""
        if value < 0:
            raise ValueError("Value must be positive")
        return f"Processed {value}"

    How errors are handled:

    1. The SDK catches the exception
    2. By default, formats it as an error message for the LLM
    3. The LLM can try again with different arguments
    4. You can customize error formatting with failure_error_function
    Custom Error Formatting
    Python
    from agents import ToolErrorFunction
    
    def custom_error_formatter(
        context: RunContextWrapper,
        error: Exception,
    ) -> str:
        """Custom error formatter."""
        return f"Custom error: {str(error)}. Please try again."
    
    @function_tool(failure_error_function=custom_error_formatter)
    def tool_with_custom_errors(param: str) -> str:
        """Tool with custom error handling."""
        raise ValueError("Something went wrong")
    Tool Output Types

    Tools can return different output types:

    Python
    from agents import ToolOutputText, ToolOutputImage, ToolOutputFileContent
    
    @function_tool
    def text_tool() -> ToolOutputText:
        """Return text output."""
        return ToolOutputText(text="Plain text result")
    
    @function_tool
    def image_tool() -> ToolOutputImage:
        """Return image output."""
        return ToolOutputImage(
            image_url="https://example.com/image.png",
            detail="high"
        )
    
    @function_tool
    def file_tool() -> ToolOutputFileContent:
        """Return file content."""
        return ToolOutputFileContent(
            file_data="base64encodedcontent",
            filename="result.txt"
        )

    Why use structured output?

    • Tell the LLM what type of content to expect
    • Enable multimodal interactions
    • Provide file outputs for further processing
    • Better integration with model capabilities

    Hosted Tools

    OpenAI Hosted Tools

    OpenAI provides several hosted tools that you can use:

    Python
    from agents import Agent, FileSearchTool, WebSearchTool, CodeInterpreterTool
    
    agent = Agent(
        name="researcher",
        instructions="Research topics using available tools",
        tools=[
            FileSearchTool(),
            WebSearchTool(),
            CodeInterpreterTool(),
        ],
    )

    FileSearchTool:

    • Search through uploaded documents
    • Supports vector search
    • Good for RAG (Retrieval-Augmented Generation)

    WebSearchTool:

    • Search the web in real-time
    • Good for current information
    • Supports filters and location

    CodeInterpreterTool:

    • Execute Python code
    • Good for data analysis
    • Supports file operations
    Image Generation Tool
    Python
    from agents import Agent, ImageGenerationTool
    
    agent = Agent(
        name="artist",
        instructions="Generate images based on descriptions",
        tools=[ImageGenerationTool()],
    )

    MCP Tools

    Model Context Protocol

    MCP is a standard protocol for exposing tools to AI models:

    Python
    from agents import Agent, MCPServerStdio, MCPServerStdioParams
    
    mcp_server = MCPServerStdio(
        params=MCPServerStdioParams(
            command="python",
            args=["-m", "my_mcp_server"],
        )
    )
    
    agent = Agent(
        name="mcp_agent",
        instructions="Use MCP tools",
        mcp_servers=[mcp_server],
    )

    MCP Benefits:

    • Standardized tool interface
    • Language-agnostic
    • Easy tool sharing
    • Community ecosystem
    MCP Configuration
    Python
    from agents import MCPConfig
    
    mcp_config = MCPConfig(
        convert_schemas_to_strict=True,  # Convert to strict JSON schema
        failure_error_function=default_tool_error_function,  # Error handling
    )
    
    agent = Agent(
        name="mcp_agent",
        instructions="Use MCP tools",
        mcp_servers=[mcp_server],
        mcp_config=mcp_config,
    )
    MCP Tool Filtering
    Python
    from agents import ToolFilter, ToolFilterCallable
    
    def filter_tools(context: ToolFilterContext) -> ToolFilter:
        """Filter MCP tools."""
        return ToolFilterStatic(include=["tool1", "tool2"])
    
    agent = Agent(
        name="mcp_agent",
        instructions="Use MCP tools",
        mcp_servers=[mcp_server],
        mcp_config=MCPConfig(
            tool_filter=filter_tools,
        ),
    )

    Agent as Tool

    Exposing Agents as Tools

    Agents can be exposed as tools to other agents:

    Python
    from agents import Agent
    
    specialist = Agent(
        name="specialist",
        instructions="Handle specialized tasks",
    )
    
    generalist = Agent(
        name="generalist",
        instructions="Delegate to specialists when needed",
        tools=[specialist.as_tool("specialist_tool", "Call the specialist")],
    )

    How it works:

    1. The agent is wrapped in a FunctionTool
    2. When called, it runs the nested agent
    3. The nested agent's output is returned to the caller
    4. The nested agent runs in isolation (doesn't see parent conversation)
    Agent Tool Configuration
    Python
    specialist_tool = specialist.as_tool(
        tool_name="specialist",
        tool_description="Call the specialist agent",
        max_turns=5,  # Limit nested agent turns
        needs_approval=True,  # Require approval
        parameters=SpecialistInput,  # Structured input
    )
    Agent Tool Streaming
    Python
    def on_agent_stream(event: AgentToolStreamEvent) -> None:
        """Handle streaming events from nested agent."""
        print(f"Event: {event.event}")
    
    specialist_tool = specialist.as_tool(
        tool_name="specialist",
        tool_description="Call the specialist",
        on_stream=on_agent_stream,
    )

    Shell Tools

    Local Shell Tool

    Execute shell commands:

    Python
    from agents import Agent, LocalShellTool
    
    agent = Agent(
        name="shell_agent",
        instructions="Execute shell commands",
        tools=[LocalShellTool()],
    )

    Security Considerations:

    • Shell tools are powerful and potentially dangerous
    • Use with appropriate guardrails
    • Consider sandboxing
    • Require approval for sensitive commands
    Shell Tool Configuration
    Python
    from agents import (
        LocalShellTool,
        ShellToolLocalEnvironment,
        ShellToolContainerNetworkPolicyDisabled,
    )
    
    tool = LocalShellTool(
        environment=ShellToolLocalEnvironment(),
        network_policy=ShellToolContainerNetworkPolicyDisabled(),
    )

    Computer Tools

    Computer Interaction

    Tools for interacting with a computer environment:

    Python
    from agents import Agent, ComputerTool
    
    agent = Agent(
        name="computer_agent",
        instructions="Use the computer to perform tasks",
        tools=[ComputerTool()],
    )

    Computer Tool Capabilities:

    • Take screenshots
    • Click buttons
    • Type text
    • Navigate UI
    • Execute computer actions

    Custom Tools

    Custom Tool Implementation

    Create custom tool implementations:

    Python
    from agents import Tool, ToolContext
    
    class MyCustomTool(Tool):
        name = "my_custom_tool"
        description = "A custom tool implementation"
        
        async def on_invoke_tool(self, context: ToolContext, args: str) -> Any:
            """Implement the tool logic."""
            # Parse args
            # Execute logic
            return "Result"
    
    agent = Agent(
        name="custom_agent",
        instructions="Use custom tools",
        tools=[MyCustomTool()],
    )
    Custom Tool with Schema
    Python
    from agents import Tool, ToolContext
    from pydantic import BaseModel
    
    class MyToolArgs(BaseModel):
        param1: str
        param2: int
    
    class MyCustomTool(Tool):
        name = "my_custom_tool"
        description = "A custom tool with schema"
        params_json_schema = MyToolArgs.model_json_schema()
        
        async def on_invoke_tool(self, context: ToolContext, args: str) -> Any:
            parsed = MyToolArgs.model_validate_json(args)
            # Use parsed.args
            return "Result"

    Tool Guardrails

    Tool Input Guardrails

    Validate tool inputs:

    Python
    from agents import tool_input_guardrail, ToolInputGuardrailFunctionOutput
    
    @tool_input_guardrail
    def validate_search_args(
        context: RunContextWrapper,
        tool_name: str,
        args_json: str,
    ) -> ToolInputGuardrailFunctionOutput:
        """Validate search tool arguments."""
        args = json.loads(args_json)
        if len(args.get("query", "")) < 3:
            return ToolInputGuardrailFunctionOutput(
                output_info="Query too short",
                tripwire_triggered=True,
            )
        return ToolInputGuardrailFunctionOutput(
            output_info="Valid",
            tripwire_triggered=False,
        )
    
    @function_tool(tool_input_guardrails=[validate_search_args])
    def search(query: str) -> str:
        """Search with input validation."""
        return f"Results for {query}"
    Tool Output Guardrails

    Validate tool outputs:

    Python
    from agents import tool_output_guardrail, ToolOutputGuardrailFunctionOutput
    
    @tool_output_guardrail
    def validate_output(
        context: RunContextWrapper,
        tool_name: str,
        output: str,
    ) -> ToolOutputGuardrailFunctionOutput:
        """Validate tool output."""
        if "sensitive" in output.lower():
            return ToolOutputGuardrailFunctionOutput(
                output_info="Sensitive content detected",
                tripwire_triggered=True,
            )
        return ToolOutputGuardrailFunctionOutput(
            output_info="Valid",
            tripwire_triggered=False,
        )
    
    @function_tool(tool_output_guardrails=[validate_output])
    def process_data(data: str) -> str:
        """Process data with output validation."""
        return f"Processed {data}"

    Tool Metadata

    Tool Origin

    Track where a tool came from:

    Python
    from agents import ToolOrigin, ToolOriginType
    
    origin = ToolOrigin(
        type=ToolOriginType.FUNCTION,
        mcp_server_name=None,
        agent_name=None,
        agent_tool_name=None,
    )

    Tool Origin Types:

    • FUNCTION - Regular function tool
    • MCP - MCP server tool
    • AGENT_AS_TOOL - Agent exposed as tool
    Tool Identity

    Tools have unique identities for tracking:

    Python
    from agents import get_function_tool_lookup_key, tool_qualified_name
    
    key = get_function_tool_lookup_key(tool, namespace="math")
    qualified_name = tool_qualified_name(tool, namespace="math")

    Tool Execution Flow

    Complete Tool Execution Flow
    Python
    # 1. Model requests tool call
    tool_call = ResponseFunctionToolCall(
        id="call_123",
        name="my_tool",
        arguments='{"param": "value"}',
    )
    
    # 2. Find the tool
    tool = find_tool(tool_call.name)
    
    # 3. Check if enabled
    if not is_tool_enabled(tool, context):
        skip_tool(tool_call)
    
    # 4. Check if approval needed
    if tool.needs_approval:
        approval = await request_approval(tool_call)
        if not approval:
            record_rejection(tool_call)
            return
    
    # 5. Run input guardrails
    guardrail_result = await run_tool_input_guardrails(tool, tool_call.arguments)
    if guardrail_result.tripwire_triggered:
        handle_guardrail_tripwire(guardrail_result)
        return
    
    # 6. Execute the tool
    try:
        result = await tool.execute(tool_call.arguments, context)
    except Exception as e:
        result = handle_tool_error(e, tool)
    
    # 7. Run output guardrails
    guardrail_result = await run_tool_output_guardrails(tool, result)
    if guardrail_result.tripwire_triggered:
        handle_guardrail_tripwire(guardrail_result)
        result = guardrail_result.output_info
    
    # 8. Record the result
    record_tool_output(tool_call, result)
    
    # 9. Return to model
    return result

    Tool Use Behavior

    Agent-Level Tool Use Behavior

    Configure how tool results are handled:

    Python
    from agents import Agent, StopAtTools
    
    agent = Agent(
        name="direct_agent",
        instructions="Use tools directly",
        tool_use_behavior="stop_on_first_tool",  # First tool result is final
    )
    
    agent = Agent(
        name="controlled_agent",
        instructions="Controlled tool use",
        tool_use_behavior=StopAtTools(
            stop_at_tool_names=["final_tool"]
        ),
    )
    Custom Tool Use Behavior
    Python
    from agents import ToolsToFinalOutputResult, ToolsToFinalOutputFunction
    
    async def custom_tool_handler(
        context: RunContextWrapper,
        tool_results: list[FunctionToolResult],
    ) -> ToolsToFinalOutputResult:
        """Custom tool result handling."""
        # Check if any tool produced an error
        if any("error" in r.output for r in tool_results):
            return ToolsToFinalOutputResult(
                is_final_output=True,
                final_output="An error occurred",
            )
        
        # Check if we got the answer
        if any("answer" in r.output for r in tool_results):
            return ToolsToFinalOutputResult(
                is_final_output=True,
                final_output="Got the answer",
            )
        
        # Continue with more turns
        return ToolsToFinalOutputResult(is_final_output=False)
    
    agent = Agent(
        name="custom_agent",
        instructions="Custom tool handling",
        tool_use_behavior=custom_tool_handler,
    )

    Tool Timeout

    Setting Tool Timeouts
    Python
    from agents import function_tool
    
    @function_tool(timeout=30)  # 30 second timeout
    def slow_operation(param: str) -> str:
        """Operation that might be slow."""
        time.sleep(60)  # This will timeout
        return "Done"

    Timeout Behavior:

    • When a timeout occurs, a ToolTimeoutError is raised
    • The error is formatted and sent to the LLM
    • The LLM can retry with different parameters

    Tool Best Practices

    1. Clear Names

    Use descriptive tool names:

    Python
    # Good
    @function_tool
    def search_academic_papers(query: str) -> str:
        ...
    
    # Avoid
    @function_tool
    def search(q: str) -> str:  # Too vague
        ...
    2. Type Hints

    Always use type hints:

    Python
    # Good
    @function_tool
    def process_data(
        user_id: str,
        limit: int,
        filters: Optional[List[str]] = None,
    ) -> str:
        ...
    
    # Avoid
    @function_tool
    def process_data(user_id, limit, filters=None):  # No type hints
        ...
    3. Docstrings

    Write clear docstrings:

    Python
    # Good
    @function_tool
    def calculate_discount(price: float, discount_percent: float) -> float:
        """
        Calculate the discounted price.
        
        Args:
            price: The original price.
            discount_percent: The discount percentage (0-100).
        
        Returns:
            The discounted price.
        
        Example:
            calculate_discount(100.0, 20) returns 80.0
        """
        return price * (1 - discount_percent / 100)
    
    # Avoid
    @function_tool
    def calculate_discount(price: float, discount_percent: float) -> float:
        """Calculate discount."""  # Too vague
        ...
    4. Error Handling

    Handle errors gracefully:

    Python
    # Good
    @function_tool
    def api_call(endpoint: str) -> str:
        """Call an API endpoint."""
        try:
            response = requests.get(endpoint)
            response.raise_for_status()
            return response.text
        except requests.RequestException as e:
            return f"API error: {str(e)}"
    
    # Avoid
    @function_tool
    def api_call(endpoint: str) -> str:
        """Call an API endpoint."""
        return requests.get(endpoint).text  # No error handling
    5. Input Validation

    Validate inputs:

    Python
    # Good
    @function_tool
    def divide(a: float, b: float) -> float:
        """Divide two numbers."""
        if b == 0:
            raise ValueError("Cannot divide by zero")
        return a / b
    
    # Avoid
    @function_tool
    def divide(a: float, b: float) -> float:
        """Divide two numbers."""
        return a / b  # Will crash on b=0

    Common Tool Patterns

    1. Database Query Tool
    Python
    @function_tool
    def query_database(table: str, filters: dict) -> str:
        """Query a database table with filters."""
        # Connect to database
        # Execute query
        # Return results as string
        return json.dumps(results)
    2. API Integration Tool
    Python
    @function_tool
    def call_external_api(endpoint: str, method: str = "GET") -> str:
        """Call an external API."""
        response = requests.request(method, endpoint)
        return response.text
    3. File Operations Tool
    Python
    @function_tool
    def read_file(path: str) -> str:
        """Read a file's contents."""
        with open(path, 'r') as f:
            return f.read()
    
    @function_tool
    def write_file(path: str, content: str) -> str:
        """Write content to a file."""
        with open(path, 'w') as f:
            f.write(content)
        return "File written"
    4. Data Processing Tool
    Python
    @function_tool
    def process_csv(file_path: str, operation: str) -> str:
        """Process a CSV file."""
        df = pd.read_csv(file_path)
        
        if operation == "summarize":
            return df.describe().to_string()
        elif operation == "filter":
            return df.head().to_string()
        else:
            return "Unknown operation"

    Summary

    The Tool system is what gives agents their capabilities. Key takeaways:

    1. Function Tools are Python functions decorated with @function_tool
    2. Tool schemas are automatically generated from function signatures
    3. Context can be accessed via RunContextWrapper or ToolContext
    4. Namespaces organize tools and prevent name collisions
    5. Dynamic enablement allows context-dependent tool availability
    6. Hosted tools are OpenAI-provided tools (file search, web search, etc.)
    7. MCP tools come from Model Context Protocol servers
    8. Agents as tools enable powerful multi-agent patterns
    9. Shell tools execute commands
    10. Computer tools interact with computer environments
    11. Custom tools allow custom implementations
    12. Tool guardrails validate inputs and outputs
    13. Tool metadata tracks tool origin and identity
    14. Tool use behavior configures how results are handled
    15. Timeouts prevent hanging tools
    16. Error handling should be graceful
    17. Type hints are essential for schema generation
    18. Docstrings become tool descriptions
    19. Input validation prevents errors
    20. Parallel execution improves performance

    Understanding tools is crucial for building capable agents that can actually do things.