# How to Implement Tool Use and Function Calling in the Anthropic Python SDK

> Learn to implement tool use and function calling in the Anthropic Python SDK. Integrate Claude with external functions using @beta_tool and tool_runner for seamless interaction.

- Repository: [Anthropic/anthropic-sdk-python](https://github.com/anthropics/anthropic-sdk-python)
- Tags: how-to-guide
- Published: 2026-02-23

---

**The Anthropic Python SDK enables Claude to call external functions by decorating Python callables with `@beta_tool`, passing the resulting `BetaFunctionTool` objects to `client.messages.create(tools=[...])`, and processing the returned `tool_use` blocks either manually or via the automatic `tool_runner` utility.**

The `anthropics/anthropic-sdk-python` repository provides native support for function calling through its beta tooling module, allowing developers to expose Python functions as AI-callable tools. This implementation leverages Pydantic v2 for automatic JSON schema generation and offers both synchronous and asynchronous execution patterns. Below is a comprehensive guide to implementing tool use and function calling based on the actual SDK source code.

## Defining Tools with the `@beta_tool` Decorator

The foundation of tool use begins with the `@beta_tool` decorator defined in [[`src/anthropic/lib/tools/_beta_functions.py`](https://github.com/anthropics/anthropic-sdk-python/blob/main/src/anthropic/lib/tools/_beta_functions.py)](https://github.com/anthropics/anthropic-sdk-python/blob/main/src/anthropic/lib/tools/_beta_functions.py). When applied to a Python function, this decorator returns a `BetaFunctionTool` instance that automatically infers the tool's JSON schema from type hints and extracts descriptions from docstrings.

The decorator performs three critical operations during initialization:

- **Schema inference**: The `_create_schema_from_function` method (lines 28-71) uses Pydantic v2's `TypeAdapter` to generate the `input_schema` from function type annotations
- **Description extraction**: The `_get_description_from_docstring` helper (lines 19-26) parses the function docstring to populate the tool description
- **Validation setup**: The resulting object provides a `call(input: object)` method that validates incoming arguments against the generated schema before invoking the original function

```python
from typing_extensions import Literal
from anthropic import beta_tool
import json

@beta_tool
def get_weather(location: str, units: Literal["c", "f"]) -> str:
    """Lookup the weather for a city.
    
    Args:
        location: City name (e.g. "San Francisco, CA").
        units: Desired temperature units, "c" for Celsius or "f" for Fahrenheit.
    """
    return json.dumps({
        "location": location,
        "temperature": "20°C" if units == "c" else "68°F",
        "condition": "Sunny"
    })

```

Decorated functions expose a `to_dict()` method that serializes the tool into the `BetaToolParam` format expected by the API, including the `name`, `description`, and `input_schema` fields.

## Sending Tool Definitions to the API

Once defined, tools are passed to the Messages API via the `tools` parameter in `client.messages.create()`. The implementation in [[`src/anthropic/resources/messages/messages.py`](https://github.com/anthropics/anthropic-sdk-python/blob/main/src/anthropic/resources/messages/messages.py)](https://github.com/anthropics/anthropic-sdk-python/blob/main/src/anthropic/resources/messages/messages.py) (lines 48-66) accepts an `Iterable[ToolUnionParam]`, which includes both built-in tool types and the generic `ToolParam` produced by `BetaFunctionTool`.

During request construction, the SDK's `maybe_transform` utility (lines 1000-1016) automatically converts `BetaFunctionTool` instances to their dictionary representation via `to_dict()`. The resulting payload structure sent to Claude resembles:

```json
{
  "type": "tool",
  "name": "get_weather",
  "description": "Lookup the weather for a city.",
  "input_schema": {
    "type": "object",
    "properties": {
      "location": {"type": "string"},
      "units": {"enum": ["c", "f"], "type": "string"}
    },
    "required": ["location", "units"]
  }
}

```

**Manually defining tools** without the decorator is also supported by constructing `ToolParam` dictionaries directly, though this requires manually specifying the JSON schema:

```python
from anthropic.types import ToolParam

tools: list[ToolParam] = [
    {
        "name": "get_weather",
        "description": "Get the weather for a specific location",
        "input_schema": {
            "type": "object",
            "properties": {
                "location": {"type": "string"},
                "units": {"enum": ["c", "f"], "type": "string"}
            },
            "required": ["location", "units"]
        }
    }
]

message = client.messages.create(
    model="claude-3-5-sonnet-latest",
    max_tokens=1024,
    tools=tools,
    messages=[{"role": "user", "content": "What is the weather in SF?"}]
)

```

## Handling `tool_use` Blocks Manually

When Claude decides to invoke a tool, the response contains a `tool_use` content block. The SDK parses this into a `ToolUseBlock` model defined in [[`src/anthropic/types/tool_use_block.py`](https://github.com/anthropics/anthropic-sdk-python/blob/main/src/anthropic/types/tool_use_block.py)](https://github.com/anthropics/anthropic-sdk-python/blob/main/src/anthropic/types/tool_use_block.py), which includes:

- `id`: A unique identifier (e.g., `toolu_01D...`) for correlating results
- `name`: The registered tool name matching the function definition
- `input`: A dictionary conforming to the declared `input_schema`

To handle this manually, iterate through the message content to locate the block, execute the corresponding Python function, and return a `tool_result` message:

```python
import json
from anthropic.types import MessageParam

# Initial request

user_msg: MessageParam = {"role": "user", "content": "What is the weather in SF?"}
msg = client.messages.create(
    model="claude-3-5-sonnet-latest",
    max_tokens=1024,
    messages=[user_msg],
    tools=[get_weather]  # List of BetaFunctionTool objects

)

# Check for tool_use stop reason

if msg.stop_reason == "tool_use":
    tool_use = next(c for c in msg.content if c.type == "tool_use")
    
    # Execute the function with validated arguments

    result = get_weather(**tool_use.input)
    
    # Send tool_result back to Claude

    final_msg = client.messages.create(
        model="claude-3-5-sonnet-latest",
        max_tokens=1024,
        messages=[
            user_msg,
            {"role": msg.role, "content": msg.content},
            {
                "role": "user",
                "content": [{
                    "type": "tool_result",
                    "tool_use_id": tool_use.id,
                    "content": [{"type": "text", "text": result}]
                }]
            }
        ]
    )
    print(final_msg.content)

```

This manual flow provides full control over error handling, logging, and result formatting, as demonstrated in the official [[`examples/tools.py`](https://github.com/anthropics/anthropic-sdk-python/blob/main/examples/tools.py)](https://github.com/anthropics/anthropic-sdk-python/blob/main/examples/tools.py).

## Automatic Execution with the Beta Tool Runner

For streamlined implementations, the SDK provides `client.beta.messages.tool_runner`, an abstraction that automates the request-response loop. This utility, showcased in [[`examples/tools_runner.py`](https://github.com/anthropics/anthropic-sdk-python/blob/main/examples/tools_runner.py)](https://github.com/anthropics/anthropic-sdk-python/blob/main/examples/tools_runner.py), handles tool detection, execution, and result submission automatically.

The runner operates as an iterator, yielding each assistant message in the conversation flow:

```python
from anthropic import Anthropic, beta_tool

client = Anthropic()

@beta_tool
def get_weather(location: str, units: Literal["c", "f"]) -> str:
    """Lookup weather for a location."""
    return json.dumps({
        "location": location,
        "temperature": "20°C" if units == "c" else "68°F",
        "condition": "Sunny"
    })

runner = client.beta.messages.tool_runner(
    model="claude-3-5-sonnet-latest",
    max_tokens=1024,
    tools=[get_weather],
    messages=[{"role": "user", "content": "What is the weather in San Francisco?"}]
)

for message in runner:
    print(message.content)

```

Under the hood, the runner:
1. Sends the initial request with tool definitions
2. Detects `tool_use` blocks in assistant responses
3. Locates the matching `BetaFunctionTool` by name and validates inputs
4. Invokes `tool.call(input)` and packages the return value into a `tool_result` message
5. Resubmits the conversation history, allowing Claude to continue with the function result

## Asynchronous Tool Implementation

For I/O-bound operations, the SDK supports asynchronous functions via the `@beta_async_tool` decorator (lines 93-121 in [`_beta_functions.py`](https://github.com/anthropics/anthropic-sdk-python/blob/main/_beta_functions.py)). The runner automatically detects async functions and awaits their execution:

```python
from anthropic import beta_async_tool
import asyncio

@beta_async_tool
async def async_get_weather(location: str, units: Literal["c", "f"]) -> str:
    """Async weather lookup suitable for external HTTP calls."""
    # Simulate async I/O

    await asyncio.sleep(0.1)
    return json.dumps({
        "location": location,
        "temperature": "68°F",
        "condition": "Sunny"
    })

runner = client.beta.messages.tool_runner(
    model="claude-3-5-sonnet-latest",
    max_tokens=1024,
    tools=[async_get_weather],
    messages=[{"role": "user", "content": "What's the weather in New York?"}]
)

# Iterate normally—the runner handles awaiting internally

for msg in runner:
    print(msg.content)

```

## Summary

- **Tool definition**: Apply `@beta_tool` or `@beta_async_tool` to Python functions to generate JSON schemas automatically via Pydantic v2, as implemented in [`_beta_functions.py`](https://github.com/anthropics/anthropic-sdk-python/blob/main/_beta_functions.py)
- **API integration**: Pass decorated functions to `client.messages.create(tools=[...])` or use the `tool_runner` utility; the Messages resource handles serialization in [`messages.py`](https://github.com/anthropics/anthropic-sdk-python/blob/main/messages.py)
- **Manual handling**: Inspect response content for `ToolUseBlock` objects, execute functions with `tool_use.input`, and return `tool_result` blocks with the original `tool_use_id`
- **Automatic execution**: Use `client.beta.messages.tool_runner` to handle the entire request-execute-respond loop without manual state management
- **Async support**: The beta runner detects coroutines automatically and awaits them, enabling non-blocking I/O operations for external API calls

## Frequently Asked Questions

### How does the `@beta_tool` decorator generate JSON schemas automatically?

The decorator invokes `BaseFunctionTool._create_schema_from_function` (lines 28-71 in [`_beta_functions.py`](https://github.com/anthropics/anthropic-sdk-python/blob/main/_beta_functions.py)), which uses Pydantic v2's `TypeAdapter` and `ValidateCall` to inspect function type hints and build the JSON schema. It also extracts the description from the function docstring using `_get_description_from_docstring`, requiring no manual schema writing for standard Python functions.

### What is the difference between manual tool handling and the beta tool runner?

Manual handling requires the developer to check `message.stop_reason`, locate `ToolUseBlock` instances in the content array, call the Python function directly, and construct the `tool_result` message structure manually. The beta tool runner (`client.beta.messages.tool_runner`) automates this entire workflow, managing the conversation state and automatically invoking registered functions when Claude emits `tool_use` blocks.

### Can I mix synchronous and asynchronous tools in the same conversation?

No, a single tool runner instance must use either all synchronous or all asynchronous tools. However, you can create separate runner instances for different conversation flows. If using manual handling, you control the execution context and can mix sync and async functions by awaiting async tools explicitly while calling synchronous tools normally.

### What should I do if my tool function raises an exception?

When using the beta runner, exceptions are automatically caught and formatted as error content blocks in the `tool_result` message sent back to Claude. For manual implementations, wrap the function call in a try-except block and return the error message as text content within the `tool_result` block, ensuring you maintain the `tool_use_id` correlation for proper conversation continuity.