{"record":{"id":"8554522c47658361","repo":"BerriAI/litellm","slug":"tool-call-function-arguments-must-be-a-json-stri","errorCode":null,"errorMessage":"Tool call `function.arguments` must be a JSON string","messagePattern":"Tool call `function\\.arguments` must be a JSON string","errorType":"exception","errorClass":"OCIError","httpStatus":400,"severity":"error","filePath":"litellm/llms/oci/chat/generic.py","lineNumber":137,"sourceCode":"            raise OCIError(status_code=400, message=\"Each tool call must be a dictionary\")\n        if tool_call.get(\"type\") != \"function\":\n            raise OCIError(status_code=400, message=\"OCI only supports function tool calls\")\n\n        tool_call_id = tool_call.get(\"id\")\n        if not isinstance(tool_call_id, str):\n            raise OCIError(status_code=400, message=\"Tool call `id` must be a string\")\n\n        tool_function = tool_call.get(\"function\")\n        if not isinstance(tool_function, dict):\n            raise OCIError(status_code=400, message=\"Tool call `function` must be a dictionary\")\n\n        function_name = tool_function.get(\"name\")\n        if not isinstance(function_name, str):\n            raise OCIError(status_code=400, message=\"Tool call `function.name` must be a string\")\n\n        arguments = tool_call[\"function\"].get(\"arguments\", \"{}\")\n        if not isinstance(arguments, str):\n            raise OCIError(\n                status_code=400,\n                message=\"Tool call `function.arguments` must be a JSON string\",\n            )\n\n        tool_calls_formatted.append(\n            OCIToolCall(\n                id=tool_call_id,\n                type=\"FUNCTION\",\n                name=function_name,\n                arguments=arguments,\n            )\n        )\n\n    return OCIMessage(\n        role=open_ai_to_generic_oci_role_map[role],\n        content=None,\n        toolCalls=tool_calls_formatted,\n        toolCallId=None,","sourceCodeStart":119,"sourceCodeEnd":155,"githubUrl":"https://github.com/BerriAI/litellm/blob/77b7c6c40c0c5aa5fbcb1d6a1825ac39ca8829b8/litellm/llms/oci/chat/generic.py#L119-L155","documentation":"OCI GENERIC carries a tool call's arguments as a JSON string (matching OpenAI's convention), so function.arguments must be a str. A dict, list, None-as-absent-then-dict, or number raises OCIError 400. Note the translator defaults missing arguments to \"{}\" — only a present-but-non-string value fails.","triggerScenarios":"Replaying assistant tool_calls where arguments was left as a parsed dict (common after json.loads of the whole history or from frameworks that auto-parse), e.g. {\"function\":{\"name\":\"f\",\"arguments\":{\"city\":\"SF\"}}} to an oci/generic.* model.","commonSituations":"Auto-parsing arguments for display/editing and forgetting to re-serialize; frameworks (some agent libs) exposing arguments as dicts; hand-constructed tool_calls from code; LLM-generated JSON where arguments is naturally an object.","solutions":["Serialize dict arguments back to a JSON string: json.dumps(args) if isinstance(args, dict) else args","Send arguments exactly as received from the previous model response (always a string there)","Add a normalization pass over history before OCI calls (see validationCode)","Beware mixed histories: normalize per-item, not globally"],"exampleFix":"# before\n{\"function\": {\"name\": \"get_weather\", \"arguments\": {\"city\": \"SF\"}}}\n\n# after\n{\"function\": {\"name\": \"get_weather\", \"arguments\": \"{\\\"city\\\":\\\"SF\\\"}\"}}","handlingStrategy":"validation","validationCode":"import json\n\ndef normalize_tool_arguments(tool_calls: list) -> list:\n    out = []\n    for tc in tool_calls:\n        fn = tc.get(\"function\", {})\n        args = fn.get(\"arguments\", \"{}\")\n        if not isinstance(args, str):\n            args = json.dumps(args)\n            tc = {**tc, \"function\": {**fn, \"arguments\": args}}\n        out.append(tc)\n    return out","typeGuard":"def tool_arguments_is_json_string(tc) -> bool:\n    args = tc.get(\"function\", {}).get(\"arguments\", \"{}\")\n    return isinstance(args, str)","tryCatchPattern":null,"preventionTips":["One canonical history format: arguments always a JSON string; convert at both load and save boundaries","Never auto-parse arguments in stored history without re-serializing on the way out"],"tags":["oci","tool-calls","arguments","json-serialization","request-validation"],"backgroundTag":"request-body-validation-failed","analyzedSha":"77b7c6c40c0c5aa5fbcb1d6a1825ac39ca8829b8","analyzedAt":"2026-08-18T11:44:31.656Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}