BerriAI/litellm · error · OCIError
Tool result message must have a string `tool_call_id`
Error message
Tool result message must have a string `tool_call_id`
What it means
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.
Source
Thrown at litellm/llms/oci/chat/generic.py:195
tool_calls = message.get("tool_calls")
tool_call_id = message.get("tool_call_id")
if role == "assistant" and tool_calls is not None:
if not isinstance(tool_calls, list):
raise OCIError(status_code=400, message="Message `tool_calls` must be a list")
new_messages.append(adapt_messages_to_generic_oci_standard_tool_call(role, tool_calls))
elif role in ["system", "user", "assistant"] and content is not None:
if not isinstance(content, (str, list)):
raise OCIError(
status_code=400,
message="Message `content` must be a string or list of content parts",
)
new_messages.append(adapt_messages_to_generic_oci_standard_content_message(role, content))
elif role == "tool":
if not isinstance(tool_call_id, str):
raise OCIError(
status_code=400,
message="Tool result message must have a string `tool_call_id`",
)
if not isinstance(content, str):
raise OCIError(
status_code=400,
message="Tool result message `content` must be a string",
)
new_messages.append(adapt_messages_to_generic_oci_standard_tool_response(role, tool_call_id, content))
return new_messages
# ---------------------------------------------------------------------------
# Tool definition adaptation
# ---------------------------------------------------------------------------
View on GitHub (pinned to 6c2dcb801b)
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.
Example fix
# before
messages.append({'role':'tool','content':'21C'})
# after
messages.append({'role':'tool','tool_call_id': tc.id, 'content':'21C'}) Defensive patterns
Strategy: validation
Validate before calling
# when returning tool results from the assistant's tool calls
for tc in assistant_msg['tool_calls']:
result = run_tool(tc['function']['name'], tc['function']['arguments'])
assert isinstance(tc.get('id'), str) and tc['id']
messages.append({'role': 'tool', 'tool_call_id': tc['id'], 'content': str(result)}) Type guard
def tool_message_is_valid(msg: object) -> bool:
return (
isinstance(msg, dict)
and msg.get('role') == 'tool'
and isinstance(msg.get('tool_call_id'), str)
and bool(msg['tool_call_id'])
) Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- Tool call `id` must be a string
- Tool call `function.name` must be a string
- Each tool call must be a dictionary
- OCI only supports function tool calls
- Tool call `function` must be a dictionary
AI-assisted analysis of BerriAI/litellm@6c2dcb801b (2026-08-15).
Data as JSON: /api/errors/f70e9d7a325c204a.
Report an issue: GitHub.