n4nAI

Why LLMs hallucinate function call arguments

Analyze why LLMs invent invalid function call arguments, from schema drift to token prediction, and how engineers can enforce reliable tool use.

n4n Team4 min read946 words

Audio narration

Coming soon — every post will get a voice note here.

LLM hallucinated function arguments are not random bugs; they are the predictable output of a system optimized for fluent text generation being forced to emit structured JSON. The root cause is that function calling is a thin serialization layer over next-token prediction, not a type-safe RPC mechanism that the model internally understands.

The core mismatch: language modeling vs. structured extraction

Autoregressive language models assign probability to the next token given the previous tokens. When you attach a function schema, you are essentially prepending a JSON template to the context and hoping the model fills it correctly. The model has no compiler, no runtime type checker, and no penalty for emitting "user_id": "abc" when the schema demanded an integer.

Function calling APIs (OpenAI, Anthropic, others) convert your tool definition into a prompt suffix or a constrained logits mask. But the underlying weights were trained on web text where arguments to imaginary functions are loosely typed and often inconsistent. The model learns statistical correlations: if the conversation mentions a “customer email”, it will happily produce "email": "user@example.com" even if your schema calls for a customer_id integer and explicitly forbids free-form strings.

This is why LLM hallucinated function arguments persist even with models that score well on benchmarks. The benchmark prompts are curated; production traffic is not.

Common triggers for hallucinated arguments

Under-specified schemas

A schema like this invites trouble:

{
  "name": "schedule_meeting",
  "parameters": {
    "type": "object",
    "properties": {
      "attendees": {"type": "string"},
      "time": {"type": "string"}
    }
  }
}

The model sees attendees as a string and will often emit "attendees": "Bob and Alice" or "time": "next Tuesday sometime". Without explicit format hints ("format": "email", "pattern": "...", or an array of objects), the token prior dominates.

Missing enums and numeric bounds

If a parameter must be one of ["low", "medium", "high"], but you omit enum, the model may output "priority": "critical" because that word appeared frequently in its training data near similar contexts. Same for integers without minimum/maximum: you get temperature: 95 when your API expects 0–1.

Context truncation

Long conversations get trimmed. If the user said “use the project from the Acme onboarding” 20 turns ago, and that context is dropped, the model fabricates a project_id that looks plausible. LLM hallucinated function arguments frequently trace back to lost disambiguating context.

Distribution shift

Your internal tool names (erp_sync_invoice_v2) do not appear in the model’s training corpus. The model approximates based on fuzzy similarity to public APIs, producing arguments shaped like Stripe or GitHub calls instead of your domain.

Walking through a failure

Assume this tool:

{
  "name": "refund_order",
  "parameters": {
    "type": "object",
    "properties": {
      "order_id": {"type": "integer"},
      "reason": {"type": "string", "maxLength": 100}
    },
    "required": ["order_id"]
  }
}

A user says: “Refund the order from last week, the customer complained about sizing.”

A weak model might return:

{
  "name": "refund_order",
  "arguments": {
    "order_id": "ORD-4821",
    "reason": "customer said the shoes were too small and wants money back immediately, also mentioned they might chargeback"
  }
}

Two hallucinations: order_id is a string with a prefix, and reason violates maxLength. The model guessed an ID format from e-commerce text and padded the reason to sound helpful. Neither error is caught by the model’s own sampling; it only surfaces when your validator rejects the call.

Why naive validation falls short

The obvious fix is JSON Schema validation plus a retry:

from jsonschema import validate, ValidationError

try:
    validate(instance=args, schema=refund_schema)
except ValidationError as e:
    # send error back to model and ask to fix
    ...

This works, but each retry is another round-trip, another few hundred milliseconds, and another chance the model repeats the same mistake with slightly different text. In our testing, a single retry fixes about 60–70% of type mismatches; the remainder need explicit few-shot correction or a different model. Relying on validation alone turns a latency-sensitive agent into a polling loop.

Engineering patterns that reduce hallucination

Grammar-constrained decoding

Some inference stacks support constrained beam search or outline sampling (e.g., guidance, lm-format-enforcer). You pass the schema and the engine masks logits that would violate it. This eliminates type errors at the source:

# pseudo-code using a constrained generator
from guidance import gen, json as gjson

program = f"""
{user_msg}
""" + gen(name="call", grammar=gjson(refund_schema))

If your gateway or local server supports this, it is the highest-leverage fix. The model can still pick a wrong order_id value, but it will be a valid integer.

Few-shot tool examples

Inject 2–3 exemplar calls into the system prompt. Show the exact ID format and terse reason style. Models are imitation machines; explicit examples beat abstract descriptions.

Atomic tools over kitchen-sink tools

Split refund_order from lookup_order_id. Let the model call a read tool first, get a real integer, then call the write tool. This removes the guesswork that produces LLM hallucinated function arguments.

Explicit “ask if unknown” instruction

Add to the system prompt: “If any required parameter is not present in the conversation, output a request_clarification call instead of guessing.” This converts hallucination into a controllable user interaction.

Validation with repair feedback

When you reject a call, return a compact error: "order_id must be integer, got string 'ORD-4821'. Re-emit with numeric id only." Vague “invalid arguments” messages cause loops.

Tradeoffs: latency, cost, model support

Constrained decoding adds overhead at the tokenizer level but saves round-trips. Few-shot examples increase prompt size and thus pre-fill cost. Atomic tools multiply call count, raising total token spend but improving debuggability. There is no free lunch: stricter reliability means more tokens or more infrastructure.

Smaller open-weight models hallucinate arguments more often than frontier models, but they are cheaper. If you route to a smaller model for cost, budget for a validation+repair layer.

Infrastructure that helps without hiding the problem

Routing through a gateway such as n4n.ai that honors client routing directives lets you fail over to a model with stronger schema adherence when validation rejects a call, but this is a safety net, not a cure. The decisive engineering work is schema design and constrained generation. Automatic fallback when a provider is degraded keeps the agent online; it does not teach the model your order_id format.

Takeaway

LLM hallucinated function arguments are an artifact of treating function calls as text the model should author rather than structures it should complete under constraint. You reduce them by (1) writing schemas with real types, enums, and patterns; (2) using grammar-constrained decoding wherever the stack allows; (3) decomposing tools so the model never has to invent identifiers; and (4) feeding validation errors back as precise instructions. Do that, and the hallucination rate drops from a daily fire to a rare edge case you can log and ignore.

Tagsfunction-callinghallucinationreliability

Written by

n4n Team

The team building n4n — a single OpenAI-compatible API in front of 240+ models, with automatic fallback, load balancing and pay-per-token metering.

More from n4n Team →

All function calling fundamentals posts →