Brunobkr/llama.cpp_AlgMor24_github
ΩFFFΣLLIa • llama.cpp • AlgMor24 ██████╗ ███████╗███████╗███████╗██╗ ██╗ ██╗ █████╗ ██╔═══██╗██╔════╝██╔════╝██╔════╝██║ ██║ ██║██╔══██╗ ██║ ██║█████╗ █████╗ █████╗ ██║ ██║ ██║███████║ ██║ ██║██╔══╝ ██╔══╝ ██╔══╝ ██║ ██║ ██║██╔══██║ ╚██████╔╝██║ ██║ ███████╗███████╗███████╗██║██║ ██║ ╚═════╝ ╚═╝ ╚═╝ ╚══════╝╚══════╝╚══════╝╚═╝╚═╝ ╚═╝ High-Performance LLM / VLM Inference & Autonomous Agentic Ecosystem… See the full description on the dataset page: https://huggingface.co/datasets/Brunobkr/llama.cpp_AlgMor24_github.
03k
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);60static void test_laguna_analysis(testing & t);61static void test_laguna_reasoning_detection(testing & t);62static void test_laguna_tool_format(testing & t);63static void test_laguna_s_analysis(testing & t);64static void test_laguna_s_reasoning_detection(testing & t);65static void test_laguna_s_tool_format(testing & t);66static void test_laguna_s_preserve_reasoning(testing & t);67static void test_laguna_xs2_analysis(testing & t);68static void test_laguna_xs2_reasoning_detection(testing & t);69static void test_laguna_xs2_tool_format(testing & t);70 71// CohereForAI template analysis tests72static void test_cohere_reasoning_detection(testing & t);73static void test_cohere_analysis(testing & t);74 75// SmolLM3 template analysis tests76static void test_smollm3_analysis(testing & t);77 78// Marker separation79static void test_marker_separation(testing & t);80 81// standard_json_tools format tests82static void test_standard_json_tools_formats(testing & t);83static void test_standard_json_tools_openai(testing & t);84static void test_standard_json_tools_cohere(testing & t);85static void test_standard_json_tools_function_key(testing & t);86 87// normalize_quotes_to_json tests88static void test_normalize_quotes_to_json(testing & t);89static void test_normalize_quotes_with_embedded_quotes(testing & t);90 91// TAG_WITH_TAGGED argument parsing tests92static void test_tagged_args_with_embedded_quotes(testing & t);93 94static void test_role_markers_all_templates(testing & t);95 96int main(int argc, char * argv[]) {97 testing t(std::cout);98 t.verbose = true;99 100 // usage: test-chat-auto-parser-helpers [filter_regex]101 102 if (argc > 1) {103 t.set_filter(argv[1]);104 }105 106 t.test("diff_split", test_calculate_diff_split);107 t.test("common_prefix", test_until_common_prefix);108 t.test("common_suffix", test_after_common_suffix);109 t.test("compare_variants", test_compare_variants);110 t.test("segments", test_marker_separation);111 t.test("seed_oss_diffs", test_seed_oss_tool_analysis);112 t.test("cohere", test_cohere_analysis);113 t.test("nemotron", test_nemotron_analysis);114 t.test("laguna", test_laguna_analysis);115 t.test("laguna-s", test_laguna_s_analysis);116 t.test("laguna-xs2", test_laguna_xs2_analysis);117 t.test("smollm3", test_smollm3_analysis);118 t.test("standard_json_tools", test_standard_json_tools_formats);119 t.test("normalize_quotes_to_json", test_normalize_quotes_to_json);120 t.test("tagged_args_embedded_quotes", test_tagged_args_with_embedded_quotes);121 t.test("role_markers_all_templates", test_role_markers_all_templates);122 123 return t.summary();124}125 126static void test_marker_separation(testing & t) {127 auto single_square_marker = segmentize_markers("pre_marker[marker]post_marker");128 auto single_diag_marker = segmentize_markers("pre_marker<marker>post_marker");129 auto paired_markers = segmentize_markers("<hello>world</hello>");130 auto double_different_markers = segmentize_markers("<hello>[hello]<world>[world]");131 auto in_between = segmentize_markers("im<blue>daba<dee>da[hey]");132 133 t.test("single_square_marker", [&] (testing & t) {134 t.assert_equal("first is text", segment_type::TEXT, single_square_marker[0].type);135 t.assert_equal("second is marker", segment_type::MARKER, single_square_marker[1].type);136 t.assert_equal("last is text", segment_type::TEXT, single_square_marker[2].type);137 138 t.assert_equal("first is 'pre_marker'", "pre_marker", single_square_marker[0].value);139 t.assert_equal("second is '[marker]'", "[marker]", single_square_marker[1].value);140 t.assert_equal("last is 'post_marker'", "post_marker", single_square_marker[2].value);141 });142 143 t.test("single_diagonal_marker", [&] (testing & t) {144 t.assert_equal("first is text", segment_type::TEXT, single_diag_marker[0].type);145 t.assert_equal("second is marker", segment_type::MARKER, single_diag_marker[1].type);146 t.assert_equal("last is text", segment_type::TEXT, single_diag_marker[2].type);147 148 t.assert_equal("first is 'pre_marker'", "pre_marker", single_diag_marker[0].value);149 t.assert_equal("second is '<marker>'", "<marker>", single_diag_marker[1].value);150 t.assert_equal("last is 'post_marker'", "post_marker", single_diag_marker[2].value);151 });152 153 t.test("paired_markers", [&] (testing & t) {154 t.assert_equal("first is marker", segment_type::MARKER, paired_markers[0].type);155 t.assert_equal("second is text", segment_type::TEXT, paired_markers[1].type);156 t.assert_equal("third is marker", segment_type::MARKER, paired_markers[2].type);157 158 t.assert_equal("first is '<hello>'", "<hello>", paired_markers[0].value);159 t.assert_equal("second is 'world'", "world", paired_markers[1].value);160 t.assert_equal("third is '</hello>'", "</hello>", paired_markers[2].value);161 });162 163 t.test("double_different_markers", [&] (testing & t) {164 t.assert_equal("first is marker", segment_type::MARKER, double_different_markers[0].type);165 t.assert_equal("second is marker", segment_type::MARKER, double_different_markers[1].type);166 t.assert_equal("third is marker", segment_type::MARKER, double_different_markers[2].type);167 t.assert_equal("fourth is marker", segment_type::MARKER, double_different_markers[3].type);168 169 t.assert_equal("first is '<hello>'", "<hello>", double_different_markers[0].value);170 t.assert_equal("second is '[hello]'", "[hello]", double_different_markers[1].value);171 t.assert_equal("third is '<world>'", "<world>", double_different_markers[2].value);172 t.assert_equal("fourth is '[world]'", "[world]", double_different_markers[3].value);173 });174 175 t.test("in_between", [&] (testing & t) {176 t.assert_equal("first is text", segment_type::TEXT, in_between[0].type);177 t.assert_equal("second is marker", segment_type::MARKER, in_between[1].type);178 t.assert_equal("third is text", segment_type::TEXT, in_between[2].type);179 t.assert_equal("fourth is marker", segment_type::MARKER, in_between[3].type);180 t.assert_equal("fifth is text", segment_type::TEXT, in_between[4].type);181 t.assert_equal("sixth is marker", segment_type::MARKER, in_between[5].type);182 183 t.assert_equal("first is 'im'", "im", in_between[0].value);184 t.assert_equal("second is '<blue>'", "<blue>", in_between[1].value);185 t.assert_equal("third is 'daba'", "daba", in_between[2].value);186 t.assert_equal("fourth is '<dee>'", "<dee>", in_between[3].value);187 t.assert_equal("fifth is 'da'", "da", in_between[4].value);188 t.assert_equal("sixth is '[hey]'", "[hey]", in_between[5].value);189 });190}191 192static void test_calculate_diff_split(testing & t) {193 t.test("calculate_diff_split basic", test_calculate_diff_split_basic);194 t.test("calculate_diff_split identical", test_calculate_diff_split_identical);195 t.test("calculate_diff_split common prefix", test_calculate_diff_split_common_prefix);196 t.test("calculate_diff_split common suffix", test_calculate_diff_split_common_suffix);197 t.test("calculate_diff_split common both", test_calculate_diff_split_common_both);198 t.test("calculate_diff_split empty cases", test_calculate_diff_split_empty_cases);199 t.test("calculate_diff_split no common", test_calculate_diff_split_no_common);200 t.test("calculate_diff_split single char", test_calculate_diff_split_single_char);201 t.test("calculate_diff_split overlaps", test_calculate_diff_split_overlaps);202 t.test("calculate_diff_split tag boundaries", test_calculate_diff_split_tag_boundaries);203 t.test("calculate_diff_split generation prompt", test_calculate_diff_split_generation_prompt);204}205 206static void test_calculate_diff_split_basic(testing & t) {207 diff_split result = calculate_diff_split("hello world", "hello test");208 t.assert_equal("prefix should be 'hello '", "hello ", result.prefix);209 t.assert_equal("left should be 'world'", "world", result.left);210 t.assert_equal("right should be 'test'", "test", result.right);211 t.assert_equal("suffix should be empty", "", result.suffix);212 213 result = calculate_diff_split("abc", "xyz");214 t.assert_equal("prefix should be empty", "", result.prefix);215 t.assert_equal("left should be 'abc'", "abc", result.left);216 t.assert_equal("right should be 'xyz'", "xyz", result.right);217 t.assert_equal("suffix should be empty", "", result.suffix);218 219 result = calculate_diff_split("prefixA suffix", "prefixB suffix");220 t.assert_equal("prefix should be 'prefix'", "prefix", result.prefix);221 t.assert_equal("left should be 'A'", "A", result.left);222 t.assert_equal("right should be 'B'", "B", result.right);223 t.assert_equal("suffix should be ' suffix'", " suffix", result.suffix);224}225 226static void test_calculate_diff_split_identical(testing & t) {227 diff_split result = calculate_diff_split("hello", "hello");228 t.assert_equal("prefix should be 'hello'", "hello", result.prefix);229 t.assert_equal("left should be empty", "", result.left);230 t.assert_equal("right should be empty", "", result.right);231 t.assert_equal("suffix should be empty", "", result.suffix);232 233 result = calculate_diff_split("", "");234 t.assert_equal("prefix should be empty", "", result.prefix);235 t.assert_equal("left should be empty", "", result.left);236 t.assert_equal("right should be empty", "", result.right);237 t.assert_equal("suffix should be empty", "", result.suffix);238 239 result = calculate_diff_split("a", "a");240 t.assert_equal("prefix should be 'a'", "a", result.prefix);241 t.assert_equal("left should be empty", "", result.left);242 t.assert_equal("right should be empty", "", result.right);243 t.assert_equal("suffix should be empty", "", result.suffix);244 245 result = calculate_diff_split("<row><row><row><your><boat><gently>", "<row><row><row><your><boat><gently>");246 t.assert_equal("prefix should be '<row><row><row><your><boat><gently>'", "<row><row><row><your><boat><gently>", result.prefix);247 t.assert_equal("left should be empty", "", result.left);248 t.assert_equal("right should be empty", "", result.right);249 t.assert_equal("suffix should be empty", "", result.suffix);250}251 252static void test_calculate_diff_split_common_prefix(testing & t) {253 diff_split result = calculate_diff_split("abcdef", "abcxyz");254 t.assert_equal("prefix should be 'abc'", "abc", result.prefix);255 t.assert_equal("left should be 'def'", "def", result.left);256 t.assert_equal("right should be 'xyz'", "xyz", result.right);257 t.assert_equal("suffix should be empty", "", result.suffix);258 259 result = calculate_diff_split("same", "sameagain");260 t.assert_equal("prefix should be 'same'", "same", result.prefix);261 t.assert_equal("left should be empty", "", result.left);262 t.assert_equal("right should be 'again'", "again", result.right);263 t.assert_equal("suffix should be empty", "", result.suffix);264 265 result = calculate_diff_split("test", "testing");266 t.assert_equal("prefix should be 'test'", "test", result.prefix);267 t.assert_equal("left should be empty", "", result.left);268 t.assert_equal("right should be 'ing'", "ing", result.right);269 t.assert_equal("suffix should be empty", "", result.suffix);270}271 272static void test_calculate_diff_split_common_suffix(testing & t) {273 diff_split result = calculate_diff_split("123end", "456end");274 t.assert_equal("prefix should be empty", "", result.prefix);275 t.assert_equal("left should be '123'", "123", result.left);276 t.assert_equal("right should be '456'", "456", result.right);277 t.assert_equal("suffix should be 'end'", "end", result.suffix);278 279 result = calculate_diff_split("start", "end");280 t.assert_equal("prefix should be empty", "", result.prefix);281 t.assert_equal("left should be 'start'", "start", result.left);282 t.assert_equal("right should be 'end'", "end", result.right);283 t.assert_equal("suffix should be empty", "", result.suffix);284 285 result = calculate_diff_split("abcsuffix", "xyzsuffix");286 t.assert_equal("prefix should be empty", "", result.prefix);287 t.assert_equal("left should be 'abc'", "abc", result.left);288 t.assert_equal("right should be 'xyz'", "xyz", result.right);289 t.assert_equal("suffix should be 'suffix'", "suffix", result.suffix);290}291 292static void test_calculate_diff_split_common_both(testing & t) {293 diff_split result = calculate_diff_split("helloXworld", "helloYworld");294 t.assert_equal("prefix should be 'hello'", "hello", result.prefix);295 t.assert_equal("left should be 'X'", "X", result.left);296 t.assert_equal("right should be 'Y'", "Y", result.right);297 t.assert_equal("suffix should be 'world'", "world", result.suffix);298 299 result = calculate_diff_split("ABCmiddleXYZ", "ABCdifferentXYZ");300 t.assert_equal("prefix should be 'ABC'", "ABC", result.prefix);301 t.assert_equal("left should be 'middle'", "middle", result.left);302 t.assert_equal("right should be 'different'", "different", result.right);303 t.assert_equal("suffix should be 'XYZ'", "XYZ", result.suffix);304 305 result = calculate_diff_split("startAend", "startBend");306 t.assert_equal("prefix should be 'start'", "start", result.prefix);307 t.assert_equal("left should be 'A'", "A", result.left);308 t.assert_equal("right should be 'B'", "B", result.right);309 t.assert_equal("suffix should be 'end'", "end", result.suffix);310 311 // Edge case: common prefix and suffix overlap312 result = calculate_diff_split("aa", "ab");313 t.assert_equal("prefix should be 'a'", "a", result.prefix);314 t.assert_equal("left should be 'a'", "a", result.left);315 t.assert_equal("right should be 'b'", "b", result.right);316 t.assert_equal("suffix should be empty", "", result.suffix);317}318 319static void test_calculate_diff_split_empty_cases(testing & t) {320 // Empty left, non-empty right321 diff_split result = calculate_diff_split("", "hello");322 t.assert_equal("prefix should be empty", "", result.prefix);323 t.assert_equal("left should be empty", "", result.left);324 t.assert_equal("right should be 'hello'", "hello", result.right);325 t.assert_equal("suffix should be empty", "", result.suffix);326 327 // Non-empty left, empty right328 result = calculate_diff_split("hello", "");329 t.assert_equal("prefix should be empty", "", result.prefix);330 t.assert_equal("left should be 'hello'", "hello", result.left);331 t.assert_equal("right should be empty", "", result.right);332 t.assert_equal("suffix should be empty", "", result.suffix);333 334 // Both empty335 result = calculate_diff_split("", "");336 t.assert_equal("prefix should be empty", "", result.prefix);337 t.assert_equal("left should be empty", "", result.left);338 t.assert_equal("right should be empty", "", result.right);339 t.assert_equal("suffix should be empty", "", result.suffix);340 341 // Left single char, empty right342 result = calculate_diff_split("a", "");343 t.assert_equal("prefix should be empty", "", result.prefix);344 t.assert_equal("left should be 'a'", "a", result.left);345 t.assert_equal("right should be empty", "", result.right);346 t.assert_equal("suffix should be empty", "", result.suffix);347 348 // Empty left, right single char349 result = calculate_diff_split("", "a");350 t.assert_equal("prefix should be empty", "", result.prefix);351 t.assert_equal("left should be empty", "", result.left);352 t.assert_equal("right should be 'a'", "a", result.right);353 t.assert_equal("suffix should be empty", "", result.suffix);354}355 356static void test_calculate_diff_split_no_common(testing & t) {357 diff_split result = calculate_diff_split("abc", "xyz");358 t.assert_equal("prefix should be empty", "", result.prefix);359 t.assert_equal("left should be 'abc'", "abc", result.left);360 t.assert_equal("right should be 'xyz'", "xyz", result.right);361 t.assert_equal("suffix should be empty", "", result.suffix);362 363 result = calculate_diff_split("left", "right");364 // The algorithm finds "t" as a common suffix since both strings end with 't'365 // This is the algorithm's actual behavior - it finds maximal common suffix366 t.assert_equal("prefix should be empty", "", result.prefix);367 t.assert_equal("left should be 'lef'", "lef", result.left);368 t.assert_equal("right should be 'righ'", "righ", result.right);369 t.assert_equal("suffix should be 't'", "t", result.suffix);370 371 result = calculate_diff_split("123", "456");372 t.assert_equal("prefix should be empty", "", result.prefix);373 t.assert_equal("left should be '123'", "123", result.left);374 t.assert_equal("right should be '456'", "456", result.right);375 t.assert_equal("suffix should be empty", "", result.suffix);376}377 378static void test_calculate_diff_split_single_char(testing & t) {379 diff_split result = calculate_diff_split("a", "b");380 t.assert_equal("prefix should be empty", "", result.prefix);381 t.assert_equal("left should be 'a'", "a", result.left);382 t.assert_equal("right should be 'b'", "b", result.right);383 t.assert_equal("suffix should be empty", "", result.suffix);384 385 result = calculate_diff_split("a", "a");386 t.assert_equal("prefix should be 'a'", "a", result.prefix);387 t.assert_equal("left should be empty", "", result.left);388 t.assert_equal("right should be empty", "", result.right);389 t.assert_equal("suffix should be empty", "", result.suffix);390 391 result = calculate_diff_split("a", "ab");392 t.assert_equal("prefix should be 'a'", "a", result.prefix);393 t.assert_equal("left should be empty", "", result.left);394 t.assert_equal("right should be 'b'", "b", result.right);395 t.assert_equal("suffix should be empty", "", result.suffix);396 397 result = calculate_diff_split("ab", "a");398 t.assert_equal("prefix should be 'a'", "a", result.prefix);399 t.assert_equal("left should be 'b'", "b", result.left);400 t.assert_equal("right should be empty", "", result.right);401 t.assert_equal("suffix should be empty", "", result.suffix);402}403 404static void test_calculate_diff_split_overlaps(testing & t) {405 // One string is substring of another406 diff_split result = calculate_diff_split("test", "testing");407 t.assert_equal("prefix should be 'test'", "test", result.prefix);408 t.assert_equal("left should be empty", "", result.left);409 t.assert_equal("right should be 'ing'", "ing", result.right);410 t.assert_equal("suffix should be empty", "", result.suffix);411 412 result = calculate_diff_split("testing", "test");413 t.assert_equal("prefix should be 'test'", "test", result.prefix);414 t.assert_equal("left should be 'ing'", "ing", result.left);415 t.assert_equal("right should be empty", "", result.right);416 t.assert_equal("suffix should be empty", "", result.suffix);417 418 // Similar strings with one extra char at start419 result = calculate_diff_split("Xtest", "Ytest");420 // The algorithm finds "test" as a common suffix since both strings end with "test"421 // This is the algorithm's actual behavior - it finds maximal common suffix422 t.assert_equal("prefix should be empty", "", result.prefix);423 t.assert_equal("left should be 'X'", "X", result.left);424 t.assert_equal("right should be 'Y'", "Y", result.right);425 t.assert_equal("suffix should be 'test'", "test", result.suffix);426 427 // Similar strings with one extra char at end428 result = calculate_diff_split("testX", "testY");429 t.assert_equal("prefix should be 'test'", "test", result.prefix);430 t.assert_equal("left should be 'X'", "X", result.left);431 t.assert_equal("right should be 'Y'", "Y", result.right);432 t.assert_equal("suffix should be empty", "", result.suffix);433 434 // Strings that are reverses435 result = calculate_diff_split("abc", "cba");436 t.assert_equal("prefix should be empty", "", result.prefix);437 t.assert_equal("left should be 'abc'", "abc", result.left);438 t.assert_equal("right should be 'cba'", "cba", result.right);439 t.assert_equal("suffix should be empty", "", result.suffix);440}441 442static void test_calculate_diff_split_tag_boundaries(testing & t) {443 // Test with unclosed XML tags444 diff_split result = calculate_diff_split("test<tag", "test>content");445 // The fix_tag_boundaries should move incomplete tags appropriately446 t.assert_true("prefix should start with 'test'", result.prefix.find("test") == 0);447 t.assert_true("should handle tag boundaries", result.left != "" || result.right != "" || result.suffix != "");448 449 // Test with unclosed brackets450 result = calculate_diff_split("test[", "test]value");451 t.assert_true("should handle bracket boundaries", result.left != "" || result.right != "" || result.suffix != "");452 453 // Test with partial tags on both sides454 result = calculate_diff_split("prefix<tag>", "prefix</tag>suffix");455 // fix_tag_boundaries moves the incomplete '<' from prefix to left/right456 t.assert_equal("prefix should be 'prefix'", "prefix", result.prefix);457 t.assert_equal("left should be '<tag>'", "<tag>", result.left);458 t.assert_equal("right should be '</tag>suffix'", "</tag>suffix", result.right);459 t.assert_equal("suffix should be empty", "", result.suffix);460 461 // Test with complex nested tags462 result = calculate_diff_split("prefix<div>content</div>", "prefix<div>different</div>");463 // Algorithm finds "ent</div>" as a common suffix because both strings end with it464 // This is the actual algorithm behavior, though not semantically ideal465 t.assert_equal("prefix should be 'prefix<div>'", "prefix<div>", result.prefix);466 t.assert_equal("left should be 'cont'", "cont", result.left);467 t.assert_equal("right should be 'differ'", "differ", result.right);468 t.assert_equal("suffix should be 'ent</div>'", "ent</div>", result.suffix);469 470 // Test with unclosed angle bracket471 result = calculate_diff_split("Hello <world>", "Hello test");472 t.assert_equal("prefix should be 'Hello '", "Hello ", result.prefix);473 t.assert_true("left should contain '<world>'", result.left.find("<world>") != std::string::npos);474 t.assert_equal("right should be 'test'", "test", result.right);475 t.assert_equal("suffix should be empty", "", result.suffix);476 477 // Test with unclosed square bracket478 result = calculate_diff_split("test [array]", "test other");479 t.assert_equal("prefix should be 'test '", "test ", result.prefix);480 t.assert_true("left should contain '[array]'", result.left.find("[array]") != std::string::npos);481 t.assert_equal("right should be 'other'", "other", result.right);482 t.assert_equal("suffix should be empty", "", result.suffix);483 484 // Test empty prefix and suffix with tags485 result = calculate_diff_split("<tag>left</tag>", "<tag>righ</tag>");486 t.assert_equal("prefix should be '<tag>'", "<tag>", result.prefix);487 t.assert_equal("left should be 'left'", "left", result.left);488 t.assert_equal("right should be 'righ'", "righ", result.right);489 t.assert_equal("suffix should be '</tag>'", "</tag>", result.suffix);490 491 {492 // real case from template tests, simplified493 std::string left = "PREFIX</think>Sure";494 std::string right = "PREFIX<think>Lemme think</think>Sure";495 result = calculate_diff_split(left, right);496 t.assert_equal("prefix should be PREFIX", "PREFIX", result.prefix);497 t.assert_equal("suffix should be </think>Sure", "</think>Sure", result.suffix);498 t.assert_equal("left should be empty", "", result.left);499 t.assert_equal("right should be <think>Lemme think", "<think>Lemme think", result.right);500 }501 502 {503 // Real case: special tokens with |> boundary issue504 // The suffix starts with |> which should be moved to complete <|END_RESPONSE and <|END_ACTION505 std::string prefix = "SOME_PREFIX";506 std::string suffix = "|><|END_OF_TURN_TOKEN|><|START_OF_TURN_TOKEN|><|CHATBOT_TOKEN|>";507 std::string left_diff = "<|START_RESPONSE|>Let me help you.<|END_RESPONSE";508 std::string right_diff =509 "<|START_THINKING|><|END_THINKING|><|START_ACTION|>[\n"510 " {\"tool_call_id\": \"0\", \"tool_name\": \"test_function_name\", "511 "\"parameters\": {\"param1\": \"value1\", \"param2\": \"value2\"}}\n"512 "]<|END_ACTION";513 514 std::string left = prefix + left_diff + suffix;515 std::string right = prefix + right_diff + suffix;516 result = calculate_diff_split(left, right);517 518 t.assert_equal("special token prefix", prefix, result.prefix);519 // The |> should be moved from suffix to complete the tokens520 t.assert_equal("special token left", "<|START_RESPONSE|>Let me help you.<|END_RESPONSE|>", result.left);521 t.assert_true("special token right ends with |>", result.right.find("<|END_ACTION|>") != std::string::npos);522 t.assert_equal("special token suffix", "<|END_OF_TURN_TOKEN|><|START_OF_TURN_TOKEN|><|CHATBOT_TOKEN|>",523 result.suffix);524 }525}526 527static void test_calculate_diff_split_generation_prompt(testing & t) {528 // ChatML thinking template: left is a prefix of right, generation_prompt is the appended part.529 // The trailing \n in left matches the trailing \n in the generation_prompt, causing530 // the suffix matcher to steal it and rotate the diff result.531 {532 // Simplified reproduction: left ends with \n, right = left + "<|im_start|>assistant\n<think>\n"533 std::string left = "<|im_start|>user\nHello<|im_end|>\n";534 std::string right = left + "<|im_start|>assistant\n<think>\n";535 diff_split result = calculate_diff_split(left, right);536 t.assert_equal("chatml prefix", left, result.prefix);537 t.assert_equal("chatml left", "", result.left);538 t.assert_equal("chatml right should be generation prompt",539 "<|im_start|>assistant\n<think>\n", result.right);540 t.assert_equal("chatml suffix", "", result.suffix);541 }542 543 {544 // More realistic: longer conversation ending with tool_response545 std::string common =546 "<|im_start|>system\nYou are a helpful assistant.<|im_end|>\n"547 "<|im_start|>user\nSearch for files<|im_end|>\n"548 "<|im_start|>assistant\n<think>\nLet me search.\n</think>\n\n"549 "<tool_call>\n<function=search>\n</function>\n</tool_call><|im_end|>\n"550 "<|im_start|>user\n<tool_response>\nNo files found\n</tool_response><|im_end|>\n";551 std::string left = common;552 std::string right = common + "<|im_start|>assistant\n<think>\n";553 diff_split result = calculate_diff_split(left, right);554 t.assert_equal("tool_response left", "", result.left);555 t.assert_equal("tool_response right should be generation prompt",556 "<|im_start|>assistant\n<think>\n", result.right);557 }558}559 560static void test_until_common_prefix(testing & t) {561 t.test("until_common_prefix basic", test_until_common_prefix_basic);562}563 564static void test_until_common_prefix_basic(testing & t) {565 // Test case from the user request566 std::string result = until_common_prefix("<function name=foo><arg name=bar>", "<arg name=bar>", "<arg name=baz>");567 t.assert_equal("untilCommonPrefix should return '<function name=foo>'", "<function name=foo>", result);568 569 // Additional test cases to ensure robustness570 // Test with different common prefix lengths571 result = until_common_prefix("prefix<test>suffix", "<test>different", "<test>other");572 t.assert_equal("should return 'prefix'", "prefix", result);573 574 // Test when common prefix is at the start575 result = until_common_prefix("<common>rest", "<common>left", "<common>right");576 t.assert_equal("should return empty string when common prefix at start", "", result);577 578 // Test when there's no common prefix579 result = until_common_prefix("something", "left", "right");580 t.assert_equal("should return empty string when no common prefix", "", result);581 582 // Test with empty strings583 result = until_common_prefix("test", "", "right");584 t.assert_equal("should return empty string when left is empty", "", result);585 586 // Test with longer common prefix587 result = until_common_prefix("abcXYZ<shared_prefix>rest", "<shared_prefix>left", "<shared_prefix>right");588 t.assert_equal("should return 'abcXYZ'", "abcXYZ", result);589}590 591static void test_after_common_suffix(testing & t) {592 t.test("after_common_suffix basic", test_after_common_suffix_basic);593}594 595static void test_after_common_suffix_basic(testing & t) {596 // Test case from the user request597 std::string result = after_common_suffix("<function name=foo><arg name=bar>100</arg></function>",598 "<arg name=bar>100</arg>",599 "<arg name=baz>535</arg>");600 t.assert_equal("afterCommonSuffix should return '</function>'", "</function>", result);601 602 // Test when common suffix is at the end603 result = after_common_suffix("rest<common>", "left<common>", "right<common>");604 t.assert_equal("should return empty string when common suffix at end", "", result);605 606 // Test with empty strings607 result = after_common_suffix("test", "left", "");608 t.assert_equal("should return empty string when right is empty", "", result);609 610 // Test case with XML-like structure similar to the main example611 result = after_common_suffix("<outer><inner>value</inner></outer>",612 "<inner>value</inner>",613 "<inner>different</inner>");614 t.assert_equal("should return '</outer>'", "</outer>", result);615 616 // Test with longer common suffix appearing at the end of full617 result = after_common_suffix("prefix<shared>rest</shared>", "prefix<shared>left</shared>", "prefix<shared>right</shared>");618 t.assert_equal("should return '' when common suffix is at end of full", "", result);619 620 // Test with common suffix appearing in middle but not at end621 result = after_common_suffix("<tag>content</tag><extra>", "<tag>value</tag>", "<tag>other</tag>");622 t.assert_equal("should return '<extra>' when common suffix appears before end", "<extra>", result);623 624 // Test with multi-character common suffix at the very end of full625 result = after_common_suffix("start<middle>end</middle>", "prefix<middle>left</middle>", "prefix<middle>right</middle>");626 t.assert_equal("should return '' when common suffix </middle> is at end of full", "", result);627}628 629static void test_compare_variants(testing & t) {630 t.test("compare_variants basic", test_compare_variants_basic);631 t.test("compare_variants messages modifier", test_compare_variants_messages_modifier);632 t.test("compare_variants tools modifier", test_compare_variants_tools_modifier);633 t.test("compare_variants both modifiers", test_compare_variants_both_modifiers);634 t.test("compare_variants template failure", test_compare_variants_template_failure);635 t.test("compare_variants identity", test_compare_variants_identity);636}637 638static void test_compare_variants_basic(testing & t) {639 // Create a simple template that just echoes messages640 common_chat_template tmpl("{{ messages[0]['content'] }}", "", "");641 642 template_params params;643 params.messages = json::array({644 json {{"role", "user"}, {"content", "Hello"}}645 });646 647 auto modifier = [](template_params & p) {648 p.messages[0]["content"] = "World";649 };650 651 auto result = ::compare_variants(tmpl, params, modifier);652 653 if (!t.assert_true("result should have value", result.has_value())) {654 return;655 }656 // The template might not output anything if messages is empty or format is different657 // Check that we get a valid result658 t.assert_true("prefix or left should have content", !result->diff.prefix.empty() || !result->diff.left.empty());659}660 661static void test_compare_variants_messages_modifier(testing & t) {662 // Test with messages modifier only663 common_chat_template tmpl("{% for message in messages %}{{ message['role'] }}:{{ message['content'] }}{% endfor %}", "", "");664 665 template_params params;666 params.messages = json::array({667 json {{"role", "user"}, {"content", "A"}}668 });669 670 auto modifier = [](template_params & p) {671 p.messages[0]["content"] = "B";672 };673 674 std::optional<compare_variants_result> result = ::compare_variants(tmpl, params, modifier);675 676 if (!t.assert_true("result should have value", result.has_value())) {677 return;678 }679 t.assert_equal("left should be 'A'", "A", result->diff.left);680 t.assert_equal("right should be 'B'", "B", result->diff.right);681}682 683static void test_compare_variants_tools_modifier(testing & t) {684 // Test with tools modifier only685 common_chat_template tmpl(686 "{% for tool in tools %}{{ tool['name'] }}{% endfor %}", "", "");687 688 template_params params;689 params.tools = json::array({690 json {{"name", "foo"}}691 });692 693 auto modifier = [](template_params & p) {694 p.tools[0]["name"] = "bar";695 };696 697 auto result = ::compare_variants(tmpl, params, modifier);698 699 if (!t.assert_true("result should have value", result.has_value())) {700 return;701 }702 t.assert_equal("left should be 'foo'", "foo", result->diff.left);703 t.assert_equal("right should be 'bar'", "bar", result->diff.right);704}705 706static void test_compare_variants_both_modifiers(testing & t) {707 // Test with both messages and tools modifiers using the for loop approach708 common_chat_template tmpl(709 "{% for message in messages %}{{ message['role'] }}:{{ message['content'] }}{% endfor %}", "", "");710 711 template_params params;712 params.messages = json::array({713 json {{"role", "user"}, {"content", "A"}}714 });715 716 auto modifier = [](template_params & p) {717 p.messages[0]["content"] = "B";718 p.messages[0]["role"] = "newuser";719 };720 721 auto result = ::compare_variants(tmpl, params, modifier);722 723 if (!t.assert_true("result should have value", result.has_value())) {724 return;725 }726 t.assert_equal("left should be 'user:A'", "user:A", result->diff.left);727 t.assert_equal("right should be 'newuser:B'", "newuser:B", result->diff.right);728}729 730static void test_compare_variants_template_failure(testing & t) {731 // Test with template that causes failure during application (not construction)732 // We use a valid template syntax but one that will fail during application733 common_chat_template tmpl("{{ messages.cahoot()[0]['nonexistent_field'] }}", "", "");734 735 template_params params;736 params.messages = json::array({737 json {{"role", "user"}, {"content", "Hello"}}738 });739 740 auto modifier = [](template_params & p) {741 p.messages[0]["content"] = "World";742 };743 744 auto result = ::compare_variants(tmpl, params, modifier);745 746 t.assert_true("result should be nullopt on template failure", !result.has_value());747}748 749static void test_compare_variants_identity(testing & t) {750 // Test with identity modifier (no change)751 common_chat_template tmpl("{{ messages[0]['content'] }}", "", "");752 753 template_params params;754 params.messages = json::array({755 json {{"role", "user"}, {"content", "Hello"}}756 });757 758 // No modifier - should use identity759 auto result = ::compare_variants(tmpl, params, nullptr);760 761 if (!t.assert_true("result should have value", result.has_value())) {762 return;763 }764 t.assert_equal("prefix should be 'Hello'", "Hello", result->diff.prefix);765 t.assert_equal("left should be empty", "", result->diff.left);766 t.assert_equal("right should be empty", "", result->diff.right);767 t.assert_equal("suffix should be empty", "", result->diff.suffix);768}769 770// ============================================================================771// Seed-OSS Template Tool Calling Analysis Tests772// ============================================================================773 774static void test_seed_oss_tool_analysis(testing & t) {775 t.test("Seed-OSS tool presence", test_seed_oss_tool_presence);776 t.test("Seed-OSS call count", test_seed_oss_call_count);777 t.test("Seed-OSS function names", test_seed_oss_function_names);778 t.test("Seed-OSS argument count", test_seed_oss_argument_count);779 t.test("Seed-OSS args presence", test_seed_oss_args_presence);780 t.test("Seed-OSS tool with reasoning", test_seed_oss_tool_with_reasoning);781}782 783// Helper to load Seed-OSS template784static common_chat_template load_seed_oss_template(testing & t) {785 std::string template_path = "models/templates/ByteDance-Seed-OSS.jinja";786 std::ifstream fin(template_path, std::ios::binary);787 std::ostringstream buf;788 if (fin.is_open()) {789 buf << fin.rdbuf();790 }791 std::string template_source = buf.str();792 common_chat_template tmpl(template_source, "", "");793 t.assert_true("Seed-OSS template loaded successfully", template_source.length() > 0);794 return tmpl;795}796 797// Helper to build tool call JSON798static json build_tool_call(const std::string & name, const json & args, const std::string & id = "call_001") {799 return json{800 {"id", id},801 {"type", "function"},802 {"function", json{803 {"name", name},804 {"arguments", args}805 }}806 };807}808 809// Helper to build tools definition810static json build_tools_definition() {811 json parameters_schema = json::object();812 parameters_schema["type"] = "object";813 parameters_schema["properties"] = json::object();814 parameters_schema["properties"]["param1"] = json::object({815 {"type", "string"},816 {"description", "First parameter"}817 });818 parameters_schema["properties"]["param2"] = json::object({819 {"type", "string"},820 {"description", "Second parameter"}821 });822 parameters_schema["required"] = json::array({"param1", "param2"});823 824 return json::array({825 json{826 {"type", "function"},827 {"function", json{828 {"name", "test_function_name"},829 {"description", "A test function for debugging"},830 {"parameters", parameters_schema}831 }}832 }833 });834}835 836// T1: Compare with/without tool call (user, assistant)837static void test_seed_oss_tool_presence(testing & t) {838 common_chat_template tmpl = load_seed_oss_template(t);839 840 json assistant_no_tools = json{841 {"role", "assistant"},842 {"content", "Let me help you."}843 };844 845 json assistant_with_tools = json{846 {"role", "assistant"},847 {"content", nullptr},848 {"tool_calls", json::array({849 build_tool_call("test_function_name", json::object({{"param1", "value1"}, {"param2", "value2"}}))850 })}851 };852 853 json user_msg = json{854 {"role", "user"},855 {"content", "Hello, please help me."}856 };857 858 template_params params_no_tools;859 params_no_tools.messages = json::array({user_msg, assistant_no_tools});860 params_no_tools.tools = build_tools_definition();861 params_no_tools.add_generation_prompt = false;862 params_no_tools.enable_thinking = true;863 864 template_params params_with_tools;865 params_with_tools.messages = json::array({user_msg, assistant_with_tools});866 params_with_tools.tools = build_tools_definition();867 params_with_tools.add_generation_prompt = false;868 params_with_tools.enable_thinking = true;869 870 auto result = ::compare_variants(tmpl, params_no_tools,871 [&](template_params & p) {872 p.messages = params_with_tools.messages;873 });874 875 if (!t.assert_true("T1 result should have value", result.has_value())) {876 return;877 }878 879 const auto & diff = result->diff;880 t.assert_true("T1 prefix should contain system", diff.prefix.find("system") != std::string::npos);881 t.assert_true("T1 prefix should contain user", diff.prefix.find("user") != std::string::npos);882 t.assert_true("T1 prefix should contain assistant", diff.prefix.find("assistant") != std::string::npos);883 884 // Left should be the assistant content without tool885 t.assert_equal("T1 left should contain 'Let me help you.'", "Let me help you.", diff.left);886 887 // Right should contain the tool call markers888 t.assert_true("T1 right should contain tool_call begin", diff.right.find("<seed:tool_call>") != std::string::npos);889 t.assert_true("T1 right should contain function tag", diff.right.find("<function=test_function_name>") != std::string::npos);890 t.assert_true("T1 right should contain parameter=param1", diff.right.find("<parameter=param1>") != std::string::npos);891 t.assert_true("T1 right should contain parameter=param2", diff.right.find("<parameter=param2>") != std::string::npos);892 t.assert_true("T1 right should contain value1", diff.right.find("value1") != std::string::npos);893 t.assert_true("T1 right should contain value2", diff.right.find("value2") != std::string::npos);894 t.assert_true("T1 right should contain tool_call end", diff.right.find("</seed:tool_call>") != std::string::npos);895 896 // Suffix should be the eos token897 t.assert_equal("T1 suffix should be '<seed:eos>'", "<seed:eos>", diff.suffix);898}899 900// T2: Compare one vs two tool calls901static void test_seed_oss_call_count(testing & t) {902 common_chat_template tmpl = load_seed_oss_template(t);903 904 json assistant_one_call = json{905 {"role", "assistant"},906 {"content", nullptr},907 {"tool_calls", json::array({908 build_tool_call("test_function_name", json::object({{"param1", "value1"}, {"param2", "value2"}}))909 })}910 };911 912 json assistant_two_calls = json{913 {"role", "assistant"},914 {"content", nullptr},915 {"tool_calls", json::array({916 build_tool_call("test_function_name", json::object({{"param1", "value1"}, {"param2", "value2"}})),917 build_tool_call("test_function_name", json::object({{"param1", "value3"}, {"param2", "value4"}}), "call_002")918 })}919 };920 921 json user_msg = json{922 {"role", "user"},923 {"content", "Hello, please help me."}924 };925 926 template_params params_one;927 params_one.messages = json::array({user_msg, assistant_one_call});928 params_one.tools = build_tools_definition();929 params_one.add_generation_prompt = false;930 params_one.enable_thinking = true;931 932 auto result = ::compare_variants(tmpl, params_one,933 [&](template_params & p) {934 p.messages = json::array({user_msg, assistant_two_calls});935 });936 937 if (!t.assert_true("T2 result should have value", result.has_value())) {938 return;939 }940 941 const auto & diff = result->diff;942 943 // Prefix should include the first tool call944 t.assert_true("T2 prefix should contain first tool_call begin", diff.prefix.find("<seed:tool_call>") != std::string::npos);945 t.assert_true("T2 prefix should contain first function", diff.prefix.find("<function=test_function_name>") != std::string::npos);946 t.assert_true("T2 prefix should contain value1", diff.prefix.find("value1") != std::string::npos);947 t.assert_true("T2 prefix should contain value2", diff.prefix.find("value2") != std::string::npos);948 t.assert_true("T2 prefix should contain first tool_call end", diff.prefix.find("</seed:tool_call>") != std::string::npos);949 950 // Left should be empty (no second tool call in variant A)951 t.assert_equal("T2 left should be empty", "", diff.left);952 953 // Right should contain the second tool call954 t.assert_true("T2 right should contain second tool_call begin", diff.right.find("<seed:tool_call>") != std::string::npos);955 t.assert_true("T2 right should contain second function", diff.right.find("<function=test_function_name>") != std::string::npos);956 t.assert_true("T2 right should contain value3", diff.right.find("value3") != std::string::npos);957 t.assert_true("T2 right should contain value4", diff.right.find("value4") != std::string::npos);958 t.assert_true("T2 right should contain second tool_call end", diff.right.find("</seed:tool_call>") != std::string::npos);959 960 // Suffix should end with the eos token961 t.assert_equal("T2 suffix should end with '<seed:eos>'", "<seed:eos>", diff.suffix.substr(diff.suffix.length() - 10, 10));962}963 964// T3: Compare different function names965static void test_seed_oss_function_names(testing & t) {966 common_chat_template tmpl = load_seed_oss_template(t);967 968 // Build tools with two different function names969 json parameters_schema = json::object();970 parameters_schema["type"] = "object";971 parameters_schema["properties"] = json::object();972 parameters_schema["properties"]["arg1"] = json::object({973 {"type", "string"},974 {"description", "Argument 1"}975 });976 parameters_schema["required"] = json::array({"arg1"});977 978 json tools = json::array({979 json{980 {"type", "function"},981 {"function", json{982 {"name", "func_alpha"},983 {"description", "First function"},984 {"parameters", parameters_schema}985 }}986 },987 json{988 {"type", "function"},989 {"function", json{990 {"name", "func_beta"},991 {"description", "Second function"},992 {"parameters", parameters_schema}993 }}994 }995 });996 997 json assistant_func_alpha = json{998 {"role", "assistant"},999 {"content", nullptr},1000 {"tool_calls", json::array({1001 build_tool_call("func_alpha", json::object({{"arg1", "test_value"}}))1002 })}1003 };1004 1005 json assistant_func_beta = json{1006 {"role", "assistant"},1007 {"content", nullptr},1008 {"tool_calls", json::array({1009 build_tool_call("func_beta", json::object({{"arg1", "test_value"}}))1010 })}1011 };1012 1013 json user_msg = json{1014 {"role", "user"},1015 {"content", "Hello"}1016 };1017 1018 template_params params_alpha;1019 params_alpha.messages = json::array({user_msg, assistant_func_alpha});1020 params_alpha.tools = tools;1021 params_alpha.add_generation_prompt = false;1022 params_alpha.enable_thinking = true;1023 1024 auto result = ::compare_variants(tmpl, params_alpha,1025 [&](template_params & p) {1026 p.messages = json::array({user_msg, assistant_func_beta});1027 });1028 1029 if (!t.assert_true("T3 result should have value", result.has_value())) {1030 return;1031 }1032 1033 const auto & diff = result->diff;1034 1035 bool func_alpha_in_left = diff.left.find("func_alpha") != std::string::npos;1036 bool func_alpha_in_prefix = diff.prefix.find("func_alpha") != std::string::npos;1037 bool func_beta_in_right = diff.right.find("func_beta") != std::string::npos;1038 bool func_beta_in_prefix = diff.prefix.find("func_beta") != std::string::npos;1039 bool func_beta_in_suffix = diff.suffix.find("func_beta") != std::string::npos;1040 1041 // Left should contain func_alpha (or be in prefix)1042 t.assert_true("T3 left should contain func_alpha (or prefix)", func_alpha_in_left || func_alpha_in_prefix);1043 1044 // Right should contain func_beta1045 t.assert_true("T3 right should contain func_beta", func_beta_in_right || func_beta_in_prefix || func_beta_in_suffix);1046 1047 // Both should have the same parameter value (in common parts, not in diffs)1048 // Since both have same args, test_value will be in prefix/suffix1049 t.assert_true("T3 diff should contain test_value (in prefix or suffix)",1050 diff.prefix.find("test_value") != std::string::npos || diff.suffix.find("test_value") != std::string::npos);1051}1052 1053// T4: Compare different argument counts (zero, one, two parameters)1054static void test_seed_oss_argument_count(testing & t) {1055 common_chat_template tmpl = load_seed_oss_template(t);1056 1057 // Build tools with 0, 1, or 2 required parameters1058 json params_2_required = json::object();1059 params_2_required["type"] = "object";1060 params_2_required["properties"] = json::object();1061 params_2_required["properties"]["arg1"] = json::object({1062 {"type", "string"},1063 {"description", "Argument 1"}1064 });1065 params_2_required["properties"]["arg2"] = json::object({1066 {"type", "string"},1067 {"description", "Argument 2"}1068 });1069 params_2_required["required"] = json::array({"arg1", "arg2"});1070 1071 json params_1_required = json::object();1072 params_1_required["type"] = "object";1073 params_1_required["properties"] = json::object();1074 params_1_required["properties"]["arg1"] = json::object({1075 {"type", "string"},1076 {"description", "Argument 1"}1077 });1078 params_1_required["required"] = json::array({"arg1"});1079 1080 json tools = json::array({1081 json{1082 {"type", "function"},1083 {"function", json{1084 {"name", "test_func"},1085 {"description", "Test function"},1086 {"parameters", params_2_required}1087 }}1088 }1089 });1090 1091 // Test: zero args vs one arg1092 json assistant_zero_args = json{1093 {"role", "assistant"},1094 {"content", nullptr},1095 {"tool_calls", json::array({1096 build_tool_call("test_func", json::object())1097 })}1098 };1099 1100 json assistant_one_arg = json{1101 {"role", "assistant"},1102 {"content", nullptr},1103 {"tool_calls", json::array({1104 build_tool_call("test_func", json::object({{"arg1", "value1"}}))1105 })}1106 };1107 1108 json assistant_two_args = json{1109 {"role", "assistant"},1110 {"content", nullptr},1111 {"tool_calls", json::array({1112 build_tool_call("test_func", json::object({{"arg1", "value1"}, {"arg2", "value2"}}))1113 })}1114 };1115 1116 json user_msg = json{1117 {"role", "user"},1118 {"content", "Hello"}1119 };1120 1121 // Test zero vs one1122 template_params params_zero;1123 params_zero.messages = json::array({user_msg, assistant_zero_args});1124 params_zero.tools = tools;1125 params_zero.add_generation_prompt = false;1126 params_zero.enable_thinking = true;1127 1128 auto result_zero_one = ::compare_variants(tmpl, params_zero,1129 [&](template_params & p) {1130 p.messages = json::array({user_msg, assistant_one_arg});1131 });1132 1133 if (!t.assert_true("T4 zero vs one result should have value", result_zero_one.has_value())) {1134 return;1135 }1136 t.assert_true("T4 zero vs one left should be empty or minimal", result_zero_one->diff.left.empty() || result_zero_one->diff.left == "");1137 t.assert_true("T4 zero vs one right should contain arg1", result_zero_one->diff.right.find("arg1") != std::string::npos);1138 1139 // Test one vs two1140 template_params params_one;1141 params_one.messages = json::array({user_msg, assistant_one_arg});1142 params_one.tools = tools;1143 params_one.add_generation_prompt = false;1144 params_one.enable_thinking = true;1145 1146 auto result_one_two = ::compare_variants(tmpl, params_one,1147 [&](template_params & p) {1148 p.messages = json::array({user_msg, assistant_two_args});1149 });1150 1151 if (!t.assert_true("T4 one vs two result should have value", result_one_two.has_value())) {1152 return;1153 }1154 1155 const auto & diff4 = result_one_two->diff;1156 t.assert_true("T4 one vs two left should contain arg1 (or prefix)",1157 diff4.left.find("arg1") != std::string::npos || diff4.prefix.find("arg1") != std::string::npos);1158 t.assert_true("T4 one vs two right should contain arg1 (or prefix)",1159 diff4.right.find("arg1") != std::string::npos || diff4.prefix.find("arg1") != std::string::npos);1160 t.assert_true("T4 one vs two right should contain arg2 (or prefix/suffix)",1161 diff4.right.find("arg2") != std::string::npos || diff4.prefix.find("arg2") != std::string::npos || diff4.suffix.find("arg2") != std::string::npos);1162}1163 1164// T5: Compare different argument values1165static void test_seed_oss_args_presence(testing & t) {1166 common_chat_template tmpl = load_seed_oss_template(t);1167 1168 json assistant_same_arg = json{1169 {"role", "assistant"},1170 {"content", nullptr},1171 {"tool_calls", json::array({1172 build_tool_call("test_function_name", json::object({{"param1", "value1"}}))1173 })}1174 };1175 1176 json assistant_other_arg = json{1177 {"role", "assistant"},1178 {"content", nullptr},1179 {"tool_calls", json::array({1180 build_tool_call("test_function_name", json::object({{"param2", "value2"}}))1181 })}1182 };1183 1184 json assistant_both_args = json{1185 {"role", "assistant"},1186 {"content", nullptr},1187 {"tool_calls", json::array({1188 build_tool_call("test_function_name", json::object({{"param1", "value1"}, {"param2", "value2"}}))1189 })}1190 };1191 1192 json user_msg = json{1193 {"role", "user"},1194 {"content", "Hello"}1195 };1196 1197 template_params params_same;1198 params_same.messages = json::array({user_msg, assistant_same_arg});1199 params_same.tools = build_tools_definition();1200 params_same.add_generation_prompt = false;