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
- Restructure the dialog before calling: one optional 'system' first, then strictly user/assistant alternating, ending anywhere (last-message role is checked separately)
- Collapse consecutive user turns into one merged user message (join with newlines) and consecutive assistant turns similarly
- Check for role typos/case: roles must be exactly 'system', 'user', 'assistant'
- 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
- Normalize conversations at ingestion: merge consecutive same-role messages, keep system only at position 0
- Whitelist roles to {'system','user','assistant'} and reject/case-fold anything else
- Add a preflight validator that runs both role checks (alternation + last-is-user) before every batch call
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
- Last message must be from user, got {dialog[-1]['role']}
- Error: special tags are not allowed as part of the prompt.
- no checkpoint files found in {ckpt_dir}
- Loading a checkpoint for MP={len(checkpoints)} but world siz
AI-assisted analysis of meta-llama/llama@689c7f261b (2026-08-15).
Data as JSON: /api/errors/cbe1ef2030261701.
Report an issue: GitHub.