fix(model_tools): log warnings for failed JSON-array coercion

When _coerce_json fails to parse a string as JSON or parses to the wrong
type, log a clear WARNING instead of silently returning the original
value. When coerce_tool_args wraps a bare string into a single-element
list AND the string looks like a JSON array (starts with '['), warn
that the model likely emitted a JSON-encoded string instead of a
native array.

This improves diagnostics for the open-weight model output drift
described in #21933 (JSON-array-as-string), as well as any other tool
whose array-typed argument arrives stringified through
handle_function_call.

Note: delegate_task does NOT go through coerce_tool_args (it is in
_AGENT_LOOP_TOOLS and dispatched directly from run_agent.py with raw
function_args from json.loads). The actual delegate_task fix for #21933
is the previous commit. These logging changes apply to all other
array-typed arguments coerced via the shared pipeline.

Salvaged from PR #22092.
This commit is contained in:
uzunkuyruk 2026-05-09 14:43:53 +05:30 committed by kshitij
parent 326ca754ad
commit 7330183d08

View file

@ -550,6 +550,16 @@ def coerce_tool_args(tool_name: str, args: Dict[str, Any]) -> Dict[str, Any]:
# nullable "null" → None).
args[key] = coerced
continue
# If the string looks like a JSON array but _coerce_value
# failed to parse it, warn clearly instead of silently wrapping.
if value.strip().startswith("["):
logger.warning(
"coerce_tool_args: %s.%s looks like a JSON array string "
"but could not be parsed — model may have emitted a "
"JSON-encoded string instead of a native array. "
"Falling back to single-element list.",
tool_name, key,
)
args[key] = [value]
logger.info(
"coerce_tool_args: wrapped bare string in list for %s.%s",
@ -637,7 +647,12 @@ def _coerce_json(value: str, expected_python_type: type):
"""
try:
parsed = json.loads(value)
except (ValueError, TypeError):
except (ValueError, TypeError) as exc:
logger.warning(
"coerce_tool_args: failed to parse string as JSON for expected type %s: %s",
expected_python_type.__name__,
exc,
)
return value
if isinstance(parsed, expected_python_type):
logger.debug(
@ -645,6 +660,11 @@ def _coerce_json(value: str, expected_python_type: type):
expected_python_type.__name__,
)
return parsed
logger.warning(
"coerce_tool_args: JSON-parsed value is %s, expected %s — skipping coercion",
type(parsed).__name__,
expected_python_type.__name__,
)
return value