Parse a raw JSON tool-call arguments string into a dict. First tries strict parsing; if the JSON is malformed (common with smaller / open-weight models that fumble special tokens or escaping), falls back to ``json_repair`` and then strips known chat-template token leaks. Raises ``V
(json_arguments: str)
| 415 | |
| 416 | |
| 417 | def parse_function_arguments(json_arguments: str) -> dict[str, Any]: |
| 418 | """Parse a raw JSON tool-call arguments string into a dict. |
| 419 | |
| 420 | First tries strict parsing; if the JSON is malformed (common with smaller / |
| 421 | open-weight models that fumble special tokens or escaping), falls back to |
| 422 | ``json_repair`` and then strips known chat-template token leaks. |
| 423 | |
| 424 | Raises ``ValueError`` if the arguments can't be recovered or don't decode |
| 425 | to a dict-shaped value. |
| 426 | """ |
| 427 | try: |
| 428 | args_dict: Any = from_json(json_arguments) |
| 429 | except ValueError as strict_err: |
| 430 | repaired = json_repair.loads(json_arguments) |
| 431 | if repaired == "": |
| 432 | # json_repair returns "" when it can't recover anything meaningful. |
| 433 | raise ValueError( |
| 434 | f"could not parse function arguments as JSON: {strict_err}: {json_arguments[:200]}" |
| 435 | ) from strict_err |
| 436 | # After a repair, also strip leaked chat-template tokens — many of |
| 437 | # the failures we see are caused by `<|...|>` markers bleeding into |
| 438 | # the model's structured output. |
| 439 | cleaned = _strip_template_tokens(repaired) |
| 440 | logger.warning( |
| 441 | "repaired malformed function-call JSON arguments", |
| 442 | extra={ |
| 443 | "raw_arguments": json_arguments[:500], |
| 444 | "repaired": cleaned, |
| 445 | "error": str(strict_err), |
| 446 | }, |
| 447 | ) |
| 448 | args_dict = cleaned |
| 449 | |
| 450 | # Some providers (e.g. Nova Sonic) double-encode tool arguments as nested |
| 451 | # JSON strings. Unwrap until we reach a non-string value. |
| 452 | while isinstance(args_dict, str): |
| 453 | try: |
| 454 | args_dict = from_json(args_dict) |
| 455 | except Exception: |
| 456 | raise ValueError( |
| 457 | f"function arguments decoded to a non-JSON string: {args_dict[:200]}" |
| 458 | ) from None |
| 459 | |
| 460 | if args_dict is None: |
| 461 | return {} |
| 462 | if not isinstance(args_dict, dict): |
| 463 | raise ValueError( |
| 464 | f"expected dict from function arguments, " |
| 465 | f"got {type(args_dict).__name__}: {json_arguments[:200]}" |
| 466 | ) |
| 467 | return args_dict |
| 468 | |
| 469 | |
| 470 | def prepare_function_arguments( |