Repository: ggerganov/llama.cpp
File: tests/test-chat-parser.cpp (C++)

Code before:
}

template<typename T>
static void assert_equals(const char* label, const T& expected, const T& actual) {
    if (!(expected == actual)) {
        std::ostringstream oss; oss << label << "\nExpected: " << expected << "\nActual: " << actual;
        throw std::runtime_error(oss.str());
    }
}

static void test_deepseek_v3_1_tool_calls() {
    //common_log_set_verbosity_thold(LOG_DEFAULT_DEBUG);
    // variant: happy path for when it works as the model card says it should
    const char* variant = "simple";
    common_chat_syntax syntax = {
        /* .format = */ COMMON_CHAT_FORMAT_DEEPSEEK_V3_1,
        /* .reasoning_format = */ COMMON_REASONING_FORMAT_DEEPSEEK,
        /* .reasoning_in_content = */ false,
        /* .thinking_forced_open = */ false,
        /* .parse_tool_calls = */ true,
    };
    const std::string input = "<｜tool▁calls▁begin｜><｜tool▁call▁begin｜>get_time<｜tool▁sep｜>{\"city\": \"Tokyo\"}<｜tool▁call▁end｜><｜tool▁calls▁end｜>";
    auto msg = common_chat_parse(input, false, syntax);
    assert_equals(variant, static_cast<std::size_t>(1), msg.tool_calls.size());
    assert_equals(variant, std::string("get_time"), msg.tool_calls[0].name);
    // JSON arguments are dumped without spaces
    assert_equals(variant, std::string("{\"city\":\"Tokyo\"}"), msg.tool_calls[0].arguments);
    assert_equals(variant, std::string(""), msg.content);
    assert_equals(variant, std::string(""), msg.reasoning_content);

    // variant: function + fenced JSON
    {
        const char* variant = "fenced";
        const std::string in = "<｜tool▁calls▁begin｜><｜tool▁call▁begin｜>function<｜tool▁sep｜>get_time\n```json\n{\"city\": \"Tokyo\"}\n```<｜tool▁call▁end｜><｜tool▁calls▁end｜>";
        auto m = common_chat_parse(in, false, syntax);
        assert_equals<std::size_t>(variant, 1, m.tool_calls.size());
        assert_equals(variant, std::string("get_time"), m.tool_calls[0].name);
        assert_equals(variant, std::string("{\"city\":\"Tokyo\"}"), m.tool_calls[0].arguments);
        assert_equals(variant, std::string(""), m.content);
        assert_equals(variant, std::string(""), m.reasoning_content);
    }

    // variant: function + fenced JSON + thinking open
    {
        common_chat_syntax syntax = {
            /* .format = */ COMMON_CHAT_FORMAT_DEEPSEEK_V3_1,
            /* .reasoning_format = */ COMMON_REASONING_FORMAT_DEEPSEEK,
            /* .reasoning_in_content = */ false,
            /* .thinking_forced_open = */ true,
            /* .parse_tool_calls = */ true,
        };

Review comment (suggestion):
These tests are fine, but it would be even better to add tests in `test-chat.cpp` too where you have `assert_msg_equals`.

Code after:
  "{\"name\":\"python\\",
  "{\",",
  "{\":",
  "{\"[",
  "{\"]",
  "{\"{",
  "{\"}",
  "{\"1",
  "{\"name\":\",",
  "{\"name\":\":",
  "{\"name\":\"[",
  "{\"name\":\"]",
  "{\"name\":\"{",
  "{\"name\":\"}",
  "{\"name\":\"1",
};

static void test(const std::string & input, bool is_partial, const std::vector<std::vector<std::string>> & args_paths, const std::vector<std::vector<std::string>> & content_paths, const std::string & expected) {
  common_chat_msg_parser builder(input, is_partial, {});
  auto js = builder.try_consume_json_with_dumped_args(args_paths, content_paths);
  assert_equals(true, js.has_value());
  assert_equals(is_partial, js->is_partial);
  assert_equals(expected, args_paths.size() == 1 && args_paths[0].empty() ? js->value.get<std::string>() : js->value.dump());
}

static void test_deepseek_v3_1_tool_calls() {
    //common_log_set_verbosity_thold(LOG_DEFAULT_DEBUG);
    // variant: happy path for when it works as the model card says it should
    const std::string variant("simple");
    common_chat_syntax syntax = {
        /* .format = */ COMMON_CHAT_FORMAT_DEEPSEEK_V3_1,
        /* .reasoning_format = */ COMMON_REASONING_FORMAT_DEEPSEEK,
        /* .reasoning_in_content = */ false,
        /* .thinking_forced_open = */ false,
        /* .parse_tool_calls = */ true,
    };
    const std::string input = "<｜tool▁calls▁begin｜><｜tool▁call▁begin｜>get_time<｜tool▁sep｜>{\"city\": \"Tokyo\"}<｜tool▁call▁end｜><｜tool▁calls▁end｜>";
    auto msg = common_chat_parse(input, false, syntax);
    assert_equals<std::size_t>(variant, 1, msg.tool_calls.size());
    assert_equals(variant, std::string("get_time"), msg.tool_calls[0].name);
    // JSON arguments are dumped without spaces
    assert_equals(variant, std::string("{\"city\":\"Tokyo\"}"), msg.tool_calls[0].arguments);
    assert_equals(variant, std::string(""), msg.content);
    assert_equals(variant, std::string(""), msg.reasoning_content);

    // variant: simple + thinking open
    {
        common_chat_syntax syntax = {
            /* .format = */ COMMON_CHAT_FORMAT_DEEPSEEK_V3_1,
            /* .reasoning_format = */ COMMON_REASONING_FORMAT_DEEPSEEK,
            /* .reasoning_in_content = */ false,