BerriAI/litellm · error · Exception

Invalid first message. Should always start with 'role'='user

Error message

Invalid first message. Should always start with 'role'='user' for Anthropic. System prompt is sent separately for Anthropic. set 'litellm.modify_params = True' or 'litellm_settings:modify_params = True' on proxy, to insert a placeholder user message - '.' as the first message, 

What it means

Anthropic's Messages API requires the first non-system message to have role 'user' (system is a separate parameter). After converting your OpenAI-format messages, LiteLLM found no leading user message (e.g. you started with an assistant or tool message) and modify_params is off, so it raises instead of silently mutating your payload.

Source

Thrown at litellm/litellm_core_utils/prompt_templates/factory.py:1082

        assistant_content = []
        ## MERGE CONSECUTIVE ASSISTANT CONTENT ##
        while msg_i < len(messages) and messages[msg_i]["role"] == "assistant":
            assistant_text = messages[msg_i].get("content") or ""  # either string or none
            if messages[msg_i].get("tool_calls", []):  # support assistant tool invoke conversion
                assistant_text += convert_to_anthropic_tool_invoke_xml(messages[msg_i]["tool_calls"])

            assistant_content.append({"type": "text", "text": assistant_text})
            msg_i += 1

        if assistant_content:
            new_messages.append({"role": "assistant", "content": assistant_content})

    if not new_messages or new_messages[0]["role"] != "user":
        if litellm.modify_params:
            new_messages.insert(0, {"role": "user", "content": [{"type": "text", "text": "."}]})
        else:
            raise Exception(
                "Invalid first message. Should always start with 'role'='user' for Anthropic. System prompt is sent separately for Anthropic. set 'litellm.modify_params = True' or 'litellm_settings:modify_params = True' on proxy, to insert a placeholder user message - '.' as the first message, "
            )

    if new_messages[-1]["role"] == "assistant":
        for content in new_messages[-1]["content"]:
            if isinstance(content, dict) and content["type"] == "text":
                content["text"] = content["text"].rstrip()  # no trailing whitespace for final assistant message

    return new_messages


# ------------------------------------------------------------------------------


def _azure_tool_call_invoke_helper(
    function_call_params: ChatCompletionToolCallFunctionChunk,
) -> ChatCompletionToolCallFunctionChunk | None:
    """

View on GitHub (pinned to 6c2dcb801b)

Solutions

  1. Prepend a user message: messages.insert(0, {"role": "user", "content": "..."})
  2. Or opt into auto-repair: litellm.modify_params = True (proxy: litellm_settings: modify_params: true) so LiteLLM inserts a '.' placeholder user message
  3. When history-trimming, always keep at least the first user message

Example fix

# before
messages = [
    {"role": "assistant", "content": "Sure, let me help."},
    {"role": "user", "content": "What is 2+2?"},
]
resp = litellm.completion(model="claude-3-5-sonnet-20241022", messages=messages)

# after
messages = [
    {"role": "user", "content": "Hello"},
    {"role": "assistant", "content": "Sure, let me help."},
    {"role": "user", "content": "What is 2+2?"},
]
resp = litellm.completion(model="claude-3-5-sonnet-20241022", messages=messages)
Defensive patterns

Strategy: validation

Validate before calling

def ensure_leading_user_message(messages: list[dict]) -> list[dict]:
    msgs = [m for m in messages if m.get("role") != "system"]
    if not msgs or msgs[0].get("role") != "user":
        return [{"role": "user", "content": "."}, *messages]
    return messages

messages = ensure_leading_user_message(messages)

Type guard

def starts_with_user(messages: list[dict]) -> bool:
    non_system = [m for m in messages if m.get("role") != "system"]
    return bool(non_system) and non_system[0].get("role") == "user"

Prevention

When it happens

Trigger: messages=[{"role":"assistant",...}, ...] or messages starting with a tool result, sent to any anthropic/claude or Bedrock Anthropic model; also when all messages are system-only. Only occurs when litellm.modify_params is False (default).

Common situations: Building conversation histories from stored assistant transcripts; prefilling the assistant's first turn; agent loops that resume mid-conversation starting with a tool response; trimming history with a sliding window that cuts off the original user turn.

Related errors


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