BerriAI/litellm · error · BadRequestError

BedrockException - {error_str}\n. Pass in default user messa

Error message

BedrockException - {error_str}\n. Pass in default user message via `completion(..,user_continue_message=)` or enable `litellm.modify_params=True`.\nFor Proxy: do via `litellm_settings::modify_params: True` or user_continue_message under `litellm_params`

What it means

LiteLLM throws BadRequestError when Bedrock Converse rejects a conversation whose first message is not from the user (typically it starts with an assistant or tool message). The mapper's message names the two supported remedies: pass user_continue_message=... to completion() so LiteLLM inserts a default user message, or enable litellm.modify_params=True (proxy: litellm_settings::modify_params: True). It is purely a message-ordering constraint of the Converse API, not a size or auth problem.

Source

Thrown at litellm/litellm_core_utils/exception_mapping_utils.py:852

            model=model,
            llm_provider="bedrock",
        )
    elif "Conversation blocks and tool result blocks cannot be provided in the same turn." in error_str:
        raise BadRequestError(
            message=f"BedrockException - {error_str}\n. Enable 'litellm.modify_params=True' (for PROXY do: `litellm_settings::modify_params: True`) to insert a dummy assistant message and fix this error.",
            model=model,
            llm_provider="bedrock",
            response=getattr(original_exception, "response", None),
        )
    elif "Malformed input request" in error_str:
        raise BadRequestError(
            message=f"BedrockException - {error_str}",
            model=model,
            llm_provider="bedrock",
            response=getattr(original_exception, "response", None),
        )
    elif "A conversation must start with a user message." in error_str:
        raise BadRequestError(
            message=f"BedrockException - {error_str}\n. Pass in default user message via `completion(..,user_continue_message=)` or enable `litellm.modify_params=True`.\nFor Proxy: do via `litellm_settings::modify_params: True` or user_continue_message under `litellm_params`",
            model=model,
            llm_provider="bedrock",
            response=getattr(original_exception, "response", None),
        )
    elif (
        "Unable to locate credentials" in error_str
        or "The security token included in the request is invalid" in error_str
    ):
        raise AuthenticationError(
            message=f"BedrockException Invalid Authentication - {error_str}",
            model=model,
            llm_provider="bedrock",
            response=getattr(original_exception, "response", None),
        )
    elif "AccessDeniedException" in error_str:
        raise PermissionDeniedError(
            message=f"BedrockException PermissionDeniedError - {error_str}",

View on GitHub (pinned to 77b7c6c40c)

Solutions

  1. Pass user_continue_message='...' (or litellm_params user_continue_message on the proxy) so a default user message is prepended
  2. Enable litellm.modify_params=True in code, or litellm_settings::modify_params: True on the proxy
  3. Reorder locally: ensure messages[0]['role'] == 'user' before calling (prepend a user turn or drop leading assistant turns)
  4. When trimming history, always keep or synthesize a leading user message

Example fix

# before
messages = [{'role': 'assistant', 'content': 'Continuing earlier answer...'}, ...]
resp = litellm.completion(model="bedrock/...", messages=messages)

# after
messages = [{'role': 'user', 'content': 'Continue, please.'}] + messages
resp = litellm.completion(model="bedrock/...", messages=messages, user_continue_message='Continue, please.')
Defensive patterns

Strategy: validation

Validate before calling

def starts_with_user(messages: list) -> bool:
    return bool(messages) and messages[0].get('role') == 'user'

# or pass a default so LiteLLM handles it:
# litellm.completion(..., user_continue_message='Continue, please.')

Type guard

import litellm

def is_bad_request(e: BaseException) -> bool:
    return isinstance(e, litellm.BadRequestError) and 'must start with a user message' in str(e)

Try / catch

try:
    resp = litellm.completion(model='bedrock/...', messages=messages)
except litellm.BadRequestError as e:
    if 'must start with a user message' in str(e):
        messages = [{'role': 'user', 'content': 'Continue, please.'}] + messages
        resp = litellm.completion(model='bedrock/...', messages=messages)
    else:
        raise

Prevention

When it happens

Trigger: messages=[{'role':'assistant',...}, ...] as the first element; history trimming that removes so many early turns the first surviving message is an assistant/tool turn; assistant-prefilled continuations that OpenAI tolerated but Bedrock Converse rejects as the opening turn.

Common situations: Chat apps that persist and reload trimmed histories; agent loops resuming from the model's previous answer; porting OpenAI-style conversations where assistant-first arrays were accepted.

Related errors


AI-assisted analysis of BerriAI/litellm@77b7c6c40c (2026-08-18). Data as JSON: /api/errors/2bf944a31cd9fec4. Report an issue: GitHub.