Skip to content

Tool-call arguments: no parse validation, no repair hook — malformed JSON silently degrades to a string #162

Description

@cunninghamcard-bit

Current behavior

When a provider returns tool-call arguments that are not valid JSON, the conversion does this (e.g. aimux-providers/src/openai/model.rs):

let input: Value = serde_json::from_str(&tc.function.arguments)
    .unwrap_or_else(|_| Value::String(tc.function.arguments.clone()));

A malformed argument payload (truncated by max-tokens, trailing comma, unescaped quote — all common LLM failure modes) is silently wrapped as a JSON string and delivered as ToolCall.input. The caller expecting an object gets a string, and the failure surfaces far from its cause — in the user's tool executor, with no indication that the model emitted broken JSON.

There is also no validation that the called tool exists or that the input matches the tool's declared schema.

What the AI SDK does here (for comparison)

packages/ai/src/generate-text/parse-tool-call.ts:

  1. unknown tool name → typed NoSuchToolError;
  2. safeParseJSON + schema validation of the arguments → typed InvalidToolInputError;
  3. an optional repairToolCall hook (ToolCallRepairFunction) that gets one chance to fix the broken call (structurally re-parse, or re-ask the model) before the typed error is thrown.

The interesting part: half the machinery already exists

aimux-core/src/json_repair.rs already ports the AI SDK's fix-json.ts and parse-partial-json.ts — but it is only wired into generate_object (repairing the text output before schema parsing). Tool-call arguments never touch it.

Question for the author

Is the current pass-through-and-degrade behavior a deliberate "keep the core thin, let callers validate" choice, or is tool-call parsing/repair simply not built yet?

If it's worth building, the natural shape would be:

  1. Parse + validate in the core tool-call path: malformed JSON → a typed error (or at minimum an explicit marker on the ToolCall), unknown tool / schema mismatch → typed errors, instead of silent string degradation;
  2. Repair: try the existing fix_json on malformed arguments first (free, already in-tree), and optionally expose a repair_tool_call hook for caller-driven repair (the AI SDK's repairToolCall equivalent);
  3. Streaming: parse_partial_json is likewise already in-tree and could back partial tool-input parsing for ToolInputDelta consumers.

If it's deliberate, a doc note on ToolCall.input saying "may be a raw string when the provider emitted invalid JSON" would at least make the degradation visible.

References

  • aimux-providers/src/openai/model.rs — the unwrap_or_else(Value::String) fallback (same pattern in other providers)
  • aimux-core/src/json_repair.rs — existing fix_json / parse_partial_json ports, currently used only by generate_object
  • AI SDK: packages/ai/src/generate-text/parse-tool-call.ts, tool-call-repair-function.ts

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions