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

Many calls, some fail

Tutor:

Ask “Is it warmer in Paris or Tokyo right now?” and Claude doesn't make one call, wait, then make another. It asks for both at once: one reply with two tool_use blocks. That's parallel tool use, and it's on by default.

Your job is the same as before, times two. Run every call, then send one user message holding one tool_result per call, each with its own tool_use_id.

import type Anthropic from "@anthropic-ai/sdk";
type Handler = (input: unknown) => Promise<unknown>;
export async function runCalls(
reply: Anthropic.Message,
handlers: Record<string, Handler>,
): Promise<Anthropic.MessageParam> {
const calls = reply.content.filter((block) => block.type === "tool_use");
// Independent lookups can run at the same time.
const results = await Promise.all(
calls.map(async (call): Promise<Anthropic.ToolResultBlockParam> => {
const handler = handlers[call.name];
try {
if (!handler) throw new Error(`Unknown tool "${call.name}".`);
const output = await handler(call.input);
return { type: "tool_result", tool_use_id: call.id, content: JSON.stringify(output) };
} catch (error) {
// A failed call still gets an answer, marked as an error.
const message = error instanceof Error ? error.message : String(error);
return { type: "tool_result", tool_use_id: call.id, content: message, is_error: true };
}
}),
);
// Every result in ONE user message.
return { role: "user", content: results };
}
TypeScriptUTF-8● lines 9–28: run every call, answer every call
Tutor:

Two mistakes to avoid. Don't send each result in its own message: the API lets you, but Anthropic's docs warn it teaches Claude to stop calling in parallel. And don't run calls in parallel when they depend on each other or change things (charge a card, then email the receipt). Run those in order. If you skip a call because an earlier one failed, it still gets a result, marked as an error.

quick check+20 neurons
Tutor:

Claude's reply has two tool_use blocks. How do you send the results back?

    Type your answer or tap an option · Enter checks

    Tutor:

    Tools fail: the city doesn't exist, the API is down, you hit a rate limit. Don't throw the error away and don't crash. Send it back as the result with is_error: true. Claude reads it like any other result and adapts: it retries with a fix, tries something else, or tells the user what went wrong.

    What you write in the error matters as much as a description does. "Error 404" tells Claude nothing. “No city called Pariss. Check the spelling, or add the country, e.g. Paris, France.” tells it exactly what to try next.

    Treat good results the same way. Return the fields Claude needs (temp_c, sky), not the weather API's whole response. Every byte you return is input tokens, and noise makes the useful part harder to find.

    One warning. A tool result can carry text you didn't write: a web page, an email, a customer's message. Someone can hide instructions in it (“ignore your rules and…”). That's called indirect prompt injection. Keep outside text inside tool_result blocks, never paste it into your system prompt, and don't let a tool result alone trigger anything risky.

    Your turn. The sandbox has no tools, so play it out. Write the error message your weather tool would return for a misspelled city, then ask Claude, in one sentence, what it would do next.

    Your turn

    Run your prompt and the replies land here.

      • Says it's a tool error, not yet
      • Says what went wrong, not yet
      • Says what to try instead, not yet
      • Asks what Claude would do, not yet
      • Ran at 1.0, not yet
      run at
      ~0 tokens

      Turn on JavaScript to run prompts.