diff --git a/src/llamafactory/data/converter.py b/src/llamafactory/data/converter.py index ad49deded4..cdd7308549 100644 --- a/src/llamafactory/data/converter.py +++ b/src/llamafactory/data/converter.py @@ -198,19 +198,19 @@ def __call__(self, example: dict[str, Any]) -> dict[str, Any]: or rejected[self.dataset_attr.role_tag] not in accept_tags[-1] ): logger.warning_rank0(f"Invalid role tag in {[chosen, rejected]}.") - broken_data = True - - prompt = aligned_messages - response = [ - { - "role": tag_mapping[chosen[self.dataset_attr.role_tag]], - "content": chosen[self.dataset_attr.content_tag], - }, - { - "role": tag_mapping[rejected[self.dataset_attr.role_tag]], - "content": rejected[self.dataset_attr.content_tag], - }, - ] + prompt, response = [], [] + else: + prompt = aligned_messages + response = [ + { + "role": tag_mapping[chosen[self.dataset_attr.role_tag]], + "content": chosen[self.dataset_attr.content_tag], + }, + { + "role": tag_mapping[rejected[self.dataset_attr.role_tag]], + "content": rejected[self.dataset_attr.content_tag], + }, + ] else: # normal example prompt = aligned_messages[:-1] response = aligned_messages[-1:] @@ -275,6 +275,11 @@ def __call__(self, example: dict[str, Any]) -> dict[str, Any]: ) tool_responses = [] + if role not in tag_mapping: + logger.warning_rank0(f"Invalid role tag in {messages}.") + broken_data = True + break + aligned_messages.append( { "role": tag_mapping[role], @@ -319,19 +324,19 @@ def __call__(self, example: dict[str, Any]) -> dict[str, Any]: or rejected[self.dataset_attr.role_tag] not in accept_tags[-1] ): logger.warning_rank0(f"Invalid role tag in {[chosen, rejected]}.") - broken_data = True - - prompt = aligned_messages - response = [ - { - "role": tag_mapping[chosen[self.dataset_attr.role_tag]], - "content": chosen[self.dataset_attr.content_tag], - }, - { - "role": tag_mapping[rejected[self.dataset_attr.role_tag]], - "content": rejected[self.dataset_attr.content_tag], - }, - ] + prompt, response = [], [] + else: + prompt = aligned_messages + response = [ + { + "role": tag_mapping[chosen[self.dataset_attr.role_tag]], + "content": chosen[self.dataset_attr.content_tag], + }, + { + "role": tag_mapping[rejected[self.dataset_attr.role_tag]], + "content": rejected[self.dataset_attr.content_tag], + }, + ] else: # normal example prompt = aligned_messages[:-1] response = aligned_messages[-1:] diff --git a/tests/data/test_converter.py b/tests/data/test_converter.py index 6b411aed53..ffb357efd0 100644 --- a/tests/data/test_converter.py +++ b/tests/data/test_converter.py @@ -62,3 +62,41 @@ def test_sharegpt_converter(): "_videos": None, "_audios": None, } + + +@pytest.mark.runs_on(["cpu", "mps"]) +def test_sharegpt_converter_ranking_skips_invalid_role(): + # A pairwise (ranking) example whose chosen/rejected carries a role tag not + # in the accepted set must be skipped, not crash with KeyError on tag_mapping. + dataset_attr = DatasetAttr("hf_hub", "llamafactory/tiny-supervised-dataset") + dataset_attr.ranking = True + dataset_attr.chosen = "chosen" + dataset_attr.rejected = "rejected" + data_args = DataArguments() + example = { + "conversations": [{"from": "human", "value": "Solve the math problem.\n3 + 4"}], + "chosen": {"from": "not_a_role", "value": "The answer is 7."}, + "rejected": {"from": "gpt", "value": "The answer is 8."}, + } + dataset_converter = get_dataset_converter("sharegpt", dataset_attr, data_args) + result = dataset_converter(example) + assert result["_prompt"] == [] + assert result["_response"] == [] + + +@pytest.mark.runs_on(["cpu", "mps"]) +def test_openai_converter_main_loop_skips_invalid_role(): + # An openai-format message list containing a role tag not in tag_mapping must + # be skipped (prompt=[], response=[]), not crash with KeyError. + dataset_attr = DatasetAttr("hf_hub", "llamafactory/tiny-supervised-dataset") + data_args = DataArguments() + example = { + "conversations": [ + {"from": "human", "value": "Solve the math problem.\n3 + 4"}, + {"from": "not_a_role", "value": "The answer is 7."}, + ] + } + dataset_converter = get_dataset_converter("openai", dataset_attr, data_args) + result = dataset_converter(example) + assert result["_prompt"] == [] + assert result["_response"] == []