From 7330183d087f65c57db424fb03adf55d72661c32 Mon Sep 17 00:00:00 2001 From: uzunkuyruk Date: Sat, 9 May 2026 14:43:53 +0530 Subject: [PATCH] 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. --- model_tools.py | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/model_tools.py b/model_tools.py index 679a0934c44..253cf02fe8d 100644 --- a/model_tools.py +++ b/model_tools.py @@ -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