> ## Documentation Index
> Fetch the complete documentation index at: https://praison.ai/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Tools

> Give agents the ability to search the web, run code, read files, and call APIs

Tools give agents the ability to take actions — search the web, run code, read files, and call APIs — beyond what an LLM knows from training.

```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
from praisonaiagents import Agent
from praisonaiagents.tools import duckduckgo

agent = Agent(
    name="Researcher",
    instructions="You are a research assistant. Use web search for current information.",
    tools=[duckduckgo],
)

agent.start("What are the latest developments in fusion energy?")
```

The user asks a research question; the agent calls web search and returns an answer grounded in live results.

```mermaid theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
graph LR
    subgraph "Tools Flow"
        Input[💬 User Request] --> Agent[🤖 Agent]
        Agent --> Decision{🧠 Need tool?}
        Decision -->|Yes| Tool[🔧 Tool]
        Tool --> Result[📄 Result]
        Result --> Agent
        Decision -->|No| Response[✅ Response]
        Agent --> Response
    end

    classDef input fill:#6366F1,stroke:#7C90A0,color:#fff
    classDef agent fill:#8B0000,stroke:#7C90A0,color:#fff
    classDef tool fill:#189AB4,stroke:#7C90A0,color:#fff
    classDef output fill:#10B981,stroke:#7C90A0,color:#fff

    class Input input
    class Agent,Decision agent
    class Tool,Result tool
    class Response output
```

<Note>
  Tool turns are treated specially: a mid-response network failure surfaces `provider_outcome_unknown` instead of silently re-running the tool. See [Replay-Safe Retries](/docs/features/replay-safe-retries).
</Note>

## Quick Start

<Steps>
  <Step title="Built-in Tools">
    ```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
    from praisonaiagents import Agent
    from praisonaiagents.tools import duckduckgo

    agent = Agent(
        instructions="Search the web to answer questions.",
        tools=[duckduckgo],
    )
    agent.start("What is today's top AI news?")
    ```
  </Step>

  <Step title="Custom Tool Function">
    ```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
    from praisonaiagents import Agent

    def get_stock_price(ticker: str) -> str:
        """Get the current stock price for a ticker symbol."""
        return f"${ticker}: $150.25 (mock data)"

    agent = Agent(
        instructions="You are a finance assistant.",
        tools=[get_stock_price],
    )
    agent.start("What is the current price of AAPL?")
    ```
  </Step>

  <Step title="Multiple Tools">
    ```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
    from praisonaiagents import Agent
    from praisonaiagents.tools import duckduckgo, wikipedia

    agent = Agent(
        instructions="You are a comprehensive research assistant.",
        tools=[duckduckgo, wikipedia],
    )
    agent.start("Research the history and current state of quantum computing.")
    ```
  </Step>

  <Step title="Async def tools work on both sync and async loops">
    ```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
    from praisonaiagents import Agent

    async def fetch_weather(city: str) -> str:
        """Fetch current weather for a city."""
        # ... any awaitable work ...
        return f"Weather for {city}: 72°F, sunny"

    agent = Agent(
        name="Weather Agent",
        instructions="Answer weather questions using the tool.",
        tools=[fetch_weather],
    )

    # As of PraisonAI PR #4634, the sync tool loop awaits async def tools
    # automatically via the built-in bridge — the tool body actually runs
    # and the model receives its real return value. Before PR #4634 the
    # coroutine was returned un-awaited and the model saw an empty string.
    agent.start("What is the weather in New York?")
    ```

    <Note>
      `async def` tools work identically on the sync tool loop (`chat()` / `start()`) and the async tool loop (`achat()` / `astart()`) as of [PraisonAI PR #4634](https://github.com/MervinPraison/PraisonAI/pull/4634). This applies to plain-callable tools, `BaseTool` subclasses (`.run` may be `async def`), LangChain-style `.run` methods, and CrewAI-style `._run` methods.
    </Note>
  </Step>
</Steps>

***

## Which Tools to Use?

```mermaid theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
graph TB
    Q{What does the agent need to do?}
    Q -->|Search current info| Web[duckduckgo / tavily\nweb search tools]
    Q -->|Read/write files| File[file tools\nread_file, write_file]
    Q -->|Run code| Code[code execution\nExecutionConfig(code_execution=True)]
    Q -->|Call external APIs| Custom[custom function\ndef my_tool(param) -> str]
    Q -->|MCP server tools| MCP[MCP integration\ndocs/features/mcp]

    classDef decision fill:#F59E0B,stroke:#7C90A0,color:#fff
    classDef tool fill:#6366F1,stroke:#7C90A0,color:#fff

    class Q decision
    class Web,File,Code,Custom,MCP tool
```

***

## How It Works

```mermaid theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
sequenceDiagram
    participant User
    participant Agent
    participant LLM
    participant Tool

    User->>Agent: "What's today's weather in Tokyo?"
    Agent->>LLM: Prompt with tool definitions
    LLM-->>Agent: Tool call: get_weather("Tokyo")
    Agent->>Tool: Execute get_weather("Tokyo")
    Tool-->>Agent: "15°C, partly cloudy"
    Agent->>LLM: Continue with tool result
    LLM-->>Agent: "Today in Tokyo it's 15°C..."
    Agent-->>User: "Today in Tokyo it's 15°C..."
```

| Phase         | What happens                                               |
| ------------- | ---------------------------------------------------------- |
| 1. Discover   | Agent receives tool definitions alongside the user request |
| 2. Decide     | LLM chooses whether to call a tool                         |
| 3. Execute    | Agent runs the tool and captures the result                |
| 4. Synthesize | LLM uses the tool result to form the final answer          |

***

## Common Patterns

### Pattern 1 — Web research agent

```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
from praisonaiagents import Agent
from praisonaiagents.tools import duckduckgo, wikipedia

agent = Agent(
    name="Researcher",
    instructions="You research topics using web search and Wikipedia.",
    tools=[duckduckgo, wikipedia],
)
response = agent.start("Explain the history of the Internet Protocol (TCP/IP).")
print(response)
```

### Pattern 2 — Custom API tool

```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
from praisonaiagents import Agent

def get_weather(city: str) -> str:
    """Fetch the current weather for a given city."""
    return f"{city}: 22°C, sunny (mock)"

def get_forecast(city: str, days: int = 3) -> str:
    """Get weather forecast for a city for the next N days."""
    return f"{city}: sunny for {days} days (mock)"

agent = Agent(
    instructions="You are a weather assistant.",
    tools=[get_weather, get_forecast],
)
agent.start("What's the weather in Paris and will it rain this week?")
```

<Tip>
  Drop a `@tool`-decorated function into `.praisonai/tools/*.py` for auto-load with no imports required — see [Project-local tools](/docs/docs/features/custom-agents-commands#project-local-tools).
</Tip>

### Pattern 3 — Tool search for large toolsets

```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
from praisonaiagents import Agent

agent = Agent(
    instructions="You have access to many tools. Use them as needed.",
    tools=[...],
    tool_search=True,
)
agent.start("Find the best tool for calculating compound interest.")
```

### Pattern 4 — Gate a custom tool behind approval

```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
from praisonaiagents import Agent, tool

@tool(approval=True)
def send_refund(order_id: str, amount: float) -> str:
    """Issue a refund on an order."""
    return f"Refunded {amount} for {order_id}"

agent = Agent(
    instructions="Help support agents process refunds.",
    tools=[send_refund],
)
agent.start("Refund $30 on order O-101")
```

Declare approval right on the `@tool` decorator — the agent pauses and asks a human before the tool runs. See [Tool Approval](/docs/features/tool-requires-approval).

<Card title="Tool Approval" icon="shield-check" href="/docs/features/tool-requires-approval">
  Declare a tool needs human sign-off in one line
</Card>

### Pattern 5 — Guard one tool's arguments or result

```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
from praisonaiagents import Agent, tool

def internal_recipients_only(arguments: dict):
    if not arguments.get("to", "").endswith("@corp.com"):
        return False, "Recipient is outside the company domain."
    return True, arguments

@tool(input_guardrails=[internal_recipients_only])
def send_email(to: str, body: str) -> str:
    """Send an email."""
    return f"sent to {to}"

agent = Agent(instructions="Send emails on request.", tools=[send_email])
agent.start("Email alice@gmail.com the release notes")
```

Declare `input_guardrails=` / `output_guardrails=` on the `@tool` decorator — alongside `approval` and `trust` — to validate, rewrite, or block just that one tool. A blocked call feeds a message back to the model rather than raising at the user.

| Option              | Type               | Fires             | Description                                                                                                                      |
| ------------------- | ------------------ | ----------------- | -------------------------------------------------------------------------------------------------------------------------------- |
| `approval`          | `bool`             | before the call   | Pause and ask a human before the tool runs. See [Tool Approval](/docs/features/tool-requires-approval).                               |
| `trust`             | `str`              | on the result     | Wrap external tool output in a prompt-injection fence.                                                                           |
| `input_guardrails`  | `callable \| list` | before the call   | Validate or rewrite the arguments before the tool runs. See [Per-Tool Guardrails](/docs/features/per-tool-guardrails).                |
| `output_guardrails` | `callable \| list` | on the raw result | Validate or substitute the result before it re-enters the LLM context. See [Per-Tool Guardrails](/docs/features/per-tool-guardrails). |

<Card title="Per-Tool Guardrails" icon="shield-halved" href="/docs/features/per-tool-guardrails">
  Guard one tool's arguments and results without hand-wrapping the function
</Card>

***

## Timeouts

Cap how long a tool may run with `ToolConfig(timeout=...)` — a stuck tool no longer hangs the agent.

```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
from praisonaiagents import Agent, ToolConfig

agent = Agent(
    name="Runner",
    instructions="Call tools with a 10s ceiling.",
    tools=[my_slow_tool],
    tool_config=ToolConfig(timeout=10),
)
```

<Note>
  **Since PraisonAI PR #3790**, async tool calls (`await agent.achat(...)`) honour the timeout. A tool that exceeds the limit returns a result instead of hanging forever:

  ```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
  {"error": "Tool timed out after 10s", "timeout": True}
  ```

  The `timeout: True` flag marks the call terminal, so it is **not** retried — the executor thread cannot be cancelled, so a retry would launch a duplicate DB write, API call, or file mutation. Timeout results are surfaced immediately, the same rule already applied to `approval_denied`, `permission_denied`, and `circuit_open`. The framework's own retry verdict lives on a private `_praison_retryable` key that is stripped before the result reaches the model or the caller, so it never appears on the surfaced dict.

  A tool's own result may include a field called `retryable` (common for HTTP-API wrappers). That field is the tool's payload — it never drives the framework's retry loop.
</Note>

***

## Best Practices

<AccordionGroup>
  <Accordion title="Write clear docstrings for custom tools">
    The LLM reads your tool's docstring to decide when and how to use it. Write clear, specific descriptions: "Fetch the current stock price for a given ticker symbol (e.g., 'AAPL', 'GOOGL'). Returns price in USD."
  </Accordion>

  <Accordion title="Return strings from tools">
    Tools should return strings (or JSON-serializable data that gets converted to strings). Complex objects confuse the LLM — format results as readable text.
  </Accordion>

  <Accordion title="Use tool_search for large toolsets">
    When you have more than 10–15 tools, enable `tool_search=True` to let the agent dynamically find the right tools instead of sending all tool definitions with every request.
  </Accordion>

  <Accordion title="Set timeouts for external tools">
    Wrap external API calls with timeouts using `ToolConfig(timeout=30)`. Without timeouts, a slow API can block the entire agent run.
  </Accordion>
</AccordionGroup>

***

## Related

<CardGroup cols={2}>
  <Card icon="wrench" href="/docs/features/tool-config">
    Tool Config — timeouts, retries, and artifact storage
  </Card>

  <Card icon="magnifying-glass" href="/docs/features/tool-search">
    Tool Search — dynamic tool discovery for large toolsets
  </Card>
</CardGroup>
