common: chat parser for Deepseek V3.2
This commit is contained in:
parent
0e6502eaf9
commit
79e5b248a6
|
|
@ -169,16 +169,10 @@ void build_grammar_xml_tool_call(common_chat_params & data, const json & tools,
|
|||
GGML_ASSERT(!form.tool_start.empty());
|
||||
GGML_ASSERT(!form.tool_sep.empty());
|
||||
GGML_ASSERT(!form.key_start.empty());
|
||||
GGML_ASSERT(!form.key_val_sep.empty());
|
||||
GGML_ASSERT(!form.val_end.empty());
|
||||
GGML_ASSERT(!form.tool_end.empty());
|
||||
|
||||
std::string key_val_sep = form.key_val_sep;
|
||||
if (form.key_val_sep2) {
|
||||
key_val_sep += "\n";
|
||||
key_val_sep += *form.key_val_sep2;
|
||||
}
|
||||
GGML_ASSERT(!key_val_sep.empty());
|
||||
|
||||
if (tools.is_array() && !tools.empty()) {
|
||||
data.grammar = build_grammar([&](const common_grammar_builder &builder) {
|
||||
auto string_arg_val = form.last_val_end ?
|
||||
|
|
@ -224,14 +218,29 @@ void build_grammar_xml_tool_call(common_chat_params & data, const json & tools,
|
|||
for (const auto & [key, value] : parameters.at("properties").items()) {
|
||||
std::string quoted_key = key;
|
||||
bool required = std::binary_search(requiredParameters.begin(), requiredParameters.end(), key);
|
||||
if (form.key_start.back() == '"' && key_val_sep[0] == '"') {
|
||||
if (form.key_start.back() == '"' && form.key_val_sep[0] == '"') {
|
||||
quoted_key = gbnf_format_literal(key);
|
||||
quoted_key = quoted_key.substr(1, quoted_key.size() - 2);
|
||||
}
|
||||
std::string kvsep = gbnf_format_literal(form.key_val_sep);
|
||||
if (!form.allowed_literal_between_kvsep.empty()) {
|
||||
kvsep += " (";
|
||||
for (auto s: form.allowed_literal_between_kvsep) {
|
||||
kvsep += " ";
|
||||
kvsep += gbnf_format_literal(s);
|
||||
kvsep += " |";
|
||||
}
|
||||
kvsep.resize(kvsep.size() - 2);
|
||||
kvsep += " )";
|
||||
}
|
||||
if (form.key_val_sep2) {
|
||||
kvsep += " ";
|
||||
kvsep += gbnf_format_literal(*form.key_val_sep2);
|
||||
}
|
||||
arg_rules.push_back(parameter_rule {builder.add_rule("func-" + name + "-kv-" + key,
|
||||
gbnf_format_literal(form.key_start) + " " +
|
||||
gbnf_format_literal(quoted_key) + " " +
|
||||
gbnf_format_literal(key_val_sep) + " " +
|
||||
kvsep + " " +
|
||||
((value.contains("type") && value["type"].is_string() && value["type"] == "string" && (!form.raw_argval || *form.raw_argval)) ?
|
||||
(form.raw_argval ?
|
||||
string_arg_val :
|
||||
|
|
@ -476,6 +485,23 @@ inline bool parse_xml_tool_calls(common_chat_msg_parser & builder, const struct
|
|||
auto &key = key_res->prelude;
|
||||
recovery = false;
|
||||
|
||||
if (!form.allowed_literal_between_kvsep.empty()) {
|
||||
for (bool consumed = true; consumed;) {
|
||||
consumed = false;
|
||||
auto pos = builder.pos();
|
||||
for (auto s: form.allowed_literal_between_kvsep) {
|
||||
if (auto tc = builder.try_find_literal(s)) {
|
||||
if (all_space(tc->prelude)) {
|
||||
consumed = true;
|
||||
pos = builder.pos();
|
||||
} else {
|
||||
builder.move_to(pos);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Parse arg_value
|
||||
if (form.key_val_sep2) {
|
||||
if (auto tc = builder.try_find_literal(*form.key_val_sep2)) {
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ struct xml_tool_call_format {
|
|||
std::optional<std::string> last_tool_end = std::nullopt;
|
||||
bool trim_raw_argval = false;
|
||||
bool allow_toolcall_in_think = false;
|
||||
std::vector<std::string> allowed_literal_between_kvsep = {};
|
||||
};
|
||||
|
||||
// make a GBNF that accept any strings except those containing any of the forbidden strings.
|
||||
|
|
|
|||
|
|
@ -877,6 +877,24 @@ static void common_chat_parse_deepseek_v3_1(common_chat_msg_parser & builder) {
|
|||
}
|
||||
}
|
||||
|
||||
static void common_chat_parse_deepseek_v3_2(common_chat_msg_parser & builder) {
|
||||
static const xml_tool_call_format form = ([]() {
|
||||
xml_tool_call_format form {};
|
||||
form.scope_start = "<|DSML|function_calls>";
|
||||
form.tool_start = "<|DSML|invoke name=\"";
|
||||
form.tool_sep = "\">";
|
||||
form.key_start = "<|DSML|parameter name=\"";
|
||||
form.key_val_sep = "\" string=\"";
|
||||
form.allowed_literal_between_kvsep = {"true", "false"};
|
||||
form.key_val_sep2 = "\">";
|
||||
form.val_end = "</|DSML|parameter>";
|
||||
form.tool_end = "</|DSML|invoke>";
|
||||
form.scope_end = "</|DSML|function_calls>";
|
||||
return form;
|
||||
})();
|
||||
builder.consume_reasoning_with_xml_tool_calls(form, "<think>", "</think>");
|
||||
}
|
||||
|
||||
static void common_chat_parse_minimax_m2(common_chat_msg_parser & builder) {
|
||||
static const xml_tool_call_format form {
|
||||
/* form.scope_start = */ "<minimax:tool_call>",
|
||||
|
|
@ -1477,6 +1495,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_DEEPSEEK_V3_2:
|
||||
common_chat_parse_deepseek_v3_2(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_DEEPSEEK_V3_2: return "DeepSeek V3.2";
|
||||
default:
|
||||
throw std::runtime_error("Unknown chat format");
|
||||
}
|
||||
|
|
@ -1481,6 +1482,76 @@ static common_chat_params common_chat_params_init_deepseek_v3_1(const common_cha
|
|||
return data;
|
||||
}
|
||||
|
||||
static common_chat_params common_chat_params_init_deepseek_v3_2(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;
|
||||
data.format = COMMON_CHAT_FORMAT_DEEPSEEK_V3_2;
|
||||
|
||||
/*minja::chat_template_inputs tmpl_inputs;
|
||||
tmpl_inputs.messages = params.messages;
|
||||
tmpl_inputs.tools = params.tools.empty() ? json() : params.tools;
|
||||
tmpl_inputs.add_generation_prompt = params.add_generation_prompt;
|
||||
tmpl_inputs.extra_context = params.extra_context;
|
||||
tmpl_inputs.extra_context["enable_thinking"] = params.enable_thinking;
|
||||
|
||||
minja::chat_template_options tmpl_opts;
|
||||
tmpl_opts.apply_polyfills = true;
|
||||
tmpl_opts.polyfill_object_arguments = true;
|
||||
tmpl_opts.polyfill_tools = false;
|
||||
tmpl_opts.polyfill_tool_calls = false;
|
||||
tmpl_opts.polyfill_tool_responses = false;
|
||||
tmpl_opts.polyfill_system_role = false;
|
||||
auto prompt = tmpl.apply(tmpl_inputs, tmpl_opts);
|
||||
if (params.add_bos && string_starts_with(prompt, tmpl.bos_token())) {
|
||||
prompt = prompt.substr(tmpl.bos_token().size());
|
||||
}
|
||||
if (params.add_eos && string_ends_with(prompt, tmpl.eos_token())) {
|
||||
prompt = prompt.substr(0, prompt.size() - tmpl.eos_token().size());
|
||||
}*/
|
||||
|
||||
data.prompt = apply(tmpl, params);
|
||||
|
||||
if (string_ends_with(data.prompt, "<think>")) {
|
||||
if (!params.enable_thinking) {
|
||||
// Close the thinking tag immediately if thinking is disabled
|
||||
data.prompt += "</think>";
|
||||
} else {
|
||||
// Mark thinking as forced open (template started with <think>)
|
||||
data.thinking_forced_open = true;
|
||||
}
|
||||
}
|
||||
|
||||
data.preserved_tokens = {
|
||||
"<think>",
|
||||
"</think>",
|
||||
"function_calls>",
|
||||
"invoke>",
|
||||
"<|end▁of▁sentence|>",
|
||||
};
|
||||
|
||||
data.additional_stops.insert(data.additional_stops.end(), {
|
||||
"<|end▁of▁sentence|>"
|
||||
});
|
||||
// build grammar for tool call
|
||||
static const xml_tool_call_format form = ([]() {
|
||||
xml_tool_call_format form {};
|
||||
form.scope_start = "<|DSML|function_calls>\n";
|
||||
form.tool_start = "<|DSML|invoke name=\"";
|
||||
form.tool_sep = "\">\n";
|
||||
form.key_start = "<|DSML|parameter name=\"";
|
||||
form.key_val_sep = "\" string=\"";
|
||||
form.allowed_literal_between_kvsep = {"true", "false"};
|
||||
form.key_val_sep2 = "\">";
|
||||
form.val_end = "</|DSML|parameter>\n";
|
||||
form.tool_end = "</|DSML|invoke>\n";
|
||||
form.scope_end = "</|DSML|function_calls>";
|
||||
return form;
|
||||
})();
|
||||
build_grammar_xml_tool_call(data, params.tools, form);
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
static common_chat_params common_chat_params_init_minimax_m2(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;
|
||||
|
|
@ -2465,6 +2536,21 @@ static common_chat_params common_chat_templates_apply_jinja(
|
|||
return common_chat_params_init_apriel_1_5(tmpl, params);
|
||||
}
|
||||
|
||||
// DeepSeek V3.2 format detection
|
||||
if (src.find("<think>") != std::string::npos &&
|
||||
src.find("</think>") != std::string::npos &&
|
||||
src.find("<|begin▁of▁sentence|>") != std::string::npos &&
|
||||
src.find("<|end▁of▁sentence|>") != std::string::npos &&
|
||||
src.find("|DSML|") != std::string::npos &&
|
||||
src.find("function_calls>") != std::string::npos &&
|
||||
src.find("<function_results>") != std::string::npos &&
|
||||
src.find("</function_results>") != std::string::npos &&
|
||||
src.find("invoke name=") != std::string::npos &&
|
||||
src.find("parameter name=") != std::string::npos &&
|
||||
src.find("string=\"true|false\">") != std::string::npos) {
|
||||
return common_chat_params_init_deepseek_v3_2(tmpl, params);
|
||||
}
|
||||
|
||||
// Use generic handler when mixing tools + JSON schema.
|
||||
// TODO: support that mix in handlers below.
|
||||
if ((params.tools.is_array() && params.json_schema.is_object())) {
|
||||
|
|
|
|||
|
|
@ -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_DEEPSEEK_V3_2,
|
||||
|
||||
COMMON_CHAT_FORMAT_COUNT, // Not a format, just the # formats
|
||||
};
|
||||
|
|
|
|||
Loading…
Reference in New Issue