--- common/chat.cpp.orig 2026-03-08 11:52:53 +++ common/chat.cpp 2026-03-08 11:53:41 @@ -105,10 +105,18 @@ } else { auto & parts = jmsg["content"] = json::array(); for (const auto & part : content_parts) { - parts.push_back({ + json jpart = { {"type", part.type}, {"text", part.text}, - }); + }; + if (part.extra_fields.is_object()) { + for (const auto & [k, v] : part.extra_fields.items()) { + jpart[k] = v; + } + } + parts.push_back(jpart); } } } else { @@ -291,6 +299,11 @@ common_chat_msg_content_part msg_part; msg_part.type = type; msg_part.text = part.at("text"); + for (const auto & [k, v] : part.items()) { + if (k != "type" && k != "text") { + msg_part.extra_fields[k] = v; + } + } msg.content_parts.push_back(msg_part); } } else if (!content.is_null()) { @@ -1145,6 +1153,63 @@ return data; } +static common_chat_params common_chat_params_init_function_gemma( + const common_chat_template & tmpl, + const autoparser::generation_params & inputs) { + common_chat_params data; + + data.prompt = common_chat_template_direct_apply(tmpl, inputs); + + auto has_tools = inputs.tools.is_array() && !inputs.tools.empty(); + auto include_grammar = has_tools && inputs.tool_choice != COMMON_CHAT_TOOL_CHOICE_NONE; + + if (!has_tools || inputs.tool_choice == COMMON_CHAT_TOOL_CHOICE_NONE) { + data.format = COMMON_CHAT_FORMAT_CONTENT_ONLY; + return data; + } + + data.format = COMMON_CHAT_FORMAT_PEG_NATIVE; + data.preserved_tokens = { + "", + "", + }; + + auto parser = build_chat_peg_parser([&](common_chat_peg_builder & p) { + 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"); + + auto tool_parser = p.tool( + p.tool_open( + p.literal("call:") + + p.tool_name(p.literal(name))) + + p.tool_args(p.schema(p.json(), "tool-" + name + "-schema", schema)) + + p.tool_close(p.literal("")) + ); + + tool_choice |= p.rule("tool-" + name, tool_parser); + }); + + auto ret = inputs.parallel_tool_calls ? p.one_or_more(tool_choice) : tool_choice; + return wrap_for_generation_prompt(p, p.trigger_rule("tool-call", ret) + p.end(), inputs); + }); + + 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) { + auto schema = tool.at("function").at("parameters"); + builder.resolve_refs(schema); + }); + parser.build_grammar(builder, data.grammar_lazy); + }); + + data.grammar_triggers = { + { COMMON_GRAMMAR_TRIGGER_TYPE_WORD, "" } + }; + } + + return data; +} + // Kimi K2 Thinking - uses unique tool call ID format: functions.: // The ID contains both the function name and an incrementing counter static common_chat_params common_chat_params_init_kimi_k2(const common_chat_template & tmpl, @@ -1509,6 +1574,12 @@ return common_chat_params_init_functionary_v3_2(tmpl, params); } + // FunctionGemma - uses call:name{...} + if (src.find("") != std::string::npos) { + LOG_DBG("Using specialized template: FunctionGemma\n"); + return common_chat_params_init_function_gemma(tmpl, params); + } + // Kimi K2 Thinking - uses unique tool call ID format: functions.: // Detection: template has "<|tool_calls_section_begin|>" and "functions." prefix in tool call IDs if (src.find("<|tool_calls_section_begin|>") != std::string::npos && @@ -1570,5 +1641,35 @@ LM_GGML_ASSERT(chat_templates != nullptr); LM_GGML_ASSERT(chat_templates->template_default != nullptr); return chat_templates->template_default->caps.to_map(); +} + +common_chat_template_caps common_chat_templates_get_caps(const struct common_chat_templates * tmpls, const std::string & variant) { + common_chat_template_caps result; + const common_chat_template * tmpl = nullptr; + + if (!variant.empty() && variant == "tool_use") { + tmpl = tmpls->template_tool_use.get(); + } else { + tmpl = tmpls->template_default.get(); + } + + if (tmpl) { + auto caps = tmpl->original_caps(); + result.supports_tools = caps.supports_tools; + result.supports_tool_calls = caps.supports_tool_calls; + result.supports_system_role = caps.supports_system_role; + result.supports_parallel_tool_calls = caps.supports_parallel_tool_calls; + } + + return result; } +bool common_chat_templates_has_variant(const struct common_chat_templates * tmpls, const std::string & variant) { + if (variant.empty() || variant == "default") { + return tmpls->template_default != nullptr; + } + if (variant == "tool_use") { + return tmpls->template_tool_use != nullptr; + } + return false; +}