|
|
@@ -133,6 +133,7 @@ struct templates_params {
|
|
|
bool stream;
|
|
|
std::string grammar;
|
|
|
bool add_generation_prompt = true;
|
|
|
+ bool enable_thinking = true;
|
|
|
std::chrono::system_clock::time_point now = std::chrono::system_clock::now();
|
|
|
};
|
|
|
|
|
|
@@ -573,7 +574,7 @@ common_chat_templates_ptr common_chat_templates_init(
|
|
|
return tmpls;
|
|
|
}
|
|
|
|
|
|
-std::string common_chat_format_name(common_chat_format format) {
|
|
|
+const char * common_chat_format_name(common_chat_format format) {
|
|
|
switch (format) {
|
|
|
case COMMON_CHAT_FORMAT_CONTENT_ONLY: return "Content-only";
|
|
|
case COMMON_CHAT_FORMAT_GENERIC: return "Generic";
|
|
|
@@ -591,6 +592,15 @@ std::string common_chat_format_name(common_chat_format format) {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+const char * common_reasoning_format_name(common_reasoning_format format) {
|
|
|
+ switch (format) {
|
|
|
+ case COMMON_REASONING_FORMAT_NONE: return "none";
|
|
|
+ case COMMON_REASONING_FORMAT_DEEPSEEK: return "deepseek";
|
|
|
+ default:
|
|
|
+ throw std::runtime_error("Unknown reasoning format");
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
static std::string wrap_code_as_arguments(common_chat_msg_parser & builder, const std::string & code) {
|
|
|
std::string arguments;
|
|
|
if (builder.is_partial()) {
|
|
|
@@ -918,7 +928,13 @@ static common_chat_params common_chat_params_init_command_r7b(const common_chat_
|
|
|
data.prompt = apply(tmpl, adjusted_messages, inputs.tools.empty() ? json() : inputs.tools, inputs.add_generation_prompt, {});
|
|
|
data.format = COMMON_CHAT_FORMAT_COMMAND_R7B;
|
|
|
if (string_ends_with(data.prompt, "<|START_THINKING|>")) {
|
|
|
- data.thinking_forced_open = true;
|
|
|
+ if (!inputs.enable_thinking) {
|
|
|
+ data.prompt += "<|END_THINKING|>";
|
|
|
+ } else {
|
|
|
+ data.thinking_forced_open = true;
|
|
|
+ }
|
|
|
+ } else if (!inputs.enable_thinking && string_ends_with(data.prompt, "<|CHATBOT_TOKEN|>")) {
|
|
|
+ data.prompt += "<|START_THINKING|><|END_THINKING|>";
|
|
|
}
|
|
|
|
|
|
data.grammar_lazy = inputs.tool_choice != COMMON_CHAT_TOOL_CHOICE_REQUIRED;
|
|
|
@@ -1186,7 +1202,11 @@ static common_chat_params common_chat_params_init_deepseek_r1(const common_chat_
|
|
|
data.prompt = prompt;
|
|
|
data.format = COMMON_CHAT_FORMAT_DEEPSEEK_R1;
|
|
|
if (string_ends_with(data.prompt, "<think>\n")) {
|
|
|
- data.thinking_forced_open = true;
|
|
|
+ if (!inputs.enable_thinking) {
|
|
|
+ data.prompt += "</think>";
|
|
|
+ } else {
|
|
|
+ data.thinking_forced_open = true;
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
if (inputs.tools.is_array() && !inputs.tools.empty()) {
|
|
|
@@ -1460,104 +1480,114 @@ static void common_chat_parse_functionary_v3_1_llama_3_1(common_chat_msg_parser
|
|
|
static common_chat_params common_chat_params_init_hermes_2_pro(const common_chat_template & tmpl, const struct templates_params & inputs) {
|
|
|
common_chat_params data;
|
|
|
|
|
|
- data.prompt = apply(tmpl, inputs.messages, inputs.tools.empty() ? json() : inputs.tools, inputs.add_generation_prompt);
|
|
|
+ json additional_context = {
|
|
|
+ {"enable_thinking", inputs.enable_thinking},
|
|
|
+ };
|
|
|
+
|
|
|
+ data.prompt = apply(tmpl, inputs.messages, inputs.tools.empty() ? json() : inputs.tools, inputs.add_generation_prompt, additional_context);
|
|
|
data.format = COMMON_CHAT_FORMAT_HERMES_2_PRO;
|
|
|
if (string_ends_with(data.prompt, "<think>\n")) {
|
|
|
- data.thinking_forced_open = true;
|
|
|
+ if (!inputs.enable_thinking) {
|
|
|
+ data.prompt += "</think>";
|
|
|
+ } else {
|
|
|
+ data.thinking_forced_open = true;
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
- // (content)?(<tool_call>{"name": "foo", "arguments": {"a": 1}}</tool_call>)*
|
|
|
- data.grammar_lazy = inputs.tool_choice != COMMON_CHAT_TOOL_CHOICE_REQUIRED;
|
|
|
- data.grammar = build_grammar([&](const common_grammar_builder & builder) {
|
|
|
- std::vector<std::string> tool_rules;
|
|
|
- std::vector<std::string> tool_call_alts;
|
|
|
- std::vector<std::string> escaped_names;
|
|
|
- foreach_function(inputs.tools, [&](const json & tool) {
|
|
|
- const auto & function = tool.at("function");
|
|
|
- std::string name = function.at("name");
|
|
|
- auto parameters = function.at("parameters");
|
|
|
- builder.resolve_refs(parameters);
|
|
|
- tool_rules.push_back(builder.add_schema(name + "-call", {
|
|
|
- {"type", "object"},
|
|
|
- {"properties", json {
|
|
|
- {"name", json {{"const", name}}},
|
|
|
- {"arguments", parameters},
|
|
|
- }},
|
|
|
- {"required", json::array({"name", "arguments"})},
|
|
|
- }));
|
|
|
- tool_call_alts.push_back(builder.add_rule(
|
|
|
- name + "-function-tag",
|
|
|
- "\"<function\" ( \"=" + name + "\" | \" name=\\\"" + name + "\\\"\" ) \">\" space " +
|
|
|
- builder.add_schema(name + "-args", parameters) + " "
|
|
|
- "\"</function>\" space"));
|
|
|
+ if (!inputs.tools.is_null()) {
|
|
|
+ // (content)?(<tool_call>{"name": "foo", "arguments": {"a": 1}}</tool_call>)*
|
|
|
+ data.grammar_lazy = inputs.tool_choice != COMMON_CHAT_TOOL_CHOICE_REQUIRED;
|
|
|
+ data.grammar = build_grammar([&](const common_grammar_builder & builder) {
|
|
|
+ std::vector<std::string> tool_rules;
|
|
|
+ std::vector<std::string> tool_call_alts;
|
|
|
+ std::vector<std::string> escaped_names;
|
|
|
+ foreach_function(inputs.tools, [&](const json & tool) {
|
|
|
+ const auto & function = tool.at("function");
|
|
|
+ std::string name = function.at("name");
|
|
|
+ auto parameters = function.at("parameters");
|
|
|
+ builder.resolve_refs(parameters);
|
|
|
+ tool_rules.push_back(builder.add_schema(name + "-call", {
|
|
|
+ {"type", "object"},
|
|
|
+ {"properties", json {
|
|
|
+ {"name", json {{"const", name}}},
|
|
|
+ {"arguments", parameters},
|
|
|
+ }},
|
|
|
+ {"required", json::array({"name", "arguments"})},
|
|
|
+ }));
|
|
|
+ tool_call_alts.push_back(builder.add_rule(
|
|
|
+ name + "-function-tag",
|
|
|
+ "\"<function\" ( \"=" + name + "\" | \" name=\\\"" + name + "\\\"\" ) \">\" space " +
|
|
|
+ builder.add_schema(name + "-args", parameters) + " "
|
|
|
+ "\"</function>\" space"));
|
|
|
|
|
|
- data.grammar_triggers.push_back({
|
|
|
- COMMON_GRAMMAR_TRIGGER_TYPE_WORD,
|
|
|
- "<function=" + name + ">",
|
|
|
+ data.grammar_triggers.push_back({
|
|
|
+ COMMON_GRAMMAR_TRIGGER_TYPE_WORD,
|
|
|
+ "<function=" + name + ">",
|
|
|
+ });
|
|
|
+ auto escaped_name = regex_escape(name);
|
|
|
+ data.grammar_triggers.push_back({
|
|
|
+ COMMON_GRAMMAR_TRIGGER_TYPE_PATTERN,
|
|
|
+ "<function\\s+name\\s*=\\s*\"" + escaped_name + "\"",
|
|
|
+ });
|
|
|
+ escaped_names.push_back(escaped_name);
|
|
|
});
|
|
|
- auto escaped_name = regex_escape(name);
|
|
|
+ auto any_tool_call = builder.add_rule("any_tool_call", "( " + string_join(tool_rules, " | ") + " ) space");
|
|
|
+ std::vector<std::string> alt_tags {
|
|
|
+ any_tool_call,
|
|
|
+ "\"<tool_call>\" space " + any_tool_call + " \"</tool_call>\"",
|
|
|
+ // The rest is just to accommodate common "good bad" outputs.
|
|
|
+ "\"<function_call>\" space " + any_tool_call + " \"</function_call>\"",
|
|
|
+ "\"<response>\" space " + any_tool_call + " \"</response>\"",
|
|
|
+ "\"<tools>\" space " + any_tool_call + " \"</tools>\"",
|
|
|
+ "\"<json>\" space " + any_tool_call + " \"</json>\"",
|
|
|
+ "\"<xml>\" space " + any_tool_call + " \"</xml>\"",
|
|
|
+ "\"<JSON>\" space " + any_tool_call + " \"</JSON>\"",
|
|
|
+ };
|
|
|
+ auto wrappable_tool_call = builder.add_rule("wrappable_tool_call", "( " + string_join(alt_tags, " | ") + " ) space");
|
|
|
+ tool_call_alts.push_back(wrappable_tool_call);
|
|
|
+ tool_call_alts.push_back(
|
|
|
+ "( \"```\\n\" | \"```json\\n\" | \"```xml\\n\" ) space " + wrappable_tool_call + " space \"```\" space ");
|
|
|
+ auto tool_call = builder.add_rule("tool_call", string_join(tool_call_alts, " | "));
|
|
|
+ builder.add_rule("root",
|
|
|
+ std::string(data.thinking_forced_open ? "( \"</think>\" space )? " : "") +
|
|
|
+ (inputs.parallel_tool_calls ? "(" + tool_call + ")+" : tool_call));
|
|
|
+ // Trigger on some common known "good bad" outputs (only from the start and with a json that's about a specific argument name to avoid false positives)
|
|
|
data.grammar_triggers.push_back({
|
|
|
- COMMON_GRAMMAR_TRIGGER_TYPE_PATTERN,
|
|
|
- "<function\\s+name\\s*=\\s*\"" + escaped_name + "\"",
|
|
|
+ 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*)?") + (
|
|
|
+ "(\\s*"
|
|
|
+ "(?:<tool_call>"
|
|
|
+ "|<function"
|
|
|
+ "|(?:```(?:json|xml)?\n\\s*)?(?:<function_call>|<tools>|<xml><json>|<response>)?"
|
|
|
+ "\\s*\\{\\s*\"name\"\\s*:\\s*\"(?:" + string_join(escaped_names, "|") + ")\""
|
|
|
+ ")"
|
|
|
+ ")[\\s\\S]*"
|
|
|
+ ),
|
|
|
});
|
|
|
- escaped_names.push_back(escaped_name);
|
|
|
- });
|
|
|
- auto any_tool_call = builder.add_rule("any_tool_call", "( " + string_join(tool_rules, " | ") + " ) space");
|
|
|
- std::vector<std::string> alt_tags {
|
|
|
- any_tool_call,
|
|
|
- "\"<tool_call>\" space " + any_tool_call + " \"</tool_call>\"",
|
|
|
- // The rest is just to accommodate common "good bad" outputs.
|
|
|
- "\"<function_call>\" space " + any_tool_call + " \"</function_call>\"",
|
|
|
- "\"<response>\" space " + any_tool_call + " \"</response>\"",
|
|
|
- "\"<tools>\" space " + any_tool_call + " \"</tools>\"",
|
|
|
- "\"<json>\" space " + any_tool_call + " \"</json>\"",
|
|
|
- "\"<xml>\" space " + any_tool_call + " \"</xml>\"",
|
|
|
- "\"<JSON>\" space " + any_tool_call + " \"</JSON>\"",
|
|
|
- };
|
|
|
- auto wrappable_tool_call = builder.add_rule("wrappable_tool_call", "( " + string_join(alt_tags, " | ") + " ) space");
|
|
|
- tool_call_alts.push_back(wrappable_tool_call);
|
|
|
- tool_call_alts.push_back(
|
|
|
- "( \"```\\n\" | \"```json\\n\" | \"```xml\\n\" ) space " + wrappable_tool_call + " space \"```\" space ");
|
|
|
- auto tool_call = builder.add_rule("tool_call", string_join(tool_call_alts, " | "));
|
|
|
- builder.add_rule("root",
|
|
|
- std::string(data.thinking_forced_open ? "( \"</think>\" space )? " : "") +
|
|
|
- (inputs.parallel_tool_calls ? "(" + tool_call + ")+" : tool_call));
|
|
|
- // Trigger on some common known "good bad" outputs (only from the start and with a json that's about a specific argument name to avoid false positives)
|
|
|
- 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*)?") + (
|
|
|
- "(\\s*"
|
|
|
- "(?:<tool_call>"
|
|
|
- "|<function"
|
|
|
- "|(?:```(?:json|xml)?\n\\s*)?(?:<function_call>|<tools>|<xml><json>|<response>)?"
|
|
|
- "\\s*\\{\\s*\"name\"\\s*:\\s*\"(?:" + string_join(escaped_names, "|") + ")\""
|
|
|
- ")"
|
|
|
- ")[\\s\\S]*"
|
|
|
- ),
|
|
|
+ data.preserved_tokens = {
|
|
|
+ "<think>",
|
|
|
+ "</think>",
|
|
|
+ "<tool_call>",
|
|
|
+ "</tool_call>",
|
|
|
+ "<function",
|
|
|
+ "<tools>",
|
|
|
+ "</tools>",
|
|
|
+ "<response>",
|
|
|
+ "</response>",
|
|
|
+ "<function_call>",
|
|
|
+ "</function_call>",
|
|
|
+ "<json>",
|
|
|
+ "</json>",
|
|
|
+ "<JSON>",
|
|
|
+ "</JSON>",
|
|
|
+ "```",
|
|
|
+ "```json",
|
|
|
+ "```xml",
|
|
|
+ };
|
|
|
});
|
|
|
- data.preserved_tokens = {
|
|
|
- "<think>",
|
|
|
- "</think>",
|
|
|
- "<tool_call>",
|
|
|
- "</tool_call>",
|
|
|
- "<function",
|
|
|
- "<tools>",
|
|
|
- "</tools>",
|
|
|
- "<response>",
|
|
|
- "</response>",
|
|
|
- "<function_call>",
|
|
|
- "</function_call>",
|
|
|
- "<json>",
|
|
|
- "</json>",
|
|
|
- "<JSON>",
|
|
|
- "</JSON>",
|
|
|
- "```",
|
|
|
- "```json",
|
|
|
- "```xml",
|
|
|
- };
|
|
|
- });
|
|
|
+ }
|
|
|
|
|
|
return data;
|
|
|
}
|
|
|
@@ -1669,6 +1699,7 @@ static common_chat_params common_chat_templates_apply_jinja(
|
|
|
params.messages = common_chat_msgs_to_json_oaicompat<json>(inputs.messages, /* concat_text= */ !tmpl.original_caps().requires_typed_content);
|
|
|
params.add_generation_prompt = inputs.add_generation_prompt;
|
|
|
params.tool_choice = inputs.tool_choice;
|
|
|
+ params.enable_thinking = inputs.enable_thinking;
|
|
|
params.grammar = inputs.grammar;
|
|
|
params.now = inputs.now;
|
|
|
if (!inputs.json_schema.empty()) {
|
|
|
@@ -1702,7 +1733,7 @@ static common_chat_params common_chat_templates_apply_jinja(
|
|
|
}
|
|
|
|
|
|
// Hermes 2/3 Pro, Qwen 2.5 Instruct (w/ tools)
|
|
|
- if (src.find("<tool_call>") != std::string::npos && params.json_schema.is_null() && params.tools.is_array() && params.json_schema.is_null()) {
|
|
|
+ if (src.find("<tool_call>") != std::string::npos && params.json_schema.is_null()) {
|
|
|
return common_chat_params_init_hermes_2_pro(tmpl, params);
|
|
|
}
|
|
|
|
|
|
@@ -1821,7 +1852,7 @@ static void common_chat_parse_content_only(common_chat_msg_parser & builder) {
|
|
|
}
|
|
|
|
|
|
static void common_chat_parse(common_chat_msg_parser & builder, common_chat_format format) {
|
|
|
- LOG_DBG("Parsing input with format %s: %s\n", common_chat_format_name(format).c_str(), builder.input().c_str());
|
|
|
+ LOG_DBG("Parsing input with format %s: %s\n", common_chat_format_name(format), builder.input().c_str());
|
|
|
|
|
|
switch (format) {
|
|
|
case COMMON_CHAT_FORMAT_CONTENT_ONLY:
|
|
|
@@ -1858,7 +1889,7 @@ static void common_chat_parse(common_chat_msg_parser & builder, common_chat_form
|
|
|
common_chat_parse_command_r7b(builder);
|
|
|
break;
|
|
|
default:
|
|
|
- throw std::runtime_error("Unsupported format: " + common_chat_format_name(format));
|
|
|
+ throw std::runtime_error(std::string("Unsupported format: ") + common_chat_format_name(format));
|
|
|
}
|
|
|
builder.finish();
|
|
|
}
|