char-fork/llama.cpp

git clone https://git.t4t.associates/char-fork/llama.cpp

Aldehir Rojascommon : implement common_schema internal representation for JSON schemas (#28736)acecd5603

char/aliceai-t5-moe
5.3 KiB126 linesraw
1#include "parsers.h"
2
3common_chat_params common_chat_params_init_ministral_3(const common_chat_template &    tmpl,
4                                                              const autoparser::generation_params & inputs) {
5    common_chat_params data;
6
7    // Build up messages to follow the format: https://huggingface.co/mistralai/Ministral-3-14B-Reasoning-2512/blob/main/chat_template.jinja
8    auto adjusted_messages = json::array();
9    for (const auto & msg : inputs.messages) {
10        auto role = msg.value("role", "");
11        if (role != "system" && role != "assistant") {
12            // Only adjust system and assistant messages. Interestingly, the system message may contain thinking.
13            adjusted_messages.push_back(msg);
14            continue;
15        }
16
17        auto content = json::array();
18
19        // If message contains `reasoning_content`, add it as a block of type `thinking`
20        if (msg.contains("reasoning_content") && msg.at("reasoning_content").is_string()) {
21            content.push_back({
22                { "type",     "thinking"                                     },
23                { "thinking", msg.at("reasoning_content").get<std::string>() },
24            });
25        }
26
27        // If message contains `content`, add it as a block of type `text`
28        if (msg.contains("content")) {
29            if (msg.at("content").is_string()) {
30                content.push_back({
31                    { "type", "text"                               },
32                    { "text", msg.at("content").get<std::string>() },
33                });
34            } else if (msg.at("content").is_array()) {
35                auto blocks = msg.at("content");
36                content.insert(blocks);
37            }
38        }
39
40        auto adjusted       = msg;
41        adjusted["content"] = content;
42        adjusted.erase("reasoning_content");
43        adjusted_messages.push_back(adjusted);
44    }
45
46    auto has_tools            = inputs.tools.is_array() && !inputs.tools.empty();
47    auto has_response_format  = inputs.json_schema.is_object() && !inputs.json_schema.empty();
48    auto extract_reasoning    = inputs.reasoning_format != COMMON_REASONING_FORMAT_NONE;
49    auto include_grammar      = true;
50
51    data.supports_thinking  = true;
52    data.thinking_start_tag = "[THINK]";
53    data.thinking_end_tags  = {"[/THINK]"};
54    data.prompt            = common_chat_template_direct_apply_impl(tmpl, inputs, /* messages_override = */ adjusted_messages);
55    data.generation_prompt = common_chat_template_generation_prompt_impl(tmpl, inputs, /* messages_override = */ adjusted_messages);
56    data.format            = COMMON_CHAT_FORMAT_PEG_NATIVE;
57    data.preserved_tokens  = {
58        "[THINK]",
59        "[/THINK]",
60        "[TOOL_CALLS]",
61        "[ARGS]",
62    };
63
64    if (inputs.has_continuation()) {
65        const auto & msg = inputs.continue_msg;
66
67        data.generation_prompt = "[THINK]" + msg.reasoning_content;
68        if (inputs.continue_final_message == COMMON_CHAT_CONTINUATION_CONTENT) {
69            data.generation_prompt += "[/THINK]" + msg.render_content();
70        }
71
72        data.prompt += data.generation_prompt;
73    }
74
75    auto parser = build_chat_peg_parser([&](common_chat_peg_builder & p) {
76        auto generation_prompt = p.eps();
77        auto reasoning =
78            extract_reasoning ? p.optional("[THINK]" + p.reasoning(p.until("[/THINK]")) + "[/THINK]") : p.eps();
79
80        // Response format parser
81        if (has_response_format) {
82            // Ministral wants to emit json surrounded by code fences
83            return generation_prompt + (reasoning << "```json" << p.content(p.schema(p.json(), "response-format", inputs.json_schema)) << "```");
84        }
85
86        // Tool call parser
87        if (has_tools && inputs.tool_choice != COMMON_CHAT_TOOL_CHOICE_NONE) {
88            auto tool_choice = p.choice();
89            foreach_function(inputs.tools, [&](const json & tool) {
90                const auto & function = tool.at("function");
91                std::string  name     = function.at("name");
92                const auto   schema   = common_chat_tool_parameters(function);
93
94                tool_choice |=
95                    p.rule("tool-" + name, p.tool_open(p.tool_name(p.literal(name)) + "[ARGS]") +
96                                               p.tool_args(p.schema(p.json(), "tool-" + name + "-schema", schema)));
97            });
98
99            auto min_calls  = inputs.tool_choice == COMMON_CHAT_TOOL_CHOICE_REQUIRED ? 1 : 0;
100            auto max_calls  = inputs.parallel_tool_calls ? -1 : 1;
101            auto tool_calls = p.trigger_rule("tool-call", p.repeat("[TOOL_CALLS]" + tool_choice, min_calls, max_calls));
102
103            return generation_prompt + (reasoning << p.content(p.until("[TOOL_CALLS]")) << tool_calls);
104        }
105
106        // Content only parser
107        include_grammar = false;
108        return generation_prompt + (reasoning << p.content(p.rest()));
109    });
110
111    data.parser = parser.save();
112
113    if (include_grammar) {
114        data.grammar_lazy = has_tools && inputs.tool_choice == COMMON_CHAT_TOOL_CHOICE_AUTO;
115
116        data.grammar = build_grammar([&](const common_grammar_builder & builder) {
117            parser.build_grammar(builder, data.grammar_lazy);
118        });
119
120        data.grammar_triggers = {
121            { COMMON_GRAMMAR_TRIGGER_TYPE_WORD, "[TOOL_CALLS]" }
122        };
123    }
124
125    return data;
126}