Skip to content
Claudexia TeamPRACTICE

Tool use in practice: schema design, multi-step loops, and common failures

How to design a tool schema the model actually understands, how a multi-step tool-calling loop is structured, and the mistakes that most often break function calling in production.

Tool use looks simple in the docs and breaks in production for a dozen reasons at once. Here is how to build schemas and loops so it does not.

How it works at the protocol level

You describe a set of functions to the model: a name, a description, a JSON schema for the parameters. The model never calls the function itself, it returns a structured tool_use block with a name and arguments. You run the code on your side, send the result back as a tool_result, and the model continues the conversation with that result in hand.

An important detail: the model can request several tools in a single step if the task allows it. Your code needs to execute them, possibly in parallel, and return the results in the right order, otherwise the loop gets confused about which result belongs to which call.

Designing the schema

The function name and the parameter descriptions are the only things the model has to go on when picking a tool. A vague description like "works with data" leads to random calls in the wrong situations.

A good schema answers three questions directly in the description: what the function does, when to call it, and when not to. Example:

{
  "name": "search_orders",
  "description": "Looks up a customer's orders by phone number or email. Call only when the user explicitly asks about their own order. Do not call for general questions about shipping or pricing.",
  "input_schema": {
    "type": "object",
    "properties": {
      "query": { "type": "string", "description": "Customer phone or email" },
      "status": { "type": "string", "enum": ["any", "pending", "shipped", "delivered"] }
    },
    "required": ["query"]
  }
}

Enums instead of free text cut the share of invalid calls more than any amount of prompt tuning. If a parameter can only take three values, give it an enum of three values, not a paragraph of prose.

The multi-step loop

A real task rarely resolves in one call. The model calls a tool, looks at the result, decides whether another step is needed, and repeats until it produces a final text answer. A loop skeleton in Python:

messages = [{"role": "user", "content": user_query}]

while True:
    response = client.messages.create(
        model="claude-sonnet-4.6",
        max_tokens=1024,
        tools=tools,
        messages=messages,
    )
    messages.append({"role": "assistant", "content": response.content})

    tool_calls = [b for b in response.content if b.type == "tool_use"]
    if not tool_calls:
        break

    results = [run_tool(call.name, call.input) for call in tool_calls]
    messages.append({
        "role": "user",
        "content": [
            {"type": "tool_result", "tool_use_id": call.id, "content": result}
            for call, result in zip(tool_calls, results)
        ],
    })

Every turn of the loop is a fresh request with a growing message history. Track token count on every step, the history swells fast, especially when tools return large chunks of text.

Common schema mistakes

Too many tools at once. If the model has twenty similar functions to choose from, it mixes them up more often. Group related operations into one tool with a mode parameter instead of ten near-identical ones.

A description written for a human, not for the model. A note like "see the API docs" is useless, the model does not follow external links, it needs everything it requires spelled out in the description text itself.

No error handling in tool_result. If the function fails, return that as a result with a clear error message instead of throwing an exception outward. The model can react to a visible error and try a different approach far better than your code assumes.

Guarding against loops that never end

The model can get stuck repeating the same call with slightly different arguments, especially when a tool returns an empty or ambiguous result. Set a hard cap on the number of loop steps, five to ten is usually enough, and stop execution with a clear error once the cap is hit.

It also helps to log every tool call with its arguments. When a loop starts spinning, the log shows exactly which step the model began repeating.

Where the two formats diverge

Both formats are available through Claudexia. Anthropic uses tool_use and tool_result blocks inside content; OpenAI uses a tool_calls field on the assistant message and a tool role for the next message. The logic is the same, but the JSON shape differs, so loop code for the two formats does not transfer one-to-one if you are working directly against REST rather than through an SDK.

In short

A tool schema needs to tell the model what a function does and when to call it, ideally with enums instead of free text. The loop grows in tokens with every step, track that and cap the number of iterations. Return tool errors as text inside tool_result rather than as exceptions, the model handles them better than your code expects.