diff --git a/common/chat-peg-parser.h b/common/chat-peg-parser.h
index 7304ca7e61..f5d49a403a 100644
--- a/common/chat-peg-parser.h
+++ b/common/chat-peg-parser.h
@@ -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
diff --git a/common/peg-parser.cpp b/common/peg-parser.cpp
index 7a4c1cc398..f1b10b21a5 100644
--- a/common/peg-parser.cpp
+++ b/common/peg-parser.cpp
@@ -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() });
});
}
diff --git a/tests/test-chat.cpp b/tests/test-chat.cpp
index f55ab398bd..e64e362129 100644
--- a/tests/test-chat.cpp
+++ b/tests/test-chat.cpp
@@ -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"
+ "\n"
+ "\n"
+ "\n"
+ "\n-14\n\n"
+ "\n"
+ "")
+ .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"
+ "\n"
+ "\n"
+ "\n"
+ "\n3.14\n\n"
+ "\n"
+ "")
+ .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();
+
}
}