Skip to content

Commit e0ee297

Browse files
common : add GLM-4.5 tool calling support
- Add COMMON_CHAT_FORMAT_GLM_4_5 format enum - Implement GLM-4.5 tool call parser for <tool_call><arg_key><arg_value> format - Add template detection based on <arg_key> and <arg_value> tags - Fix null content handling in message parsing and serialization - Ensure GLM-4.5 detection runs before Hermes to avoid misidentification This enables tool calling functionality for GLM-4.5 models when using --jinja flag. The parser handles GLM-4.5's XML-like tool call format with key-value argument pairs.
1 parent 2241453 commit e0ee297

File tree

2 files changed

+72
-2
lines changed

2 files changed

+72
-2
lines changed

common/chat.cpp

Lines changed: 71 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -203,7 +203,10 @@ std::vector<common_chat_msg> common_chat_msgs_parse_oaicompat(const json & messa
203203
msg_part.text = part.at("text");
204204
msg.content_parts.push_back(msg_part);
205205
}
206-
} else if (!content.is_null()) {
206+
} else if (content.is_null()) {
207+
// Handle null content by setting it to empty string
208+
msg.content = "";
209+
} else {
207210
throw std::runtime_error("Invalid 'content' type: expected string or array, got " + content.dump() + " (ref: https://github.com/ggml-org/llama.cpp/issues/8367)");
208211
}
209212
}
@@ -292,7 +295,7 @@ json common_chat_msgs_to_json_oaicompat(const std::vector<common_chat_msg> & msg
292295
}
293296
}
294297
} else {
295-
jmsg["content"] = json(); // null
298+
jmsg["content"] = ""; // empty string instead of null
296299
}
297300
if (!msg.reasoning_content.empty()) {
298301
jmsg["reasoning_content"] = msg.reasoning_content;
@@ -607,6 +610,7 @@ const char * common_chat_format_name(common_chat_format format) {
607610
case COMMON_CHAT_FORMAT_HERMES_2_PRO: return "Hermes 2 Pro";
608611
case COMMON_CHAT_FORMAT_COMMAND_R7B: return "Command R7B";
609612
case COMMON_CHAT_FORMAT_GPT_OSS: return "GPT-OSS";
613+
case COMMON_CHAT_FORMAT_GLM_4_5: return "GLM 4.5";
610614
default:
611615
throw std::runtime_error("Unknown chat format");
612616
}
@@ -1325,6 +1329,63 @@ static void common_chat_parse_gpt_oss(common_chat_msg_parser & builder) {
13251329
}
13261330
}
13271331

1332+
static common_chat_params common_chat_params_init_glm_4_5(const common_chat_template & tmpl, const struct templates_params & inputs) {
1333+
common_chat_params data;
1334+
data.prompt = apply(tmpl, inputs);
1335+
data.format = COMMON_CHAT_FORMAT_GLM_4_5;
1336+
return data;
1337+
}
1338+
1339+
static void common_chat_parse_glm_4_5(common_chat_msg_parser & builder) {
1340+
builder.try_parse_reasoning("<think>", "</think>");
1341+
if (!builder.syntax().parse_tool_calls) {
1342+
builder.add_content(builder.consume_rest());
1343+
return;
1344+
}
1345+
1346+
// GLM 4.5 uses format: <tool_call>function_name\n<arg_key>key</arg_key>\n<arg_value>value</arg_value>\n</tool_call>
1347+
static const common_regex tool_call_start("<tool_call>([^\n<]+)");
1348+
static const common_regex arg_key_regex("<arg_key>([^<]+)</arg_key>");
1349+
static const common_regex arg_value_regex("<arg_value>([^<]*)</arg_value>");
1350+
static const common_regex tool_call_end("</tool_call>");
1351+
1352+
while (auto res = builder.try_find_regex(tool_call_start)) {
1353+
// Move to the start of the tool call and consume it
1354+
builder.move_to(res->groups[0].begin);
1355+
builder.consume_regex(tool_call_start);
1356+
1357+
std::string function_name = builder.str(res->groups[1]);
1358+
json arguments = json::object();
1359+
1360+
builder.consume_spaces();
1361+
1362+
// Parse all arg_key/arg_value pairs
1363+
while (auto key_res = builder.try_consume_regex(arg_key_regex)) {
1364+
std::string key = builder.str(key_res->groups[1]);
1365+
builder.consume_spaces();
1366+
1367+
if (auto value_res = builder.try_consume_regex(arg_value_regex)) {
1368+
std::string value = builder.str(value_res->groups[1]);
1369+
arguments[key] = value;
1370+
builder.consume_spaces();
1371+
} else {
1372+
throw common_chat_msg_partial_exception("Expected <arg_value> after <arg_key>");
1373+
}
1374+
}
1375+
1376+
// Consume closing tag
1377+
builder.consume_regex(tool_call_end);
1378+
builder.consume_spaces();
1379+
1380+
// Add the parsed tool call
1381+
if (!builder.add_tool_call(function_name, "", arguments.dump())) {
1382+
throw common_chat_msg_partial_exception("Failed to add GLM tool call");
1383+
}
1384+
}
1385+
1386+
builder.add_content(builder.consume_rest());
1387+
}
1388+
13281389
static common_chat_params common_chat_params_init_firefunction_v2(const common_chat_template & tmpl, const struct templates_params & inputs) {
13291390
LOG_DBG("%s\n", __func__);
13301391
common_chat_params data;
@@ -1805,6 +1866,11 @@ static common_chat_params common_chat_templates_apply_jinja(
18051866
return common_chat_params_init_command_r7b(tmpl, params);
18061867
}
18071868

1869+
// GLM 4.5: detect by <arg_key> and <arg_value> tags (check before Hermes since both use <tool_call>)
1870+
if (src.find("<arg_key>") != std::string::npos && src.find("<arg_value>") != std::string::npos && params.json_schema.is_null()) {
1871+
return common_chat_params_init_glm_4_5(tmpl, params);
1872+
}
1873+
18081874
// Hermes 2/3 Pro, Qwen 2.5 Instruct (w/ tools)
18091875
if (src.find("<tool_call>") != std::string::npos && params.json_schema.is_null()) {
18101876
return common_chat_params_init_hermes_2_pro(tmpl, params);
@@ -1969,6 +2035,9 @@ static void common_chat_parse(common_chat_msg_parser & builder) {
19692035
case COMMON_CHAT_FORMAT_GPT_OSS:
19702036
common_chat_parse_gpt_oss(builder);
19712037
break;
2038+
case COMMON_CHAT_FORMAT_GLM_4_5:
2039+
common_chat_parse_glm_4_5(builder);
2040+
break;
19722041
default:
19732042
throw std::runtime_error(std::string("Unsupported format: ") + common_chat_format_name(builder.syntax().format));
19742043
}

common/chat.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,7 @@ enum common_chat_format {
110110
COMMON_CHAT_FORMAT_HERMES_2_PRO,
111111
COMMON_CHAT_FORMAT_COMMAND_R7B,
112112
COMMON_CHAT_FORMAT_GPT_OSS,
113+
COMMON_CHAT_FORMAT_GLM_4_5,
113114

114115
COMMON_CHAT_FORMAT_COUNT, // Not a format, just the # formats
115116
};

0 commit comments

Comments
 (0)