Skip to content

Commit 42cb16f

Browse files
author
ochafik
committed
fix partial json crash after comma
1 parent 1d25178 commit 42cb16f

File tree

3 files changed

+210
-265
lines changed

3 files changed

+210
-265
lines changed

common/json-partial.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -218,7 +218,7 @@ bool common_json_parse(
218218
}
219219
} else if (err_loc.stack.back().type == COMMON_JSON_STACK_ELEMENT_OBJECT) {
220220
if ((last_non_sp_char == '{' && can_parse(str + closing)) ||
221-
(last_non_sp_char == ',' && can_parse(str + "1" + closing))) {
221+
(last_non_sp_char == ',' && can_parse(str + "\"\": 1" + closing))) {
222222
// Was about to create an object key+value
223223
str += (out.healing_marker.json_dump_marker = "\"" + magic_seed) + "\": 1" + closing;
224224
} else if (!was_maybe_number() && can_parse(str + ",\"\": 1" + closing)) {

tests/test-chat.cpp

Lines changed: 47 additions & 255 deletions
Original file line numberDiff line numberDiff line change
@@ -391,211 +391,36 @@ const common_chat_msg message_user_parts {
391391
/* .tool_name = */ "",
392392
/* .tool_call_id = */ "",
393393
};
394-
const common_chat_msg message_assist {
395-
"assistant",
396-
"Hello, world!\nWhat's up?",
397-
/* .content_parts = */ {},
398-
/* .tool_calls = */ {},
399-
/* .reasoning_content = */ "",
400-
/* .tool_name = */ "",
401-
/* .tool_call_id = */ "",
402-
};
403-
const common_chat_msg message_assist_thoughts_unparsed_deepseek {
404-
"assistant",
405-
"<think>I'm thinking</think>Hello, world!\nWhat's up?",
406-
/* .content_parts = */ {},
407-
/* .tool_calls = */ {},
408-
/* .reasoning_content = */ "",
409-
/* .tool_name = */ "",
410-
/* .tool_call_id = */ "",
411-
};
412-
const common_chat_msg message_assist_thoughts_unparsed_r7b {
413-
"assistant",
414-
"<|START_THINKING|>I'm thinking<|END_THINKING|>Hello, world!\nWhat's up?",
415-
/* .content_parts = */ {},
416-
/* .tool_calls = */ {},
417-
/* .reasoning_content = */ "",
418-
/* .tool_name = */ "",
419-
/* .tool_call_id = */ "",
420-
};
421-
const common_chat_msg message_assist_thoughts {
422-
"assistant",
423-
"Hello, world!\nWhat's up?",
424-
/* .content_parts = */ {},
425-
/* .tool_calls = */ {},
426-
/* .reasoning_content = */ "I'm thinking",
427-
/* .tool_name = */ "",
428-
/* .tool_call_id = */ "",
429-
};
430-
const common_chat_msg message_assist_thoughts_unopened_unparsed {
431-
"assistant",
432-
"I'm thinking</think>Hello, world!\nWhat's up?",
433-
/* .content_parts = */ {},
434-
/* .tool_calls = */ {},
435-
/* .reasoning_content = */ "",
436-
/* .tool_name = */ "",
437-
/* .tool_call_id = */ "",
438-
};
439-
const std::vector<common_chat_tool_call> tool_calls {
440-
{ "special_function", "{\"arg1\": 1}", /* .id = */ "" },
441-
};
442-
const std::vector<common_chat_tool_call> tool_calls_cutoff_args {
443-
{ "special_function", "{\"arg", /* .id = */ "" },
444-
};
445-
const std::vector<common_chat_tool_call> tool_calls_empty_args {
446-
{ "special_function", "", /* .id = */ "" },
447-
};
448-
const std::vector<common_chat_tool_call> tool_calls_idx {
449-
{ "special_function", "{\"arg1\": 1}", /* .id = */ "0" },
450-
};
451-
const std::vector<common_chat_tool_call> tool_calls_id {
452-
{ "special_function", "{\"arg1\": 1}", /* .id = */ "123456789" },
453-
};
454-
const std::vector<common_chat_tool_call> tool_calls_python {
455-
{ "python", "{\"code\": \"print('hey')\"}", /* .id = */ "" },
456-
};
457-
const std::vector<common_chat_tool_call> tool_calls_python_lines {
458-
{ "python", "{\"code\": \"# This is a program:\\nprint('hey')\"}", /* .id = */ "" },
459-
};
460-
const std::vector<common_chat_tool_call> tool_calls_python_lines_unclosed {
461-
{ "python", "{\"code\":\"# This is a program:\\nprint('hey')", /* .id = */ "" },
462-
};
463-
464-
const common_chat_msg message_assist_empty {
465-
"assistant",
466-
"",
467-
/* .content_parts = */ {},
468-
/* .tool_calls = */ {},
469-
/* .reasoning_content = */ "",
470-
/* .tool_name = */ "",
471-
/* .tool_call_id = */ "",
472-
};
473-
const common_chat_msg message_assist_call {
474-
"assistant",
475-
"",
476-
/* .content_parts = */ {},
477-
tool_calls,
478-
/* .reasoning_content = */ "",
479-
/* .tool_name = */ "",
480-
/* .tool_call_id = */ "",
481-
};
482-
const common_chat_msg message_assist_call_content {
483-
"assistant",
484-
"Hello, world!\nWhat's up?",
485-
/* .content_parts = */ {},
486-
tool_calls,
487-
/* .reasoning_content = */ "",
488-
/* .tool_name = */ "",
489-
/* .tool_call_id = */ "",
490-
};
491-
const common_chat_msg message_assist_thoughts_no_content {
492-
"assistant",
493-
"",
494-
/* .content_parts = */ {},
495-
/* .tool_calls = */ {},
496-
/* .reasoning_content = */ "I'm\nthinking",
497-
/* .tool_name = */ "",
498-
/* .tool_call_id = */ "",
499-
};
500-
const common_chat_msg message_assist_call_empty_args {
501-
"assistant",
502-
"",
503-
/* .content_parts = */ {},
504-
tool_calls_empty_args,
505-
/* .reasoning_content = */ "",
506-
/* .tool_name = */ "",
507-
/* .tool_call_id = */ "",
508-
};
509-
const common_chat_msg message_assist_call_cutoff_args {
510-
"assistant",
511-
"",
512-
/* .content_parts = */ {},
513-
tool_calls_cutoff_args,
514-
/* .reasoning_content = */ "",
515-
/* .tool_name = */ "",
516-
/* .tool_call_id = */ "",
517-
};
518-
const common_chat_msg message_assist_call_thoughts = {
519-
"assistant",
520-
/* .content = */ "",
521-
/* .content_parts = */ {},
522-
tool_calls,
523-
/* .reasoning_content = */ "I'm\nthinking",
524-
/* .tool_name = */ "",
525-
/* .tool_call_id = */ "",
526-
};
527-
const common_chat_msg message_assist_call_thoughts_unparsed = {
528-
"assistant",
529-
/* .content = */ "<think>I'm\nthinking</think>\n\n",
530-
/* .content_parts = */ {},
531-
tool_calls,
532-
/* .reasoning_content = */ "",
533-
/* .tool_name = */ "",
534-
/* .tool_call_id = */ "",
535-
};
536-
const common_chat_msg message_assist_call_id {
537-
"assistant",
538-
"",
539-
/* .content_parts = */ {},
540-
tool_calls_id,
541-
/* .reasoning_content = */ "",
542-
/* .tool_name = */ "",
543-
/* .tool_call_id = */ "",
544-
};
545-
const common_chat_msg message_assist_call_idx {
546-
"assistant",
547-
"",
548-
/* .content_parts = */ {},
549-
tool_calls_idx,
550-
/* .reasoning_content = */ "",
551-
/* .tool_name = */ "",
552-
/* .tool_call_id = */ "",
553-
};
554-
const common_chat_msg message_assist_thoughts_call_idx {
555-
"assistant",
556-
"",
557-
/* .content_parts = */ {},
558-
tool_calls_idx,
559-
/* .reasoning_content = */ "I'm\nthinking",
560-
/* .tool_name = */ "",
561-
/* .tool_call_id = */ "",
562-
};
563-
const common_chat_msg message_assist_call_python {
564-
"assistant",
565-
"",
566-
/* .content_parts = */ {},
567-
tool_calls_python,
568-
/* .reasoning_content = */ "",
569-
/* .tool_name = */ "",
570-
/* .tool_call_id = */ "",
571-
};
572-
const common_chat_msg message_assist_call_python_lines {
573-
"assistant",
574-
"",
575-
/* .content_parts = */ {},
576-
tool_calls_python_lines,
577-
/* .reasoning_content = */ "",
578-
/* .tool_name = */ "",
579-
/* .tool_call_id = */ "",
580-
};
581-
const common_chat_msg message_assist_call_python_lines_unclosed {
582-
"assistant",
583-
"",
584-
/* .content_parts = */ {},
585-
tool_calls_python_lines_unclosed,
586-
/* .reasoning_content = */ "",
587-
/* .tool_name = */ "",
588-
/* .tool_call_id = */ "",
589-
};
590-
const common_chat_msg message_assist_call_code_interpreter {
591-
"assistant",
592-
"",
593-
/* .content_parts = */ {},
594-
{ { "code_interpreter", "{\"code\": \"print('hey')\"}", /* .id = */ "" } },
595-
/* .reasoning_content = */ "",
596-
/* .tool_name = */ "",
597-
/* .tool_call_id = */ "",
598-
};
394+
static common_chat_msg simple_assist_msg(const std::string & content, const std::string & reasoning_content = "", const std::string & tool_name = "", const std::string & arguments = "", const std::string & id = "") {
395+
common_chat_msg msg;
396+
msg.role = "assistant";
397+
msg.content = content;
398+
msg.reasoning_content = reasoning_content;
399+
if (!tool_name.empty()) {
400+
msg.tool_calls.push_back({ tool_name, arguments, id });
401+
}
402+
return msg;
403+
}
404+
const common_chat_msg message_assist = simple_assist_msg("Hello, world!\nWhat's up?");
405+
const common_chat_msg message_assist_empty = simple_assist_msg("");
406+
const common_chat_msg message_assist_thoughts_unparsed_deepseek = simple_assist_msg("<think>I'm thinking</think>Hello, world!\nWhat's up?");
407+
const common_chat_msg message_assist_thoughts_unparsed_r7b = simple_assist_msg("<|START_THINKING|>I'm thinking<|END_THINKING|>Hello, world!\nWhat's up?");
408+
const common_chat_msg message_assist_thoughts = simple_assist_msg("Hello, world!\nWhat's up?", "I'm thinking");
409+
const common_chat_msg message_assist_thoughts_unopened_unparsed = simple_assist_msg("I'm thinking</think>Hello, world!\nWhat's up?");
410+
const common_chat_msg message_assist_thoughts_no_content = simple_assist_msg("", "I'm\nthinking");
411+
const common_chat_msg message_assist_call = simple_assist_msg("", "", "python", "{\"code\": \"print('hey')\"}");
412+
const common_chat_msg message_assist_call_content = simple_assist_msg("Hello, world!\nWhat's up?", "", "special_function", "{\"arg1\": 1}");
413+
const common_chat_msg message_assist_call_empty_args = simple_assist_msg("", "", "special_function", "{}");
414+
const common_chat_msg message_assist_call_cutoff_args = simple_assist_msg("python", "{\"arg");
415+
const common_chat_msg message_assist_call_thoughts = simple_assist_msg("", "I'm\nthinking", "special_function", "{\"arg1\": 1}");
416+
const common_chat_msg message_assist_call_thoughts_unparsed = simple_assist_msg("<think>I'm\nthinking</think>\n\n", "", "special_function", "{\"arg1\": 1}");
417+
const common_chat_msg message_assist_call_id = simple_assist_msg("", "", "special_function", "{\"arg1\": 1}", /* .id = */ "123456789");
418+
const common_chat_msg message_assist_call_idx = simple_assist_msg("", "", "special_function", "{\"arg1\": 1}", /* .id = */ "0");
419+
const common_chat_msg message_assist_thoughts_call_idx = simple_assist_msg("", "I'm thinking", "special_function", "{\"arg1\": 1}", /* id = */ "0");
420+
const common_chat_msg message_assist_call_python = simple_assist_msg("", "", "python", "{\"code\": \"print('hey')\"}");
421+
const common_chat_msg message_assist_call_python_lines = simple_assist_msg("", "", "python", "# This is a program:\nprint('hey')\n");
422+
const common_chat_msg message_assist_call_python_lines_unclosed = simple_assist_msg("", "", "python", "{\"code\":\"# This is a program:\\nprint('hey')");
423+
const common_chat_msg message_assist_call_code_interpreter = simple_assist_msg("", "", "code_interpreter", "{\"code\": \"print('hey')\"}");
599424

600425
static void test_msgs_oaicompat_json_conversion() {
601426
printf("[%s]\n", __func__);
@@ -855,6 +680,14 @@ static void test_template_output_parsers() {
855680
"{ \"tool_call\" : { \"name\" : \"t",
856681
/* is_partial= */ true,
857682
{COMMON_CHAT_FORMAT_GENERIC}));
683+
684+
assert_equals(
685+
simple_assist_msg("", "", "puppeteer_screenshot", "{\"name\":\"servethehome_homepage\","),
686+
common_chat_parse(
687+
R"({"tool_call": {"name": "puppeteer_screenshot", "arguments": {"name": "servethehome_homepage",)",
688+
/* is_partial= */ true,
689+
{COMMON_CHAT_FORMAT_GENERIC}));
690+
858691
assert_equals(
859692
message_assist_call_empty_args,
860693
common_chat_parse(
@@ -933,21 +766,7 @@ static void test_template_output_parsers() {
933766

934767
// Test parsing
935768
assert_msg_equals(
936-
{
937-
/* .role = */ "assistant",
938-
/* .content = */ "",
939-
/* .content_parts = */ {},
940-
/* .tool_calls = */ {
941-
{
942-
/* .name = */ "python",
943-
/* .arguments = */ "",
944-
/* .id = */ "",
945-
}
946-
},
947-
/* .reasoning_content = */ "",
948-
/* .tool_name = */ "",
949-
/* .tool_call_id = */ "",
950-
},
769+
simple_assist_msg("", "", "python", ""),
951770
common_chat_parse(
952771
"```json\n"
953772
"<function_call> { \"name\" : \"python\"",
@@ -1110,16 +929,9 @@ static void test_template_output_parsers() {
1110929
{COMMON_CHAT_FORMAT_HERMES_2_PRO}));
1111930

1112931
assert_msg_equals(
1113-
{
1114-
/* .role = */ "assistant",
932+
simple_assist_msg(
1115933
"This is not a tool call:\n"
1116-
"{\"name\": \"special_function\", \"arguments\": {\"arg1\": 1}}",
1117-
/* .content_parts = */ {},
1118-
/* .tool_calls = */ {},
1119-
/* .reasoning_content = */ "",
1120-
/* .tool_name = */ "",
1121-
/* .tool_call_id = */ "",
1122-
},
934+
"{\"name\": \"special_function\", \"arguments\": {\"arg1\": 1}}"),
1123935
common_chat_parse(
1124936
"This is not a tool call:\n"
1125937
"{\"name\": \"special_function\", \"arguments\": {\"arg1\": 1}}",
@@ -1246,15 +1058,11 @@ static void test_template_output_parsers() {
12461058
assert_equals(COMMON_CHAT_FORMAT_FUNCTIONARY_V3_2, common_chat_templates_apply(tmpls.get(), inputs_tools).format);
12471059

12481060
assert_msg_equals(
1249-
common_chat_msg {
1250-
"assistant",
1061+
simple_assist_msg(
12511062
"Hello, world!\nnono\nWhat's up?",
1252-
/* .content_parts = */ {},
1253-
/* .tool_calls = */ tool_calls,
1254-
/* .reasoning_content = */ "",
1255-
/* .tool_name = */ "",
1256-
/* .tool_call_id = */ ""
1257-
},
1063+
"",
1064+
"special_function",
1065+
"{\"arg1\": 1}"),
12581066
common_chat_parse(
12591067
"all\n"
12601068
"Hello, world!\n"
@@ -1323,15 +1131,7 @@ static void test_template_output_parsers() {
13231131
test_templates(tmpls.get(), end_tokens, message_assist, tools, "Hello, world!\nWhat's up?", /* expect_grammar_triggered= */ false);
13241132
test_templates(tmpls.get(), end_tokens, message_assist_thoughts, tools, "Hello, world!\nWhat's up?", /* expect_grammar_triggered= */ false);
13251133
assert_msg_equals(
1326-
{
1327-
/* .role = */ "assistant",
1328-
/* .content = */ "Hello, world!\nWhat's up?",
1329-
/* .content_parts = */ {},
1330-
/* .tool_calls = */ {},
1331-
/* .reasoning_content = */ "<think>I'm thinking",
1332-
/* .tool_name = */ "",
1333-
/* .tool_call_id = */ ""
1334-
},
1134+
simple_assist_msg("Hello, world!\nWhat's up?", "<think>I'm thinking"),
13351135
common_chat_parse(
13361136
"<think>I'm thinking</think>Hello, world!\nWhat's up?",
13371137
/* is_partial= */ false,
@@ -1342,15 +1142,7 @@ static void test_template_output_parsers() {
13421142
/* .thinking_forced_open = */ true,
13431143
}));
13441144
assert_msg_equals(
1345-
{
1346-
/* .role = */ "assistant",
1347-
/* .content = */ "",
1348-
/* .content_parts = */ {},
1349-
/* .tool_calls = */ {},
1350-
/* .reasoning_content = */ "I need to remember the correct syntax. It starts with <|tool▁calls▁begin|> and ends with",
1351-
/* .tool_name = */ "",
1352-
/* .tool_call_id = */ ""
1353-
},
1145+
simple_assist_msg("", "I need to remember the correct syntax. It starts with <|tool▁calls▁begin|> and ends with"),
13541146
common_chat_parse(
13551147
"I need to remember the correct syntax. It starts with <|tool▁calls▁begin|> and ends with",
13561148
/* is_partial= */ true,

0 commit comments

Comments
 (0)