A9.0 What This Teaches
- Tool schemas - how to describe a function to the model
- The
toolsparameter inmessages.create() - Detecting a
tool_usecontent block in the response - Calling the actual Python function with the model's arguments
- Returning a
tool_resultblock so the model can continue - The two-step call pattern that tool use requires
A9.1 What Tool Use Is
- Detects the tool call in the response
- Calls the actual Python function
- Sends the result back to the model
- Receives the model's final answer
A9.2 Defining a Tool Schema
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"]
}
}
]
A9.3 Sending Tools to the API
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
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
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
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 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,
}
list_directory automatically. Ask "How big is app.py?" and it calls
get_file_size.
A9.8 Exercise
Exercise
Add a
The model should call
read_first_line(path) tool that returns the first line of a
text file.
- Define the Python function using
open()andreadline(). - Add the tool schema to the
toolslist with an appropriate description. - Add it to
FUNCTION_MAP. - Run the script and ask: "What is the first line of requirements.txt?"
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
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
{"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
stop_reason again after the second API
call. A well-written agent loops until stop_reason == "end_turn".
A9.10 Key Terms
| Term | Meaning |
|---|---|
| tool schema | A dict describing a function: its name, what it does, and what arguments it accepts |
| input_schema | JSON Schema object inside a tool schema that describes the function's parameters |
| tool_use block | A content block in the API response containing the function name and arguments the model wants to call |
| tool_result block | A content block in a user message returning the actual result of a called function |
| stop_reason | String in the API response indicating why generation stopped: "end_turn" or "tool_use" |
| function calling | Another name for tool use - letting the model request that your code call a specific function |
| two-step call pattern | Call the API once to get a tool request, call the function, then call the API again with the result |