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

Code before:
    {
        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,
        };
        const char* variant = "thinking_forced_open_tool_call_in_reasoning_no_closing_think";
        const std::string in = "REASONING<｜tool▁calls▁begin｜><｜tool▁call▁begin｜>get_time2<｜tool▁sep｜>{\"city\": \"Tokyo2\"}<｜tool▁call▁end｜><｜tool▁calls▁end｜>REASONING";
        auto m = common_chat_parse(in, false, syntax);
        assert_equals<std::size_t>(variant, 0, m.tool_calls.size());
        assert_equals(variant, std::string(""), m.content);
        assert_equals(variant, std::string("REASONING<｜tool▁calls▁begin｜><｜tool▁call▁begin｜>get_time2<｜tool▁sep｜>{\"city\": \"Tokyo2\"}<｜tool▁call▁end｜><｜tool▁calls▁end｜>REASONING"), m.reasoning_content);
    }

    // variant: thinking not forced open + missing reasoning + no tool calls
    {
        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 char* variant = "thinking_not_forced_open_missing_reasoning_no_tool_calls";
        const std::string in = "CONTENT";
        auto m = common_chat_parse(in, false, syntax);
        assert_equals<std::size_t>(variant, 0, m.tool_calls.size());
        assert_equals(variant, std::string("CONTENT"), m.content);
        assert_equals(variant, std::string(""), m.reasoning_content);
    }
}

static void test_with_args(const std::string & input, const std::string & expected, bool parse_as_partial = true, bool is_partial = true) {
  common_chat_msg_parser builder(input, parse_as_partial, {});
  auto js = builder.try_consume_json_with_dumped_args({{"args"}}, {});
  assert_equals(true, js.has_value());
  assert_equals(is_partial, js->is_partial);
  assert_equals(expected, js->value.dump());
}

static void test_json_with_dumped_args_no_args() {
  // Normal JSON, nothing to heal, nothing to dump
  test("{\"name\": \"python\"}", false, {}, {}, "{\"name\":\"python\"}");
  // Full json is args
  test("{\"name\": \"python\"}", false, {{}}, {}, "{\"name\":\"python\"}");

  // If the arguments are further down, don't heal partial content.
  for (const auto & src : barely_healable_jsons) {
    test(src, true, {{"arguments"}}, {}, "{}");

Review comment (suggestion):
```suggestion
        const std::string variant("thinking_not_forced_open_missing_reasoning_no_tool_calls");
```

Code after:
        assert_equals(variant, std::string("{\"city\":\"Tokyo\"}"), m.tool_calls[0].arguments);
        assert_equals(variant, std::string(""), m.content);
        assert_equals(variant, std::string("REASONING<｜tool▁calls▁begin｜><｜tool▁call▁begin｜>get_time2<｜tool▁sep｜>{\"city\": \"Tokyo2\"}<｜tool▁call▁end｜><｜tool▁calls▁end｜>REASONING"), m.reasoning_content);
    }

    // variant: thinking forced open + tool call in reasoning content + no closing think + not partial
    //          This is a bit of a fine tuning issue on the model's part IMO. It really should not be attempting
    //          to make tool calls in reasoning content according to the model card, but it does sometimes, so
    //          add the reasoning content as regular content and parse the tool calls.
    {
        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,
        };
        const std::string variant("thinking_forced_open_tool_call_in_reasoning_no_closing_think_not_partial");
        const std::string in = "REASONING<｜tool▁calls▁begin｜><｜tool▁call▁begin｜>get_time<｜tool▁sep｜>{\"city\": \"Tokyo\"}<｜tool▁call▁end｜><｜tool▁calls▁end｜>";
        auto m = common_chat_parse(in, false, syntax);
        assert_equals(variant, std::string("REASONING"), m.content);
        assert_equals(variant, std::string(""), m.reasoning_content);
        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);
    }

    // variant: thinking forced open + tool call in reasoning content + no closing think + partial
    {
        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,
        };
        const std::string variant("thinking_forced_open_tool_call_in_reasoning_no_closing_think_partial");
        const std::string in = "REASONING<｜tool▁calls▁begin｜><｜tool▁call▁begin｜>get_time<｜tool▁sep｜>{\"city\": \"Tokyo\"}<｜tool▁call▁end｜><｜tool▁calls▁end｜>";
        auto m = common_chat_parse(in, /* is_partial= */ true, syntax);
        assert_equals(variant, std::string("REASONING<｜tool▁calls▁begin｜><｜tool▁call▁begin｜>get_time<｜tool▁sep｜>{\"city\": \"Tokyo\"}<｜tool▁call▁end｜><｜tool▁calls▁end｜>"), m.reasoning_content);
        assert_equals(variant, std::string(""), m.content);
        assert_equals<std::size_t>(variant, 0, m.tool_calls.size());
    }

    // variant: thinking not forced open + reasoning + regular content + no tool calls
    {
        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,