trendingzones
← Back to the Intermediate level

AI & ML — ADVANCED

Tool Calling in Production: Errors, Structured Output, and a Real Dispatcher

Every example so far has assumed the tool call succeeds. Production systems have to handle the moment it doesn’t — and know when tool calling is even the right feature to reach for in the first place.

The Quick Answer

A failed tool call is reported back the same way a successful one is — as a tool_result block — just with is_error: true and a specific message describing what went wrong. Separately, it’s worth being precise about what tool calling actually solves: it’s for requesting real actions, not for controlling the shape of the model’s own final answer — that’s a distinct feature, structured output, and the two solve different problems.

Reporting a Failed Tool Call: is_error

When a tool call fails, the result still gets sent back as a tool_result — not silence, not a crash — with an extra field:

{
  "role": "user",
  "content": [
    {
      "type": "tool_result",
      "tool_use_id": "toolu_01A09q90qw90lq917835lq9",
      "content": "ConnectionError: the weather service API is not available (HTTP 500)",
      "is_error": true
    }
  ]
}

The model reads this exactly like any other result and decides what to do — often explaining the failure to the user (“I’m sorry, I was unable to retrieve the current weather because the service is unavailable”). The message itself matters: a specific, instructive error like “Rate limit exceeded. Retry after 60 seconds” gives the model something concrete to act on, where a bare “failed” doesn’t.

Source: Claude Platform Docs, “Handle tool calls”

Structured Output vs. Tool Calling: Not the Same Feature

Both features can guarantee a predictable JSON shape, which makes them easy to confuse. The actual distinction is about whatis being controlled: structured output controls the format of the model’s own final answer — useful for tasks like extracting fields from an invoice or classifying feedback, where the model’s reply isthe JSON. Tool calling is about requesting that the application execute a real action — the model isn’t producing its final answer, it’s asking for something to be done. The two aren’t mutually exclusive: a model can call a tool with strictly validated parameters and still return its eventual final answer as structured JSON.

AspectStructured outputTool calling
What it controlsThe format of the model’s own final answer to the user.A request for the application to execute a real action.
Who acts on the resultThe application reads the JSON as the final answer — nothing further needs to run.The application executes a real function, then reports the result back to the model.
Typical use caseExtracting invoice fields, classifying feedback, generating an API-ready summary.Checking live weather, booking something, querying a database, calling an external API.
Is this the model’s last step?Usually yes — the JSON returned is the final response.No — a tool_result typically comes back, and the model continues from there.
Can they be combined?Yes — a model can call a tool with validated parameters and still return its final answer as structured JSON.Yes — same as the previous column, this is one combined workflow, not two separate features.

Source: Claude Platform Docs, “Structured outputs”

A Real Multi-Tool Dispatcher, in Code

The Problem

Every example so far has handled exactly one tool, checked by hand. A real application usually registers several tools at once, and needs one consistent piece of code that: takes whatever tool_use block the model produced, figures out which real function that name corresponds to, runs it with the arguments the model supplied, and — critically — never lets a bad tool name or a function that throws an exception crash the whole request. Instead, any failure has to come back as a normal tool_result with is_error: true, exactly as covered above, so the model can react to it instead of the application simply dying. This single piece of routing logic is usually called a dispatcher.

The Code

def get_weather(location):
    known = {'Tokyo, Japan': '21C, partly cloudy', 'Mumbai, India': '31C, humid'}
    if location not in known:
        raise ValueError(f'Unknown location: {location!r}')
    return known[location]

def convert_currency(amount, from_currency, to_currency):
    rates = {('USD', 'INR'): 83.2}
    key = (from_currency, to_currency)
    if key not in rates:
        raise ValueError(f'No exchange rate available for {from_currency} to {to_currency}')
    return round(amount * rates[key], 2)

TOOL_REGISTRY = {'get_weather': get_weather, 'convert_currency': convert_currency}

def dispatch_tool_call(tool_use_block):
    name = tool_use_block['name']
    tool_use_id = tool_use_block['id']
    tool_fn = TOOL_REGISTRY.get(name)
    if tool_fn is None:
        return {'type': 'tool_result', 'tool_use_id': tool_use_id,
                'content': f'Error: unknown tool {name!r}', 'is_error': True}
    try:
        result = tool_fn(**tool_use_block['input'])
        return {'type': 'tool_result', 'tool_use_id': tool_use_id, 'content': str(result)}
    except (TypeError, ValueError) as e:
        return {'type': 'tool_result', 'tool_use_id': tool_use_id,
                'content': f'Error: {e}', 'is_error': True}

Line by Line: What Each Part Is Doing

Run against a real location, this correctly returns a plain tool_result with the weather. Run against location="Atlantis" or a currency pair with no known exchange rate, it correctly catches the error and returns is_error: True with a specific message — verified line for line, in both Python and Node, producing the same results either way.

Fun Fact

A registry like the one above is also exactly where the hallucinated-argument problem from AI Agent Advanced gets caught in practice — the tool function itself is the last line of defense, since it can reject an argument the model generated but that doesn’t actually correspond to anything real.

Test Yourself

What should the content of an is_error tool_result look like?

What happens when a model’s tool call is missing a required parameter?

What is the real difference between structured output and tool calling?

In the multi-tool dispatcher code on this page, what happens when an unknown location is passed to get_weather?