meta-llama/llama · error · AssertionError

model only supports 'system', 'user' and 'assistant' roles,

Error message

model only supports 'system', 'user' and 'assistant' roles, starting with 'system', then 'user' and alternating (u/a/u/a/u...)

What it means

This assertion inside chat_completion (llama/generation.py:334) enforces the Llama 2 chat format after the optional system message has been merged into the first user turn: the remaining dialog must strictly alternate user, assistant, user, assistant... (checks dialog[::2] are all 'user' and dialog[1::2] are all 'assistant'). Any deviation — two consecutive user messages, an assistant message first, an unknown role, or a system message placed in the middle — aborts the batch.

Source

Thrown at llama/generation.py:334

        if max_gen_len is None:
            max_gen_len = self.model.params.max_seq_len - 1
        prompt_tokens = []
        unsafe_requests = []
        for dialog in dialogs:
            unsafe_requests.append(
                any([tag in msg["content"] for tag in SPECIAL_TAGS for msg in dialog])
            )
            if dialog[0]["role"] == "system":
                dialog = [
                    {
                        "role": dialog[1]["role"],
                        "content": B_SYS
                        + dialog[0]["content"]
                        + E_SYS
                        + dialog[1]["content"],
                    }
                ] + dialog[2:]
            assert all([msg["role"] == "user" for msg in dialog[::2]]) and all(
                [msg["role"] == "assistant" for msg in dialog[1::2]]
            ), (
                "model only supports 'system', 'user' and 'assistant' roles, "
                "starting with 'system', then 'user' and alternating (u/a/u/a/u...)"
            )
            dialog_tokens: List[int] = sum(
                [
                    self.tokenizer.encode(
                        f"{B_INST} {(prompt['content']).strip()} {E_INST} {(answer['content']).strip()} ",
                        bos=True,
                        eos=True,
                    )
                    for prompt, answer in zip(
                        dialog[::2],
                        dialog[1::2],
                    )
                ],
                [],

View on GitHub (pinned to 689c7f261b)

Solutions

  1. Restructure the dialog before calling: one optional 'system' first, then strictly user/assistant alternating, ending anywhere (last-message role is checked separately)
  2. Collapse consecutive user turns into one merged user message (join with newlines) and consecutive assistant turns similarly
  3. Check for role typos/case: roles must be exactly 'system', 'user', 'assistant'
  4. If you need a system message to apply to every turn, merge it into the first user message yourself (the code only supports it as message 0)

Example fix

# before
dialog = [
  {"role": "system", "content": "You are helpful."},
  {"role": "user", "content": "hi"},
  {"role": "user", "content": "what's the weather?"},
]
llama.chat_completion([dialog], max_gen_len=64)  # AssertionError

# after
dialog = [
  {"role": "system", "content": "You are helpful."},
  {"role": "user", "content": "hi\nwhat's the weather?"},
]
llama.chat_completion([dialog], max_gen_len=64)
Defensive patterns

Strategy: validation

Validate before calling

def dialog_roles_valid(dialog):
    if dialog and dialog[0]["role"] == "system":
        dialog = dialog[1:]
    return (
        len(dialog) >= 1
        and all(m["role"] == "user" for m in dialog[::2])
        and all(m["role"] == "assistant" for m in dialog[1::2])
    )

assert all(dialog_roles_valid(d) for d in dialogs), "bad role alternation"

Type guard

def is_valid_dialog(dialog) -> bool:
    if not isinstance(dialog, list) or not dialog:
        return False
    roles = [m.get("role") for m in dialog if isinstance(m, dict)]
    if len(roles) != len(dialog):
        return False
    if roles and roles[0] == "system":
        roles = roles[1:]
    return all(r == "user" for r in roles[::2]) and all(r == "assistant" for r in roles[1::2])

Try / catch

try:
    result = llama.chat_completion(dialogs, max_gen_len=128)
except AssertionError as e:
    if "roles" in str(e):
        dialogs = [normalize_dialog(d) for d in dialogs]  # merge consecutive same-role turns
        result = llama.chat_completion(dialogs, max_gen_len=128)
    else:
        raise

Prevention

When it happens

Trigger: Calling llama.chat_completion(dialogs, ...) with a dialog that, after a leading system message is folded into the next user turn, is not an exact u/a/u/a... sequence: e.g. [{'role':'user'},{'role':'user'}], [{'role':'assistant'},...], system appearing at index 2+, or a role string like 'tool' or 'system ' (typo/case).

Common situations: - Porting OpenAI-style chat logs where multiple consecutive 'user' messages are common into this stricter format. - Merging consecutive same-role turns incorrectly, or dropping an assistant reply during preprocessing so two user turns become adjacent. - Dataset bugs: role fields like 'User', 'SYSTEM', or None; system prompts injected mid-conversation by a RAG/orchestrator layer. - An empty dialog (len<2) with a system message: dialog[1] indexing or the parity checks fail this assert.

Related errors


AI-assisted analysis of meta-llama/llama@689c7f261b (2026-08-15). Data as JSON: /api/errors/cbe1ef2030261701. Report an issue: GitHub.