learn-claude-with-phoebe / Deep dive 6.3
Learn Claude with Phoebe · Deep-dive track 6.3

Tool use

Deep dive 6.1 gave you the API; 6.2 taught you to prompt and measure. Now you give Claude hands: you define functions, Claude decides when to call them, you execute and return the results. This is the mechanism every agent is built on - and by the end you will have written the loop yourself.

🔴 Deep dive DS & AI 45 min self-paced or live
0-5 · Setup 5-35 · Core 35-45 · Try it
Part 0

Why this page exists

A model that can only emit text is an advisor. A model that can call your functions is a worker. Tool use is the one API feature that turns "Claude tells you the SQL" into "Claude runs the query, reads the result, and writes the summary". Every skill, every MCP server, every agent framework you will ever touch is this loop underneath.

What tool use adds over plain chat you define tools Claude decides you execute + report WORKER (tool use) calls your functions, reads results, decides next step ADVISOR (plain chat) emits text only, tells you the SQL, never runs it = Claude runs it and reports back Every skill, MCP server, and agent you touch is this loop underneath.
🔍 Click to zoom - tool use turns an advisor into a worker with hands
Core - the loop everyone needs Advanced - read when you need it Tool Use module of all 3 engineering courses
★ What you walk out with A working tool loop you wrote yourself, a routing setup with multiple tools, and the forced-tool-call trick that makes structured extraction actually reliable in production.
Part 1 · the mechanism

The tool loop 14 min core

Three roles, strictly separated: you define tools, Claude decides, you execute. Claude never runs anything - it asks, you act, you report back.

CoreWhat tool use actually is4 min

You pass a list of function descriptions with your request. Claude reads the user's question, and if a tool would help, it stops generating text and instead emits a structured request: "call get_weather with {"city": "Auckland"}". Your code executes the real function and sends the result back as a new message. Claude reads it and either answers or asks for another tool call.

  • You define the tools: name, description, input schema. Claude only ever sees these descriptions - it has no idea what your code does beyond what you wrote.
  • Claude decides whether to call a tool at all, which one, and with what arguments. This decision is the intelligence you are renting.
  • You execute. The API never touches your database, your filesystem, or your network. Every side effect goes through code you wrote, which is exactly where you enforce permissions and limits.

That request-execute-report cycle, repeated until Claude has what it needs, is the agent loop. Claude Code, Cowork, and every MCP integration are this loop with a bigger toolbox.

Mental model Claude is a sharp new analyst who can only communicate by chat. They can ask you to run things and read what you paste back, but their hands never touch the keyboard. Design your tools the way you would brief that analyst.
CoreDefining a tool: name, description, JSON Schema4 min

A tool is a dict with three keys. The input_schema is standard JSON Schema - the same vocabulary you know from API specs.

★ A complete tool definitiontools = [ { "name": "get_weather", "description": ( "Get the current weather for a city. " "Use when the user asks about weather, temperature, " "rain, or outdoor conditions in a specific place. " "Returns temperature in Celsius and a short conditions text. " "Do NOT use for historical weather or forecasts." ), "input_schema": { "type": "object", "properties": { "city": { "type": "string", "description": "City name, e.g. 'Auckland' or 'Osaka'", } }, "required": ["city"], }, } ]

The description carries almost all the weight. Claude uses it to decide WHEN to call the tool - which is the exact same lesson as skill descriptions in Session 3: vague description, tool never fires (or fires when it shouldn't). Say what it does, when to use it, what it returns, and what it is NOT for.

  • Name: verb_noun, snake_case, unambiguous. query_orders beats helper2.
  • Schema descriptions matter too: per-property descriptions steer the arguments Claude fills in. Include an example value.
  • Fewer, sharper tools beat many overlapping ones. If two tools could both plausibly handle a request, Claude has to guess - and so would a human reading your descriptions.
CoreThe loop mechanics: stop_reason, tool_use, tool_result6 min

When Claude wants a tool, the response comes back with stop_reason == "tool_use" and one or more content blocks of type tool_use, each carrying an id, the tool name, and the parsed input. You execute, then append TWO messages: the assistant turn exactly as received, and a user turn containing tool_result blocks that echo each tool_use_id. Then you call the API again. Here is the whole thing, runnable:

★ The complete tool loop (runnable)import anthropic client = anthropic.Anthropic() def get_weather(city): # toy implementation - swap in a real API call return {"city": city, "temp_c": 14, "conditions": "light rain"} TOOL_FUNCTIONS = {"get_weather": get_weather} tools = [ { "name": "get_weather", "description": ( "Get the current weather for a city. Use when the user " "asks about weather or temperature in a specific place." ), "input_schema": { "type": "object", "properties": {"city": {"type": "string"}}, "required": ["city"], }, } ] messages = [{"role": "user", "content": "Should I bike to work in Auckland today?"}] response = client.messages.create( model="claude-sonnet-5", max_tokens=1024, tools=tools, messages=messages, ) while response.stop_reason == "tool_use": tool_results = [] for block in response.content: if block.type == "tool_use": fn = TOOL_FUNCTIONS[block.name] result = fn(**block.input) # YOUR code runs here tool_results.append( { "type": "tool_result", "tool_use_id": block.id, "content": str(result), } ) messages.append({"role": "assistant", "content": response.content}) messages.append({"role": "user", "content": tool_results}) response = client.messages.create( model="claude-sonnet-5", max_tokens=1024, tools=tools, messages=messages, ) print(response.content[0].text)

Things that bite people the first time:

  • Echo the assistant message untouched. The tool_use blocks must be in the history or the tool_result has nothing to attach to - the API will reject the request.
  • tool_result goes in a USER message. You are reporting back, so it is your turn. All results for one response go in one user message.
  • Errors are results too. If your function throws, catch it and return the error text with "is_error": True on the tool_result block. Claude will read the error and usually retry sensibly or explain the failure.
  • Cap the loop. A for _ in range(10) guard beats an infinite while loop the day a tool starts returning garbage.
Part 2 · routing and extraction

Multiple tools and structured data 8 min core

One tool is a feature. Several tools plus Claude's judgment about which to call is a system.

CoreMulti-turn with multiple tools: Claude routes4 min

Pass several tools and Claude picks per request - no if/else router needed on your side. A data-team trio:

★ Three tools, Claude pickstools = [ { "name": "query_database", "description": ( "Run a read-only SQL query against the analytics warehouse. " "Use for questions about orders, revenue, or user counts." ), "input_schema": { "type": "object", "properties": {"sql": {"type": "string"}}, "required": ["sql"], }, }, { "name": "get_dashboard_url", "description": ( "Return the link to an existing dashboard by topic. Use when " "a dashboard already answers the question - prefer this over " "running a fresh query for standard metrics." ), "input_schema": { "type": "object", "properties": {"topic": {"type": "string"}}, "required": ["topic"], }, }, { "name": "file_ticket", "description": ( "File a data-request ticket. Use ONLY when the question needs " "data we do not have or a pipeline change." ), "input_schema": { "type": "object", "properties": {"summary": {"type": "string"}}, "required": ["summary"], }, }, ]
  • The descriptions do the routing. "Prefer this over running a fresh query" is an instruction Claude follows. You are programming the router in English.
  • Parallel calls: one response can contain SEVERAL tool_use blocks when the calls are independent (weather in two cities, three metrics at once). Your loop already handles this - it iterates over all blocks and returns all results in one user message. Execute them concurrently if the functions are slow.
  • Multi-turn chains: Claude can call query_database, read the result, decide it needs a second query, and call again - that is just the while loop going around twice. You wrote an agent and it took no extra code.
Real world

A four-person data team wrapped exactly this trio around their warehouse as a Slack bot. Stakeholder questions that used to become Jira tickets ("how many active users in Germany last month?") now get answered in 20 seconds, and only the genuinely-new requests reach the team. The entire bot is the loop from Part 1 plus these three definitions - about 120 lines.

CoreTools for structured data: the forced-call trick4 min

In 6.1 you extracted JSON by prompting "reply with only JSON" and hoping. The robust version: define a tool whose input schema IS your desired output shape, then force Claude to call it with tool_choice. The tool never executes anything - you just harvest block.input, which the API guarantees matches your schema.

★ Extraction via forced tool callimport anthropic client = anthropic.Anthropic() extraction_tool = { "name": "record_extraction", "description": "Record the structured fields extracted from a support ticket.", "input_schema": { "type": "object", "properties": { "customer": {"type": "string"}, "product": {"type": "string"}, "severity": {"type": "string", "enum": ["low", "medium", "high"]}, "summary": {"type": "string", "description": "One sentence"}, }, "required": ["customer", "product", "severity", "summary"], }, } response = client.messages.create( model="claude-sonnet-5", max_tokens=1024, tools=[extraction_tool], tool_choice={"type": "tool", "name": "record_extraction"}, messages=[{"role": "user", "content": f"Extract fields from this ticket:\n\n{ticket_text}"}], ) data = next(b.input for b in response.content if b.type == "tool_use") print(data["severity"], "-", data["summary"])
  • No markdown fences, no "here is the JSON:", no parse errors. The schema is enforced, enums included.
  • tool_choice modes: {"type": "auto"} (default - Claude decides), {"type": "any"} (must call some tool), {"type": "tool", "name": ...} (must call this one). Forced extraction uses the last.
  • This is the "structured data with tools" and "flexible tool extraction" lesson from the Bedrock/Vertex courses - same trick, any platform.
Part 3 · what Anthropic runs for you

Server-side tools and batch processing 8 min core

Some tools you never have to implement - Anthropic executes them server-side. And when the job is high-volume and not urgent, there is a cheaper lane entirely.

AdvancedServer-side tools: web search and the text editor4 min

Client tools (everything above) follow the loop: Claude asks, YOU execute. Server tools invert it: you enable them in the request and ANTHROPIC executes them mid-response - no loop on your side for the execution.

Who executes this action Does Anthropic run it server-side already? yes no Server tool enable + max_uses cap Is it a standardized file-edit pattern? yes no Text editor schema given, you run it Client tool you define and execute You define client tools; Anthropic executes server tools; the text editor sits in between.
🔍 Click to zoom - three tool types split by who actually runs the code
ToolWho executesWhat it gives you
Your functions (client tools)You, in your codeAnything: your DB, your APIs, your files. Full control, full responsibility.
Web search (server tool)AnthropicLive web results with citations, mid-response. You just enable it and set a max_uses cap.
Text editor (client tool, Anthropic-defined)YouA standardized view/create/str_replace file-editing interface Claude is trained on. You implement the file operations; the schema and Claude's fluency with it come free.
★ Enabling web searchresponse = client.messages.create( model="claude-sonnet-5", max_tokens=1024, tools=[ { "type": "web_search_20250305", "name": "web_search", "max_uses": 3, } ], messages=[{"role": "user", "content": "What did Anthropic announce this week?"}], )

The text editor tool sits in between: Anthropic defines the schema (commands like view, create, str_replace) and Claude has been trained to use it well, but the file operations run in YOUR code - which is why it is safe. This is the exact mechanism Claude Code uses to edit your repo. Note the platform delta: web search is a direct-API feature; on Bedrock and Vertex you cover the same ground with your own search tool.

AdvancedBatch processing: the Message Batches API4 min

Everything so far was real-time: one request, seconds of latency, full price. The Message Batches API takes up to thousands of requests in one submission, processes them asynchronously (most finish well within an hour, guaranteed within 24), and costs 50% less per token. Tools, system prompts, forced extraction - everything above works inside a batch.

★ Nightly ticket scoring as a batchimport anthropic client = anthropic.Anthropic() batch = client.messages.batches.create( requests=[ { "custom_id": f"ticket-{t['id']}", "params": { "model": "claude-sonnet-5", "max_tokens": 512, "tools": [extraction_tool], "tool_choice": {"type": "tool", "name": "record_extraction"}, "messages": [ {"role": "user", "content": f"Extract fields:\n\n{t['text']}"} ], }, } for t in tickets ] ) print(batch.id, batch.processing_status) # later (poll or cron): stream the results for result in client.messages.batches.results(batch.id): if result.result.type == "succeeded": blocks = result.result.message.content data = next(b.input for b in blocks if b.type == "tool_use") save(result.custom_id, data)
  • When batch beats real-time: nightly scoring, backfills, bulk classification, eval runs from 6.2 - anywhere nobody is watching a spinner. Half price for changing a cron schedule is the easiest cost win in this whole track.
  • When it doesn't: anything interactive, and client-tool loops that need YOUR code between turns (each batch request must be self-contained - forced extraction is fine, a multi-turn query loop is not).
  • custom_id is your join key - results can arrive in any order.
35-45 · hands on

Try it yourself ◐ 3 exercises

1 · Calculator loop. Define a calculate tool (one string property: expression) whose implementation evaluates basic arithmetic safely. Wire it into the Part 1 loop and ask: "What is 17.5% of 2,340, minus 89?" Print every tool_use block so you can watch Claude decompose the question. Then break it on purpose: return an error string with is_error and see how Claude recovers.

2 · Warehouse read-only query tool. Build query_database against a local SQLite or DuckDB file. In YOUR implementation, reject anything that is not a single SELECT (no INSERT/UPDATE/DELETE/DROP, no semicolon chaining) and only allow tables from an explicit allowlist. Note where the safety lives: in your executor, not in the prompt. Ask three analytics questions and check the SQL Claude wrote.

3 · Forced extraction. Take 5 messy real texts (emails, ticket comments) and run the record_extraction pattern with an enum field and a required field that is sometimes missing from the text. Inspect what Claude does with missing data, then tighten the property descriptions until all 5 come back right. Bonus: resubmit all 5 as one Message Batch.

Grade it like 6.2 Exercise 2 is an eval waiting to happen: save your three questions plus the correct answers, and you have a regression test for the day you swap models or rewrite the tool descriptions.
Source material

Official courses covered

This page teaches the Tool Use module that all three 8-hour engineering courses share, plus the Bedrock/Vertex-specific tool lessons.

Tool Use module - Building with the Claude APIfunctions, schemas, message blocks, tool results, multi-turn, multiple tools, text editor, web search
Bedrock/Vertex variants incl. batch tool + flexible extractionthe batch tool, structured data with tools, flexible tool extraction - same patterns, platform deltas noted

Deep dive 6.3 cheat sheet · pin this

The loopwhile stop_reason == "tool_use": execute each tool_use block, append assistant msg + user msg with tool_result blocks, call again.
Wiring rulestool_result lives in a USER message, echoes tool_use_id, all results in one message. Echo the assistant turn untouched.
Descriptions routeClaude picks tools by reading descriptions - say what, when, returns, and NOT-for. Same lesson as skill descriptions.
Structured datatool_choice={"type": "tool", "name": ...} + a schema-shaped tool = guaranteed-valid extraction. Harvest block.input.
Client vs serverYour functions: you execute (safety lives in your code). Web search: Anthropic executes. Text editor: their schema, your file ops.
Batch = half priceMessage Batches API for nightly scoring, backfills, evals: async, within 24 h, 50% off. custom_id is the join key.