Applies the template to 1 user message w/ add_generation_prompt=true, then w/ the test message w/ add_generation_prompt=false, gets the diff, removes any end tokens and parses the result w/ the grammar, checking that the parsed message is the same as the test_message */
| 318 | the parsed message is the same as the test_message |
| 319 | */ |
| 320 | static void test_templates(const struct common_chat_templates * tmpls, const std::vector<std::string> & end_tokens, |
| 321 | const common_chat_msg & test_message, |
| 322 | const std::vector<common_chat_tool> & tools = {}, |
| 323 | const std::string & expected_delta = "", |
| 324 | bool expect_grammar_triggered = true, |
| 325 | bool test_grammar_if_triggered = true, |
| 326 | common_reasoning_format reasoning_format = COMMON_REASONING_FORMAT_NONE, |
| 327 | bool ignore_whitespace_differences = false |
| 328 | ) { |
| 329 | common_chat_msg user_message; |
| 330 | user_message.role = "user"; |
| 331 | user_message.content = "Hello, world!"; |
| 332 | |
| 333 | for (const auto & tool_choice : std::vector<common_chat_tool_choice> {COMMON_CHAT_TOOL_CHOICE_AUTO, COMMON_CHAT_TOOL_CHOICE_REQUIRED}) { |
| 334 | auto data = init_delta(tmpls, end_tokens, user_message, test_message, tools, tool_choice); |
| 335 | if (!expected_delta.empty()) { |
| 336 | if (ignore_whitespace_differences) { |
| 337 | assert_equals(string_strip(expected_delta), string_strip(data.delta)); |
| 338 | } else { |
| 339 | assert_equals(expected_delta, data.delta); |
| 340 | } |
| 341 | } |
| 342 | |
| 343 | if (expect_grammar_triggered) { |
| 344 | // TODO @ngxson : refactor common_chat_parse to avoid passing format/reasoning_format every time |
| 345 | common_chat_parser_params params; |
| 346 | params.format = data.params.format; |
| 347 | params.reasoning_format = reasoning_format; |
| 348 | const auto msg = common_chat_parse(data.delta, /* is_partial= */ false, params); |
| 349 | assert_msg_equals(test_message, msg, ignore_whitespace_differences); |
| 350 | } |
| 351 | |
| 352 | if (!test_message.tool_calls.empty()) { |
| 353 | GGML_ASSERT(!data.params.grammar.empty()); |
| 354 | } |
| 355 | if (!data.params.grammar.empty()) { |
| 356 | auto grammar = build_grammar(data.params.grammar); |
| 357 | if (!grammar) { |
| 358 | throw std::runtime_error("Failed to build grammar"); |
| 359 | } |
| 360 | auto earliest_trigger_pos = std::string::npos; |
| 361 | auto constrained = data.delta; |
| 362 | for (const auto & trigger : data.params.grammar_triggers) { |
| 363 | size_t pos = std::string::npos; |
| 364 | std::smatch match; |
| 365 | switch (trigger.type) { |
| 366 | case COMMON_GRAMMAR_TRIGGER_TYPE_WORD: |
| 367 | { |
| 368 | const auto & word = trigger.value; |
| 369 | pos = constrained.find(word); |
| 370 | break; |
| 371 | } |
| 372 | case COMMON_GRAMMAR_TRIGGER_TYPE_PATTERN: |
| 373 | { |
| 374 | const auto & pattern = trigger.value; |
| 375 | if (std::regex_search(constrained, match, std::regex(pattern))) { |
| 376 | pos = match.position(1); |
| 377 | } |
no test coverage detected