Fix incorrect coercion of strings to non-string types during parsing

This commit is contained in:
Piotr Wilkin 2026-02-03 18:49:02 +01:00
parent fa52b43c2a
commit 1bcedc2bbb
2 changed files with 55 additions and 0 deletions

View File

@ -301,6 +301,25 @@ void common_chat_peg_unified_mapper::map(const common_peg_ast_node & node) {
} else {
buffer_needs_closing_quote = true;
}
} else if (is_arg_string_value) {
// Schema declares this as string type but it parsed as non-string (e.g., number)
// Force treatment as string value - add opening quote and escape content
if (!current_tool->name.empty()) {
if (!needs_closing_quote) {
value_to_add = "\"";
needs_closing_quote = true;
}
} else {
if (!buffer_needs_closing_quote) {
value_to_add = "\"";
buffer_needs_closing_quote = true;
}
}
std::string escaped = json(value_content).dump();
if (escaped.size() >= 2 && escaped.front() == '"' && escaped.back() == '"') {
escaped = escaped.substr(1, escaped.size() - 2);
}
value_to_add += escaped;
} else {
// For non-string values (number, bool, null, object, array), add raw value content
// Using raw content instead of dump() ensures monotonicity for streaming

View File

@ -321,6 +321,23 @@ static common_chat_tool edit_tool{
})",
};
static common_chat_tool magic_tool{
/* .name = */ "magic",
/* .description = */ "Magic tool that takes a hash",
/* .parameters = */ R"({
"type": "object",
"properties": {
"name": {
"type": "string"
},
"ref": {
"type": "string"
}
},
"required": ["name", "ref"]
})",
};
static std::vector<common_chat_tool> tools{ special_function_tool, special_function_tool_with_optional_param,
python_tool, html_tool, todo_list };
@ -2079,6 +2096,25 @@ static void test_template_output_peg_parsers(bool detailed_debug) {
expect_reasoning("I was thinking").
expect_content("Now I'm not.")
.run();
// Test that numeric-looking string values are coerced to strings per the schema
tst.test(
"Let me call the magic tool\n"
"</think>\n"
"<tool_call>\n"
"<function=magic>\n"
"<parameter=name>\nfooBar\n</parameter>\n"
"<parameter=ref>\n5123123\n</parameter>\n"
"</function>\n"
"</tool_call>")
.enable_thinking(true)
.reasoning_format(COMMON_REASONING_FORMAT_DEEPSEEK)
.tools({ magic_tool })
.expect_reasoning("Let me call the magic tool")
.expect_tool_calls({
{ "magic", R"({"name": "fooBar", "ref": "5123123"})", {} },
})
.run();
}
}