{"record":{"id":"2a8066aa48852e33","repo":"hiyouga/LlamaFactory","slug":"unexpected-role","errorCode":null,"errorMessage":"Unexpected role: {}","messagePattern":"Unexpected role: (.+?)","errorType":"exception","errorClass":"NotImplementedError","httpStatus":null,"severity":"error","filePath":"src/llamafactory/data/template.py","lineNumber":166,"sourceCode":"\n            if i == 0:\n                elements += self.format_prefix.apply()\n                if system or tools:\n                    tool_text = self.format_tools.apply(content=tools)[0] if tools else \"\"\n                    elements += self.format_system.apply(content=(system + tool_text))\n\n            if message[\"role\"] == Role.USER:\n                elements += self.format_user.apply(content=message[\"content\"], idx=str(i // 2))\n            elif message[\"role\"] == Role.ASSISTANT:\n                elements += self.format_assistant.apply(content=message[\"content\"])\n            elif message[\"role\"] == Role.OBSERVATION:\n                elements += self.format_observation.apply(content=message[\"content\"])\n            elif message[\"role\"] == Role.FUNCTION:\n                elements += self.format_function.apply(\n                    content=message[\"content\"], thought_words=self.thought_words, tool_call_words=self.tool_call_words\n                )\n            else:\n                raise NotImplementedError(\"Unexpected role: {}\".format(message[\"role\"]))\n\n            encoded_messages.append(self._convert_elements_to_ids(tokenizer, elements))\n\n        return encoded_messages\n\n    @staticmethod\n    def _add_or_replace_eos_token(tokenizer: \"PreTrainedTokenizer\", eos_token: str) -> None:\n        r\"\"\"Add or replace eos token to the tokenizer.\"\"\"\n        if tokenizer.eos_token == eos_token:\n            return\n\n        is_added = tokenizer.eos_token_id is None\n        num_added_tokens = tokenizer.add_special_tokens({\"eos_token\": eos_token})\n\n        if is_added:\n            logger.info_rank0(f\"Add eos token: {tokenizer.eos_token}.\")\n        else:\n            logger.info_rank0(f\"Replace eos token: {tokenizer.eos_token}.\")","sourceCodeStart":148,"sourceCodeEnd":184,"githubUrl":"https://github.com/hiyouga/LlamaFactory/blob/f28afaf6355af515454dfb16c97d728307c93897/src/llamafactory/data/template.py#L148-L184","documentation":"While encoding a conversation, each message's role must be one of Role.USER, Role.ASSISTANT, Role.OBSERVATION, or Role.FUNCTION. A message with any other role string (including typos and wrong casing) raises NotImplementedError in Template._encode. The role is compared against the Role enum, so the value must match exactly.","triggerScenarios":"A dataset sample whose messages contain a role like 'system', 'tool', 'assistant ', 'Bot', or 'function_call' instead of the supported enum values; converting a sharegpt-format dataset where the role field was renamed; using an observation message without the tool template that expects it (role mismatch, e.g. 'observation' vs 'function' depending on template).","commonSituations":"Preparing sharegpt data with 'from' values like 'system' or 'tool' mapped incorrectly in the dataset_info.json conversion script; datasets generated by other frameworks (axolotl, chatml) using 'tool' role; casing or whitespace differences after JSON editing.","solutions":["Inspect the failing sample (the exception context) and change the message role to one of 'user', 'assistant', 'observation', 'function'.","Move system content into the top-level 'system' field of the sample instead of a message with role 'system'.","If the role is a tool response, use 'observation' (for tool results fed back) and ensure the template defines format_observation; 'function' is for assistant tool calls rendered by format_function.","Validate the whole dataset before training with a small script that asserts allowed roles (see validationCode)."],"exampleFix":"// before\n{\"conversations\": [{\"from\": \"system\", \"value\": \"You are helpful.\"}, {\"from\": \"human\", \"value\": \"Hi\"}]}\n\n// after\n{\"system\": \"You are helpful.\", \"conversations\": [{\"from\": \"human\", \"value\": \"Hi\"}, {\"from\": \"gpt\", \"value\": \"Hello!\"}]}","handlingStrategy":"validation","validationCode":"ALLOWED = {\"user\", \"assistant\", \"observation\", \"function\"}\nfor i, sample in enumerate(dataset):\n    for turn in sample[\"conversations\"]:\n        role = turn[\"from\"]  # map to 'role' if alpaca-style tools data\n        assert role in ALLOWED, f\"sample {i}: bad role {role!r}\"","typeGuard":"def valid_sharegpt_roles(sample) -> bool:\n    allowed = {\"user\", \"assistant\", \"observation\", \"function\"}\n    return all(turn[\"from\"] in allowed for turn in sample[\"conversations\"])","tryCatchPattern":"try:\n    processor._encode_data_example(...)\nexcept NotImplementedError as e:\n    if \"Unexpected role\" in str(e):\n        log_bad_sample_and_continue(sample)  # quarantine, do not abort silently","preventionTips":["Normalize roles to user/assistant/observation/function at data-prep time; system goes to the sample-level field.","Never invent roles like 'tool' or 'bot' when hand-editing sharegpt JSON."],"tags":["data","roles","sharegpt","template"],"backgroundTag":null,"analyzedSha":"f28afaf6355af515454dfb16c97d728307c93897","analyzedAt":"2026-08-14T21:57:28.298Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}