Careeroza
The Tool Use Loop
How a Tool Call Flows
1. you → model: messages + tools
2. model → you: stop_reason "tool_use", content: [text?, tool_use {id, name, input}]
3. you: run the function with input
4. you → model: append assistant content, then a user message with
tool_result {tool_use_id, content}
5. model → you: final text answer (stop_reason "end_turn") ... or another tool_use → go to 3The Loop with a Scripted Model
Example
A scripted stand-in model returns the same block shapes as the real API, so the loop code is the real pattern:
import json
ORDERS = {"SE-10423": {"status": "out for delivery", "eta": "today by 8 pm"}}
def get_order_status(order_id):
return ORDERS.get(order_id, {"error": "order not found"})
TOOLS = {"get_order_status": get_order_status}
def scripted_model(messages):
"""Stand-in for the model. Turn 1: ask for a tool. Turn 2: answer from the tool result."""
last = messages[-1]["content"]
if isinstance(last, str):
return {"stop_reason": "tool_use", "content": [
{"type": "text", "text": "Let me check that order."},
{"type": "tool_use", "id": "toolu_01", "name": "get_order_status", "input": {"order_id": "SE-10423"}}]}
result = json.loads(last[0]["content"])
return {"stop_reason": "end_turn", "content": [
{"type": "text", "text": f"Your order is {result['status']} and should arrive {result['eta']}."}]}
messages = [{"role": "user", "content": "Where is my order SE-10423?"}]
while True:
response = scripted_model(messages)
messages.append({"role": "assistant", "content": response["content"]})
if response["stop_reason"] != "tool_use":
break
results = []
for block in response["content"]:
if block["type"] == "tool_use":
output = TOOLS[block["name"]](**block["input"])
print(f"tool call: {block['name']}({block['input']}) -> {output}")
results.append({"type": "tool_result", "tool_use_id": block["id"], "content": json.dumps(output)})
messages.append({"role": "user", "content": results})
print("final answer:", response["content"][0]["text"])
print("messages in conversation:", len(messages))Output
tool call: get_order_status({'order_id': 'SE-10423'}) -> {'status': 'out for delivery', 'eta': 'today by 8 pm'}
final answer: Your order is out for delivery and should arrive today by 8 pm.
messages in conversation: 4The Same Loop with the Claude API
import json, anthropic
client = anthropic.Anthropic()
tools = [{
"name": "get_order_status",
"description": "Get the live status of a ShopEase order. Call this whenever the customer asks about an order.",
"input_schema": {"type": "object", "properties": {"order_id": {"type": "string"}}, "required": ["order_id"]},
}]
messages = [{"role": "user", "content": "Where is my order SE-10423?"}]
while True:
response = client.messages.create(model="claude-sonnet-5-5", max_tokens=1000,
system="You are ShopEase support.", tools=tools, messages=messages)
messages.append({"role": "assistant", "content": response.content})
if response.stop_reason != "tool_use":
break
results = []
for block in response.content:
if block.type == "tool_use":
output = TOOLS[block.name](**block.input)
results.append({"type": "tool_result", "tool_use_id": block.id, "content": json.dumps(output)})
messages.append({"role": "user", "content": results})
print(next(b.text for b in response.content if b.type == "text"))Run it
python order_tool.pyExample output
Good news! Your order SE-10423 is out for delivery and should reach you today by 8 pm.
Tip: The Python SDK also has a tool runner (client.beta.messages.tool_runner(...) with @beta_tool functions) that runs this loop for you. Learn the manual loop first so you understand what it does.