Home ยป Tool Call Arguments That Will Not Parse: Three Causes
Tool Call Arguments That Will Not Parse: Three Causes

Tool Call Arguments That Will Not Parse: Three Causes

Figures on this page were verified 31 August 2026 against the providers' own documentation. Pricing, context windows and rate limits change without notice, so confirm any number against the provider before you rely on it. Tell us if something here is out of date.

When a tool call arrives with arguments you cannot parse, the model is almost never inventing malformed JSON at random. Three causes account for nearly all of it: the arguments were truncated by max_tokens, your schema permitted something you did not intend, or you are parsing a streamed call before it has finished arriving. Each has a different fix, and only one of them is a prompting problem.

What you actually receive

Both providers return tool arguments as a string that you are expected to parse, not as a nested object. OpenAI returns tool_calls[].function.arguments as serialised JSON; Anthropic returns a tool_use block whose input is already an object but which can still be incomplete if generation stopped early.

// OpenAI: arguments is a STRING, and json.loads can fail on it
{
  "tool_calls": [{
    "id": "call_abc123",
    "type": "function",
    "function": {
      "name": "search_orders",
      "arguments": "{"customer_id": "C-4471", "status": "shi"
    }
  }]
}

That example is the signature of cause one. The JSON is not wrong, it is unfinished. Nothing in the parse error will tell you that; you have to look at the stop reason.

Cause 1: the call was truncated

Tool arguments are generated tokens like any other. If the response hits the output ceiling mid-argument, you get a fragment. Check the stop reason before you parse.

choice = response.choices[0]
if choice.finish_reason == "length":          # OpenAI
    raise Truncated("raise max_tokens or shrink the schema")

# Anthropic
if response.stop_reason == "max_tokens":
    raise Truncated("tool input is incomplete")

args = json.loads(choice.message.tool_calls[0].function.arguments)

The usual trigger is a schema with a free-text field. One description or query property with no length guidance invites the model to write several hundred tokens into it. See why responses get cut off at max_tokens for the general case.

Cause 2: your schema allowed it

A permissive schema is the most common source of “wrong” arguments that parse perfectly well. If a property has no enum, no type, or is not listed in required, the model is free to omit it or fill it with something plausible. That is not a model failure; it is the schema doing exactly what it says.

SymptomSchema causeFix
Field missing entirelyNot in requiredAdd it to required
Invented status or categoryFree string with no enumConstrain with enum
Number arrives as "42"type omitted or stringDeclare "type": "integer"
Extra unexpected keysadditionalProperties unsetSet it to false
Date in three formatsNo format or patternAdd "format": "date" and say so in the description

Enable strict schema adherence where the provider offers it. On OpenAI that is "strict": true on the function definition, which makes the API enforce the schema during decoding rather than hoping the model complies. The property descriptions are also read by the model, so write them as instructions rather than as documentation.

Cause 3: you parsed a stream too early

When streaming, tool arguments arrive as deltas across many chunks. Any single chunk is meaningless on its own. You must accumulate the fragments by tool-call index and parse once the stream completes.

from collections import defaultdict
buffers = defaultdict(str)

for chunk in stream:
    for call in (chunk.choices[0].delta.tool_calls or []):
        buffers[call.index] += (call.function.arguments or "")

# parse only after the stream has ended
tools = {i: json.loads(b) for i, b in buffers.items()}

If the connection itself drops mid-stream you have a different problem: an incomplete buffer that will never be completed. See streaming connection closed mid-response.

Handle the failure without crashing

Even with a strict schema, treat parsing as fallible. The robust pattern is to validate against your schema and, on failure, hand the error back to the model as a tool result so it can correct itself.

try:
    args = Arguments.model_validate_json(raw)     # pydantic
except ValidationError as e:
    messages.append({
        "role": "tool",
        "tool_call_id": call_id,
        "content": f"Invalid arguments: {e}. Re-issue the call matching the schema.",
    })
    # one retry, then give up rather than loop

Cap that correction loop at one or two attempts. A model that cannot satisfy the schema twice will not satisfy it on the fifth try, and each attempt costs a full round trip.

Related errors

The non-tool version of this problem is covered in the model returned invalid JSON. For the full decision table on which failures to retry, see the LLM API error reference.

Frequently asked questions

Why are tool arguments a string rather than an object?

On OpenAI, function arguments come back as serialised JSON inside tool_calls function arguments and you are expected to parse them yourself. Anthropic returns a tool_use block whose input is already an object, but it can still be incomplete if generation stopped early.

How do I stop the model inventing or omitting fields?

Constrain the schema rather than the prompt. Mark fields as required, use enum for closed sets, declare types explicitly, and set additionalProperties to false. Where the provider offers strict schema adherence, enable it so the shape is enforced during decoding instead of merely requested.

Should I retry when arguments fail validation?

Once, or at most twice, by returning the validation error to the model as a tool result so it can correct itself. A model that cannot satisfy the schema twice will not satisfy it on the fifth attempt, and every try costs a full round trip.

Chirag Darji

Chirag Darji is the founder of VGraple and the editor of It's About You. He writes about the LLM APIs and developer tooling he works with, and every figure published here is checked against the provider's own documentation before it goes live, with the date it was verified shown on the page.

More Reading

Post navigation