{"record":{"id":"475123c3a35a4e69","repo":"BerriAI/litellm","slug":"each-tool-call-must-be-a-dictionary","errorCode":null,"errorMessage":"Each tool call must be a dictionary","messagePattern":"Each tool call must be a dictionary","errorType":"validation","errorClass":"OCIError","httpStatus":400,"severity":"error","filePath":"litellm/llms/oci/chat/generic.py","lineNumber":119,"sourceCode":"                    status_code=400,\n                    message=\"Prop `image_url` must be a string or an object with a `url` property\",\n                )\n            new_content.append(OCIImageContentPart(imageUrl=OCIImageUrl(url=image_url)))\n\n    return OCIMessage(\n        role=open_ai_to_generic_oci_role_map[role],\n        content=new_content,\n        toolCalls=None,\n        toolCallId=None,\n    )\n\n\ndef adapt_messages_to_generic_oci_standard_tool_call(role: str, tool_calls: list) -> OCIMessage:\n    \"\"\"Convert an assistant tool-call message to OCI format.\"\"\"\n    tool_calls_formatted: Final = []\n    for tool_call in tool_calls:\n        if not isinstance(tool_call, dict):\n            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(","sourceCodeStart":101,"sourceCodeEnd":137,"githubUrl":"https://github.com/BerriAI/litellm/blob/6c2dcb801bf2b75c18f1bb24140e7cf57465cc4d/litellm/llms/oci/chat/generic.py#L101-L137","documentation":"When adapting an assistant message that carries tool_calls, the OCI GENERIC adapter iterates the list and requires every element to be a dict. A non-dict element (string, tuple, Pydantic object, None) raises OCIError(400) 'Each tool call must be a dictionary' during request construction.","triggerScenarios":"Sending messages=[{'role':'assistant','tool_calls':['finish()']}] or tool_calls containing objects produced by another SDK (e.g. OpenAI's ChatCompletionMessageToolCall Pydantic instances dumped incompletely) to an oci/ GENERIC model.","commonSituations":"Replaying captured OpenAI responses where tool_calls were serialized to strings; a conversation store that JSON-round-trips and occasionally flattens tool call dicts; appending hand-written tool call shorthands instead of full dicts.","solutions":["Make each tool call a full dict: {'id':str,'type':'function','function':{'name':str,'arguments':json_str}}.","When echoing back assistant history from another provider, dump tool call objects with model_dump() (Pydantic) or equivalent before inserting them.","Add a pre-flight check that all(isinstance(tc, dict) for tc in msg.get('tool_calls', []))."],"exampleFix":"# before\n{'role':'assistant','tool_calls': ['call_abc|get_weather|{}']}\n\n# after\n{'role':'assistant','tool_calls': [{'id':'call_abc','type':'function','function':{'name':'get_weather','arguments':'{\"city\":\"SF\"}'}}]}","handlingStrategy":"type-guard","validationCode":"for msg in messages:\n    tcs = msg.get('tool_calls')\n    if tcs is not None:\n        assert all(isinstance(tc, dict) for tc in tcs), 'tool_calls must all be dicts'","typeGuard":"def are_valid_tool_call_dicts(tool_calls: object) -> bool:\n    return isinstance(tool_calls, list) and all(isinstance(tc, dict) for tc in tool_calls)","tryCatchPattern":null,"preventionTips":["Echo tool calls from the model's response object instead of reconstructing them from strings.","Use .model_dump() when converting Pydantic tool call objects from other SDKs into message history."],"tags":["oci","input-validation","tool-calls","openai-compat"],"backgroundTag":null,"analyzedSha":"6c2dcb801bf2b75c18f1bb24140e7cf57465cc4d","analyzedAt":"2026-08-15T07:12:03.035Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}