From 685c01d8ad4a458c988730f747c92e0b6b1a2854 Mon Sep 17 00:00:00 2001 From: hksdpc255 <43977088+hksdpc255@users.noreply.github.com> Date: Mon, 16 Mar 2026 14:17:52 +0800 Subject: [PATCH] Implement MiroThinker tool call parser --- common/chat.cpp | 118 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 118 insertions(+) diff --git a/common/chat.cpp b/common/chat.cpp index cfd5df30a7..79c4c278e2 100644 --- a/common/chat.cpp +++ b/common/chat.cpp @@ -1278,6 +1278,116 @@ static common_chat_params common_chat_params_init_kimi_k2(const common_chat_temp return data; } +// MiroThinker - uses MCP style toolcalling +static common_chat_params common_chat_params_init_mirothinker(const common_chat_template & tmpl, + const autoparser::templates_params & inputs) { + common_chat_params data; + + data.prompt = common_chat_template_direct_apply(tmpl, inputs); + data.format = COMMON_CHAT_FORMAT_PEG_NATIVE; + data.supports_thinking = true; + data.thinking_start_tag = ""; + data.thinking_end_tag = ""; + data.preserved_tokens = { + "", + "", + }; + + auto has_tools = inputs.tools.is_array() && !inputs.tools.empty(); + auto extract_reasoning = inputs.reasoning_format != COMMON_REASONING_FORMAT_NONE; + auto include_grammar = has_tools && inputs.tool_choice != COMMON_CHAT_TOOL_CHOICE_NONE; + + auto parser = build_chat_peg_parser([&](common_chat_peg_builder & p) { + // MiroThinker Thinking format: + // - Reasoning: {reasoning} + // - Content: text after reasoning + // - Tool calls section: + // + // {server_name} + // {tool_name} + // + // {json_args} + // + // ... + // + + auto reasoning = extract_reasoning ? p.optional("" + p.reasoning(p.until("")) + "") : p.eps(); + + // Tool call markers + const std::string SECTION_BEGIN = ""; + const std::string SECTION_END = ""; + const std::string CALL_BEGIN = ""; + const std::string ARGS_BEGIN = ""; + const std::string CALL_END = ""; + + auto end = p.end(); + + // Content only parser (no tools) + if (!has_tools || inputs.tool_choice == COMMON_CHAT_TOOL_CHOICE_NONE) { + return reasoning + p.content(p.rest()) + end; + } + + // Build tool call parsers for each available function + // Function name format is: {tool_name} + // We need to match: {what_ever}{spaces}{tool_name} + auto tool_choice = p.choice(); + foreach_function(inputs.tools, [&](const json & tool) { + const auto & function = tool.at("function"); + std::string name = function.at("name"); + const auto & schema = function.at("parameters"); + + // Match: {what_ever}{spaces}{tool_name} + auto tool_parser = p.tool( + p.tool_open( + p.until("") + + p.literal("") + + p.space() + + p.literal("") + + p.tool_name(p.literal(name)) + + p.literal(ARGS_BEGIN) + ) + p.space() + + p.tool_args(p.schema(p.json(), "tool-" + name + "-schema", schema)) + + p.space() + p.tool_close(p.literal(CALL_END)) + ); + + tool_choice |= p.rule("tool-" + name, tool_parser); + }); + + // Tool calls section: tool_calls + auto min_calls = inputs.tool_choice == COMMON_CHAT_TOOL_CHOICE_REQUIRED ? 1 : 0; + auto max_calls = inputs.parallel_tool_calls ? -1 : 1; + auto tool_calls = p.trigger_rule("tool-calls", + p.literal(SECTION_BEGIN) + p.space() + + p.rule("tool-call", p.repeat(CALL_BEGIN + tool_choice, min_calls, max_calls) + + p.space() + p.literal(SECTION_END)) + ); + + auto content_before_tools = p.content(p.until(SECTION_BEGIN)); + + return reasoning + content_before_tools + tool_calls + end; + }); + + data.parser = parser.save(); + + if (include_grammar) { + data.grammar_lazy = inputs.tool_choice == COMMON_CHAT_TOOL_CHOICE_AUTO; + data.grammar = build_grammar([&](const common_grammar_builder & builder) { + foreach_function(inputs.tools, [&](const json & tool) { + const auto & function = tool.at("function"); + auto schema = function.at("parameters"); + builder.resolve_refs(schema); + }); + parser.build_grammar(builder, data.grammar_lazy); + }); + + data.grammar_triggers = { + { COMMON_GRAMMAR_TRIGGER_TYPE_WORD, "" } + }; + } + + return data; +} + // LFM2 format: // - Reasoning: {reasoning} (optional, only if enable_thinking is true) // - Content: text after reasoning (optional) @@ -1588,6 +1698,14 @@ static common_chat_params common_chat_templates_apply_jinja(const struct common_ return common_chat_params_init_kimi_k2(tmpl, params); } + // MiroThinker - uses MCP style toolcalling ... + // Detection: template has "" and "" + if (src.find("") != std::string::npos && + src.find("") != std::string::npos) { + LOG_DBG("Using specialized template: MiroThinker\n"); + return common_chat_params_init_mirothinker(tmpl, params); + } + // LFM2 - uses <|tool_list_start|>/<|tool_list_end|> markers and <|tool_call_start|>[name(args)]<|tool_call_end|> format // Detection: template has "<|tool_list_start|>" and "<|tool_list_end|>" markers if (src.find("<|tool_list_start|>") != std::string::npos &&