Site

App: Tool Use — Function Calling

Tutorial A9.0  •  AI / Learn / Applications

A9.0 What This Teaches

This tutorial shows how Claude can call Python functions you define, by declaring tool schemas. It covers:

A9.1 What Tool Use Is

The model cannot run code. But it can emit a structured "call this function with these arguments" block when it needs data it cannot produce itself. Your code:
  1. Detects the tool call in the response
  2. Calls the actual Python function
  3. Sends the result back to the model
  4. Receives the model's final answer
This lets Claude answer questions that require live data - file sizes, database queries, API calls, or anything else your code can compute.

A9.2 Defining a Tool Schema

A tool schema is a Python dict with three fields: name, description, and input_schema. The description tells the model what the function does and when to call it. The input_schema uses JSON Schema to describe the arguments.
# tool_schema.py - describe a file-size tool to the model.
tools = [
    {
        "name": "get_file_size",
        "description": "Return the size in bytes of a file on disk.",
        "input_schema": {
            "type": "object",
            "properties": {
                "path": {
                    "type": "string",
                    "description": "Absolute or relative file path"
                }
            },
            "required": ["path"]
        }
    }
]
Write the description as if explaining to a colleague what the function does and when they should call it. The model uses the description to decide whether to call the tool.

A9.3 Sending Tools to the API

Pass the tool list to messages.create() as the tools parameter. If the model decides to call a tool, the response has stop_reason == "tool_use" and the content list contains a ToolUseBlock.
# First API call - ask a question and provide the tool.
response = client.messages.create(
    model="claude-sonnet-4-6",
    max_tokens=1024,
    tools=tools,
    messages=[
        {"role": "user", "content": "How large is requirements.txt?"}
    ],
)

print(response.stop_reason)  # "tool_use" if the model wants to call a function

A9.4 Detecting a Tool Call

When stop_reason is "tool_use", find the ToolUseBlock in response.content. It has a name (function to call) and an input dict (arguments).
# Detect and extract the tool call.
if response.stop_reason == "tool_use":
    tool_use_block = next(
        block for block in response.content
        if block.type == "tool_use"
    )
    tool_name  = tool_use_block.name    # "get_file_size"
    tool_input = tool_use_block.input   # {"path": "requirements.txt"}
    tool_id    = tool_use_block.id      # needed for the tool_result block
Save the id - you must echo it back in the tool_result message so the API knows which call the result belongs to.

A9.5 Calling the Function and Returning the Result

Call the real Python function with the extracted arguments. Then make a second API call with the original messages, the assistant's tool_use block, and a new user message containing a tool_result block.
# Call the function and send the result back.
import os

result = str(os.path.getsize(tool_input["path"]))   # e.g. "342"

second_response = client.messages.create(
    model="claude-sonnet-4-6",
    max_tokens=1024,
    tools=tools,
    messages=[
        {"role": "user",      "content": "How large is requirements.txt?"},
        {"role": "assistant", "content": response.content},   # the tool_use block
        {
            "role": "user",
            "content": [
                {
                    "type": "tool_result",
                    "tool_use_id": tool_id,
                    "content": result,
                }
            ],
        },
    ],
)
print(second_response.content[0].text)

A9.6 Complete Implementation

# tool_use.py - ask Claude about a file; it calls get_file_size to answer.
# Usage: python tool_use.py

import os
import anthropic

client = anthropic.Anthropic()

def get_file_size(path: str) -> str:
    # returns size as a string so it fits cleanly in tool_result content
    try:
        size = os.path.getsize(path)
        return f"{size} bytes"
    except FileNotFoundError:
        return f"Error: file not found: {path}"

tools = [
    {
        "name": "get_file_size",
        "description": "Return the size in bytes of a file on disk.",
        "input_schema": {
            "type": "object",
            "properties": {
                "path": {"type": "string", "description": "File path to check"}
            },
            "required": ["path"],
        },
    }
]

FUNCTION_MAP = {"get_file_size": get_file_size}

def run(user_question: str) -> str:
    messages = [{"role": "user", "content": user_question}]

    response = client.messages.create(
        model="claude-sonnet-4-6",
        max_tokens=1024,
        tools=tools,
        messages=messages,
    )

    if response.stop_reason == "end_turn":
        return response.content[0].text

    # handle tool_use
    tool_block = next(b for b in response.content if b.type == "tool_use")
    fn_result  = FUNCTION_MAP[tool_block.name](**tool_block.input)

    messages.append({"role": "assistant", "content": response.content})
    messages.append({
        "role": "user",
        "content": [{"type": "tool_result", "tool_use_id": tool_block.id,
                     "content": fn_result}],
    })

    final = client.messages.create(
        model="claude-sonnet-4-6",
        max_tokens=1024,
        tools=tools,
        messages=messages,
    )
    return final.content[0].text

if __name__ == "__main__":
    answer = run("How large is requirements.txt?")
    print(answer)

A9.7 Adding a Second Tool

Add more tools to the list. The model picks the right one based on the question.
# Add list_directory alongside get_file_size.
tools = [
    {
        "name": "get_file_size",
        "description": "Return the size in bytes of a file on disk.",
        "input_schema": {
            "type": "object",
            "properties": {
                "path": {"type": "string", "description": "File path to check"}
            },
            "required": ["path"],
        },
    },
    {
        "name": "list_directory",
        "description": "List the files in a directory. Returns filenames separated by commas.",
        "input_schema": {
            "type": "object",
            "properties": {
                "directory": {"type": "string", "description": "Directory path to list"}
            },
            "required": ["directory"],
        },
    },
]

def list_directory(directory: str) -> str:
    try:
        return ", ".join(os.listdir(directory))
    except FileNotFoundError:
        return f"Error: directory not found: {directory}"

FUNCTION_MAP = {
    "get_file_size":   get_file_size,
    "list_directory":  list_directory,
}
Ask "What files are in the current directory?" and the model calls list_directory automatically. Ask "How big is app.py?" and it calls get_file_size.

A9.8 Exercise

Exercise Add a read_first_line(path) tool that returns the first line of a text file.
  1. Define the Python function using open() and readline().
  2. Add the tool schema to the tools list with an appropriate description.
  3. Add it to FUNCTION_MAP.
  4. Run the script and ask: "What is the first line of requirements.txt?"
The model should call read_first_line automatically and use the result to answer.

A9.9 Common Mistakes

Forgetting to append the assistant's tool_use block before tool_result

The API requires that the tool_result message is preceded by the assistant's message that requested the tool call. If you skip appending response.content to messages, the API rejects the request with a validation error about message ordering.

Returning tool results as a user text message

Sending the function result as {"role": "user", "content": "The size is 342 bytes"} instead of a proper tool_result block confuses the model. It does not know the text is the answer to its tool call. Always use the tool_result content type with the matching tool_use_id.

Not checking stop_reason after the second call

After sending the tool result, the model may call another tool or may return its final answer. Always check stop_reason again after the second API call. A well-written agent loops until stop_reason == "end_turn".

A9.10 Key Terms

TermMeaning
tool schemaA dict describing a function: its name, what it does, and what arguments it accepts
input_schemaJSON Schema object inside a tool schema that describes the function's parameters
tool_use blockA content block in the API response containing the function name and arguments the model wants to call
tool_result blockA content block in a user message returning the actual result of a called function
stop_reasonString in the API response indicating why generation stopped: "end_turn" or "tool_use"
function callingAnother name for tool use - letting the model request that your code call a specific function
two-step call patternCall the API once to get a tool request, call the function, then call the API again with the result