diff --git a/common/chat-diff-analyzer.cpp b/common/chat-diff-analyzer.cpp index 2f0bd14af..264ace462 100644 --- a/common/chat-diff-analyzer.cpp +++ b/common/chat-diff-analyzer.cpp @@ -296,7 +296,7 @@ void analyze_reasoning::compare_reasoning_presence() { return p.literal(reasoning_content) + p.space() + p.optional(p.tag("post", (p.marker() + p.space())) + p.rest()); }); auto parser_wrapped = build_tagged_peg_parser([&](common_peg_parser_builder &p) { - return p.tag("pre", p.marker() + p.space()) + p.literal(reasoning_content) + p.space() + p.tag("post", (p.marker() + p.space())) + p.rest(); + return p.tag("pre", p.marker() + p.space()) + p.literal(reasoning_content) + p.tag("post", (p.space() + p.marker() + p.space())) + p.rest(); }); // try the more aggressive parse first, if it fails, fall back to the delimiter one auto result = parser_wrapped.parse_anywhere_and_extract(comparison->output_B); @@ -306,11 +306,11 @@ void analyze_reasoning::compare_reasoning_presence() { if (result.result.success()) { if (!result.tags["pre"].empty() && !result.tags["post"].empty()) { mode = reasoning_mode::TAG_BASED; - start = trim_leading_whitespace(result.tags["pre"]); - end = trim_trailing_whitespace(result.tags["post"]); + start = result.tags["pre"]; + end = result.tags["post"]; } else if (!result.tags["post"].empty()) { mode = reasoning_mode::TAG_BASED; - end = trim_trailing_whitespace(result.tags["post"]); + end = result.tags["post"]; } } } diff --git a/tests/test-chat-auto-parser.cpp b/tests/test-chat-auto-parser.cpp index bb23b7f2a..1d96de718 100644 --- a/tests/test-chat-auto-parser.cpp +++ b/tests/test-chat-auto-parser.cpp @@ -1331,7 +1331,7 @@ static void test_nemotron_reasoning_detection(testing & t) { // Check reasoning markers t.assert_equal("reasoning_start should be '\\n'", "\n", analysis.reasoning.start); - t.assert_equal("reasoning_end should be ''", "", analysis.reasoning.end); + t.assert_equal("reasoning_end should be '\\n\\n'", "\n\n", analysis.reasoning.end); // Check reasoning mode detection // Nemotron uses tag-based reasoning; prefill handles the template's forced markers diff --git a/tests/test-chat.cpp b/tests/test-chat.cpp index 52b480c24..b0545dc95 100644 --- a/tests/test-chat.cpp +++ b/tests/test-chat.cpp @@ -1642,22 +1642,16 @@ static void test_template_output_peg_parsers(bool detailed_debug) { // Qwen3.5 (basically same as Nemotron, but keeping separate tests just in case) auto tst = peg_tester("models/templates/Qwen3.5-4B.jinja", detailed_debug); - tst.test("I'm\nthinkingHello, world!\nWhat's up?") + tst.test("I'm\nthinking\n\n\nHello, world!\nWhat's up?") .reasoning_format(COMMON_REASONING_FORMAT_AUTO) .enable_thinking(true) .expect(message_assist_thoughts) .run(); - tst.test("I'm\nthinking\n\nHello, world!\nWhat's up?") + tst.test("I'm\nthinking\n\n\nHello, world!\nWhat's up?") .enable_thinking(true) .reasoning_format(COMMON_REASONING_FORMAT_NONE) - .expect_content("\nI'm\nthinking\n\nHello, world!\nWhat's up?") - .run(); - - tst.test("I'm\nthinking\n\nHello, world!\nWhat's up?") - .enable_thinking(true) - .reasoning_format(COMMON_REASONING_FORMAT_AUTO) - .expect(message_assist_thoughts) + .expect_content("\nI'm\nthinking\n\n\nHello, world!\nWhat's up?") .run(); tst.test( @@ -1673,7 +1667,7 @@ static void test_template_output_peg_parsers(bool detailed_debug) { .run(); tst.test( - "I'm\nthinking\n\n" + "I'm\nthinking\n\n\n" "\n" "\n" "\n1\n\n" @@ -1731,7 +1725,7 @@ static void test_template_output_peg_parsers(bool detailed_debug) { tst.test( "I need to output the invoice details in JSON\n" - "\n" + "\n\n" R"({"amount": 123.45, "date": "2025-12-03"})") .reasoning_format(COMMON_REASONING_FORMAT_AUTO) .enable_thinking(true) @@ -1751,7 +1745,7 @@ static void test_template_output_peg_parsers(bool detailed_debug) { "hello()\n" "\n" "\n" - "\n" + "\n\n\n" "\n" "\n" "\n" @@ -1994,7 +1988,7 @@ static void test_template_output_peg_parsers(bool detailed_debug) { "hello()\n" "\n" "\n" - "\n" + "\n\n" "\n" "\n" "\n" @@ -3463,7 +3457,7 @@ static void test_template_output_peg_parsers(bool detailed_debug) { .run(); // Tool call with reasoning (enable_thinking=true) - tst.test("I'm\nthinking\n{\"name\": \"special_function\", \"arguments\": {\"arg1\": 1}}") + tst.test("I'm\nthinking\n\n\n\n{\"name\": \"special_function\", \"arguments\": {\"arg1\": 1}}") .enable_thinking(true) .reasoning_format(COMMON_REASONING_FORMAT_AUTO) .tools({ special_function_tool }) @@ -3487,7 +3481,7 @@ static void test_template_output_peg_parsers(bool detailed_debug) { .run(); // Tool call with reasoning and content - tst.test("I need to call a function" + tst.test("I need to call a function\n\n\n" "Let me check the time.\n{\"name\": \"get_time\", \"arguments\": {\"city\": \"XYZCITY\"}}") .enable_thinking(true) .reasoning_format(COMMON_REASONING_FORMAT_AUTO) @@ -3514,7 +3508,7 @@ static void test_template_output_peg_parsers(bool detailed_debug) { // fake tool call marker in reasoning tst.test( - "Let me think about \n{\"name\": \"special_function\", \"arguments\": {\"arg1\": 2}} hmm" + "Let me think about \n{\"name\": \"special_function\", \"arguments\": {\"arg1\": 2}} hmm\n\n\n" "\n{\"name\": \"special_function\", \"arguments\": {\"arg1\": 1}}") .enable_thinking(true) .reasoning_format(COMMON_REASONING_FORMAT_AUTO) @@ -3542,11 +3536,11 @@ static void test_template_output_peg_parsers(bool detailed_debug) { // Format: value { auto tst = peg_tester("models/templates/MiniMax-M2.jinja", detailed_debug); - tst.test("Hello, world!\nWhat's up?").enable_thinking(true).reasoning_format(COMMON_REASONING_FORMAT_AUTO).expect(message_assist).run(); + tst.test("\n\n\nHello, world!\nWhat's up?").enable_thinking(true).reasoning_format(COMMON_REASONING_FORMAT_AUTO).expect(message_assist).run(); - tst.test("I'm\nthinkingHello, world!\nWhat's up?").enable_thinking(true).reasoning_format(COMMON_REASONING_FORMAT_AUTO).expect(message_assist_thoughts).run(); + tst.test("I'm\nthinking\n\n\nHello, world!\nWhat's up?").enable_thinking(true).reasoning_format(COMMON_REASONING_FORMAT_AUTO).expect(message_assist_thoughts).run(); - tst.test("Let's call a tool:\n\n\n"). + tst.test("Let's call a tool:\n\n\n\n\n\n"). enable_thinking(true). reasoning_format(COMMON_REASONING_FORMAT_AUTO). tools({ empty_args_tool }). @@ -3554,7 +3548,7 @@ static void test_template_output_peg_parsers(bool detailed_debug) { run(); tst.test( - "\n\n\n\n\n\n1\n\n") .tools({ special_function_tool }) .expect(message_assist_call) @@ -3714,7 +3708,7 @@ static void test_template_output_peg_parsers(bool detailed_debug) { .enable_thinking(false) .expect(message_assist) .run(); - tst.test("I'm\nthinking\n\nHello, world!\nWhat's up?") + tst.test("I'm\nthinking\n\n\nHello, world!\nWhat's up?") .enable_thinking(true) .reasoning_format(COMMON_REASONING_FORMAT_DEEPSEEK) .expect(message_assist_thoughts) @@ -3729,7 +3723,7 @@ static void test_template_output_peg_parsers(bool detailed_debug) { .tools({ special_function_tool }) .expect(message_assist_call_content) .run(); - tst.test("I'm\nthinking\n\n\n{\"name\": \"special_function\", \"arguments\": {\"arg1\": 1}}\n") + tst.test("I'm\nthinking\n\n\n\n{\"name\": \"special_function\", \"arguments\": {\"arg1\": 1}}\n") .enable_thinking(true) .reasoning_format(COMMON_REASONING_FORMAT_DEEPSEEK) .tools({ special_function_tool }) @@ -4006,7 +4000,8 @@ static void test_template_output_peg_parsers(bool detailed_debug) { { auto tst = peg_tester("models/templates/StepFun3.5-Flash.jinja", detailed_debug); - tst.test("I was thinking\nNow I'm not."). + + tst.test("I was thinking\n\nNow I'm not."). enable_thinking(true). reasoning_format(COMMON_REASONING_FORMAT_DEEPSEEK). expect_reasoning("I was thinking").