Repository: ggerganov/llama.cpp
File: common/chat.cpp (C++)

Code before:
                // Build tool call rule for Nemotron format
                tool_rules.push_back(builder.add_rule(name + "-call", builder.add_schema(name + "-args", {
                    {"type", "object"},
                    {"properties", {
                        {"name", {{"const", name}}},
                        {"arguments", parameters},
                    }},
                    {"required", json::array({"name", "arguments"})},
                })));
            });

            // Create tool call rule - Nemotron uses <TOOLCALL>[...] format
            auto tool_call = builder.add_rule("tool_call", string_join(tool_rules, " | "));
            auto tool_list = builder.add_rule("tool_list", "\"[\" space " + tool_call + " (\",\" space " + tool_call + ")* space \"]\"");

            // Grammar rule for <TOOLCALL> format
            builder.add_rule("root",
                std::string(data.thinking_forced_open ? "( \"\" space )? " : "") +
                "\"<TOOLCALL>\" space " + tool_list + " \"</TOOLCALL>\"");
        });

        // Define triggers for <TOOLCALL> token
        data.grammar_triggers.push_back({
            COMMON_GRAMMAR_TRIGGER_TYPE_WORD,
            "<TOOLCALL>"
        });
    } else {
        // Handle thinking tags for non-tool responses
        if (data.thinking_forced_open && inputs.enable_thinking) {
            data.grammar_lazy = false;
            data.grammar = build_grammar([&](const common_grammar_builder & builder) {
                builder.add_rule("root", "\"</think>\" space");
            });
        }
    }

    // Set preserved tokens for all special tokens
    data.preserved_tokens = {
        "<think>",
        "</think>",
        "<TOOLCALL>",
        "</TOOLCALL>",
        "<TOOL_RESPONSE>",
        "</TOOL_RESPONSE>",
        "<AVAILABLE_TOOLS>",
        "</AVAILABLE_TOOLS>"
    };

    return data;
}
static void common_chat_parse_llama_3_1(common_chat_msg_parser & builder, bool with_builtin_tools = false) {

Review comment (suggestion):
If `data.thinking_forced_open`, the `</think>` tag should be included in the trigger.

Code after:
                            { "name",
                            {
                                { "type", "string" },
                                { "const", function.at("name") },
                            } },
                            { "arguments", function.at("parameters") },
                        }                                                                        },
                    { "required",   json::array({ "name", "arguments" }) },
                });
            });
            auto schema = json{
                        { "type",     "array"                                                         },
                        { "items",    schemas.size() == 1 ? schemas[0] : json{ { "anyOf", schemas } } },
                        { "minItems", 1                                                               },
            };
            if (!inputs.parallel_tool_calls) {
                schema["maxItems"] = 1;
            }
            builder.add_rule("root",
                                std::string(data.thinking_forced_open ? "( \"</think>\" space )? " : "") +
                                    "\"<TOOLCALL>\" " + builder.add_schema("tool_calls", schema) +
                                    " \"</TOOLCALL>\"");
        });
        data.grammar_triggers.push_back({ COMMON_GRAMMAR_TRIGGER_TYPE_PATTERN_FULL,
            // If thinking_forced_open, then we capture the </think> tag in the grammar,
            // (important for required tool choice) and in the trigger's first capture (decides what is sent to the grammar)
            std::string(data.thinking_forced_open ?
                            "[\\s\\S]*?(</think>\\s*)" :
                            "(?:<think>[\\s\\S]*?</think>\\s*)?") +
                "(<TOOLCALL>)[\\s\\S]*" });
    }
    return data;
}
static void common_chat_parse_llama_3_1(common_chat_msg_parser & builder, bool with_builtin_tools = false) {
    if (!builder.syntax().parse_tool_calls) {
        builder.add_content(builder.consume_rest());
        return;
    }

    static const common_regex function_regex(
        "\\s*\\{\\s*(?:\"type\"\\s*:\\s*\"function\"\\s*,\\s*)?\"name\"\\s*:\\s*\"([^\"]+)\"\\s*,\\s*\"parameters\"\\s*: ");
    static const common_regex close_regex("\\}\\s*");

    static const common_regex function_name_regex("\\s*(\\w+)\\s*\\.\\s*call\\(");
    static const common_regex arg_name_regex("\\s*(\\w+)\\s*=\\s*");

    if (with_builtin_tools) {
        static const common_regex builtin_call_regex("<\\|python_tag\\|>");
        if (auto res = builder.try_find_regex(builtin_call_regex)) {
            auto fun_res = builder.consume_regex(function_name_regex);
            auto function_name = builder.str(fun_res.groups[1]);