Syllabus Lesson 116 of 239 · Calling a Real LLM
Calling a Real LLM

Parallel Tool Calls & is_error

The previous lesson handled one tool call. But modern models ask for several at once: parallel tool use is on by default, so a single assistant turn can contain three or four tool_use blocks ("get the weather AND look up the order AND check inventory"). If your code only runs the first one, the model waits for results that never come and the turn stalls.

"Parallel" here describes the model's REQUEST, not your execution: the model asked for several tools in one turn. You can simply run them one after another and send all the results back together, no threads or async required.

The contract is precise: run every tool_use block, then send back one user message whose content is a list of tool_result blocks, one per call, each echoing its own tool_use_id.

{"content": [
  {"type": "tool_use", "id": "t1", "name": "get_weather", "input": {"city": "Paris"}},
  {"type": "tool_use", "id": "t2", "name": "add",         "input": {"a": 2, "b": 5}}],
 "stop_reason": "tool_use"}

The second new idea is failure isolation. If one tool raises, you do NOT crash the whole turn and lose the other results. You return that block's tool_result with "is_error": True and the error text as its content, so the model can see what went wrong and recover. A hallucinated tool name (not in your registry) is just another error: report it as is_error, never run something you did not register.

Build run_tool_calls(resp, tools) returning the single follow-up user message. On success a tool_result has exactly type, tool_use_id, and content (the result via str(...)); on failure it ALSO carries "is_error": True. Press Run to answer a two-call turn.

Your turn

Write run_tool_calls(resp, tools) that runs EVERY tool_use block in resp["content"] (ignore other block types) and returns one user message {"role": "user", "content": [ ...tool_result blocks... ]}, one result per call in order. Each success result is {"type": "tool_result", "tool_use_id": <id>, "content": str(result)}. If a tool raises OR its name is not in tools, that result instead carries "is_error": True with the error text as content, and the other tools still run.

Spotted a problem in this lesson? Report it

Code · runs in your browser
Output