AI & LLM APIs cookbook · Intermediate
Write a bounded tool-calling loop
Let the model call your functions to answer, without infinite loops, crashes or invented results.
Approach
Run the standard loop (call, execute tool calls, return results) with a step limit, return failures as
is_error tool results instead of raising, and finish gracefully when the budget runs out.
Solution
import json
import anthropic
client = anthropic.Anthropic()
MODEL = "claude-sonnet-5-5"
TOOLS = [{
"name": "get_order",
"description": "Get an order's status and items by its 8-digit order number.",
"input_schema": {"type": "object", "properties": {"order_id": {"type": "string", "pattern": "^[0-9]{8}$"}},
"required": ["order_id"], "additionalProperties": False},
}]
def get_order(order_id: str) -> dict:
order = ORDERS_DB.get(order_id) # your data access
if order is None:
raise LookupError(f"No order {order_id}. Ask the customer to check the number on their receipt.")
return order
HANDLERS = {"get_order": get_order}
def run_tool(block) -> dict:
try:
result = HANDLERS[block.name](**block.input)
return {"type": "tool_result", "tool_use_id": block.id, "content": json.dumps(result)}
except (LookupError, ValueError) as exc:
return {"type": "tool_result", "tool_use_id": block.id, "content": str(exc), "is_error": True}
def answer(question: str, max_steps: int = 6) -> str:
messages = [{"role": "user", "content": question}]
for _ in range(max_steps):
response = client.messages.create(model=MODEL, max_tokens=1024, tools=TOOLS, messages=messages,
system="Answer order questions. Never invent order data.")
messages.append({"role": "assistant", "content": response.content})
if response.stop_reason != "tool_use":
return "".join(b.text for b in response.content if b.type == "text")
results = [run_tool(b) for b in response.content if b.type == "tool_use"]
messages.append({"role": "user", "content": results})
return "Sorry, I couldn't finish that. A colleague will follow up."
Pitfalls
while Trueloops with no step limit.- Letting exceptions escape (the conversation crashes) or returning empty strings (the model invents data).
- Vague tool descriptions and free-text parameters; use precise schemas.
Variations
- The SDK's tool runner (
client.beta.messages.tool_runner) runs the loop for you with decorated Python functions. - Add an approval step before tools that change data.
Further reading: Implement tool use
Reference: https://docs.anthropic.com/en/docs/agents-and-tools/tool-use/implement-tool-use
More AI & LLM APIs recipes
- Answer with citations that point at the source text
- Build hybrid RAG search on PostgreSQL with pgvector
- Cache a long system prompt and documents
- Cache answers to frequent questions safely
- Classify thousands of items with the Batch API
- Compare two prompt versions with an unbiased model judge
- Expose an internal API as an MCP server
- Extract structured data with a schema