diff --git a/run_agent.py b/run_agent.py index b53d1c823..71f9e8d16 100644 --- a/run_agent.py +++ b/run_agent.py @@ -9662,7 +9662,81 @@ class AIAgent: ), }} except Exception: - pass + # GLM-5.1 and similar models can generate + # malformed tool_call arguments (truncated JSON, + # trailing commas, Python None, etc.). The API + # proxy rejects these with HTTP 400 "invalid tool + # call arguments". Attempt common repairs; if + # all fail, replace with "{}" so the request + # succeeds (better than crashing the session). + raw_args = tc["function"]["arguments"] + repaired = False + raw_stripped = raw_args.strip() if isinstance(raw_args, str) else "" + + # Fast-path: empty / whitespace-only → empty object + if not raw_stripped: + tc["function"]["arguments"] = "{}" + repaired = True + logger.warning( + "Sanitized empty tool_call arguments for %s", + tc["function"].get("name", "?"), + ) + # Python-literal None → JSON null → normalise to {} + elif raw_stripped == "None": + tc["function"]["arguments"] = "{}" + repaired = True + logger.warning( + "Sanitized Python-None tool_call arguments for %s", + tc["function"].get("name", "?"), + ) + + if not repaired: + # Attempt common JSON repairs + import re as _re + fixed = raw_stripped + # 1. Strip trailing commas before } or ] + fixed = _re.sub(r',\s*([}\]])', r'\1', fixed) + # 2. Close unclosed structures + open_curly = fixed.count('{') - fixed.count('}') + open_bracket = fixed.count('[') - fixed.count(']') + if open_curly > 0: + fixed += '}' * open_curly + if open_bracket > 0: + fixed += ']' * open_bracket + # 3. Remove extra closing braces/brackets + while True: + try: + json.loads(fixed) + break + except json.JSONDecodeError: + if fixed.endswith('}') and fixed.count('}') > fixed.count('{'): + fixed = fixed[:-1] + elif fixed.endswith(']') and fixed.count(']') > fixed.count('['): + fixed = fixed[:-1] + else: + break + try: + json.loads(fixed) + tc["function"]["arguments"] = fixed + repaired = True + logger.warning( + "Repaired malformed tool_call arguments for %s: %s → %s", + tc["function"].get("name", "?"), + raw_stripped[:80], fixed[:80], + ) + except json.JSONDecodeError: + pass + + if not repaired: + # Last resort: replace with empty object so the + # API request doesn't crash the entire session. + tc["function"]["arguments"] = "{}" + logger.warning( + "Unrepairable tool_call arguments for %s — " + "replaced with empty object (was: %s)", + tc["function"].get("name", "?"), + raw_stripped[:80], + ) new_tcs.append(tc) am["tool_calls"] = new_tcs