Skip to content

Commit 7dad2f1

Browse files
authored
chat : fix LFM2 tool-call parsing double-escaping (ggml-org#24667)
* Add escape test cases * chat : fix LFM2 tool-call parsing double-escaping
1 parent e36a602 commit 7dad2f1

2 files changed

Lines changed: 25 additions & 6 deletions

File tree

common/chat-peg-parser.cpp

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -540,10 +540,11 @@ common_peg_parser common_chat_peg_builder::python_style_tool_calls(
540540
auto arg_name_parser = literal(prop_name);
541541

542542
common_peg_parser arg_value_parser = eps();
543-
auto string_value_parser = choice({
544-
literal("\"") + tool_arg_string_value(string_content('"')) + literal("\""),
545-
literal("'") + tool_arg_string_value(string_content('\'')) + literal("'")
546-
});
543+
// Quoted literal as a value: normalize_quotes_to_json preserves escapes.
544+
auto string_value_parser = tool_arg_value(choice({
545+
literal("\"") + string_content('"') + literal("\""),
546+
literal("'") + string_content('\'') + literal("'")
547+
}));
547548

548549
if (is_string_type) {
549550
arg_value_parser = string_value_parser;

tests/test-chat.cpp

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1882,11 +1882,29 @@ static void test_lfm2_parser(const std::string & template_path, bool detailed_de
18821882
.expect(simple_assist_msg("Use this format: [link text](url). Example: [Wikipedia](https://www.wikipedia.org)."))
18831883
.run();
18841884

1885-
// Python tool with multiline code in string
1885+
// Python tool with multiline code in string: the \n in the literal decodes to a real
1886+
// newline, emitted as a JSON \n escape (not a doubled backslash).
18861887
tst.test("<|tool_call_start|>[python(code=\"def hello():\\n print('hey')\")]<|tool_call_end|>")
18871888
.tools({ python_tool })
18881889
.expect_tool_calls({
1889-
{ "python", R"#({"code": "def hello():\\n print('hey')"})#", "" }
1890+
{ "python", R"#({"code": "def hello():\n print('hey')"})#", "" }
1891+
})
1892+
.run();
1893+
1894+
// String escape sequences decode to their actual characters (newline + tab here),
1895+
// so a "write a two line file" style call produces real line breaks, not literal "\n".
1896+
tst.test("<|tool_call_start|>[python(code=\"First line\\nSecond line\\tindented\")]<|tool_call_end|>")
1897+
.tools({ python_tool })
1898+
.expect_tool_calls({
1899+
{ "python", R"#({"code": "First line\nSecond line\tindented"})#", "" }
1900+
})
1901+
.run();
1902+
1903+
// Escaped quotes inside a string argument survive the round-trip.
1904+
tst.test("<|tool_call_start|>[python(code=\"print(\\\"hi\\\")\")]<|tool_call_end|>")
1905+
.tools({ python_tool })
1906+
.expect_tool_calls({
1907+
{ "python", R"#({"code": "print(\"hi\")"})#", "" }
18901908
})
18911909
.run();
18921910

0 commit comments

Comments
 (0)