How to build an AI agent with tool use on the Claude API (Python)
An AI agent on the Claude API is a loop: you send Claude a message plus a list of tools, Claude answers with a tool_use request, your code runs the tool and sends back a tool_result, and the loop repeats until Claude stops with end_turn. In Python you can write that loop yourself in about 40 lines with the official anthropic SDK, or let the SDK's tool runner drive it.
What is an AI agent on the Claude API?
An AI agent on the Claude API is a program where Claude decides which actions to take and your code carries them out. You give Claude a goal and a list of tools. Claude replies either with an answer or with a request to call one of the tools. Your code runs the tool, sends the result back, and Claude continues until the goal is done.
This pattern is called tool use (also known as function calling). It is not a separate API. It is a feature of the same Messages API endpoint you use for plain chat, so an agent is simply a chat loop that knows how to run tools.
How does tool use work, step by step?
Tool use follows the same four steps every time:
- You send the conversation plus a
toolslist. - Claude responds with
stop_reason: "tool_use"and one or moretool_useblocks. Each block has anid, the toolnameand aninputobject. - Your code runs each requested tool and builds a
tool_resultblock with the matchingtool_use_id. - You send the tool results back as the next user message. Claude reads them and either calls more tools or finishes with
stop_reason: "end_turn".
The table below lists the stop reasons your loop must handle.
stop_reason |
What it means | What your loop should do |
|---|---|---|
end_turn |
Claude is done | Show the final text and stop |
tool_use |
Claude wants tools run | Run them, send tool_result blocks, loop |
max_tokens |
The reply hit the token limit | Do not run half-written tool calls; raise the limit or stop |
refusal |
Claude declined the request | Stop and show a safe message |
pause_turn |
A long server-side tool turn paused | Send the conversation back to let it continue |
How do I define a tool?
A tool definition has three parts: a name, a description, and an input_schema written in JSON Schema. The description matters more than most people expect. Claude chooses tools by reading their descriptions, so say what the tool does, when to use it, and what it returns.
tools = [
{
"name": "get_order_status",
"description": (
"Look up the shipping status of a customer order by its order ID. "
"Use this whenever the user asks where their order is. "
"Returns the status and the last update time."
),
"input_schema": {
"type": "object",
"properties": {
"order_id": {"type": "string", "description": "Order ID, e.g. A-1042"}
},
"required": ["order_id"],
"additionalProperties": False,
},
"strict": True, # guarantees the input matches the schema exactly
}
]
Setting strict: True on the tool makes the API guarantee that input validates against your schema. It needs additionalProperties: False and a required list, as above.
How do I write the agent loop in Python?
Install the SDK with pip install anthropic and set the ANTHROPIC_API_KEY environment variable. The loop below is complete and runnable. The model ID is the current default Claude model at the time of writing (September 2026); check Anthropic's model list when you build.
import json
import anthropic
client = anthropic.Anthropic() # reads ANTHROPIC_API_KEY from the environment
MODEL = "claude-opus-5"
MAX_TURNS = 10
def get_order_status(order_id: str) -> dict:
# Replace with a real database or API call.
return {"order_id": order_id, "status": "shipped", "updated": "2026-09-23"}
TOOL_FUNCTIONS = {"get_order_status": get_order_status}
def run_tool(name: str, tool_input: dict) -> str:
return json.dumps(TOOL_FUNCTIONS[name](**tool_input))
def run_agent(user_message: str) -> str:
messages = [{"role": "user", "content": user_message}]
for _ in range(MAX_TURNS):
response = client.messages.create(
model=MODEL,
max_tokens=16000,
system="You are a support agent for an online shop. Use tools to look up facts.",
tools=tools,
messages=messages,
)
if response.stop_reason == "refusal":
return "Sorry, I can't help with that request."
if response.stop_reason == "max_tokens":
raise RuntimeError("Reply was cut off; raise max_tokens before running tools.")
# Keep the full assistant turn (text and tool_use blocks) in the history.
messages.append({"role": "assistant", "content": response.content})
if response.stop_reason == "end_turn":
return "".join(b.text for b in response.content if b.type == "text")
if response.stop_reason == "pause_turn":
continue # a server-side tool paused; send the history back to resume
results = []
for block in response.content:
if block.type != "tool_use":
continue
try:
output = run_tool(block.name, block.input)
results.append({"type": "tool_result", "tool_use_id": block.id, "content": output})
except Exception as err:
results.append({"type": "tool_result", "tool_use_id": block.id,
"content": f"Tool failed: {err}", "is_error": True})
messages.append({"role": "user", "content": results})
raise RuntimeError(f"Agent did not finish within {MAX_TURNS} turns.")
print(run_agent("Where is my order A-1042?"))
Three details in this loop prevent most bugs:
- The whole
response.contentgoes back into the history, not just the text. Claude needs its owntool_useblocks to match yourtool_resultblocks. - All tool results go in one user message. If Claude asked for two tools, answer both together.
- Errors are returned, not swallowed. A
tool_resultwithis_error: Truetells Claude the call failed, so it can retry with different input or explain the problem to the user.
Should I use the SDK's tool runner instead?
The Python SDK includes a tool runner (currently a beta feature) that runs this loop for you. You decorate plain Python functions with @beta_tool, and the SDK builds the JSON Schema from the type hints and docstring, calls the functions and feeds results back until Claude is done.
import anthropic
from anthropic import beta_tool
client = anthropic.Anthropic()
@beta_tool
def get_order_status(order_id: str) -> str:
"""Look up the shipping status of a customer order.
Args:
order_id: Order ID, e.g. A-1042.
"""
return f"Order {order_id}: shipped"
runner = client.beta.messages.tool_runner(
model="claude-opus-5",
max_tokens=16000,
tools=[get_order_status],
messages=[{"role": "user", "content": "Where is my order A-1042?"}],
)
for message in runner:
print(message)
The runner is the better default once you understand the protocol. Write the manual loop first anyway: when something goes wrong, knowing what tool_use, tool_result and stop_reason look like makes debugging much faster.
What makes a Claude agent reliable in production?
A working demo and a reliable agent are different things. These habits close the gap:
- Keep the tool list small and focused. Five well-described tools beat twenty vague ones.
- Validate tool input yourself before touching real systems, even with
stricton. Treat tool input like any user input. - Ask for confirmation before actions with side effects, such as refunds or emails. Return a "user declined" result when the user says no.
- Log every turn: the request, each tool call, each result and the stop reason. Most agent bugs are visible in the log.
- Test with real conversations. Keep a set of real user requests and rerun them after every prompt or tool change. The article on testing LLM apps walks through this.
- Handle refusals. Newer Claude models can stop with
stop_reason: "refusal". Check for it before reading the content, as the loop above does. Anthropic also offers a server-side fallback option (a beta feature) that retries a refused request on another model; read the current API docs before enabling it.
Summary
An agent on the Claude API is a loop around one endpoint: send tools, run the tool_use requests, return tool_result blocks, and stop on end_turn. Start with the manual loop to learn the protocol, then switch to the tool runner, and add turn limits, input checks and logging before real users arrive. If you want an agent like this built on your own data and systems, see AI applications, or read how RAG works to give your agent access to your documents.
Need this built? See AI applications or get in touch.
By