Skip to content
bits
Give your model tools · Lesson 3 of 10

One round trip

Tutor:

Here's what happens when Claude uses a tool, from your code's point of view. It takes two requests.

Request one. You send the question and the tools. Claude decides it needs the weather, so instead of an answer it sends back a reply with stop_reason: "tool_use" and a tool_use block: an id, the tool's name, and the input it wants to pass.

Your code runs the tool. Claude has only asked. Nothing has been fetched yet.

Request two. You send the conversation back with two more turns: Claude's reply exactly as it came, then a user turn holding a tool_result block with the same tool_use_id and your result. Now Claude writes the answer.

import Anthropic from "@anthropic-ai/sdk";
import { tools } from "./tools";
const client = new Anthropic(); // reads ANTHROPIC_API_KEY
// A stand-in for a real weather API (the checkpoint uses a real one).
function getWeather(city: string) {
return { city, temp_c: 5, sky: "overcast" };
}
const question = process.argv[2] ?? "Do I need a coat in Chicago?";
const messages: Anthropic.MessageParam[] = [{ role: "user", content: question }];
// Request one: question + tools.
const first = await client.messages.create({
model: "claude-haiku-5-5",
max_tokens: 4096,
tools,
messages,
});
const call = first.content.find((block) => block.type === "tool_use");
if (first.stop_reason !== "tool_use" || !call) {
// Claude answered without a tool. That's allowed.
for (const block of first.content) if (block.type === "text") console.log(block.text);
process.exit(0);
}
// Your code runs the tool. Claude only asked.
const { city } = call.input as { city: string };
const result = getWeather(city);
// Request two: Claude's reply as it came, then the result.
messages.push(
{ role: "assistant", content: first.content },
{
role: "user",
content: [{ type: "tool_result", tool_use_id: call.id, content: JSON.stringify(result) }],
},
);
const second = await client.messages.create({
model: "claude-haiku-5-5",
max_tokens: 4096,
tools,
messages,
});
for (const block of second.content) if (block.type === "text") console.log(block.text);
TypeScriptUTF-8● lines 14–48: the round trip
OUTPUT
npx tsx weather.ts "Do I need a coat in Chicago?"
example output, written for this lesson (not a live run)
→ request 1 stop_reason="tool_use"
{ type: "tool_use", id: "toolu_01A0…", name: "get_weather", input: { city: "Chicago" } }
← your code getWeather("Chicago") → { temp_c: 5, sky: "overcast" }
→ request 2 stop_reason="end_turn"
Yes. It's 5°C and overcast in Chicago, so bring a coat.
Tutor:

Notice what the second request carries: the whole conversation, again. The API doesn't remember the first request, so you send the question, Claude's tool call and your result together.

Tutor:

Three rules keep the API happy. Break one and the request fails with an error.

Send Claude's reply back unchanged. Push first.content as it came, not just the tool_use block. The reply can hold other blocks, like a sentence of text before the call, and they belong in the history.

Answer every call, straight away. The very next message after a tool_use must be a user message with its tool_result. Nothing can sit in between.

Results go first. If you add your own text to that user message, it goes after the tool_result blocks, never before.

quick check+20 neurons
Tutor:

How does Claude know which call your result answers?

    Type your answer or tap an option · Enter checks

    quick check+20 neurons
    Tutor:

    You send request two with the tool_result but forget to push Claude's reply (first.content) first. What happens?

      Type your answer or tap an option · Enter checks

      Tutor:

      That's one call and one answer. Real assistants often need several calls in a row (look up a city, then its weather, then a packing list). That's the agent loop, and it gets its own course. Here, you'll get the single call right first.