To define a tool, you can use the @beta_tool decorator on any python function like so:
from anthropic import beta_tool
@beta_tool
def sum(left: int, right: int) -> str:
"""Adds two integers together.
Args:
left (int): The first integer to add.
right (int): The second integer to add.
Returns:
int: The sum of left and right integers.
"""
return str(left + right)Tip
If you're using the async client, replace @beta_tool with @beta_async_tool and define the function with async def.
The @beta_tool decorator will inspect the function arguments and the docstring to extract a json schema representation of the given function, in this case it'll be turned into:
{
"name": "sum",
"description": "Adds two integers together.",
"input_schema": {
"additionalProperties": false,
"properties": {
"left": {
"description": "The first integer to add.",
"title": "Left",
"type": "integer"
},
"right": {
"description": "The second integer to add.",
"title": "Right",
"type": "integer"
}
},
"required": ["left", "right"],
"type": "object"
}
}If you want to implement calling the tool yourself, you can then pass the to the API like so:
message = client.beta.messages.create(
tools=[get_weather.to_dict()],
# ...
max_tokens=1024,
model="claude-sonnet-4-5-20250929",
messages=[{"role": "user", "content": "What is 2 + 2?"}],
)or you can use our tool runner!
We provide a client.beta.messages.tool_runner() method that can automatically call tools defined with @beta_tool(). This method returns a BetaToolRunner class that is an iterator where each iteration yields a new BetaMessage instance from an API call. Iteration is driven by each message's stop_reason: on tool_use the runner executes the requested tools and sends the results back, on pause_turn or compaction it sends the turn back unchanged so the server can resume it, and on any other stop reason it stops after yielding that final message without running tools.
runner = client.beta.messages.tool_runner(
max_tokens=1024,
model="claude-sonnet-4-5-20250929",
tools=[sum],
messages=[{"role": "user", "content": "What is 9 + 10?"}],
)
for message in runner:
rich.print(message)To report an error from a tool back to the model, raise a ToolError. Unlike a plain exception, ToolError accepts content blocks, allowing you to include images or other structured content in the error response:
from anthropic import beta_tool
from anthropic.lib.tools import ToolError
@beta_tool
def take_screenshot(url: str) -> str:
"""Take a screenshot of a URL."""
if not is_valid_url(url):
raise ToolError(f"Invalid URL: {url}")
result = capture(url)
if result.error:
# Include the error screenshot so the model can see what went wrong
raise ToolError([
{"type": "text", "text": f"Failed to load page: {result.error}"},
{"type": "image", "source": {"type": "base64", "data": result.screenshot, "media_type": "image/png"}},
])
return result.dataIf a plain exception is raised, its repr() will be sent to the model as a text error and logged. ToolError is not logged since it represents an intentional error response.