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

# Tools

Tools let your agent perform actions and retrieve information. The SDK supports three tool paradigms that differ in how they affect conversation flow.

## Defining Tools

Any properly annotated function can be a tool. The SDK uses the function's docstring as the description and type annotations for parameters:

```python theme={null}
from typing import Annotated

async def get_weather(
    ctx,
    city: Annotated[str, "The city to check weather for"],
    units: Annotated[str, "celsius or fahrenheit"] = "fahrenheit"
):
    """
    Look up the current weather in a given city.
    """
    return f"72°F and sunny in {city}"
```

<Note>
  The first parameter of every tool must be `ctx` (the tool context). This provides access to conversation state and is required for forward compatibility. Your tool parameters follow after `ctx`.
</Note>

***

## Tool Types

<Note>
  Plain functions passed to `tools` are automatically wrapped as loopback tools. Use decorators (`@loopback_tool`, `@passthrough_tool`, `@handoff_tool`) for explicit control.
</Note>

### Loopback Tools (`@loopback_tool`)

The default behavior. The tool's result is sent back to the LLM, which can then continue generating a response.

```python theme={null}
from line.llm_agent import loopback_tool

@loopback_tool
async def get_account_balance(ctx, account_id: Annotated[str, "The account ID"]):
    """Look up the balance for a customer account."""
    balance = await api.get_balance(account_id)
    return f"${balance:.2f}"
```

**Use for:** Information retrieval, calculations, API queries.

### Passthrough Tools (`@passthrough_tool`)

Output events go directly to the user, bypassing the LLM. Use for deterministic actions.

```python theme={null}
from line.llm_agent import passthrough_tool
from line.events import AgentSendText, AgentEndCall

@passthrough_tool
async def end_call_with_message(ctx, message: Annotated[str, "Goodbye message"]):
    """End the call with a custom goodbye message."""
    yield AgentSendText(text=message)
    yield AgentEndCall()
```

**Use for:** Call control (`EndCall`, `TransferCall`, `SendDtmf`), deterministic responses.

### Handoff Tools (`@handoff_tool`)

Transfers control to another handler. All future events are routed to the handoff target instead of the original agent.

```python theme={null}
from typing import Annotated
from line.llm_agent import handoff_tool
from line.events import AgentHandedOff, AgentSendText, UserTurnEnded, AgentEndCall

@handoff_tool
async def run_satisfaction_survey(
    ctx,
    customer_name: Annotated[str, "The customer's name"],
    event
):
    """Hand off to a customer satisfaction survey at the end of the call."""
    if isinstance(event, AgentHandedOff):
        # First call - send introduction
        yield AgentSendText(
            text=f"Thank you for your call, {customer_name}. "
            "Please stay on the line for a brief satisfaction survey. "
            "On a scale of 1 to 5, how would you rate your experience today?"
        )
        return

    # Subsequent calls - handle survey responses
    if isinstance(event, UserTurnEnded):
        user_response = event.content[0].content if event.content else ""
        yield AgentSendText(text=f"You rated us {user_response}. Thank you for your feedback!")
        yield AgentEndCall()
```

**Use for:** Custom multi-step flows, specialized handlers with their own logic.

When using a handoff tool, the `event` parameter receives different values depending on timing:

* **First call**: `event` is `AgentHandedOff` — use this to send a transition message
* **Subsequent calls**: `event` is the actual `InputEvent` (`UserTurnEnded`, etc.)

Once a handoff occurs, the original agent no longer receives events. The handoff tool function handles all future conversation turns.

<Tip>
  To hand off to another `LlmAgent`, use the [`agent_as_handoff`](#agent_as_handoff) helper instead of writing a raw `@handoff_tool`. It handles the delegation automatically.
</Tip>

***

## Built-in Tools

```python theme={null}
import os

from line.llm_agent import LlmAgent, LlmConfig, end_call, knowledge_base, send_dtmf, transfer_call, web_search

agent = LlmAgent(
    model="anthropic/claude-haiku-4-5-20251001",
    api_key=os.getenv("ANTHROPIC_API_KEY"),
    tools=[end_call, send_dtmf, transfer_call, web_search, knowledge_base],
    config=LlmConfig(...),
)
```

| Tool             | Description                                | When to Use                                                                                              |
| ---------------- | ------------------------------------------ | -------------------------------------------------------------------------------------------------------- |
| `end_call`       | Ends the call                              | User says "goodbye" or the agent's objective has been met                                                |
| `transfer_call`  | Transfers to another number (E.164 format) | Escalating to human agents, routing to departments                                                       |
| `web_search`     | Searches the web for real-time info        | Current events, live prices, recent news the LLM doesn't know                                            |
| `knowledge_base` | Looks up the agent's attached documents    | Product info, policies, internal facts the LLM shouldn't guess. See [Knowledge Base](../knowledge-base). |

**Examples:**

```python theme={null}
# End call: Let the LLM decide when conversation is complete
tools=[end_call]  # LLM calls this when user says "thanks, bye!"

# Transfer: Route to human support
tools=[transfer_call]  # LLM calls transfer_call(target_phone_number="+18005551234")

# Web search with custom context size
tools=[web_search(search_context_size="high")]  # More context for complex queries

# Knowledge base: look up the agent's attached documents
tools=[knowledge_base]  # LLM calls this to answer from uploaded docs

# Knowledge base scoped to a metadata filter with a tailored prompt
tools=[knowledge_base(
    filters={"category": "billing"},
    top_k=10,
    description="Look up billing and payment policy details.",
)]
```

See [Knowledge Base](../knowledge-base) for uploading documents and the full parameter list.

### `end_call`

Ends the current call and disconnects. The actual hangup occurs after the agent's final speech completes, so the user hears the full goodbye message before disconnection.

```python theme={null}
from line.llm_agent import LlmAgent, LlmConfig, end_call

agent = LlmAgent(
    model="anthropic/claude-haiku-4-5-20251001",
    api_key=os.getenv("ANTHROPIC_API_KEY"),
    tools=[end_call],
    config=LlmConfig(...),
)
```

By default, `end_call` uses a conservative policy that only ends the call when:

* The user's objective is fully complete
* The user explicitly says goodbye
* The agent has said a natural goodbye

#### Custom Description

We recommend providing a custom description tailored to your use case. The description **fully replaces** the default—it is not appended—so include complete instructions with explicit do/don't guidance.

```python theme={null}
from line.llm_agent import end_call

# Restaurant reservation agent
tools=[end_call(description="""Ends the call and disconnects.

Call when ALL of the following are true:
- The reservation is confirmed with date, time, party size, and name.
- You have repeated the reservation details back to the guest.
- The guest confirms the details are correct or says goodbye.

Do not call when:
- The guest asks to modify the reservation.
- Details are missing or unconfirmed.
- The guest says 'okay' or 'thanks' without an explicit goodbye.

If unsure, ask: 'Is there anything else I can help you with for your reservation?'
""")]

# Order confirmation agent
tools=[end_call(description="""Ends the call and disconnects.

Call when ALL of the following are true:
- The order is placed and confirmed.
- You have provided the order number and estimated delivery time.
- The customer acknowledges with a goodbye phrase.

Do not call when:
- The customer has questions about their order.
- Payment has not been confirmed.
- The customer says 'got it' without saying goodbye.
""")]
```

| Parameter     | Type            | Description                                                                                                 |
| ------------- | --------------- | ----------------------------------------------------------------------------------------------------------- |
| `description` | `Optional[str]` | Fully replaces the default description. Include complete instructions for when the LLM should end the call. |

### `agent_as_handoff`

Creates a handoff tool from another `Agent`—the easiest way to implement multi-agent workflows.

```python theme={null}
from line.llm_agent import LlmAgent, LlmConfig, agent_as_handoff, end_call, UpdateCallConfig

spanish_agent = LlmAgent(
    model="gpt-5-nano",
    api_key=os.getenv("OPENAI_API_KEY"),
    tools=[end_call],
    config=LlmConfig(
        system_prompt="You speak only in Spanish.",
        introduction="¡Hola! ¿Cómo puedo ayudarte?",
    ),
)

main_agent = LlmAgent(
    model="anthropic/claude-haiku-4-5-20251001",
    api_key=os.getenv("ANTHROPIC_API_KEY"),
    tools=[
        end_call,
        agent_as_handoff(
            spanish_agent,
            handoff_message="Transferring to Spanish support...",
            update_call=UpdateCallConfig(
                voice_id="spanish-voice-id",
                pronunciation_dict_id="spanish-pronunciation-dict-id"
            ),
            name="transfer_to_spanish",
            description="Use when user requests Spanish.",
        ),
    ],
    config=LlmConfig(...),
)
```

| Parameter         | Type                         | Description                                                                             |
| ----------------- | ---------------------------- | --------------------------------------------------------------------------------------- |
| `agent`           | `Agent`                      | The agent to hand off to                                                                |
| `handoff_message` | `Optional[str]`              | Message spoken before the handoff                                                       |
| `update_call`     | `Optional[UpdateCallConfig]` | Optional config to update call settings (voice, pronunciation, language) before handoff |
| `name`            | `Optional[str]`              | Tool name for the LLM                                                                   |
| `description`     | `Optional[str]`              | When the LLM should use this tool                                                       |

When called, `agent_as_handoff` automatically sends the handoff message, updates the call settings if specified, triggers the new agent's introduction, and routes all future events to it.

<Tip>
  See [Advanced Patterns](/line/sdk/patterns) for a complete multi-agent example with loopback, passthrough, and handoff tools.
</Tip>

***

## HTTP Server Tools

Use `http_server_tool` to call an HTTP API from JSON schemas, without writing a tool function.

```python theme={null}
import os
from line.llm_agent import LlmAgent, LlmConfig, http_server_tool

create_ticket = http_server_tool(
    name="create_ticket",
    description="Creates a support ticket.",
    url="https://api.example.com/v1/{tenant_id}/tickets",
    method="POST",
    request_body_schema={
        "type": "object",
        "required": ["subject"],
        "properties": {
            "subject": {"type": "string", "description": "Short summary of the issue."},
            "priority": {
                "type": "string",
                "enum": ["low", "medium", "high"],
                "description": "Ticket priority.",
            },
            "source": {"type": "string", "constant_value": "voice_agent"},
        },
    },
    auth={"Authorization": "Bearer ${SUPPORT_API_KEY}"},
    timeout=5.0,
)

agent = LlmAgent(
    model="anthropic/claude-haiku-4-5-20251001",
    api_key=os.getenv("ANTHROPIC_API_KEY"),
    tools=[create_ticket],
    config=LlmConfig(...),
)
```

**Use for:** Connecting an agent to an existing REST API, such as creating tickets, looking up orders, or triggering webhooks.

### Parameters

| Parameter             | Type              | Description                                                                                                      |
| --------------------- | ----------------- | ---------------------------------------------------------------------------------------------------------------- |
| `name`                | `str`             | Tool name shown to the LLM                                                                                       |
| `description`         | `str`             | Tool description shown to the LLM                                                                                |
| `url`                 | `str`             | Request URL. Supports `{param}` templating, where each variable becomes a required path parameter                |
| `method`              | `str`             | HTTP method (default `"POST"`). One of `GET`, `HEAD`, `POST`, `PUT`, `PATCH`, `DELETE`, `OPTIONS`                |
| `path_params_schema`  | `Optional[dict]`  | Maps `{param}` names to `{"type": ..., "description": ...}`. If omitted, path params default to required strings |
| `request_body_schema` | `Optional[dict]`  | Request body schema (see [Schema format](#schema-format)). Form-encoded bodies support flat scalar fields only   |
| `query_params_schema` | `Optional[dict]`  | Query string schema, same format but scalars only (no objects or arrays)                                         |
| `auth`                | `Optional[dict]`  | Headers with `${ENV_VAR}` placeholders resolved from `os.environ`                                                |
| `headers`             | `Optional[dict]`  | Additional static headers (must not overlap with `auth` keys)                                                    |
| `content_type`        | `str`             | `"application/json"` (default) or `"application/x-www-form-urlencoded"`                                          |
| `timeout`             | `Optional[float]` | Request timeout in seconds (must be positive)                                                                    |
| `is_background`       | `bool`            | Run in a shielded background task (default `True`). See [Long-Running Tools](#long-running-tools)                |

### Schema format

Both `request_body_schema` and `query_params_schema` take `"type": "object"` and a `"properties"` dict. Each property supports:

| Key              | Purpose                                                                                                                        |
| ---------------- | ------------------------------------------------------------------------------------------------------------------------------ |
| `type`           | `string`, `integer`, `number`, `boolean`, `array`, or `object`. Query and form-encoded body fields support the first four only |
| `description`    | Guides the LLM when filling the parameter                                                                                      |
| `enum`           | List of allowed values                                                                                                         |
| `constant_value` | Hidden from the LLM and sent on every request. Must match the declared `type`                                                  |

List the properties the LLM must fill in a top-level `required` array. Properties not listed are optional. Path parameters are always required.

Set `constant_value` at any nesting depth in `request_body_schema`:

```python theme={null}
request_body_schema={
    "type": "object",
    "required": ["subject"],
    "properties": {
        "subject": {"type": "string", "description": "Issue summary."},
        "metadata": {
            "type": "object",
            "properties": {
                "channel": {"type": "string", "constant_value": "phone"},
            },
        },
    },
}
```

### Path and query parameters

Path parameters come from `{param}` in the URL as required strings. Use `path_params_schema` for custom descriptions or types:

```python theme={null}
update_item = http_server_tool(
    name="update_item",
    description="Update an item.",
    url="https://api.example.com/items/{item_id}",
    method="PATCH",
    path_params_schema={
        "item_id": {"type": "integer", "description": "Numeric item ID."},
    },
    request_body_schema={
        "type": "object",
        "required": ["name"],
        "properties": {"name": {"type": "string", "description": "New item name."}},
    },
)
```

For `GET` requests, set the query string with `query_params_schema`:

```python theme={null}
search_orders = http_server_tool(
    name="search_orders",
    description="Search orders by status.",
    url="https://api.example.com/orders",
    method="GET",
    query_params_schema={
        "type": "object",
        "required": ["status"],
        "properties": {
            "status": {"type": "string", "enum": ["pending", "shipped"]},
            "limit": {"type": "integer", "description": "Max results."},
            "api_key": {"type": "string", "constant_value": "pk_live_abc"},
        },
    },
)
```

### Response format

The tool returns structured JSON to the LLM. A `2xx` status sets `ok` to `true` with the response under `body`; any other status, connection error, or timeout sets `ok` to `false` with the detail under `error`. Responses over 4096 characters are truncated.

```json theme={null}
{"ok": true,  "status": 201, "body": "{\"ticket_id\": \"TKT-001\"}"}
{"ok": false, "status": 500, "error": "Internal server error"}
{"ok": false, "status": null, "error": "Request timed out after 5.0s."}
```

<Note>
  Inputs are validated when you construct the tool. Malformed schemas, missing `${ENV_VAR}` values, type mismatches, and parameter name collisions raise `ValueError`.
</Note>

***

## Long-Running Tools

By default, tool calls are terminated when the agent is interrupted (though any reasoning and tool call response values already produced are preserved for use in the next generation).

For tools that are expected to take a long time to complete, set `is_background=True`. The tool will continue running in the background until completion regardless of interruptions, then loop back to the LLM to produce a response.

```python theme={null}
from typing import Annotated
from line.llm_agent import loopback_tool

@loopback_tool(is_background=True)
async def search_database(ctx, query: Annotated[str, "Search query"]) -> str:
    """Search the database - may take several seconds."""
    results = await slow_database_search(query)
    return format_results(results)

@loopback_tool(is_background=True)
async def generate_report(ctx, report_type: Annotated[str, "Type of report"]) -> str:
    """Generate a detailed report - runs in background."""
    report = await compile_report(report_type)
    return report
```

The built-in [`knowledge_base`](../knowledge-base) tool accepts `is_background` too, so the agent can keep talking while a retrieval is in flight:

```python theme={null}
from line.llm_agent import knowledge_base

# Knowledge base lookups run in the background
tools=[knowledge_base(is_background=True, top_k=10)]
```

<Note>
  Background tools are useful when:

  * The operation may take longer than typical user patience (e.g., complex searches, report generation)
  * You want the user to be able to speak while the operation completes
  * The result should be delivered even if the user interrupts with another question
</Note>
