{"record":{"id":"8ea677b29134578f","repo":"BerriAI/litellm","slug":"invalid-first-message-should-always-start-with-r","errorCode":null,"errorMessage":"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, ","messagePattern":"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, ","errorType":"exception","errorClass":"Exception","httpStatus":null,"severity":"error","filePath":"litellm/litellm_core_utils/prompt_templates/factory.py","lineNumber":1082,"sourceCode":"\n        assistant_content = []\n        ## MERGE CONSECUTIVE ASSISTANT CONTENT ##\n        while msg_i < len(messages) and messages[msg_i][\"role\"] == \"assistant\":\n            assistant_text = messages[msg_i].get(\"content\") or \"\"  # either string or none\n            if messages[msg_i].get(\"tool_calls\", []):  # support assistant tool invoke conversion\n                assistant_text += convert_to_anthropic_tool_invoke_xml(messages[msg_i][\"tool_calls\"])\n\n            assistant_content.append({\"type\": \"text\", \"text\": assistant_text})\n            msg_i += 1\n\n        if assistant_content:\n            new_messages.append({\"role\": \"assistant\", \"content\": assistant_content})\n\n    if not new_messages or new_messages[0][\"role\"] != \"user\":\n        if litellm.modify_params:\n            new_messages.insert(0, {\"role\": \"user\", \"content\": [{\"type\": \"text\", \"text\": \".\"}]})\n        else:\n            raise Exception(\n                \"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, \"\n            )\n\n    if new_messages[-1][\"role\"] == \"assistant\":\n        for content in new_messages[-1][\"content\"]:\n            if isinstance(content, dict) and content[\"type\"] == \"text\":\n                content[\"text\"] = content[\"text\"].rstrip()  # no trailing whitespace for final assistant message\n\n    return new_messages\n\n\n# ------------------------------------------------------------------------------\n\n\ndef _azure_tool_call_invoke_helper(\n    function_call_params: ChatCompletionToolCallFunctionChunk,\n) -> ChatCompletionToolCallFunctionChunk | None:\n    \"\"\"","sourceCodeStart":1064,"sourceCodeEnd":1100,"githubUrl":"https://github.com/BerriAI/litellm/blob/6c2dcb801bf2b75c18f1bb24140e7cf57465cc4d/litellm/litellm_core_utils/prompt_templates/factory.py#L1064-L1100","documentation":"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.","triggerScenarios":"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).","commonSituations":"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.","solutions":["Prepend a user message: messages.insert(0, {\"role\": \"user\", \"content\": \"...\"})","Or opt into auto-repair: litellm.modify_params = True (proxy: litellm_settings: modify_params: true) so LiteLLM inserts a '.' placeholder user message","When history-trimming, always keep at least the first user message"],"exampleFix":"# before\nmessages = [\n    {\"role\": \"assistant\", \"content\": \"Sure, let me help.\"},\n    {\"role\": \"user\", \"content\": \"What is 2+2?\"},\n]\nresp = litellm.completion(model=\"claude-3-5-sonnet-20241022\", messages=messages)\n\n# after\nmessages = [\n    {\"role\": \"user\", \"content\": \"Hello\"},\n    {\"role\": \"assistant\", \"content\": \"Sure, let me help.\"},\n    {\"role\": \"user\", \"content\": \"What is 2+2?\"},\n]\nresp = litellm.completion(model=\"claude-3-5-sonnet-20241022\", messages=messages)","handlingStrategy":"validation","validationCode":"def ensure_leading_user_message(messages: list[dict]) -> list[dict]:\n    msgs = [m for m in messages if m.get(\"role\") != \"system\"]\n    if not msgs or msgs[0].get(\"role\") != \"user\":\n        return [{\"role\": \"user\", \"content\": \".\"}, *messages]\n    return messages\n\nmessages = ensure_leading_user_message(messages)","typeGuard":"def starts_with_user(messages: list[dict]) -> bool:\n    non_system = [m for m in messages if m.get(\"role\") != \"system\"]\n    return bool(non_system) and non_system[0].get(\"role\") == \"user\"","tryCatchPattern":null,"preventionTips":["Always begin conversations with a user turn","When trimming history, keep the first user message","Set litellm.modify_params=True on the proxy if you accept placeholder insertion"],"tags":["anthropic","messages","role-order","validation"],"backgroundTag":null,"analyzedSha":"6c2dcb801bf2b75c18f1bb24140e7cf57465cc4d","analyzedAt":"2026-08-15T07:12:03.035Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}