echodict/llama.cpp
version https://git-lfs.github.com/spec/v1 oid sha256:cfc44b7ba25614df70e6b65e3341cae0310163bd32fd31a6b928a542df433faf size 30786
0479
1#include "chat-auto-parser-helpers.h"2#include "chat-auto-parser.h"3#include "chat-peg-parser.h"4#include "chat.h"5#include "peg-parser.h"6#include "testing.h"7 8#include <fstream>9#include <iostream>10#include <sstream>11#include <string>12 13using namespace autoparser;14 15static void test_calculate_diff_split_basic(testing & t);16static void test_calculate_diff_split_identical(testing & t);17static void test_calculate_diff_split_common_prefix(testing & t);18static void test_calculate_diff_split_common_suffix(testing & t);19static void test_calculate_diff_split_common_both(testing & t);20static void test_calculate_diff_split_empty_cases(testing & t);21static void test_calculate_diff_split_no_common(testing & t);22static void test_calculate_diff_split_single_char(testing & t);23static void test_calculate_diff_split_overlaps(testing & t);24static void test_calculate_diff_split_tag_boundaries(testing & t);25static void test_calculate_diff_split_generation_prompt(testing & t);26static void test_calculate_diff_split(testing & t);27 28static void test_until_common_prefix_basic(testing & t);29static void test_until_common_prefix(testing & t);30 31static void test_after_common_suffix_basic(testing & t);32static void test_after_common_suffix(testing & t);33 34static void test_analyze_tool_call_pure_json(testing & t);35static void test_analyze_tool_call_function_name_markers(testing & t);36static void test_analyze_tool_call_full_markers(testing & t);37static void test_analyze_tool_call_edge_cases(testing & t);38 39static void test_compare_variants_basic(testing & t);40static void test_compare_variants_messages_modifier(testing & t);41static void test_compare_variants_tools_modifier(testing & t);42static void test_compare_variants_both_modifiers(testing & t);43static void test_compare_variants_template_failure(testing & t);44static void test_compare_variants_identity(testing & t);45static void test_compare_variants(testing & t);46 47// Seed-OSS template tool calling analysis tests48static void test_seed_oss_tool_analysis(testing & t);49static void test_seed_oss_tool_presence(testing & t);50static void test_seed_oss_call_count(testing & t);51static void test_seed_oss_function_names(testing & t);52static void test_seed_oss_argument_count(testing & t);53static void test_seed_oss_args_presence(testing & t);54static void test_seed_oss_tool_with_reasoning(testing & t);55 56// Nemotron template analysis tests57static void test_nemotron_analysis(testing & t);58static void test_nemotron_reasoning_detection(testing & t);59static void test_nemotron_tool_format(testing & t);60 61// CohereForAI template analysis tests62static void test_cohere_reasoning_detection(testing & t);63static void test_cohere_analysis(testing & t);64 65// SmolLM3 template analysis tests66static void test_smollm3_analysis(testing & t);67 68// Marker separation69static void test_marker_separation(testing & t);70 71// standard_json_tools format tests72static void test_standard_json_tools_formats(testing & t);73static void test_standard_json_tools_openai(testing & t);74static void test_standard_json_tools_cohere(testing & t);75static void test_standard_json_tools_function_key(testing & t);76 77// normalize_quotes_to_json tests78static void test_normalize_quotes_to_json(testing & t);79static void test_normalize_quotes_with_embedded_quotes(testing & t);80 81// TAG_WITH_TAGGED argument parsing tests82static void test_tagged_args_with_embedded_quotes(testing & t);83 84int main(int argc, char * argv[]) {85 testing t(std::cout);86 t.verbose = true;87 88 // usage: test-chat-auto-parser-helpers [filter_regex]89 90 if (argc > 1) {91 t.set_filter(argv[1]);92 }93 94 t.test("diff_split", test_calculate_diff_split);95 t.test("common_prefix", test_until_common_prefix);96 t.test("common_suffix", test_after_common_suffix);97 t.test("compare_variants", test_compare_variants);98 t.test("segments", test_marker_separation);99 t.test("seed_oss_diffs", test_seed_oss_tool_analysis);100 t.test("cohere", test_cohere_analysis);101 t.test("nemotron", test_nemotron_analysis);102 t.test("smollm3", test_smollm3_analysis);103 t.test("standard_json_tools", test_standard_json_tools_formats);104 t.test("normalize_quotes_to_json", test_normalize_quotes_to_json);105 t.test("tagged_args_embedded_quotes", test_tagged_args_with_embedded_quotes);106 107 return t.summary();108}109 110static void test_marker_separation(testing & t) {111 auto single_square_marker = segmentize_markers("pre_marker[marker]post_marker");112 auto single_diag_marker = segmentize_markers("pre_marker<marker>post_marker");113 auto paired_markers = segmentize_markers("<hello>world</hello>");114 auto double_different_markers = segmentize_markers("<hello>[hello]<world>[world]");115 auto in_between = segmentize_markers("im<blue>daba<dee>da[hey]");116 117 t.test("single_square_marker", [&] (testing & t) {118 t.assert_equal("first is text", segment_type::TEXT, single_square_marker[0].type);119 t.assert_equal("second is marker", segment_type::MARKER, single_square_marker[1].type);120 t.assert_equal("last is text", segment_type::TEXT, single_square_marker[2].type);121 122 t.assert_equal("first is 'pre_marker'", "pre_marker", single_square_marker[0].value);123 t.assert_equal("second is '[marker]'", "[marker]", single_square_marker[1].value);124 t.assert_equal("last is 'post_marker'", "post_marker", single_square_marker[2].value);125 });126 127 t.test("single_diagonal_marker", [&] (testing & t) {128 t.assert_equal("first is text", segment_type::TEXT, single_diag_marker[0].type);129 t.assert_equal("second is marker", segment_type::MARKER, single_diag_marker[1].type);130 t.assert_equal("last is text", segment_type::TEXT, single_diag_marker[2].type);131 132 t.assert_equal("first is 'pre_marker'", "pre_marker", single_diag_marker[0].value);133 t.assert_equal("second is '<marker>'", "<marker>", single_diag_marker[1].value);134 t.assert_equal("last is 'post_marker'", "post_marker", single_diag_marker[2].value);135 });136 137 t.test("paired_markers", [&] (testing & t) {138 t.assert_equal("first is marker", segment_type::MARKER, paired_markers[0].type);139 t.assert_equal("second is text", segment_type::TEXT, paired_markers[1].type);140 t.assert_equal("third is marker", segment_type::MARKER, paired_markers[2].type);141 142 t.assert_equal("first is '<hello>'", "<hello>", paired_markers[0].value);143 t.assert_equal("second is 'world'", "world", paired_markers[1].value);144 t.assert_equal("third is '</hello>'", "</hello>", paired_markers[2].value);145 });146 147 t.test("double_different_markers", [&] (testing & t) {148 t.assert_equal("first is marker", segment_type::MARKER, double_different_markers[0].type);149 t.assert_equal("second is marker", segment_type::MARKER, double_different_markers[1].type);150 t.assert_equal("third is marker", segment_type::MARKER, double_different_markers[2].type);151 t.assert_equal("fourth is marker", segment_type::MARKER, double_different_markers[3].type);152 153 t.assert_equal("first is '<hello>'", "<hello>", double_different_markers[0].value);154 t.assert_equal("second is '[hello]'", "[hello]", double_different_markers[1].value);155 t.assert_equal("third is '<world>'", "<world>", double_different_markers[2].value);156 t.assert_equal("fourth is '[world]'", "[world]", double_different_markers[3].value);157 });158 159 t.test("in_between", [&] (testing & t) {160 t.assert_equal("first is text", segment_type::TEXT, in_between[0].type);161 t.assert_equal("second is marker", segment_type::MARKER, in_between[1].type);162 t.assert_equal("third is text", segment_type::TEXT, in_between[2].type);163 t.assert_equal("fourth is marker", segment_type::MARKER, in_between[3].type);164 t.assert_equal("fifth is text", segment_type::TEXT, in_between[4].type);165 t.assert_equal("sixth is marker", segment_type::MARKER, in_between[5].type);166 167 t.assert_equal("first is 'im'", "im", in_between[0].value);168 t.assert_equal("second is '<blue>'", "<blue>", in_between[1].value);169 t.assert_equal("third is 'daba'", "daba", in_between[2].value);170 t.assert_equal("fourth is '<dee>'", "<dee>", in_between[3].value);171 t.assert_equal("fifth is 'da'", "da", in_between[4].value);172 t.assert_equal("sixth is '[hey]'", "[hey]", in_between[5].value);173 });174}175 176static void test_calculate_diff_split(testing & t) {177 t.test("calculate_diff_split basic", test_calculate_diff_split_basic);178 t.test("calculate_diff_split identical", test_calculate_diff_split_identical);179 t.test("calculate_diff_split common prefix", test_calculate_diff_split_common_prefix);180 t.test("calculate_diff_split common suffix", test_calculate_diff_split_common_suffix);181 t.test("calculate_diff_split common both", test_calculate_diff_split_common_both);182 t.test("calculate_diff_split empty cases", test_calculate_diff_split_empty_cases);183 t.test("calculate_diff_split no common", test_calculate_diff_split_no_common);184 t.test("calculate_diff_split single char", test_calculate_diff_split_single_char);185 t.test("calculate_diff_split overlaps", test_calculate_diff_split_overlaps);186 t.test("calculate_diff_split tag boundaries", test_calculate_diff_split_tag_boundaries);187 t.test("calculate_diff_split generation prompt", test_calculate_diff_split_generation_prompt);188}189 190static void test_calculate_diff_split_basic(testing & t) {191 diff_split result = calculate_diff_split("hello world", "hello test");192 t.assert_equal("prefix should be 'hello '", "hello ", result.prefix);193 t.assert_equal("left should be 'world'", "world", result.left);194 t.assert_equal("right should be 'test'", "test", result.right);195 t.assert_equal("suffix should be empty", "", result.suffix);196 197 result = calculate_diff_split("abc", "xyz");198 t.assert_equal("prefix should be empty", "", result.prefix);199 t.assert_equal("left should be 'abc'", "abc", result.left);200 t.assert_equal("right should be 'xyz'", "xyz", result.right);201 t.assert_equal("suffix should be empty", "", result.suffix);202 203 result = calculate_diff_split("prefixA suffix", "prefixB suffix");204 t.assert_equal("prefix should be 'prefix'", "prefix", result.prefix);205 t.assert_equal("left should be 'A'", "A", result.left);206 t.assert_equal("right should be 'B'", "B", result.right);207 t.assert_equal("suffix should be ' suffix'", " suffix", result.suffix);208}209 210static void test_calculate_diff_split_identical(testing & t) {211 diff_split result = calculate_diff_split("hello", "hello");212 t.assert_equal("prefix should be 'hello'", "hello", result.prefix);213 t.assert_equal("left should be empty", "", result.left);214 t.assert_equal("right should be empty", "", result.right);215 t.assert_equal("suffix should be empty", "", result.suffix);216 217 result = calculate_diff_split("", "");218 t.assert_equal("prefix should be empty", "", result.prefix);219 t.assert_equal("left should be empty", "", result.left);220 t.assert_equal("right should be empty", "", result.right);221 t.assert_equal("suffix should be empty", "", result.suffix);222 223 result = calculate_diff_split("a", "a");224 t.assert_equal("prefix should be 'a'", "a", result.prefix);225 t.assert_equal("left should be empty", "", result.left);226 t.assert_equal("right should be empty", "", result.right);227 t.assert_equal("suffix should be empty", "", result.suffix);228 229 result = calculate_diff_split("<row><row><row><your><boat><gently>", "<row><row><row><your><boat><gently>");230 t.assert_equal("prefix should be '<row><row><row><your><boat><gently>'", "<row><row><row><your><boat><gently>", result.prefix);231 t.assert_equal("left should be empty", "", result.left);232 t.assert_equal("right should be empty", "", result.right);233 t.assert_equal("suffix should be empty", "", result.suffix);234}235 236static void test_calculate_diff_split_common_prefix(testing & t) {237 diff_split result = calculate_diff_split("abcdef", "abcxyz");238 t.assert_equal("prefix should be 'abc'", "abc", result.prefix);239 t.assert_equal("left should be 'def'", "def", result.left);240 t.assert_equal("right should be 'xyz'", "xyz", result.right);241 t.assert_equal("suffix should be empty", "", result.suffix);242 243 result = calculate_diff_split("same", "sameagain");244 t.assert_equal("prefix should be 'same'", "same", result.prefix);245 t.assert_equal("left should be empty", "", result.left);246 t.assert_equal("right should be 'again'", "again", result.right);247 t.assert_equal("suffix should be empty", "", result.suffix);248 249 result = calculate_diff_split("test", "testing");250 t.assert_equal("prefix should be 'test'", "test", result.prefix);251 t.assert_equal("left should be empty", "", result.left);252 t.assert_equal("right should be 'ing'", "ing", result.right);253 t.assert_equal("suffix should be empty", "", result.suffix);254}255 256static void test_calculate_diff_split_common_suffix(testing & t) {257 diff_split result = calculate_diff_split("123end", "456end");258 t.assert_equal("prefix should be empty", "", result.prefix);259 t.assert_equal("left should be '123'", "123", result.left);260 t.assert_equal("right should be '456'", "456", result.right);261 t.assert_equal("suffix should be 'end'", "end", result.suffix);262 263 result = calculate_diff_split("start", "end");264 t.assert_equal("prefix should be empty", "", result.prefix);265 t.assert_equal("left should be 'start'", "start", result.left);266 t.assert_equal("right should be 'end'", "end", result.right);267 t.assert_equal("suffix should be empty", "", result.suffix);268 269 result = calculate_diff_split("abcsuffix", "xyzsuffix");270 t.assert_equal("prefix should be empty", "", result.prefix);271 t.assert_equal("left should be 'abc'", "abc", result.left);272 t.assert_equal("right should be 'xyz'", "xyz", result.right);273 t.assert_equal("suffix should be 'suffix'", "suffix", result.suffix);274}275 276static void test_calculate_diff_split_common_both(testing & t) {277 diff_split result = calculate_diff_split("helloXworld", "helloYworld");278 t.assert_equal("prefix should be 'hello'", "hello", result.prefix);279 t.assert_equal("left should be 'X'", "X", result.left);280 t.assert_equal("right should be 'Y'", "Y", result.right);281 t.assert_equal("suffix should be 'world'", "world", result.suffix);282 283 result = calculate_diff_split("ABCmiddleXYZ", "ABCdifferentXYZ");284 t.assert_equal("prefix should be 'ABC'", "ABC", result.prefix);285 t.assert_equal("left should be 'middle'", "middle", result.left);286 t.assert_equal("right should be 'different'", "different", result.right);287 t.assert_equal("suffix should be 'XYZ'", "XYZ", result.suffix);288 289 result = calculate_diff_split("startAend", "startBend");290 t.assert_equal("prefix should be 'start'", "start", result.prefix);291 t.assert_equal("left should be 'A'", "A", result.left);292 t.assert_equal("right should be 'B'", "B", result.right);293 t.assert_equal("suffix should be 'end'", "end", result.suffix);294 295 // Edge case: common prefix and suffix overlap296 result = calculate_diff_split("aa", "ab");297 t.assert_equal("prefix should be 'a'", "a", result.prefix);298 t.assert_equal("left should be 'a'", "a", result.left);299 t.assert_equal("right should be 'b'", "b", result.right);300 t.assert_equal("suffix should be empty", "", result.suffix);301}302 303static void test_calculate_diff_split_empty_cases(testing & t) {304 // Empty left, non-empty right305 diff_split result = calculate_diff_split("", "hello");306 t.assert_equal("prefix should be empty", "", result.prefix);307 t.assert_equal("left should be empty", "", result.left);308 t.assert_equal("right should be 'hello'", "hello", result.right);309 t.assert_equal("suffix should be empty", "", result.suffix);310 311 // Non-empty left, empty right312 result = calculate_diff_split("hello", "");313 t.assert_equal("prefix should be empty", "", result.prefix);314 t.assert_equal("left should be 'hello'", "hello", result.left);315 t.assert_equal("right should be empty", "", result.right);316 t.assert_equal("suffix should be empty", "", result.suffix);317 318 // Both empty319 result = calculate_diff_split("", "");320 t.assert_equal("prefix should be empty", "", result.prefix);321 t.assert_equal("left should be empty", "", result.left);322 t.assert_equal("right should be empty", "", result.right);323 t.assert_equal("suffix should be empty", "", result.suffix);324 325 // Left single char, empty right326 result = calculate_diff_split("a", "");327 t.assert_equal("prefix should be empty", "", result.prefix);328 t.assert_equal("left should be 'a'", "a", result.left);329 t.assert_equal("right should be empty", "", result.right);330 t.assert_equal("suffix should be empty", "", result.suffix);331 332 // Empty left, right single char333 result = calculate_diff_split("", "a");334 t.assert_equal("prefix should be empty", "", result.prefix);335 t.assert_equal("left should be empty", "", result.left);336 t.assert_equal("right should be 'a'", "a", result.right);337 t.assert_equal("suffix should be empty", "", result.suffix);338}339 340static void test_calculate_diff_split_no_common(testing & t) {341 diff_split result = calculate_diff_split("abc", "xyz");342 t.assert_equal("prefix should be empty", "", result.prefix);343 t.assert_equal("left should be 'abc'", "abc", result.left);344 t.assert_equal("right should be 'xyz'", "xyz", result.right);345 t.assert_equal("suffix should be empty", "", result.suffix);346 347 result = calculate_diff_split("left", "right");348 // The algorithm finds "t" as a common suffix since both strings end with 't'349 // This is the algorithm's actual behavior - it finds maximal common suffix350 t.assert_equal("prefix should be empty", "", result.prefix);351 t.assert_equal("left should be 'lef'", "lef", result.left);352 t.assert_equal("right should be 'righ'", "righ", result.right);353 t.assert_equal("suffix should be 't'", "t", result.suffix);354 355 result = calculate_diff_split("123", "456");356 t.assert_equal("prefix should be empty", "", result.prefix);357 t.assert_equal("left should be '123'", "123", result.left);358 t.assert_equal("right should be '456'", "456", result.right);359 t.assert_equal("suffix should be empty", "", result.suffix);360}361 362static void test_calculate_diff_split_single_char(testing & t) {363 diff_split result = calculate_diff_split("a", "b");364 t.assert_equal("prefix should be empty", "", result.prefix);365 t.assert_equal("left should be 'a'", "a", result.left);366 t.assert_equal("right should be 'b'", "b", result.right);367 t.assert_equal("suffix should be empty", "", result.suffix);368 369 result = calculate_diff_split("a", "a");370 t.assert_equal("prefix should be 'a'", "a", result.prefix);371 t.assert_equal("left should be empty", "", result.left);372 t.assert_equal("right should be empty", "", result.right);373 t.assert_equal("suffix should be empty", "", result.suffix);374 375 result = calculate_diff_split("a", "ab");376 t.assert_equal("prefix should be 'a'", "a", result.prefix);377 t.assert_equal("left should be empty", "", result.left);378 t.assert_equal("right should be 'b'", "b", result.right);379 t.assert_equal("suffix should be empty", "", result.suffix);380 381 result = calculate_diff_split("ab", "a");382 t.assert_equal("prefix should be 'a'", "a", result.prefix);383 t.assert_equal("left should be 'b'", "b", result.left);384 t.assert_equal("right should be empty", "", result.right);385 t.assert_equal("suffix should be empty", "", result.suffix);386}387 388static void test_calculate_diff_split_overlaps(testing & t) {389 // One string is substring of another390 diff_split result = calculate_diff_split("test", "testing");391 t.assert_equal("prefix should be 'test'", "test", result.prefix);392 t.assert_equal("left should be empty", "", result.left);393 t.assert_equal("right should be 'ing'", "ing", result.right);394 t.assert_equal("suffix should be empty", "", result.suffix);395 396 result = calculate_diff_split("testing", "test");397 t.assert_equal("prefix should be 'test'", "test", result.prefix);398 t.assert_equal("left should be 'ing'", "ing", result.left);399 t.assert_equal("right should be empty", "", result.right);400 t.assert_equal("suffix should be empty", "", result.suffix);401 402 // Similar strings with one extra char at start403 result = calculate_diff_split("Xtest", "Ytest");404 // The algorithm finds "test" as a common suffix since both strings end with "test"405 // This is the algorithm's actual behavior - it finds maximal common suffix406 t.assert_equal("prefix should be empty", "", result.prefix);407 t.assert_equal("left should be 'X'", "X", result.left);408 t.assert_equal("right should be 'Y'", "Y", result.right);409 t.assert_equal("suffix should be 'test'", "test", result.suffix);410 411 // Similar strings with one extra char at end412 result = calculate_diff_split("testX", "testY");413 t.assert_equal("prefix should be 'test'", "test", result.prefix);414 t.assert_equal("left should be 'X'", "X", result.left);415 t.assert_equal("right should be 'Y'", "Y", result.right);416 t.assert_equal("suffix should be empty", "", result.suffix);417 418 // Strings that are reverses419 result = calculate_diff_split("abc", "cba");420 t.assert_equal("prefix should be empty", "", result.prefix);421 t.assert_equal("left should be 'abc'", "abc", result.left);422 t.assert_equal("right should be 'cba'", "cba", result.right);423 t.assert_equal("suffix should be empty", "", result.suffix);424}425 426static void test_calculate_diff_split_tag_boundaries(testing & t) {427 // Test with unclosed XML tags428 diff_split result = calculate_diff_split("test<tag", "test>content");429 // The fix_tag_boundaries should move incomplete tags appropriately430 t.assert_true("prefix should start with 'test'", result.prefix.find("test") == 0);431 t.assert_true("should handle tag boundaries", result.left != "" || result.right != "" || result.suffix != "");432 433 // Test with unclosed brackets434 result = calculate_diff_split("test[", "test]value");435 t.assert_true("should handle bracket boundaries", result.left != "" || result.right != "" || result.suffix != "");436 437 // Test with partial tags on both sides438 result = calculate_diff_split("prefix<tag>", "prefix</tag>suffix");439 // fix_tag_boundaries moves the incomplete '<' from prefix to left/right440 t.assert_equal("prefix should be 'prefix'", "prefix", result.prefix);441 t.assert_equal("left should be '<tag>'", "<tag>", result.left);442 t.assert_equal("right should be '</tag>suffix'", "</tag>suffix", result.right);443 t.assert_equal("suffix should be empty", "", result.suffix);444 445 // Test with complex nested tags446 result = calculate_diff_split("prefix<div>content</div>", "prefix<div>different</div>");447 // Algorithm finds "ent</div>" as a common suffix because both strings end with it448 // This is the actual algorithm behavior, though not semantically ideal449 t.assert_equal("prefix should be 'prefix<div>'", "prefix<div>", result.prefix);450 t.assert_equal("left should be 'cont'", "cont", result.left);451 t.assert_equal("right should be 'differ'", "differ", result.right);452 t.assert_equal("suffix should be 'ent</div>'", "ent</div>", result.suffix);453 454 // Test with unclosed angle bracket455 result = calculate_diff_split("Hello <world>", "Hello test");456 t.assert_equal("prefix should be 'Hello '", "Hello ", result.prefix);457 t.assert_true("left should contain '<world>'", result.left.find("<world>") != std::string::npos);458 t.assert_equal("right should be 'test'", "test", result.right);459 t.assert_equal("suffix should be empty", "", result.suffix);460 461 // Test with unclosed square bracket462 result = calculate_diff_split("test [array]", "test other");463 t.assert_equal("prefix should be 'test '", "test ", result.prefix);464 t.assert_true("left should contain '[array]'", result.left.find("[array]") != std::string::npos);465 t.assert_equal("right should be 'other'", "other", result.right);466 t.assert_equal("suffix should be empty", "", result.suffix);467 468 // Test empty prefix and suffix with tags469 result = calculate_diff_split("<tag>left</tag>", "<tag>righ</tag>");470 t.assert_equal("prefix should be '<tag>'", "<tag>", result.prefix);471 t.assert_equal("left should be 'left'", "left", result.left);472 t.assert_equal("right should be 'righ'", "righ", result.right);473 t.assert_equal("suffix should be '</tag>'", "</tag>", result.suffix);474 475 {476 // real case from template tests, simplified477 std::string left = "PREFIX</think>Sure";478 std::string right = "PREFIX<think>Lemme think</think>Sure";479 result = calculate_diff_split(left, right);480 t.assert_equal("prefix should be PREFIX", "PREFIX", result.prefix);481 t.assert_equal("suffix should be </think>Sure", "</think>Sure", result.suffix);482 t.assert_equal("left should be empty", "", result.left);483 t.assert_equal("right should be <think>Lemme think", "<think>Lemme think", result.right);484 }485 486 {487 // Real case: special tokens with |> boundary issue488 // The suffix starts with |> which should be moved to complete <|END_RESPONSE and <|END_ACTION489 std::string prefix = "SOME_PREFIX";490 std::string suffix = "|><|END_OF_TURN_TOKEN|><|START_OF_TURN_TOKEN|><|CHATBOT_TOKEN|>";491 std::string left_diff = "<|START_RESPONSE|>Let me help you.<|END_RESPONSE";492 std::string right_diff =493 "<|START_THINKING|><|END_THINKING|><|START_ACTION|>[\n"494 " {\"tool_call_id\": \"0\", \"tool_name\": \"test_function_name\", "495 "\"parameters\": {\"param1\": \"value1\", \"param2\": \"value2\"}}\n"496 "]<|END_ACTION";497 498 std::string left = prefix + left_diff + suffix;499 std::string right = prefix + right_diff + suffix;500 result = calculate_diff_split(left, right);501 502 t.assert_equal("special token prefix", prefix, result.prefix);503 // The |> should be moved from suffix to complete the tokens504 t.assert_equal("special token left", "<|START_RESPONSE|>Let me help you.<|END_RESPONSE|>", result.left);505 t.assert_true("special token right ends with |>", result.right.find("<|END_ACTION|>") != std::string::npos);506 t.assert_equal("special token suffix", "<|END_OF_TURN_TOKEN|><|START_OF_TURN_TOKEN|><|CHATBOT_TOKEN|>",507 result.suffix);508 }509}510 511static void test_calculate_diff_split_generation_prompt(testing & t) {512 // ChatML thinking template: left is a prefix of right, generation_prompt is the appended part.513 // The trailing \n in left matches the trailing \n in the generation_prompt, causing514 // the suffix matcher to steal it and rotate the diff result.515 {516 // Simplified reproduction: left ends with \n, right = left + "<|im_start|>assistant\n<think>\n"517 std::string left = "<|im_start|>user\nHello<|im_end|>\n";518 std::string right = left + "<|im_start|>assistant\n<think>\n";519 diff_split result = calculate_diff_split(left, right);520 t.assert_equal("chatml prefix", left, result.prefix);521 t.assert_equal("chatml left", "", result.left);522 t.assert_equal("chatml right should be generation prompt",523 "<|im_start|>assistant\n<think>\n", result.right);524 t.assert_equal("chatml suffix", "", result.suffix);525 }526 527 {528 // More realistic: longer conversation ending with tool_response529 std::string common =530 "<|im_start|>system\nYou are a helpful assistant.<|im_end|>\n"531 "<|im_start|>user\nSearch for files<|im_end|>\n"532 "<|im_start|>assistant\n<think>\nLet me search.\n</think>\n\n"533 "<tool_call>\n<function=search>\n</function>\n</tool_call><|im_end|>\n"534 "<|im_start|>user\n<tool_response>\nNo files found\n</tool_response><|im_end|>\n";535 std::string left = common;536 std::string right = common + "<|im_start|>assistant\n<think>\n";537 diff_split result = calculate_diff_split(left, right);538 t.assert_equal("tool_response left", "", result.left);539 t.assert_equal("tool_response right should be generation prompt",540 "<|im_start|>assistant\n<think>\n", result.right);541 }542}543 544static void test_until_common_prefix(testing & t) {545 t.test("until_common_prefix basic", test_until_common_prefix_basic);546}547 548static void test_until_common_prefix_basic(testing & t) {549 // Test case from the user request550 std::string result = until_common_prefix("<function name=foo><arg name=bar>", "<arg name=bar>", "<arg name=baz>");551 t.assert_equal("untilCommonPrefix should return '<function name=foo>'", "<function name=foo>", result);552 553 // Additional test cases to ensure robustness554 // Test with different common prefix lengths555 result = until_common_prefix("prefix<test>suffix", "<test>different", "<test>other");556 t.assert_equal("should return 'prefix'", "prefix", result);557 558 // Test when common prefix is at the start559 result = until_common_prefix("<common>rest", "<common>left", "<common>right");560 t.assert_equal("should return empty string when common prefix at start", "", result);561 562 // Test when there's no common prefix563 result = until_common_prefix("something", "left", "right");564 t.assert_equal("should return empty string when no common prefix", "", result);565 566 // Test with empty strings567 result = until_common_prefix("test", "", "right");568 t.assert_equal("should return empty string when left is empty", "", result);569 570 // Test with longer common prefix571 result = until_common_prefix("abcXYZ<shared_prefix>rest", "<shared_prefix>left", "<shared_prefix>right");572 t.assert_equal("should return 'abcXYZ'", "abcXYZ", result);573}574 575static void test_after_common_suffix(testing & t) {576 t.test("after_common_suffix basic", test_after_common_suffix_basic);577}578 579static void test_after_common_suffix_basic(testing & t) {580 // Test case from the user request581 std::string result = after_common_suffix("<function name=foo><arg name=bar>100</arg></function>",582 "<arg name=bar>100</arg>",583 "<arg name=baz>535</arg>");584 t.assert_equal("afterCommonSuffix should return '</function>'", "</function>", result);585 586 // Test when common suffix is at the end587 result = after_common_suffix("rest<common>", "left<common>", "right<common>");588 t.assert_equal("should return empty string when common suffix at end", "", result);589 590 // Test with empty strings591 result = after_common_suffix("test", "left", "");592 t.assert_equal("should return empty string when right is empty", "", result);593 594 // Test case with XML-like structure similar to the main example595 result = after_common_suffix("<outer><inner>value</inner></outer>",596 "<inner>value</inner>",597 "<inner>different</inner>");598 t.assert_equal("should return '</outer>'", "</outer>", result);599 600 // Test with longer common suffix appearing at the end of full601 result = after_common_suffix("prefix<shared>rest</shared>", "prefix<shared>left</shared>", "prefix<shared>right</shared>");602 t.assert_equal("should return '' when common suffix is at end of full", "", result);603 604 // Test with common suffix appearing in middle but not at end605 result = after_common_suffix("<tag>content</tag><extra>", "<tag>value</tag>", "<tag>other</tag>");606 t.assert_equal("should return '<extra>' when common suffix appears before end", "<extra>", result);607 608 // Test with multi-character common suffix at the very end of full609 result = after_common_suffix("start<middle>end</middle>", "prefix<middle>left</middle>", "prefix<middle>right</middle>");610 t.assert_equal("should return '' when common suffix </middle> is at end of full", "", result);611}612 613static void test_compare_variants(testing & t) {614 t.test("compare_variants basic", test_compare_variants_basic);615 t.test("compare_variants messages modifier", test_compare_variants_messages_modifier);616 t.test("compare_variants tools modifier", test_compare_variants_tools_modifier);617 t.test("compare_variants both modifiers", test_compare_variants_both_modifiers);618 t.test("compare_variants template failure", test_compare_variants_template_failure);619 t.test("compare_variants identity", test_compare_variants_identity);620}621 622static void test_compare_variants_basic(testing & t) {623 // Create a simple template that just echoes messages624 common_chat_template tmpl("{{ messages[0]['content'] }}", "", "");625 626 template_params params;627 params.messages = json::array({628 json {{"role", "user"}, {"content", "Hello"}}629 });630 631 auto modifier = [](template_params & p) {632 p.messages[0]["content"] = "World";633 };634 635 auto result = ::compare_variants(tmpl, params, modifier);636 637 if (!t.assert_true("result should have value", result.has_value())) {638 return;639 }640 // The template might not output anything if messages is empty or format is different641 // Check that we get a valid result642 t.assert_true("prefix or left should have content", !result->diff.prefix.empty() || !result->diff.left.empty());643}644 645static void test_compare_variants_messages_modifier(testing & t) {646 // Test with messages modifier only647 common_chat_template tmpl("{% for message in messages %}{{ message['role'] }}:{{ message['content'] }}{% endfor %}", "", "");648 649 template_params params;650 params.messages = json::array({651 json {{"role", "user"}, {"content", "A"}}652 });653 654 auto modifier = [](template_params & p) {655 p.messages[0]["content"] = "B";656 };657 658 std::optional<compare_variants_result> result = ::compare_variants(tmpl, params, modifier);659 660 if (!t.assert_true("result should have value", result.has_value())) {661 return;662 }663 t.assert_equal("left should be 'A'", "A", result->diff.left);664 t.assert_equal("right should be 'B'", "B", result->diff.right);665}666 667static void test_compare_variants_tools_modifier(testing & t) {668 // Test with tools modifier only669 common_chat_template tmpl(670 "{% for tool in tools %}{{ tool['name'] }}{% endfor %}", "", "");671 672 template_params params;673 params.tools = json::array({674 json {{"name", "foo"}}675 });676 677 auto modifier = [](template_params & p) {678 p.tools[0]["name"] = "bar";679 };680 681 auto result = ::compare_variants(tmpl, params, modifier);682 683 if (!t.assert_true("result should have value", result.has_value())) {684 return;685 }686 t.assert_equal("left should be 'foo'", "foo", result->diff.left);687 t.assert_equal("right should be 'bar'", "bar", result->diff.right);688}689 690static void test_compare_variants_both_modifiers(testing & t) {691 // Test with both messages and tools modifiers using the for loop approach692 common_chat_template tmpl(693 "{% for message in messages %}{{ message['role'] }}:{{ message['content'] }}{% endfor %}", "", "");694 695 template_params params;696 params.messages = json::array({697 json {{"role", "user"}, {"content", "A"}}698 });699 700 auto modifier = [](template_params & p) {701 p.messages[0]["content"] = "B";702 p.messages[0]["role"] = "newuser";703 };704 705 auto result = ::compare_variants(tmpl, params, modifier);706 707 if (!t.assert_true("result should have value", result.has_value())) {708 return;709 }710 t.assert_equal("left should be 'user:A'", "user:A", result->diff.left);711 t.assert_equal("right should be 'newuser:B'", "newuser:B", result->diff.right);712}713 714static void test_compare_variants_template_failure(testing & t) {715 // Test with template that causes failure during application (not construction)716 // We use a valid template syntax but one that will fail during application717 common_chat_template tmpl("{{ messages[0]['nonexistent_field'] }}", "", "");718 719 template_params params;720 params.messages = json::array({721 json {{"role", "user"}, {"content", "Hello"}}722 });723 724 auto modifier = [](template_params & p) {725 p.messages[0]["content"] = "World";726 };727 728 auto result = ::compare_variants(tmpl, params, modifier);729 730 t.assert_true("result should be nullopt on template failure", !result.has_value());731}732 733static void test_compare_variants_identity(testing & t) {734 // Test with identity modifier (no change)735 common_chat_template tmpl("{{ messages[0]['content'] }}", "", "");736 737 template_params params;738 params.messages = json::array({739 json {{"role", "user"}, {"content", "Hello"}}740 });741 742 // No modifier - should use identity743 auto result = ::compare_variants(tmpl, params, nullptr);744 745 if (!t.assert_true("result should have value", result.has_value())) {746 return;747 }748 t.assert_equal("prefix should be 'Hello'", "Hello", result->diff.prefix);749 t.assert_equal("left should be empty", "", result->diff.left);750 t.assert_equal("right should be empty", "", result->diff.right);751 t.assert_equal("suffix should be empty", "", result->diff.suffix);752}753 754// ============================================================================755// Seed-OSS Template Tool Calling Analysis Tests756// ============================================================================757 758static void test_seed_oss_tool_analysis(testing & t) {759 t.test("Seed-OSS tool presence", test_seed_oss_tool_presence);760 t.test("Seed-OSS call count", test_seed_oss_call_count);761 t.test("Seed-OSS function names", test_seed_oss_function_names);762 t.test("Seed-OSS argument count", test_seed_oss_argument_count);763 t.test("Seed-OSS args presence", test_seed_oss_args_presence);764 t.test("Seed-OSS tool with reasoning", test_seed_oss_tool_with_reasoning);765}766 767// Helper to load Seed-OSS template768static common_chat_template load_seed_oss_template(testing & t) {769 std::string template_path = "models/templates/ByteDance-Seed-OSS.jinja";770 std::ifstream fin(template_path, std::ios::binary);771 std::ostringstream buf;772 if (fin.is_open()) {773 buf << fin.rdbuf();774 }775 std::string template_source = buf.str();776 common_chat_template tmpl(template_source, "", "");777 t.assert_true("Seed-OSS template loaded successfully", template_source.length() > 0);778 return tmpl;779}780 781// Helper to build tool call JSON782static json build_tool_call(const std::string & name, const json & args, const std::string & id = "call_001") {783 return json{784 {"id", id},785 {"type", "function"},786 {"function", json{787 {"name", name},788 {"arguments", args}789 }}790 };791}792 793// Helper to build tools definition794static json build_tools_definition() {795 json parameters_schema = json::object();796 parameters_schema["type"] = "object";797 parameters_schema["properties"] = json::object();798 parameters_schema["properties"]["param1"] = json::object({799 {"type", "string"},800 {"description", "First parameter"}801 });802 parameters_schema["properties"]["param2"] = json::object({803 {"type", "string"},804 {"description", "Second parameter"}805 });806 parameters_schema["required"] = json::array({"param1", "param2"});807 808 return json::array({809 json{810 {"type", "function"},811 {"function", json{812 {"name", "test_function_name"},813 {"description", "A test function for debugging"},814 {"parameters", parameters_schema}815 }}816 }817 });818}819 820// T1: Compare with/without tool call (user, assistant)821static void test_seed_oss_tool_presence(testing & t) {822 common_chat_template tmpl = load_seed_oss_template(t);823 824 json assistant_no_tools = json{825 {"role", "assistant"},826 {"content", "Let me help you."}827 };828 829 json assistant_with_tools = json{830 {"role", "assistant"},831 {"content", nullptr},832 {"tool_calls", json::array({833 build_tool_call("test_function_name", json::object({{"param1", "value1"}, {"param2", "value2"}}))834 })}835 };836 837 json user_msg = json{838 {"role", "user"},839 {"content", "Hello, please help me."}840 };841 842 template_params params_no_tools;843 params_no_tools.messages = json::array({user_msg, assistant_no_tools});844 params_no_tools.tools = build_tools_definition();845 params_no_tools.add_generation_prompt = false;846 params_no_tools.enable_thinking = true;847 848 template_params params_with_tools;849 params_with_tools.messages = json::array({user_msg, assistant_with_tools});850 params_with_tools.tools = build_tools_definition();851 params_with_tools.add_generation_prompt = false;852 params_with_tools.enable_thinking = true;853 854 auto result = ::compare_variants(tmpl, params_no_tools,855 [&](template_params & p) {856 p.messages = params_with_tools.messages;857 });858 859 if (!t.assert_true("T1 result should have value", result.has_value())) {860 return;861 }862 863 const auto & diff = result->diff;864 t.assert_true("T1 prefix should contain system", diff.prefix.find("system") != std::string::npos);865 t.assert_true("T1 prefix should contain user", diff.prefix.find("user") != std::string::npos);866 t.assert_true("T1 prefix should contain assistant", diff.prefix.find("assistant") != std::string::npos);867 868 // Left should be the assistant content without tool869 t.assert_equal("T1 left should contain 'Let me help you.'", "Let me help you.", diff.left);870 871 // Right should contain the tool call markers872 t.assert_true("T1 right should contain tool_call begin", diff.right.find("<seed:tool_call>") != std::string::npos);873 t.assert_true("T1 right should contain function tag", diff.right.find("<function=test_function_name>") != std::string::npos);874 t.assert_true("T1 right should contain parameter=param1", diff.right.find("<parameter=param1>") != std::string::npos);875 t.assert_true("T1 right should contain parameter=param2", diff.right.find("<parameter=param2>") != std::string::npos);876 t.assert_true("T1 right should contain value1", diff.right.find("value1") != std::string::npos);877 t.assert_true("T1 right should contain value2", diff.right.find("value2") != std::string::npos);878 t.assert_true("T1 right should contain tool_call end", diff.right.find("</seed:tool_call>") != std::string::npos);879 880 // Suffix should be the eos token881 t.assert_equal("T1 suffix should be '<seed:eos>'", "<seed:eos>", diff.suffix);882}883 884// T2: Compare one vs two tool calls885static void test_seed_oss_call_count(testing & t) {886 common_chat_template tmpl = load_seed_oss_template(t);887 888 json assistant_one_call = json{889 {"role", "assistant"},890 {"content", nullptr},891 {"tool_calls", json::array({892 build_tool_call("test_function_name", json::object({{"param1", "value1"}, {"param2", "value2"}}))893 })}894 };895 896 json assistant_two_calls = json{897 {"role", "assistant"},898 {"content", nullptr},899 {"tool_calls", json::array({900 build_tool_call("test_function_name", json::object({{"param1", "value1"}, {"param2", "value2"}})),901 build_tool_call("test_function_name", json::object({{"param1", "value3"}, {"param2", "value4"}}), "call_002")902 })}903 };904 905 json user_msg = json{906 {"role", "user"},907 {"content", "Hello, please help me."}908 };909 910 template_params params_one;911 params_one.messages = json::array({user_msg, assistant_one_call});912 params_one.tools = build_tools_definition();913 params_one.add_generation_prompt = false;914 params_one.enable_thinking = true;915 916 auto result = ::compare_variants(tmpl, params_one,917 [&](template_params & p) {918 p.messages = json::array({user_msg, assistant_two_calls});919 });920 921 if (!t.assert_true("T2 result should have value", result.has_value())) {922 return;923 }924 925 const auto & diff = result->diff;926 927 // Prefix should include the first tool call928 t.assert_true("T2 prefix should contain first tool_call begin", diff.prefix.find("<seed:tool_call>") != std::string::npos);929 t.assert_true("T2 prefix should contain first function", diff.prefix.find("<function=test_function_name>") != std::string::npos);930 t.assert_true("T2 prefix should contain value1", diff.prefix.find("value1") != std::string::npos);931 t.assert_true("T2 prefix should contain value2", diff.prefix.find("value2") != std::string::npos);932 t.assert_true("T2 prefix should contain first tool_call end", diff.prefix.find("</seed:tool_call>") != std::string::npos);933 934 // Left should be empty (no second tool call in variant A)935 t.assert_equal("T2 left should be empty", "", diff.left);936 937 // Right should contain the second tool call938 t.assert_true("T2 right should contain second tool_call begin", diff.right.find("<seed:tool_call>") != std::string::npos);939 t.assert_true("T2 right should contain second function", diff.right.find("<function=test_function_name>") != std::string::npos);940 t.assert_true("T2 right should contain value3", diff.right.find("value3") != std::string::npos);941 t.assert_true("T2 right should contain value4", diff.right.find("value4") != std::string::npos);942 t.assert_true("T2 right should contain second tool_call end", diff.right.find("</seed:tool_call>") != std::string::npos);943 944 // Suffix should end with the eos token945 t.assert_equal("T2 suffix should end with '<seed:eos>'", "<seed:eos>", diff.suffix.substr(diff.suffix.length() - 10, 10));946}947 948// T3: Compare different function names949static void test_seed_oss_function_names(testing & t) {950 common_chat_template tmpl = load_seed_oss_template(t);951 952 // Build tools with two different function names953 json parameters_schema = json::object();954 parameters_schema["type"] = "object";955 parameters_schema["properties"] = json::object();956 parameters_schema["properties"]["arg1"] = json::object({957 {"type", "string"},958 {"description", "Argument 1"}959 });960 parameters_schema["required"] = json::array({"arg1"});961 962 json tools = json::array({963 json{964 {"type", "function"},965 {"function", json{966 {"name", "func_alpha"},967 {"description", "First function"},968 {"parameters", parameters_schema}969 }}970 },971 json{972 {"type", "function"},973 {"function", json{974 {"name", "func_beta"},975 {"description", "Second function"},976 {"parameters", parameters_schema}977 }}978 }979 });980 981 json assistant_func_alpha = json{982 {"role", "assistant"},983 {"content", nullptr},984 {"tool_calls", json::array({985 build_tool_call("func_alpha", json::object({{"arg1", "test_value"}}))986 })}987 };988 989 json assistant_func_beta = json{990 {"role", "assistant"},991 {"content", nullptr},992 {"tool_calls", json::array({993 build_tool_call("func_beta", json::object({{"arg1", "test_value"}}))994 })}995 };996 997 json user_msg = json{998 {"role", "user"},999 {"content", "Hello"}1000 };1001 1002 template_params params_alpha;1003 params_alpha.messages = json::array({user_msg, assistant_func_alpha});1004 params_alpha.tools = tools;1005 params_alpha.add_generation_prompt = false;1006 params_alpha.enable_thinking = true;1007 1008 auto result = ::compare_variants(tmpl, params_alpha,1009 [&](template_params & p) {1010 p.messages = json::array({user_msg, assistant_func_beta});1011 });1012 1013 if (!t.assert_true("T3 result should have value", result.has_value())) {1014 return;1015 }1016 1017 const auto & diff = result->diff;1018 1019 bool func_alpha_in_left = diff.left.find("func_alpha") != std::string::npos;1020 bool func_alpha_in_prefix = diff.prefix.find("func_alpha") != std::string::npos;1021 bool func_beta_in_right = diff.right.find("func_beta") != std::string::npos;1022 bool func_beta_in_prefix = diff.prefix.find("func_beta") != std::string::npos;1023 bool func_beta_in_suffix = diff.suffix.find("func_beta") != std::string::npos;1024 1025 // Left should contain func_alpha (or be in prefix)1026 t.assert_true("T3 left should contain func_alpha (or prefix)", func_alpha_in_left || func_alpha_in_prefix);1027 1028 // Right should contain func_beta1029 t.assert_true("T3 right should contain func_beta", func_beta_in_right || func_beta_in_prefix || func_beta_in_suffix);1030 1031 // Both should have the same parameter value (in common parts, not in diffs)1032 // Since both have same args, test_value will be in prefix/suffix1033 t.assert_true("T3 diff should contain test_value (in prefix or suffix)",1034 diff.prefix.find("test_value") != std::string::npos || diff.suffix.find("test_value") != std::string::npos);1035}1036 1037// T4: Compare different argument counts (zero, one, two parameters)1038static void test_seed_oss_argument_count(testing & t) {1039 common_chat_template tmpl = load_seed_oss_template(t);1040 1041 // Build tools with 0, 1, or 2 required parameters1042 json params_2_required = json::object();1043 params_2_required["type"] = "object";1044 params_2_required["properties"] = json::object();1045 params_2_required["properties"]["arg1"] = json::object({1046 {"type", "string"},1047 {"description", "Argument 1"}1048 });1049 params_2_required["properties"]["arg2"] = json::object({1050 {"type", "string"},1051 {"description", "Argument 2"}1052 });1053 params_2_required["required"] = json::array({"arg1", "arg2"});1054 1055 json params_1_required = json::object();1056 params_1_required["type"] = "object";1057 params_1_required["properties"] = json::object();1058 params_1_required["properties"]["arg1"] = json::object({1059 {"type", "string"},1060 {"description", "Argument 1"}1061 });1062 params_1_required["required"] = json::array({"arg1"});1063 1064 json tools = json::array({1065 json{1066 {"type", "function"},1067 {"function", json{1068 {"name", "test_func"},1069 {"description", "Test function"},1070 {"parameters", params_2_required}1071 }}1072 }1073 });1074 1075 // Test: zero args vs one arg1076 json assistant_zero_args = json{1077 {"role", "assistant"},1078 {"content", nullptr},1079 {"tool_calls", json::array({1080 build_tool_call("test_func", json::object())1081 })}1082 };1083 1084 json assistant_one_arg = json{1085 {"role", "assistant"},1086 {"content", nullptr},1087 {"tool_calls", json::array({1088 build_tool_call("test_func", json::object({{"arg1", "value1"}}))1089 })}1090 };1091 1092 json assistant_two_args = json{1093 {"role", "assistant"},1094 {"content", nullptr},1095 {"tool_calls", json::array({1096 build_tool_call("test_func", json::object({{"arg1", "value1"}, {"arg2", "value2"}}))1097 })}1098 };1099 1100 json user_msg = json{1101 {"role", "user"},1102 {"content", "Hello"}1103 };1104 1105 // Test zero vs one1106 template_params params_zero;1107 params_zero.messages = json::array({user_msg, assistant_zero_args});1108 params_zero.tools = tools;1109 params_zero.add_generation_prompt = false;1110 params_zero.enable_thinking = true;1111 1112 auto result_zero_one = ::compare_variants(tmpl, params_zero,1113 [&](template_params & p) {1114 p.messages = json::array({user_msg, assistant_one_arg});1115 });1116 1117 if (!t.assert_true("T4 zero vs one result should have value", result_zero_one.has_value())) {1118 return;1119 }1120 t.assert_true("T4 zero vs one left should be empty or minimal", result_zero_one->diff.left.empty() || result_zero_one->diff.left == "");1121 t.assert_true("T4 zero vs one right should contain arg1", result_zero_one->diff.right.find("arg1") != std::string::npos);1122 1123 // Test one vs two1124 template_params params_one;1125 params_one.messages = json::array({user_msg, assistant_one_arg});1126 params_one.tools = tools;1127 params_one.add_generation_prompt = false;1128 params_one.enable_thinking = true;1129 1130 auto result_one_two = ::compare_variants(tmpl, params_one,1131 [&](template_params & p) {1132 p.messages = json::array({user_msg, assistant_two_args});1133 });1134 1135 if (!t.assert_true("T4 one vs two result should have value", result_one_two.has_value())) {1136 return;1137 }1138 1139 const auto & diff4 = result_one_two->diff;1140 t.assert_true("T4 one vs two left should contain arg1 (or prefix)",1141 diff4.left.find("arg1") != std::string::npos || diff4.prefix.find("arg1") != std::string::npos);1142 t.assert_true("T4 one vs two right should contain arg1 (or prefix)",1143 diff4.right.find("arg1") != std::string::npos || diff4.prefix.find("arg1") != std::string::npos);1144 t.assert_true("T4 one vs two right should contain arg2 (or prefix/suffix)",1145 diff4.right.find("arg2") != std::string::npos || diff4.prefix.find("arg2") != std::string::npos || diff4.suffix.find("arg2") != std::string::npos);1146}1147 1148// T5: Compare different argument values1149static void test_seed_oss_args_presence(testing & t) {1150 common_chat_template tmpl = load_seed_oss_template(t);1151 1152 json assistant_same_arg = json{1153 {"role", "assistant"},1154 {"content", nullptr},1155 {"tool_calls", json::array({1156 build_tool_call("test_function_name", json::object({{"param1", "value1"}}))1157 })}1158 };1159 1160 json assistant_other_arg = json{1161 {"role", "assistant"},1162 {"content", nullptr},1163 {"tool_calls", json::array({1164 build_tool_call("test_function_name", json::object({{"param2", "value2"}}))1165 })}1166 };1167 1168 json assistant_both_args = json{1169 {"role", "assistant"},1170 {"content", nullptr},1171 {"tool_calls", json::array({1172 build_tool_call("test_function_name", json::object({{"param1", "value1"}, {"param2", "value2"}}))1173 })}1174 };1175 1176 json user_msg = json{1177 {"role", "user"},1178 {"content", "Hello"}1179 };1180 1181 template_params params_same;1182 params_same.messages = json::array({user_msg, assistant_same_arg});1183 params_same.tools = build_tools_definition();1184 params_same.add_generation_prompt = false;1185 params_same.enable_thinking = true;1186 1187 // Test same arg vs other arg1188 auto result_same_other = ::compare_variants(tmpl, params_same,1189 [&](template_params & p) {1190 p.messages = json::array({user_msg, assistant_other_arg});1191 });1192 1193 if (!t.assert_true("T5 same vs other result should have value", result_same_other.has_value())) {1194 return;1195 }1196 const auto & diff5a = result_same_other->diff;1197 t.assert_true("T5 same vs other left should contain param1 (or prefix/suffix)",1198 diff5a.left.find("param1") != std::string::npos || diff5a.prefix.find("param1") != std::string::npos || diff5a.suffix.find("param1") != std::string::npos);1199 t.assert_true("T5 same vs other left should contain value1 (or prefix/suffix)",1200 diff5a.left.find("value1") != std::string::npos || diff5a.prefix.find("value1") != std::string::npos);