Week 2 · Day 7 of 40 · Build AI

Tool use and chaining calls

You ship: 4 tools running on both providers, and a research command with citations. Builds on: Day 6.

By the end of the day you can

  • Implement Claude tool use (function calling) end-to-end
  • Handle multi-turn tool interactions where the model calls tools iteratively
  • Chain multiple LLM calls into multi-step workflows

Topics

Tool Use Fundamentals

  • How tool use works: define tools → model decides to call → you execute → return result → model continues
  • Tool definitions: name, description, input_schema (JSON Schema)
  • Writing good tool descriptions: clear, specific, include examples of when to use
  • Input schemas: using JSON Schema to constrain tool parameters, required vs optional fields

Handling Tool Calls

  • Parsing tool_use content blocks from the response
  • Executing tool functions: mapping tool names to handler functions
  • Returning results: tool_result content blocks, handling errors gracefully
  • Error messages that help the model: "No results found for query X, try broadening the search" vs "Error"

Tool Calling on OpenAI

  • Function tools on the Responses API: type: "function", name, description, parameters (JSON Schema), strict: true
  • Calls arrive as function_call output items with a call_id; results go back as function_call_output items
  • The loop is the same: run tools, append results, call again until the response has no function calls
  • Parallel calls, iteration limits and human-in-the-loop apply unchanged

Multi-Turn Tool Loops

  • The agent loop: send message → model responds with tool calls → execute tools → send results → repeat until model gives a text response
  • stop_reason: "tool_use" vs stop_reason: "end_turn": knowing when the model wants to call more tools
  • Parallel tool calls: when Claude calls multiple tools at once, execute them in parallel
  • Iteration limits: preventing infinite loops with max_turns
  • Human-in-the-loop: pausing the loop to ask the user for confirmation before executing sensitive tools

Chaining LLM Calls

  • Sequential chains: output of one LLM call becomes input to the next
  • Pattern: research → synthesize → format (each step is a separate LLM call with a focused prompt)
  • Why chaining: smaller focused prompts > one giant prompt. Each step can use a different model (Haiku for simple steps, Opus for complex)
  • Passing context between chains: extracting structured data from one response to feed the next
  • Error handling in chains: what to do when a middle step fails

Build

Features shipped today

  • WorkBot: A tool registry that runs on both providers, the seed of the action layer on Day 27; 4 tools and a research command that answers a policy question from the company's sources with citations

Build a chatbot with tools and a multi-step chain:

Tools (implement all 4)

  1. web_search - searches the web via an API (Brave Search or Tavily) and returns results
  2. read_url - fetches a URL and returns the page content as text
  3. calculator - evaluates mathematical expressions
  4. save_note - saves a note to a local JSON file for later retrieval

One tool registry, both providers: the loop runs against Anthropic or OpenAI with the Day 6 flag.

Multi-step chain

Build a "research and report" command where:

  1. Step 1 (Haiku): take the user's topic and generate 3 specific search queries
  2. Step 2 (Haiku): execute web searches for each query, collect results
  3. Step 3 (Sonnet): read the top 3 URLs, extract key information
  4. Step 4 (Sonnet): synthesize findings into a structured report with sections and citations

The chatbot should support both regular chat (with tools) and the /research <topic> command (which triggers the chain).

Walkthrough

Give the assistant tools, run the tool loop on both providers, and chain 4 steps, one model call each, into a /research command that answers a policy question from the company's documents with citations.

Part 1

4 tools, one registry

A tool is a schema for its input and a function that runs it. The registry holds both, and each provider maps the schema into its own API format.

Study first

  • Tool use on each API: Anthropic's tools with input_schema and the tool_use and tool_result blocks; OpenAI's function tools with function_call and function_call_output items.
  • Zod 4's z.toJSONSchema, which turns the input schema into the JSON Schema both APIs accept.

Build

  1. src/lib/tools.ts: a Tool type (name, description, input schema, run) and a tool() helper that parses the input before run sees it. 4 tools: calculator, read_url, save_note, and web_search when a Brave key is configured.
  2. The implementations, each its own module with a test: calculator.ts, a recursive-descent parser (one function per precedence level) for + - * / % ^ and parentheses; web.ts, readPage and searchWeb; notes.ts, a notes table through the Day 5 query function, with its create table appended to the schema. The curriculum's save_note writes a JSON file; this project already has a database.
  3. Descriptions that say when to call the tool, not only what it does. The calculator's says to call it for any calculation.

Done when

  • pnpm test covers the calculator's precedence and errors, the page reader's refusals and byte cap, and the search request's shape.
Technical notes
  • readPage refuses the private ranges, the shared range 100.64.0.0/10 and link-local 169.254.0.0/16, where cloud metadata services answer, localhost and .internal names, and any scheme but http and https. It follows redirects by hand so every hop is checked, and reads at most 512 KB of a body.
  • The text extractor drops scripts, styles and tags and keeps block boundaries as line breaks. The result is capped at 8,000 characters per page.
  • web_search is in the registry only when BRAVE_SEARCH_KEY is set.
  • The calculator refuses anything it cannot tokenise, and a non-finite result such as 1 / 0.
  • No tool today changes anything outside the app. When one does, on Day 27, a confirmation step goes between the model's call and the run.

Part 2

The tool loop on both providers

stream repeats model, tool, result until the model returns text.

Study first

  • Anthropic: stop_reason: "tool_use", the tool_use blocks in the final message, and a tool_result block per call in the next user message, is_error when the tool failed.
  • OpenAI: function_call items in the response output, function_call_output items with the call_id, and, when the caller manages the conversation itself as this loop does, reasoning items sent back so a reasoning model can continue.

Build

  1. Completion gains tools, and stream takes an events object: text(delta) and tool(call), the call being name, input, result and whether it failed.
  2. One run function for both providers: find the tool, parse the input with its schema, run it, catch what it throws. Unknown tools, invalid input and failures go back to the model as error results, never as exceptions.
  3. The loop in each provider: up to 8 rounds; any stop reason other than a tool call ends the turn, including a reply cut off at max_tokens in the middle of a call.
  4. The terminal shows each call as it happens, dimmed, with its input.
  5. Tests with recorded streams: a tool call round trip on each provider, checking the exact tool_result and function_call_output messages sent back, and a reply cut off inside a tool call, which runs nothing. In providers.live.test.ts, the small model multiplies 2 numbers through the calculator.

Done when

  • Both providers answer "What is 1234 * 5678?" by calling the calculator, and pnpm test:live shows the call.
A turn with tool calls
The assistant on the small model: the calculator runs for the arithmetic and save_note for the request to remember something, each shown as it happens.
Technical notes
  • A truncated tool input must never run: when max_tokens cuts a reply inside a tool_use block the stop reason is max_tokens, and the loop ends there.
  • Anthropic's eager_input_streaming streams a tool's input as it is produced; the loop still waits for the final message before running anything, so it adds no token cost here and matters for large inputs later.
  • OpenAI's function tools are declared strict, so the arguments the model sends match the schema; the registry parses them again, as the single validation step for both providers.
  • Parallel calls: Anthropic may ask for several tools in one message, and the results go back together in one user message, in order.

Part 3

A research chain with citations

A 4-step chain, passing each step's output to the next.

Study first

  • Which call needs which model. Planning suits the small model, claude-haiku-5-5 at $0.10 per million input tokens; extraction and writing, the large one, claude-sonnet-5-5 at $2. The provider's model list carries the prices.
  • How to get JSON out of a model without a schema feature: ask for JSON only, parse, validate with Zod, and on failure send the validation error back once.

Build

  1. src/lib/research.ts, research(question, provider, on): the small model picks up to 3 policies by slug and up to 3 web searches; the policies are read from content/policies and, with a Brave key, the top result of each search is fetched; the large model extracts the relevant facts from each source as bullet points; the large model writes the answer from the notes, a numbered citation after every fact and a source list at the end. The curriculum's chain searches the web only; WorkBot's sources are its policies first, so the planner picks both.
  2. Events: step(message) as each stage starts, text(delta) for the streamed answer, and spend(model, usage) per call, so the session's /cost counts the chain.
  3. /research <question> in the terminal, and a record(model, usage) method on the session for the chain's spend.
  4. Tests with a scripted provider: the steps, the models chosen per call, the citations in the answer, and the retry when the plan is not valid JSON. In research.live.test.ts, the chain runs on the small model in both roles and cites the refunds policy.

Done when

  • /research How much can a support agent refund without approval? answers $200 with a citation to the refunds policy, and /cost after it includes the chain's calls.
A research run
/research: the steps as they run, the answer with a citation after each fact, the source list, and the tokens the chain used.
Technical notes
  • The plan step retries once with the validation error, then fails with the model's text in the error; a run is 4 calls, or 5 with the retry.
  • Sources are numbered when they are collected, and the same numbers go to the extraction prompt and the writing prompt, so [2] means the same document in every step.
  • Extraction runs per source in parallel; the writing step is one call that streams, so the user sees the answer as it is written.
  • A page that fails to load falls back to the search result's snippet. An extraction or writing call that fails throws; the terminal prints the error, and nothing from the run enters the conversation history.
  • Without a Brave key the chain reads policies only; the planner's web searches are ignored, not sent to a tool that would fail.

Resources