Skip to content

fix: Coerce tool call arguments to match schema types#95

Closed
janhilgard wants to merge 1 commit intowaybarrios:mainfrom
janhilgard:fix/coerce-tool-arguments
Closed

fix: Coerce tool call arguments to match schema types#95
janhilgard wants to merge 1 commit intowaybarrios:mainfrom
janhilgard:fix/coerce-tool-arguments

Conversation

@janhilgard
Copy link
Copy Markdown
Collaborator

Summary

  • Add _coerce_tool_arguments() that auto-stringifies object/array values when the tool schema expects a string
  • Fixes a common LLM failure mode where models output raw JSON objects instead of JSON strings (e.g. when writing package.json, the model puts content as an object instead of a string)
  • Applied to all tool call emission paths (non-streaming, streaming, streaming fallback)

Problem

When using tool calling with coding assistants (e.g. OpenCoder), models sometimes generate:

{"file_path": "package.json", "content": {"name": "app", "version": "1.0.0"}}

instead of:

{"file_path": "package.json", "content": "{\"name\": \"app\", \"version\": \"1.0.0\"}"}

The client validates against the tool schema (content: string) and fails with:
expected string, received object

Fix

Server-side coercion: after parsing tool call arguments, check each field against the tool schema. If schema says type: "string" but the value is an object/array, json.dumps() it automatically.

Test plan

  • Verify tool calls with string content pass through unchanged
  • Verify tool calls with object content are auto-stringified
  • Verify tool calls with array content are auto-stringified
  • Verify no-op when tools schema is not provided
  • Verify streaming and non-streaming paths both coerce

🤖 Generated with Claude Code

Add _coerce_tool_arguments() that auto-stringifies object/array values
when the tool schema expects a string. Fixes a common LLM failure mode
where models output raw JSON objects instead of JSON strings (e.g. when
writing package.json, the model puts content as an object instead of a
string). Applied to all 4 tool call emission paths (non-streaming,
streaming with reasoning, streaming without reasoning, streaming fallback).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@janhilgard janhilgard force-pushed the fix/coerce-tool-arguments branch from 9655b45 to ad8078d Compare March 21, 2026 22:20
@Thump604
Copy link
Copy Markdown
Collaborator

Thump604 commented Apr 8, 2026

@waybarrios, @janhilgard: brief endorsement.

_coerce_tool_arguments() that auto-stringifies object/array values when the tool schema expects a string is the right fix for the failure mode where models generate {"content": {"name": "app"}} instead of {"content": "{\\"name\\":\\"app\\"}"}. Real production failure mode for coding assistants writing JSON config files. Applied to all tool call emission paths (non-streaming, streaming, streaming fallback). Mergeable on current main.

Last activity Mar 21, ~3 weeks ago.

@janhilgard
Copy link
Copy Markdown
Collaborator Author

@Thump604 This is already in main via #278_coerce_tool_arguments() is defined and used in all streaming/non-streaming/fallback paths (4 call sites). Closing as superseded.

@janhilgard janhilgard closed this Apr 11, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants