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
- 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
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
- 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
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
- Unable to parse anthropic tool result for message: {message}
- messages is required
- WebSearchInterception: missing follow-up messages
- Unable to parse anthropic file message: {message}
- Either file_data or file_id must be present in the file mess
AI-assisted analysis of BerriAI/litellm@6c2dcb801b (2026-08-15).
Data as JSON: /api/errors/8ea677b29134578f.
Report an issue: GitHub.