Skip to content

Commit 36ecb01

Browse files
author
ochafik
committed
tool-call: allow content prelude before hermes2 tool calls (for Qwen2.5)
1 parent 5f0450d commit 36ecb01

File tree

2 files changed

+36
-12
lines changed

2 files changed

+36
-12
lines changed

common/chat.cpp

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1449,6 +1449,7 @@ static common_chat_params common_chat_params_init_hermes_2_pro(const common_chat
14491449
data.grammar = build_grammar([&](const common_grammar_builder & builder) {
14501450
std::vector<std::string> tool_rules;
14511451
std::vector<std::string> tool_call_alts;
1452+
std::vector<std::string> escaped_names;
14521453
foreach_function(inputs.tools, [&](const json & tool) {
14531454
const auto & function = tool.at("function");
14541455
std::string name = function.at("name");
@@ -1477,6 +1478,7 @@ static common_chat_params common_chat_params_init_hermes_2_pro(const common_chat
14771478
COMMON_GRAMMAR_TRIGGER_TYPE_PATTERN,
14781479
"<function\\s+name\\s*=\\s*\"" + escaped_name + "\"",
14791480
});
1481+
escaped_names.push_back(escaped_name);
14801482
});
14811483
auto any_tool_call = builder.add_rule("any_tool_call", "( " + string_join(tool_rules, " | ") + " ) space");
14821484
std::vector<std::string> alt_tags {
@@ -1504,9 +1506,12 @@ static common_chat_params common_chat_params_init_hermes_2_pro(const common_chat
15041506
// If thinking_forced_open, then we capture the </think> tag in the grammar,
15051507
// (important for required tool choice) and in the trigger's first capture (decides what is sent to the grammar)
15061508
std::string(data.thinking_forced_open ? "[\\s\\S]*?(</think>\\s*)" : "(?:<think>[\\s\\S]*?</think>\\s*)?") + (
1507-
"(<tool_call>"
1509+
"(\\s*"
1510+
"(?:<tool_call>"
15081511
"|<function"
1509-
"|(?:```(?:json|xml)?\n\\s*)?(?:<function_call>|<tools>|<xml><json>|<response>)?\\s*\\{\\s*\""
1512+
"|(?:```(?:json|xml)?\n\\s*)?(?:<function_call>|<tools>|<xml><json>|<response>)?"
1513+
"\\s*\\{\\s*\"name\"\\s*:\\s*\"(?:" + string_join(escaped_names, "|") + ")\""
1514+
")"
15101515
")[\\s\\S]*"
15111516
),
15121517
});
@@ -1550,20 +1555,13 @@ static void common_chat_parse_hermes_2_pro(common_chat_msg_parser & builder) {
15501555
"|<xml>"
15511556
"|<JSON>"
15521557
")?"
1553-
"(\\s*\\{\\s*\"name\"\\s*:)" // match 3 (named tool call)
1558+
"(\\s*\\{\\s*\"name\")" // match 3 (named tool call)
15541559
")"
15551560
"|<function=([^>]+)>" // match 4 (function name)
15561561
"|<function name=\"([^\"]+)\">" // match 5 (function name again)
15571562
);
15581563

1559-
auto start = builder.pos();
15601564
if (auto res = builder.try_find_regex(open_regex)) {
1561-
if (res->groups[0].begin != start && builder.str(res->groups[2]) != "<tool_call>" && res->groups[4].empty() && res->groups[5].empty()) {
1562-
// The only syntaxes we allow after the very start are <tool_call>, <function=...> or <function name=...>
1563-
builder.move_to(start);
1564-
builder.add_content(builder.consume_rest());
1565-
return;
1566-
}
15671565
builder.add_content(res->prelude);
15681566

15691567
const auto & block_start = res->groups[1];

tests/test-chat.cpp

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -772,6 +772,30 @@ static void test_template_output_parsers() {
772772
"<function_call> { \"name\" : \"python\"",
773773
/* is_partial= */ true,
774774
{COMMON_CHAT_FORMAT_HERMES_2_PRO}));
775+
assert_msg_equals(
776+
simple_assist_msg("Let's call something\n"),
777+
common_chat_parse(
778+
"Let's call something\n"
779+
"<tool_call>{\"name\"",
780+
/* is_partial= */ true,
781+
{
782+
/* .format = */ COMMON_CHAT_FORMAT_HERMES_2_PRO,
783+
/* .reasoning_format = */ COMMON_REASONING_FORMAT_DEEPSEEK,
784+
/* .reasoning_in_content = */ false,
785+
/* .thinking_forced_open = */ false,
786+
}));
787+
assert_msg_equals(
788+
simple_assist_msg(""),
789+
common_chat_parse(
790+
"Let's call something\n"
791+
"<tool_call>{\"name",
792+
/* is_partial= */ true,
793+
{
794+
/* .format = */ COMMON_CHAT_FORMAT_HERMES_2_PRO,
795+
/* .reasoning_format = */ COMMON_REASONING_FORMAT_DEEPSEEK,
796+
/* .reasoning_in_content = */ false,
797+
/* .thinking_forced_open = */ false,
798+
}));
775799
assert_msg_equals(message_assist_call_thoughts,
776800
common_chat_parse(
777801
// QwQ-32B's template adds a trailing <think> if add_generation_prompt
@@ -930,8 +954,10 @@ static void test_template_output_parsers() {
930954

931955
assert_msg_equals(
932956
simple_assist_msg(
933-
"This is not a tool call:\n"
934-
"{\"name\": \"special_function\", \"arguments\": {\"arg1\": 1}}"),
957+
"This is not a tool call:",
958+
"",
959+
"special_function",
960+
"{\"arg1\": 1}"),
935961
common_chat_parse(
936962
"This is not a tool call:\n"
937963
"{\"name\": \"special_function\", \"arguments\": {\"arg1\": 1}}",

0 commit comments

Comments
 (0)