Fix pesky issue on optional trailing arguments in function calls for TAGGED format

This commit is contained in:
Piotr Wilkin 2026-02-04 22:14:36 +01:00
parent 2726bd7090
commit 8e87ba402f
2 changed files with 78 additions and 0 deletions

View File

@ -309,6 +309,12 @@ common_peg_parser universal_peg_generator::build_tool_parser(
if (!m.func_close.empty()) {
func_parser = func_parser + p.space() + p.tool_close(p.literal(m.func_close));
} else if (!m.per_call_end.empty()) {
// When there's no func_close but there is a per_call_end marker, use peek() to ensure
// we only emit tool_close when we can actually see the closing marker. This prevents
// premature closing during partial parsing when we've seen e.g. "</" which could be
// either "</tool_call>" (end) or "<arg_key>" prefix that failed to match.
func_parser = func_parser + p.tool_close(p.peek(p.literal(m.per_call_end)));
} else {
func_parser = func_parser + p.tool_close(p.space()); // force this to process tool closing callbacks in mapper
}

View File

@ -338,6 +338,24 @@ static common_chat_tool magic_tool{
})",
};
static common_chat_tool magic_int_tool{
/* .name = */ "magic_int",
/* .description = */ "Magic tool that takes a hash",
/* .parameters = */ R"({
"type": "object",
"properties": {
"ref": {
"type": "integer"
},
"name": {
"type": "string"
}
},
"required": ["ref"]
})",
};
static std::vector<common_chat_tool> tools{ special_function_tool, special_function_tool_with_optional_param,
python_tool, html_tool, todo_list };
@ -2115,6 +2133,60 @@ static void test_template_output_peg_parsers(bool detailed_debug) {
{ "magic", R"({"name": "fooBar", "ref": "5123123"})", {} },
})
.run();
// Test that numeric values are correctly interpreted as numbers when schema calls for number
tst.test(
"Let me call the special function\n"
"</think>\n"
"<tool_call>\n"
"<function=special_function>\n"
"<parameter=arg1>\n42555916\n</parameter>\n"
"</function>\n"
"</tool_call>")
.enable_thinking(true)
.reasoning_format(COMMON_REASONING_FORMAT_DEEPSEEK)
.tools({ special_function_tool })
.expect_reasoning("Let me call the special function")
.expect_tool_calls({
{ "special_function", R"({"arg1": 42555916})", {} },
})
.run();
tst.test(
"Let me call the special function with opt\n"
"</think>\n"
"<tool_call>\n"
"<function=special_function_with_opt>\n"
"<parameter=arg1>\n42555916\n</parameter>\n"
"</function>\n"
"</tool_call>")
.enable_thinking(true)
.reasoning_format(COMMON_REASONING_FORMAT_DEEPSEEK)
.tools({ special_function_tool_with_optional_param })
.expect_reasoning("Let me call the special function with opt")
.expect_tool_calls({
{ "special_function_with_opt", R"({"arg1": 42555916})", {} },
})
.run();
tst.test(
"Let me call the magic_int function\n"
"</think>\n"
"<tool_call>\n"
"<function=magic_int>\n"
"<parameter=ref>\n42555916\n</parameter>\n"
"<parameter=name>\nbaz\n</parameter>\n"
"</function>\n"
"</tool_call>")
.enable_thinking(true)
.reasoning_format(COMMON_REASONING_FORMAT_DEEPSEEK)
.tools({ magic_int_tool })
.expect_reasoning("Let me call the magic_int function")
.expect_tool_calls({
{ "magic_int", R"({"ref": 42555916, "name": "baz"})", {} },
})
.run();
}
}