{"record":{"id":"95b821b0627deadd","repo":"BerriAI/litellm","slug":"message-tool-calls-must-be-a-list","errorCode":null,"errorMessage":"Message `tool_calls` must be a list","messagePattern":"Message `tool_calls` must be a list","errorType":"validation","errorClass":"OCIError","httpStatus":400,"severity":"error","filePath":"litellm/llms/oci/chat/generic.py","lineNumber":182,"sourceCode":"        toolCalls=None,\n        toolCallId=tool_call_id,\n    )\n\n\ndef adapt_messages_to_generic_oci_standard(\n    messages: list[AllMessageValues],\n) -> list[OCIMessage]:\n    \"\"\"Convert an OpenAI-format message array to OCI GENERIC format.\"\"\"\n    new_messages: Final = []\n    for message in messages:\n        role = message[\"role\"]\n        content = message.get(\"content\")\n        tool_calls = message.get(\"tool_calls\")\n        tool_call_id = message.get(\"tool_call_id\")\n\n        if role == \"assistant\" and tool_calls is not None:\n            if not isinstance(tool_calls, list):\n                raise OCIError(status_code=400, message=\"Message `tool_calls` must be a list\")\n            new_messages.append(adapt_messages_to_generic_oci_standard_tool_call(role, tool_calls))\n\n        elif role in [\"system\", \"user\", \"assistant\"] and content is not None:\n            if not isinstance(content, (str, list)):\n                raise OCIError(\n                    status_code=400,\n                    message=\"Message `content` must be a string or list of content parts\",\n                )\n            new_messages.append(adapt_messages_to_generic_oci_standard_content_message(role, content))\n\n        elif role == \"tool\":\n            if not isinstance(tool_call_id, str):\n                raise OCIError(\n                    status_code=400,\n                    message=\"Tool result message must have a string `tool_call_id`\",\n                )\n            if not isinstance(content, str):\n                raise OCIError(","sourceCodeStart":164,"sourceCodeEnd":200,"githubUrl":"https://github.com/BerriAI/litellm/blob/6c2dcb801bf2b75c18f1bb24140e7cf57465cc4d/litellm/llms/oci/chat/generic.py#L164-L200","documentation":"At the message level, when an assistant message has a non-None 'tool_calls' field, that field must be a list. Anything else (string, dict, None-checked-elsewhere objects) raises OCIError(400) 'Message `tool_calls` must be a list' in adapt_messages_to_generic_oci_standard before an OCI request is built.","triggerScenarios":"Sending {'role':'assistant','tool_calls': {'id':...}} (single object instead of list) or tool_calls as a serialized JSON string to an oci/ GENERIC model.","commonSituations":"Wrapping a single tool call without brackets; storing tool_calls as a JSON string in a DB column and passing it through unparsed; serializers emitting an object for single-element arrays.","solutions":["Always use a list: 'tool_calls': [tc] even for one call.","json.loads() any stringified tool_calls before putting them into messages.","Configure JSON serializers not to collapse single-element arrays to scalars."],"exampleFix":"# before\n{'role':'assistant','tool_calls': {'id':'c1','type':'function','function':{...}}}\n\n# after\n{'role':'assistant','tool_calls': [{'id':'c1','type':'function','function':{'name':'f','arguments':'{}'}}]}","handlingStrategy":"validation","validationCode":"for msg in messages:\n    tcs = msg.get('tool_calls')\n    if isinstance(tcs, dict):\n        msg['tool_calls'] = [tcs]  # wrap single call\n    elif isinstance(tcs, str):\n        import json\n        msg['tool_calls'] = json.loads(tcs)  # parse stored string\n    assert msg.get('tool_calls') is None or isinstance(msg['tool_calls'], list)","typeGuard":"def tool_calls_is_list(msg: object) -> bool:\n    return not (isinstance(msg, dict) and msg.get('tool_calls') is not None) or isinstance(msg.get('tool_calls'), list)","tryCatchPattern":null,"preventionTips":["Always emit tool_calls as an array, even for one element.","If persisting conversations, parse JSON columns back into objects before sending."],"tags":["oci","input-validation","tool-calls","structure"],"backgroundTag":null,"analyzedSha":"6c2dcb801bf2b75c18f1bb24140e7cf57465cc4d","analyzedAt":"2026-08-15T07:12:03.035Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}