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

Code before:
            /* .thinking_forced_open = */ false,
            /* .parse_tool_calls = */ true,
        };
        const char* variant = "simple_multiple_tool_calls";
        const std::string in = "CONTENT<｜tool▁calls▁begin｜><｜tool▁call▁begin｜>get_time<｜tool▁sep｜>{\"city\": \"Paris\"}<｜tool▁call▁end｜><｜tool▁call▁begin｜>get_weather<｜tool▁sep｜>{\"city\": \"Paris\"}<｜tool▁call▁end｜><｜tool▁calls▁end｜>";
        auto m = common_chat_parse(in, false, syntax);
        assert_equals<std::size_t>(variant, 2, m.tool_calls.size());
        assert_equals(variant, std::string("get_time"), m.tool_calls[0].name);
        assert_equals(variant, std::string("{\"city\":\"Paris\"}"), m.tool_calls[0].arguments);
        assert_equals(variant, std::string("get_weather"), m.tool_calls[1].name);
        assert_equals(variant, std::string("{\"city\":\"Paris\"}"), m.tool_calls[1].arguments);
        assert_equals(variant, std::string("CONTENT"), m.content);
        assert_equals(variant, std::string(""), m.reasoning_content);
    }


    // variant: thinking forced open + tool call in reasoning content + function + fenced JSON
    {
        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_fenced_thinking";
        const std::string in = "REASONING<｜tool▁calls▁begin｜><｜tool▁call▁begin｜>get_time2<｜tool▁sep｜>{\"city\": \"Tokyo2\"}<｜tool▁call▁end｜><｜tool▁calls▁end｜>REASONING</think><｜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("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
    {
        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);
    }

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

Code after:
    // variant: simple + multiple 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 std::string variant("simple_multiple_tool_calls");
        const std::string in = "CONTENT<｜tool▁calls▁begin｜><｜tool▁call▁begin｜>get_time<｜tool▁sep｜>{\"city\": \"Paris\"}<｜tool▁call▁end｜><｜tool▁call▁begin｜>get_weather<｜tool▁sep｜>{\"city\": \"Paris\"}<｜tool▁call▁end｜><｜tool▁calls▁end｜>";
        auto m = common_chat_parse(in, false, syntax);
        assert_equals<std::size_t>(variant, 2, m.tool_calls.size());
        assert_equals(variant, std::string("get_time"), m.tool_calls[0].name);
        assert_equals(variant, std::string("{\"city\":\"Paris\"}"), m.tool_calls[0].arguments);
        assert_equals(variant, std::string("get_weather"), m.tool_calls[1].name);
        assert_equals(variant, std::string("{\"city\":\"Paris\"}"), m.tool_calls[1].arguments);
        assert_equals(variant, std::string("CONTENT"), m.content);
        assert_equals(variant, std::string(""), m.reasoning_content);
    }


    // variant: thinking forced open + tool call in reasoning content
    {
        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");
        const std::string in = "REASONING<｜tool▁calls▁begin｜><｜tool▁call▁begin｜>get_time2<｜tool▁sep｜>{\"city\": \"Tokyo2\"}<｜tool▁call▁end｜><｜tool▁calls▁end｜>REASONING</think><｜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<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("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,