Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 35 additions & 9 deletions common/chat-parser-xml-toolcall.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 ?
Expand Down Expand Up @@ -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 :
Expand Down Expand Up @@ -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)) {
Expand Down
1 change: 1 addition & 0 deletions common/chat-parser-xml-toolcall.h
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
21 changes: 21 additions & 0 deletions common/chat-parser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -879,6 +879,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>",
Expand Down Expand Up @@ -1479,6 +1497,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));
}
Expand Down
64 changes: 64 additions & 0 deletions common/chat.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -660,6 +660,7 @@ const char * common_chat_format_name(common_chat_format format) {
case COMMON_CHAT_FORMAT_PEG_SIMPLE: return "peg-simple";
case COMMON_CHAT_FORMAT_PEG_NATIVE: return "peg-native";
case COMMON_CHAT_FORMAT_PEG_CONSTRUCTED: return "peg-constructed";
case COMMON_CHAT_FORMAT_DEEPSEEK_V3_2: return "DeepSeek V3.2";
default:
throw std::runtime_error("Unknown chat format");
}
Expand Down Expand Up @@ -1492,6 +1493,54 @@ 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;

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;
Expand Down Expand Up @@ -2476,6 +2525,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())) {
Expand Down
1 change: 1 addition & 0 deletions common/chat.h
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,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,

// These are intended to be parsed by the PEG parser
COMMON_CHAT_FORMAT_PEG_SIMPLE,
Expand Down
Loading
Loading