BerriAI/litellm · error · OCIError

Tool result message `content` must be a string

Error message

Tool result message `content` must be a string

What it means

The content of a tool result message (role='tool') must be a plain string in the OCI GENERIC format. Passing a list of content parts, a dict, or None raises OCIError(400) "Tool result message `content` must be a string" during request construction — unlike user messages, tool replies cannot be multipart.

Source

Thrown at litellm/llms/oci/chat/generic.py:200

                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
# ---------------------------------------------------------------------------


def adapt_tool_definition_to_oci_standard(tools: list[dict], vendor: OCIVendors) -> list[OCIToolDefinition]:
    """Convert OpenAI-format tool definitions to OCI GENERIC format.

    Resolves ``$ref``/``$defs`` and ``anyOf`` that the OCI endpoint rejects.
    """

View on GitHub (pinned to 6c2dcb801b)

Solutions

  1. Serialize tool output: 'content': json.dumps(result) or str(result).
  2. Special-case role='tool' in message-building helpers so it bypasses multipart formatting.
  3. Replace None content with a placeholder string like '' or 'null'.

Example fix

# before
{'role':'tool','tool_call_id':'c1','content': [{'type':'text','text':'42'}]}

# after
{'role':'tool','tool_call_id':'c1','content': '42'}
# structured output: 'content': json.dumps({'temp_c': 21})
Defensive patterns

Strategy: validation

Validate before calling

import json

messages.append({
    'role': 'tool',
    'tool_call_id': tc['id'],
    'content': result if isinstance(result, str) else json.dumps(result),
})

Type guard

def tool_message_content_is_string(msg: object) -> bool:
    return (
        isinstance(msg, dict)
        and msg.get('role') == 'tool'
        and isinstance(msg.get('content'), str)
    )

Prevention

When it happens

Trigger: Sending {'role':'tool','tool_call_id':'c1','content':[{'type':'text','text':'42'}]} or content as a dict/None after an assistant tool call, when calling an oci/ GENERIC model.

Common situations: Reusing a generic 'build content parts' helper for all roles including tool; structured tool outputs (JSON objects) inserted directly instead of serialized; optional tool outputs leaving None in the content slot.

Related errors


AI-assisted analysis of BerriAI/litellm@6c2dcb801b (2026-08-15). Data as JSON: /api/errors/46dff8a760f10274. Report an issue: GitHub.