@@ -670,7 +670,7 @@ static std::string wrap_code_as_arguments(common_chat_msg_parser & builder, cons
670670 * Takes a prefix regex that must have 1 group to capture the function name, a closing suffix, and expects json parameters in between.
671671 * Aggregates the prefix, suffix and in-between text into the content.
672672 */
673- static void parse_json_tool_calls (
673+ static void parse_json_tool_calls_deepseek_v3_1 (
674674 common_chat_msg_parser & builder,
675675 const std::optional<common_regex> & block_open,
676676 const std::optional<common_regex> & function_regex_start_only,
@@ -753,6 +753,83 @@ static void parse_json_tool_calls(
753753 }
754754}
755755
756+ /* *
757+ * Takes a prefix regex that must have 1 group to capture the function name, a closing suffix, and expects json parameters in between.
758+ * Aggregates the prefix, suffix and in-between text into the content.
759+ */
760+ static void parse_json_tool_calls (
761+ common_chat_msg_parser & builder,
762+ const std::optional<common_regex> & block_open,
763+ const std::optional<common_regex> & function_regex_start_only,
764+ const std::optional<common_regex> & function_regex,
765+ const common_regex & close_regex,
766+ const std::optional<common_regex> & block_close,
767+ bool allow_raw_python = false ,
768+ const std::function<std::string(const common_chat_msg_parser::find_regex_result & fres)> & get_function_name = nullptr) {
769+
770+ auto parse_tool_calls = [&]() {
771+ size_t from = std::string::npos;
772+ auto first = true ;
773+ while (true ) {
774+ auto res = function_regex_start_only && first
775+ ? builder.try_consume_regex (*function_regex_start_only)
776+ : function_regex
777+ ? builder.try_find_regex (*function_regex, from)
778+ : std::nullopt ;
779+ if (res) {
780+ std::string name;
781+ if (get_function_name) {
782+ name = get_function_name (*res);
783+ } else {
784+ GGML_ASSERT (res->groups .size () == 2 );
785+ name = builder.str (res->groups [1 ]);
786+ }
787+ first = false ;
788+ if (name.empty ()) {
789+ // get_function_name signalled us that we should skip this match and treat it as content.
790+ from = res->groups [0 ].begin + 1 ;
791+ continue ;
792+ }
793+ from = std::string::npos;
794+
795+ auto maybe_raw_python = name == " python" && allow_raw_python;
796+ if (builder.input ()[builder.pos ()] == ' {' || !maybe_raw_python) {
797+ if (auto arguments = builder.try_consume_json_with_dumped_args ({{}})) {
798+ if (!builder.add_tool_call (name, " " , arguments->value ) || arguments->is_partial ) {
799+ throw common_chat_msg_partial_exception (" incomplete tool call" );
800+ }
801+ builder.consume_regex (close_regex);
802+ }
803+ continue ;
804+ }
805+ if (maybe_raw_python) {
806+ auto arguments = wrap_code_as_arguments (builder, builder.consume_rest ());
807+ if (!builder.add_tool_call (name, " " , arguments)) {
808+ throw common_chat_msg_partial_exception (" incomplete tool call" );
809+ }
810+ return ;
811+ }
812+ throw common_chat_msg_partial_exception (" incomplete tool call" );
813+ }
814+ break ;
815+ }
816+ if (block_close) {
817+ builder.consume_regex (*block_close);
818+ }
819+ builder.consume_spaces ();
820+ builder.add_content (builder.consume_rest ());
821+ };
822+ if (block_open) {
823+ if (auto res = builder.try_find_regex (*block_open)) {
824+ parse_tool_calls ();
825+ } else {
826+ builder.add_content (builder.consume_rest ());
827+ }
828+ } else {
829+ parse_tool_calls ();
830+ }
831+ }
832+
756833static void parse_prefixed_json_tool_call_array (common_chat_msg_parser & builder, const common_regex & prefix, size_t rstrip_prefix = 0 ) {
757834 static const std::vector<std::vector<std::string>> args_paths = {{" arguments" }};
758835 if (auto res = builder.try_find_regex (prefix)) {
@@ -1420,7 +1497,7 @@ static void common_chat_parse_deepseek_v3_1_content(common_chat_msg_parser & bui
14201497
14211498 LOG_DBG (" %s: parse_tool_calls\n " , __func__);
14221499
1423- parse_json_tool_calls (
1500+ parse_json_tool_calls_deepseek_v3_1 (
14241501 builder,
14251502 /* block_open= */ tool_calls_begin,
14261503 /* function_regex_start_only= */ std::nullopt ,
0 commit comments