diff --git a/src/llamafactory/data/template.py b/src/llamafactory/data/template.py index 89b70d1143..dec084cf13 100644 --- a/src/llamafactory/data/template.py +++ b/src/llamafactory/data/template.py @@ -57,6 +57,13 @@ class Template: preserve_thinking: bool mm_plugin: "BasePlugin" + def _format_system_content(self, system: str, tool_text: str) -> str: + if tool_text and self.format_tools.tool_format == "qwen3_5": + tool_text = tool_text.lstrip("\n") + return tool_text + ("\n\n" + system if system else "") + + return system + tool_text + def encode_oneturn( self, tokenizer: "PreTrainedTokenizer", @@ -150,7 +157,7 @@ def _encode( elements += self.format_prefix.apply() if system or tools: tool_text = self.format_tools.apply(content=tools)[0] if tools else "" - elements += self.format_system.apply(content=(system + tool_text)) + elements += self.format_system.apply(content=self._format_system_content(system, tool_text)) if message["role"] == Role.USER: elements += self.format_user.apply(content=message["content"], idx=str(i // 2)) diff --git a/tests/data/test_template.py b/tests/data/test_template.py index ddd7b17da8..db9d038998 100644 --- a/tests/data/test_template.py +++ b/tests/data/test_template.py @@ -349,6 +349,27 @@ def test_qwen3_template(cot_messages: bool): _check_template("Qwen/Qwen3-8B", "qwen3", prompt_str, answer_str, messages=messages) +@pytest.mark.runs_on(["cpu", "mps"]) +def test_qwen35_tools_precede_system_content(): + tokenizer = AutoTokenizer.from_pretrained(TINY_LLAMA3) + template = get_template_and_fix_tokenizer(tokenizer, DataArguments(template="qwen3_5_nothink")) + tools = ( + '[{"type":"function","function":{"name":"lookup_quote","description":"Quote lookup.",' + '"parameters":{"type":"object","properties":{},"required":[]}}}]' + ) + prompt_ids, _ = template.encode_oneturn( + tokenizer, + [ + {"role": "user", "content": "Check the quote."}, + {"role": "assistant", "content": "Done."}, + ], + system="You are a market data assistant.", + tools=tools, + ) + prompt_str = tokenizer.decode(prompt_ids) + assert prompt_str.index("# Tools") < prompt_str.index("You are a market data assistant.") + + @pytest.mark.runs_on(["cpu", "mps"]) def test_parse_llama3_template(): tokenizer = AutoTokenizer.from_pretrained(TINY_LLAMA3)