Fix number partial parsing issue

This commit is contained in:
Piotr Wilkin 2026-02-06 14:35:05 +01:00
parent 8c1d1bae36
commit c730c343de
3 changed files with 55 additions and 2 deletions

View File

@ -77,7 +77,7 @@ class common_chat_peg_unified_builder : public common_chat_peg_builder {
// Use for schema-declared string types - won't be treated as potential JSON container
common_peg_parser tool_arg_string_value(const common_peg_parser & p) { return tag(TOOL_ARG_STRING_VALUE, p); }
common_peg_parser tool_arg_json_value(const common_peg_parser & p) { return tag(TOOL_ARG_VALUE, p); }
common_peg_parser tool_arg_json_value(const common_peg_parser & p) { return atomic(tag(TOOL_ARG_VALUE, p)); }
// Legacy-compatible helper for building standard JSON tool calls
// Used by tests and manual parsers

View File

@ -1307,7 +1307,11 @@ common_peg_parser common_peg_parser_builder::json_number() {
auto int_part = choice({ literal("0"), sequence({ digit1_9, chars("[0-9]", 0, -1) }) });
auto frac = sequence({ literal("."), digits });
auto exp = sequence({ choice({ literal("e"), literal("E") }), optional(chars("[+-]", 1, 1)), digits });
return sequence({ optional(literal("-")), int_part, optional(frac), optional(exp), space() });
// Negative lookahead: only commit the number when the next character can't extend it.
// At EOF in partial mode, chars returns NEED_MORE → negate propagates NEED_MORE → number not committed.
// This prevents premature commits of partial numbers (e.g. "3" when "3.14" is incoming).
auto not_number_continuation = negate(chars("[0-9.eE+-]", 1, 1));
return sequence({ optional(literal("-")), int_part, optional(frac), optional(exp), not_number_continuation, space() });
});
}

View File

@ -355,6 +355,21 @@ static common_chat_tool magic_int_tool{
})",
};
static common_chat_tool amount_tool{
/* .name = */ "amount",
/* .description = */ "Amount converter",
/* .parameters = */ R"({
"type": "object",
"properties": {
"orig": {
"type": "number"
}
},
"required": ["orig"]
})",
};
static common_chat_tool string_param_tool{
/* .name = */ "string_param",
/* .description = */ "Tool with string parameter for testing",
@ -2274,6 +2289,40 @@ static void test_template_output_peg_parsers(bool detailed_debug) {
})
.run();
tst.test(
"Test negative number\n"
"</think>\n"
"<tool_call>\n"
"<function=magic_int>\n"
"<parameter=ref>\n-14\n</parameter>\n"
"</function>\n"
"</tool_call>")
.enable_thinking(true)
.reasoning_format(COMMON_REASONING_FORMAT_DEEPSEEK)
.tools({ magic_int_tool })
.expect_reasoning("Test negative number")
.expect_tool_calls({
{ "magic_int", R"({ "ref" : -14 })", {} }
})
.run();
tst.test(
"Test decimal number\n"
"</think>\n"
"<tool_call>\n"
"<function=amount>\n"
"<parameter=orig>\n3.14\n</parameter>\n"
"</function>\n"
"</tool_call>")
.enable_thinking(true)
.reasoning_format(COMMON_REASONING_FORMAT_DEEPSEEK)
.tools({ amount_tool })
.expect_reasoning("Test decimal number")
.expect_tool_calls({
{ "amount", R"({ "orig" : 3.14 })", {} }
})
.run();
}
}