Skip to content

Commit bb7b9fe

Browse files
author
ochafik
committed
fix QwQ 32B tool call parsing after thoughts (hermes2)
1 parent f3bfbc6 commit bb7b9fe

File tree

2 files changed

+28
-19
lines changed

2 files changed

+28
-19
lines changed

common/chat.cpp

Lines changed: 16 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1468,26 +1468,26 @@ static void common_chat_parse_hermes_2_pro(common_chat_msg_parser & builder) {
14681468

14691469
static const common_regex open_regex(
14701470
"(?:"
1471-
"(```(?:xml|json)?\\n\\s*)?" // match 1 (block_start)
1472-
"(<tool_call>" // match 2 (open_tag)
1473-
"|<function_call>"
1474-
"|<tool>"
1475-
"|<tools>"
1476-
"|<response>"
1477-
"|<json>"
1478-
"|<xml>"
1479-
"|<JSON>"
1480-
")?"
1481-
"(\\s*\\{\\s*\"name\"\\s*:[\\s\\S]*)" // match 3 (named tool call + rest)
1471+
"(```(?:xml|json)?\\n\\s*)?" // match 1 (block_start)
1472+
"(" // match 2 (open_tag)
1473+
"<tool_call>"
1474+
"|<function_call>"
1475+
"|<tool>"
1476+
"|<tools>"
1477+
"|<response>"
1478+
"|<json>"
1479+
"|<xml>"
1480+
"|<JSON>"
1481+
")?"
1482+
"(\\s*\\{\\s*\"name\"\\s*:)" // match 3 (named tool call)
14821483
")"
1483-
"|"
1484-
"(?:<function=([^>]+)>" // match 4 (function name)
1485-
"|<function name=\"([^\"]+)\">)" // match 5 (function name again)
1486-
"([\\s\\S]*)" // match 6 (function arguments + rest)})"
1484+
"|<function=([^>]+)>" // match 4 (function name)
1485+
"|<function name=\"([^\"]+)\">" // match 5 (function name again)
14871486
);
14881487

1488+
auto start = builder.pos();
14891489
if (auto res = builder.try_find_regex(open_regex)) {
1490-
if (res->groups[0].begin != 0 && res->groups[4].empty() && res->groups[5].empty()) {
1490+
if (res->groups[0].begin != start && res->groups[4].empty() && res->groups[5].empty()) {
14911491
// The only syntax we allow after the very start is <function=...> or <function name=...>
14921492
builder.add_content(builder.consume_rest());
14931493
return;
@@ -1528,9 +1528,6 @@ static void common_chat_parse_hermes_2_pro(common_chat_msg_parser & builder) {
15281528

15291529
close_tag = "</function>";
15301530

1531-
// Start parsing from after the opening tags
1532-
builder.move_to(res->groups[6].begin);
1533-
15341531
if (auto arguments = builder.try_consume_json_with_dumped_args({{}})) {
15351532
if (!builder.add_tool_call(function_name, "", arguments->value) || arguments->is_partial) {
15361533
builder.incomplete("incomplete tool call");

tests/test-chat.cpp

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -894,6 +894,18 @@ static void test_template_output_parsers() {
894894
"<function_call> { \"name\" : \"python\"",
895895
/* is_partial= */ true,
896896
{COMMON_CHAT_FORMAT_HERMES_2_PRO}));
897+
assert_msg_equals(message_assist_call_thoughts,
898+
common_chat_parse(
899+
// QwQ-32B's template adds a trailing <think> if add_generation_prompt
900+
"I'm\nthinking</think>\n"
901+
"<tool_call>{\"name\": \"special_function\", \"arguments\": {\"arg1\": 1}}</tool_call>",
902+
/* is_partial= */ false,
903+
{
904+
/* .format = */ COMMON_CHAT_FORMAT_HERMES_2_PRO,
905+
/* .reasoning_format = */ COMMON_REASONING_FORMAT_DEEPSEEK,
906+
/* .reasoning_in_content = */ false,
907+
/* .thinking_forced_open = */ true,
908+
}));
897909
assert_msg_equals(
898910
message_assist_call,
899911
common_chat_parse(

0 commit comments

Comments
 (0)