Lesson 07 Tool Use & Function Calling
Theme:

Lab Guide — Tool Use

Three exercises building toward a full multi-tool, multi-turn agent loop.

Setup: Open lab.pdf in Jupyter. Run the setup cell first — it loads your .env file and defines the three tool schemas. You will implement the functions marked with TODO.

📥 Lab Notebooks

⬇ lab.pdf ⬇ lab_answer.pdf

📥 Demo Files

⬇ tool_demo.py

Exercise 1 — Define Tools and execute_tool()

EX 1

Implement execute_tool(tool_name, tool_input)

Three tools are pre-defined in the notebook as a TOOLS list:

Your task is to implement execute_tool(tool_name: str, tool_input: dict) -> str that dispatches to the correct logic for each tool and returns a string result.

Conversion factors to support

FromToFactor
kmmiles0.621371
mileskm1.60934
kglb2.20462
lbkg0.453592
celsiusfahrenheit(C * 9/5) + 32
fahrenheitcelsius(F - 32) * 5/9

Test your implementation

print(execute_tool("calculator", {"operation": "multiply", "a": 17, "b": 843}))
# Expected: "14331.0" (or similar)

print(execute_tool("unit_converter", {"value": 100, "from_unit": "km", "to_unit": "miles"}))
# Expected: "62.1371 miles"

print(execute_tool("string_tools", {"operation": "reverse", "text": "Hello, World!"}))
# Expected: "!dlroW ,olleH"

Checkpoint: All three tool calls return correct string results without raising exceptions.

Exercise 2 — The Full Tool Loop

EX 2

Implement run_with_tools(user_message, tools, max_iterations=10)

This function sends a user message to Claude along with the tools list, handles all tool calls, and returns Claude's final text response.

Requirements

  1. Initialise messages = [{"role": "user", "content": user_message}]
  2. Loop until stop_reason == "end_turn" or max_iterations is reached
  3. On stop_reason == "tool_use": extract all tool_use blocks, call execute_tool() for each, build tool_result entries, append assistant message then user results
  4. On stop_reason == "end_turn": return the text from the first TextBlock in response.content
  5. If max iterations reached: return a fallback string

Test your implementation

answer = run_with_tools(
    "What is the square root of 144 multiplied by the number of words in 'the quick brown fox'?",
    TOOLS
)
print(answer)
# Claude should call calculator (sqrt not available, but it may use multiply)
# and string_tools (count_words). Exact answer: sqrt(144)=12, words=4, 12*4=48

Checkpoint: Claude calls tools and returns a coherent final answer that incorporates the tool results.

Exercise 3 — Parallel Tool Calls

EX 3

Verify parallel tool execution

Send a query that naturally requires two independent tool calls and verify they are returned in the same round.

query = (
    "Convert 100 USD to EUR (use 1 USD = 0.92 EUR, so multiply 100 by 0.92) "
    "AND calculate 15% tip on $47.50 (multiply 47.50 by 0.15). "
    "Give me both results."
)

# Track tool calls per round
import anthropic
client = anthropic.Anthropic()

messages = [{"role": "user", "content": query}]
round_num = 0

while True:
    response = client.messages.create(
        model="claude-opus-4-5", tools=TOOLS, messages=messages, max_tokens=512
    )
    tool_calls_this_round = [b for b in response.content if b.type == "tool_use"]
    print(f"Round {round_num}: stop_reason={response.stop_reason}, "
          f"tool_calls={len(tool_calls_this_round)}")
    for tc in tool_calls_this_round:
        print(f"  -> {tc.name}({tc.input})")

    if response.stop_reason == "end_turn":
        break

    # ... execute tools and continue ...
    round_num += 1

What to observe

Checkpoint: You can observe (and document in a comment) whether Claude used parallel or sequential calls for this query.

Tip: If Claude doesn't call tools when you expect it to, try adding "Use your tools to compute the exact answer" to the query. Some models prefer to answer simple arithmetic from memory.