{"record":{"id":"f70e9d7a325c204a","repo":"BerriAI/litellm","slug":"tool-result-message-must-have-a-string-tool-call","errorCode":null,"errorMessage":"Tool result message must have a string `tool_call_id`","messagePattern":"Tool result message must have a string `tool_call_id`","errorType":"validation","errorClass":"OCIError","httpStatus":400,"severity":"error","filePath":"litellm/llms/oci/chat/generic.py","lineNumber":195,"sourceCode":"        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(\n                    status_code=400,\n                    message=\"Tool result message `content` must be a string\",\n                )\n            new_messages.append(adapt_messages_to_generic_oci_standard_tool_response(role, tool_call_id, content))\n\n    return new_messages\n\n\n# ---------------------------------------------------------------------------\n# Tool definition adaptation\n# ---------------------------------------------------------------------------\n\n","sourceCodeStart":177,"sourceCodeEnd":213,"githubUrl":"https://github.com/BerriAI/litellm/blob/6c2dcb801bf2b75c18f1bb24140e7cf57465cc4d/litellm/llms/oci/chat/generic.py#L177-L213","documentation":"Tool result messages (role='tool') must reference the assistant tool call they answer via a string 'tool_call_id'. If it is missing, None, or non-string, OCIError(400) 'Tool result message must have a string `tool_call_id`' is raised while building the OCI request. Without this id, OCI cannot pair the result with its FUNCTION invocation.","triggerScenarios":"Sending {'role':'tool','content':'42'} without tool_call_id, or with tool_call_id as an integer/None, after an assistant tool-call turn to an oci/ GENERIC model.","commonSituations":"Executing tools in parallel and losing track of which call id to echo; trimming ids for token savings; storing tool results keyed by tool name instead of call id and forgetting to map back.","solutions":["Copy the id from the corresponding assistant tool call: for tc in response.choices[0].message.tool_calls: reply {'role':'tool','tool_call_id':tc.id,'content':...}.","When fanning out parallel tool calls, keep a mapping id -> result and emit one tool message per id.","Pre-flight check that every role='tool' message has a non-empty string tool_call_id matching some prior assistant call id."],"exampleFix":"# before\nmessages.append({'role':'tool','content':'21C'})\n\n# after\nmessages.append({'role':'tool','tool_call_id': tc.id, 'content':'21C'})","handlingStrategy":"validation","validationCode":"# when returning tool results from the assistant's tool calls\nfor tc in assistant_msg['tool_calls']:\n    result = run_tool(tc['function']['name'], tc['function']['arguments'])\n    assert isinstance(tc.get('id'), str) and tc['id']\n    messages.append({'role': 'tool', 'tool_call_id': tc['id'], 'content': str(result)})","typeGuard":"def tool_message_is_valid(msg: object) -> bool:\n    return (\n        isinstance(msg, dict)\n        and msg.get('role') == 'tool'\n        and isinstance(msg.get('tool_call_id'), str)\n        and bool(msg['tool_call_id'])\n    )","tryCatchPattern":null,"preventionTips":["In parallel tool execution, key results by tool call id, not by tool name.","Write a loop invariant: every role='tool' message references an id that appeared in the immediately preceding assistant tool_calls."],"tags":["oci","tool-calls","input-validation","missing-parameter"],"backgroundTag":null,"analyzedSha":"6c2dcb801bf2b75c18f1bb24140e7cf57465cc4d","analyzedAt":"2026-08-15T07:12:03.035Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}