mirror of
https://github.com/ikawrakow/ik_llama.cpp.git
synced 2026-01-26 17:20:01 +00:00
Add chat parser for MiroThinker (#1138)
* Add chat parser for MiroThinker * Add MiroThinker template * delete space
This commit is contained in:
@@ -266,6 +266,17 @@ void build_grammar_xml_tool_call(common_chat_params & data, const json & tools,
|
||||
if (data.format == COMMON_CHAT_FORMAT_KIMI_K2) {
|
||||
quoted_name = "\"functions.\" " + quoted_name + " \":\" [0-9]+";
|
||||
}
|
||||
// MiroThinker uses {{ name_part_1 }}</server_name>\n<tool_name>{{ name_part_2 }} as function name
|
||||
if (data.format == COMMON_CHAT_FORMAT_MIROTHINKER) {
|
||||
auto server_split_pos = name.find("_");
|
||||
if (std::string::npos == server_split_pos) {
|
||||
quoted_name = "\"system_default</server_name>\\n<tool_name>\" " + quoted_name;
|
||||
} else {
|
||||
quoted_name = gbnf_format_literal(name.substr(0, server_split_pos)) +
|
||||
" \"</server_name>\\n<tool_name>\" " +
|
||||
gbnf_format_literal(name.substr(server_split_pos + 1));
|
||||
}
|
||||
}
|
||||
tool_rules.push_back(builder.add_rule(name + "-call",
|
||||
gbnf_format_literal(form.tool_start) + " " +
|
||||
quoted_name + " " +
|
||||
@@ -422,6 +433,10 @@ inline bool parse_xml_tool_calls(common_chat_msg_parser & builder, const struct
|
||||
auto [sz, tc] = try_find_tool_end();
|
||||
func_name = tc;
|
||||
}
|
||||
// Skip when tool_sep may be partial
|
||||
if (builder.pos() == builder.input().size()) {
|
||||
throw common_chat_msg_partial_exception("Partial literal: " + gbnf_format_literal(form.key_start));
|
||||
}
|
||||
|
||||
// Parse tool name
|
||||
builder.move_to(all_space(form.tool_sep) ? func_name->groups[0].begin : func_name->groups[0].end);
|
||||
@@ -435,6 +450,17 @@ inline bool parse_xml_tool_calls(common_chat_msg_parser & builder, const struct
|
||||
}
|
||||
}
|
||||
}
|
||||
// MiroThinker uses {{ name_part_1 }}</server_name>\n<tool_name>{{ name_part_2 }} as function name
|
||||
if (builder.syntax().format == COMMON_CHAT_FORMAT_MIROTHINKER) {
|
||||
if (string_starts_with(function_name, "system_default</server_name>\n<tool_name>")) {
|
||||
function_name = function_name.substr(14 + 26);
|
||||
} else {
|
||||
auto server_split_pos = function_name.find("</server_name>\n<tool_name>");
|
||||
if (std::string::npos != server_split_pos) {
|
||||
function_name = function_name.substr(0, server_split_pos) + "_" + function_name.substr(server_split_pos + 26);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Argument JSON
|
||||
json arguments = json::object();
|
||||
|
||||
@@ -31,7 +31,7 @@ struct xml_tool_call_format {
|
||||
std::optional<std::string> last_val_end = std::nullopt;
|
||||
std::optional<std::string> last_tool_end = std::nullopt;
|
||||
bool trim_raw_argval = false;
|
||||
bool allow_toolcall_in_think = false; // TODO: UNTESTED!!!
|
||||
bool allow_toolcall_in_think = false;
|
||||
};
|
||||
|
||||
// make a GBNF that accept any strings except those containing any of the forbidden strings.
|
||||
|
||||
@@ -967,6 +967,24 @@ static void common_chat_parse_xiaomi_mimo(common_chat_msg_parser & builder) {
|
||||
builder.consume_reasoning_with_xml_tool_calls(form);
|
||||
}
|
||||
|
||||
static void common_chat_parse_mirothinker(common_chat_msg_parser & builder) {
|
||||
static const xml_tool_call_format form = ([]() {
|
||||
xml_tool_call_format form {};
|
||||
form.scope_start = "<use_mcp_tool>";
|
||||
form.tool_start = "<server_name>";
|
||||
form.tool_sep = "</tool_name>\n<arguments>\n{";
|
||||
form.key_start = "\"";
|
||||
form.key_val_sep = "\":";
|
||||
form.val_end = ",";
|
||||
form.tool_end = "}\n</arguments>";
|
||||
form.scope_end = "</use_mcp_tool>";
|
||||
form.raw_argval = false;
|
||||
form.last_val_end = "";
|
||||
return form;
|
||||
})();
|
||||
builder.consume_reasoning_with_xml_tool_calls(form);
|
||||
}
|
||||
|
||||
static void common_chat_parse_gpt_oss(common_chat_msg_parser & builder) {
|
||||
static const std::string constraint = "(?: (<\\|constrain\\|>)?([a-zA-Z0-9_-]+))";
|
||||
static const std::string recipient("(?: to=functions\\.([^<\\s]+))");
|
||||
@@ -1480,6 +1498,9 @@ static void common_chat_parse(common_chat_msg_parser & builder) {
|
||||
case COMMON_CHAT_FORMAT_XIAOMI_MIMO:
|
||||
common_chat_parse_xiaomi_mimo(builder);
|
||||
break;
|
||||
case COMMON_CHAT_FORMAT_MIROTHINKER:
|
||||
common_chat_parse_mirothinker(builder);
|
||||
break;
|
||||
default:
|
||||
throw std::runtime_error(std::string("Unsupported format: ") + common_chat_format_name(builder.syntax().format));
|
||||
}
|
||||
|
||||
@@ -649,6 +649,7 @@ const char * common_chat_format_name(common_chat_format format) {
|
||||
case COMMON_CHAT_FORMAT_QWEN3_CODER_XML: return "Qwen3 Coder";
|
||||
case COMMON_CHAT_FORMAT_APRIEL_1_5: return "Apriel 1.5";
|
||||
case COMMON_CHAT_FORMAT_XIAOMI_MIMO: return "Xiaomi MiMo";
|
||||
case COMMON_CHAT_FORMAT_: return "";
|
||||
default:
|
||||
throw std::runtime_error("Unknown chat format");
|
||||
}
|
||||
@@ -696,7 +697,8 @@ static std::string apply(
|
||||
const struct templates_params & inputs,
|
||||
const std::optional<json> & messages_override = std::nullopt,
|
||||
const std::optional<json> & tools_override = std::nullopt,
|
||||
const std::optional<json> & additional_context = std::nullopt)
|
||||
const std::optional<json> & additional_context = std::nullopt,
|
||||
const std::optional<minja::chat_template_options> & tmpl_opts = std::nullopt)
|
||||
{
|
||||
minja::chat_template_inputs tmpl_inputs;
|
||||
tmpl_inputs.messages = messages_override ? *messages_override : inputs.messages;
|
||||
@@ -713,11 +715,11 @@ static std::string apply(
|
||||
// TODO: add flag to control date/time, if only for testing purposes.
|
||||
// tmpl_inputs.now = std::chrono::system_clock::now();
|
||||
|
||||
minja::chat_template_options tmpl_opts;
|
||||
minja::chat_template_options default_tmpl_opts;
|
||||
// To avoid double BOS / EOS tokens, we're manually removing begining / trailing tokens
|
||||
// instead of using `chat_template_options.use_bos_token = false`, since these tokens
|
||||
// may be needed inside the template / between messages too.
|
||||
auto result = tmpl.apply(tmpl_inputs, tmpl_opts);
|
||||
auto result = tmpl.apply(tmpl_inputs, tmpl_opts ? *tmpl_opts : default_tmpl_opts);
|
||||
if (inputs.add_bos && string_starts_with(result, tmpl.bos_token())) {
|
||||
result = result.substr(tmpl.bos_token().size());
|
||||
}
|
||||
@@ -1351,6 +1353,53 @@ static common_chat_params common_chat_params_init_xiaomi_mimo(const common_chat_
|
||||
form.tool_end = "}\n</tool_call>";
|
||||
form.scope_end = "";
|
||||
form.raw_argval = false;
|
||||
form.last_val_end = "}";
|
||||
return form;
|
||||
})();
|
||||
build_grammar_xml_tool_call(data, params.tools, form);
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
static common_chat_params common_chat_params_init_(const common_chat_template & tmpl, const struct templates_params & params) {
|
||||
common_chat_params data;
|
||||
data.grammar_lazy = params.tools.is_array() && !params.tools.empty() && params.tool_choice != COMMON_CHAT_TOOL_CHOICE_REQUIRED;
|
||||
|
||||
// Disable every Minja polyfill
|
||||
minja::chat_template_options topts;
|
||||
topts.apply_polyfills = true;
|
||||
topts.polyfill_tools = false;
|
||||
topts.polyfill_tool_call_examples = false;
|
||||
topts.polyfill_tool_calls = false;
|
||||
topts.polyfill_tool_responses = false;
|
||||
topts.polyfill_system_role = false;
|
||||
topts.polyfill_object_arguments = true;
|
||||
topts.polyfill_typed_content = false;
|
||||
topts.use_bos_token = true;
|
||||
topts.use_eos_token = true;
|
||||
|
||||
data.prompt = apply(tmpl, params, std::nullopt, std::nullopt, std::nullopt, topts);
|
||||
data.format = COMMON_CHAT_FORMAT_;
|
||||
|
||||
data.preserved_tokens = {
|
||||
"<use_mcp_tool>", "</use_mcp_tool>",
|
||||
"<server_name>", "</server_name>",
|
||||
"<tool_name>", "</tool_name>",
|
||||
"<arguments>", "</arguments>",
|
||||
};
|
||||
|
||||
// build grammar for tool call
|
||||
static const xml_tool_call_format form = ([]() {
|
||||
xml_tool_call_format form {};
|
||||
form.scope_start = "<use_mcp_tool>\n";
|
||||
form.tool_start = "<server_name>\n";
|
||||
form.tool_sep = "</tool_name>\n<arguments>\n{";
|
||||
form.key_start = "\"";
|
||||
form.key_val_sep = "\": ";
|
||||
form.val_end = ", ";
|
||||
form.tool_end = "}\n</arguments>";
|
||||
form.scope_end = "</use_mcp_tool>";
|
||||
form.raw_argval = false;
|
||||
form.last_val_end = "";
|
||||
return form;
|
||||
})();
|
||||
@@ -2032,6 +2081,14 @@ static common_chat_params common_chat_templates_apply_jinja(
|
||||
return common_chat_params_init_xiaomi_mimo(tmpl, params);
|
||||
}
|
||||
|
||||
// MiroThinker format detection (must come before Hermes 2 Pro)
|
||||
if (src.find("</use_mcp_tool>") != std::string::npos &&
|
||||
src.find("</server_name>") != std::string::npos &&
|
||||
src.find("</tool_name>") != std::string::npos &&
|
||||
src.find("</arguments>") != std::string::npos) {
|
||||
return common_chat_params_init_mirothinker(tmpl, params);
|
||||
}
|
||||
|
||||
// Hermes 2/3 Pro, Qwen 2.5 Instruct (w/ tools)
|
||||
if (src.find("<tool_call>") != std::string::npos && params.json_schema.is_null()) {
|
||||
return common_chat_params_init_hermes_2_pro(tmpl, params);
|
||||
|
||||
@@ -123,6 +123,7 @@ enum common_chat_format {
|
||||
COMMON_CHAT_FORMAT_QWEN3_CODER_XML,
|
||||
COMMON_CHAT_FORMAT_APRIEL_1_5,
|
||||
COMMON_CHAT_FORMAT_XIAOMI_MIMO,
|
||||
COMMON_CHAT_FORMAT_MIROTHINKER,
|
||||
|
||||
COMMON_CHAT_FORMAT_COUNT, // Not a format, just the # formats
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user