char-fork/llama.cpp
git clone https://git.t4t.associates/char-fork/llama.cpp
acecd5603
char/aliceai-t5-moe
1#include "parsers.h" 2 3// The DeepSeek V4 reference implementation renders consecutive tool results into a single 4// user block, ordered by the tool call order of the preceding assistant message (matched 5// by tool call id) rather than by the order they appear in the conversation. 6static json deepseek_v4_sort_tool_results (const json & messages ) { 7json adjusted = messages ; 8 std::map < std::string ,size_t > call_order ; 9 10for (size_t i = 0 ;i < adjusted .size ();) { 11const auto & msg = adjusted [i ]; 12const auto role = msg .value ("role" ,"" ); 13 14if (role == "assistant" && msg .contains ("tool_calls" )&& 15msg .at ("tool_calls" ).is_array ()&& !msg .at ("tool_calls" ).empty ()) { 16call_order .clear (); 17const auto & tool_calls = msg .at ("tool_calls" ); 18for (size_t idx = 0 ;idx < tool_calls .size ();idx ++ ) { 19auto id = tool_calls [idx ].value ("id" ,"" ); 20if (!id .empty ()) { 21call_order [id ]= idx ; 22 } 23 } 24i ++ ; 25continue ; 26 } 27 28if (role != "user" && role != "tool" ) { 29i ++ ; 30continue ; 31 } 32 33// collect a maximal run of user/tool messages - they render into one user block 34 std::vector < size_t > tool_positions ; 35size_t run_end = i ; 36for (;run_end < adjusted .size ();run_end ++ ) { 37const auto r = adjusted [run_end ].value ("role" ,"" ); 38if (r == "tool" ) { 39tool_positions .push_back (run_end ); 40 }else if (r != "user" ) { 41break ; 42 } 43 } 44 45if (tool_positions .size ()> 1 && !call_order .empty ()) { 46 std::vector < json > results ; 47results .reserve (tool_positions .size ()); 48for (auto pos :tool_positions ) { 49results .push_back (adjusted [pos ]); 50 } 51 std::stable_sort (results .begin (),results .end (), [& ](const json & a ,const json & b ) { 52const auto order = [& ](const json & m ) { 53auto it = call_order .find (m .value ("tool_call_id" ,"" )); 54return it == call_order .end () ? (size_t )0 :it -> second ; 55 }; 56return order (a )< order (b ); 57 }); 58for (size_t k = 0 ;k < tool_positions .size ();k ++ ) { 59adjusted [tool_positions [k ]]= std::move (results [k ]); 60 } 61 } 62 63i = run_end ; 64 } 65 66return adjusted ; 67} 68 69common_chat_params common_chat_params_init_deepseek_v3_2 (const common_chat_template & tmpl , 70const autoparser::generation_params & inputs ) { 71common_chat_params data ; 72 73// V4 uses the same DSML markup as V3.2, but names the tool call block "tool_calls" 74// instead of "function_calls", renders tool results in tool call order and its 75// non-thinking generation prompt ends with a bare </think> instead of an empty 76// <think></think> pair. 77const bool is_v4 = tmpl .source ().find ("function_calls" )== std::string::npos ; 78 79 std::optional < json > adjusted_messages ; 80if (is_v4 ) { 81adjusted_messages = deepseek_v4_sort_tool_results (inputs .messages ); 82 } 83 84auto has_tools = inputs .tools .is_array ()&& !inputs .tools .empty (); 85auto has_response_format = !inputs .json_schema .is_null ()&& inputs .json_schema .is_object (); 86auto extract_reasoning = inputs .reasoning_format != COMMON_REASONING_FORMAT_NONE ; 87auto include_grammar = has_response_format || (has_tools && inputs .tool_choice != COMMON_CHAT_TOOL_CHOICE_NONE ); 88 89 std::optional < json > additional_context ; 90if (is_v4 && has_response_format ) { 91additional_context = json { {"response_format" ,inputs .json_schema } }; 92 } 93 94const std::string DSML = "|DSML|" ; 95const std::string THINK_START = "<think>" ; 96const std::string THINK_END = "</think>" ; 97const std::string TC_BLOCK = is_v4 ?"tool_calls" :"function_calls" ; 98const std::string FC_START = "<" + DSML + TC_BLOCK + ">" ; 99const std::string FC_END = "</" + DSML + TC_BLOCK + ">" ; 100const std::string INVOKE_START = "<" + DSML + "invoke" ; 101const std::string INVOKE_END = "</" + DSML + "invoke>" ; 102const std::string PARAM_START = "<" + DSML + "parameter" ; 103const std::string PARAM_END = "</" + DSML + "parameter>" ; 104const std::string GEN_PROMPT = "<|Assistant|>" ; 105const std::string TC_SEPARATOR = "\n\n" ; 106 107data .prompt = common_chat_template_direct_apply_impl ( 108tmpl ,inputs ,adjusted_messages , std::nullopt ,additional_context ); 109data .generation_prompt = common_chat_template_generation_prompt_impl ( 110tmpl ,inputs ,adjusted_messages , std::nullopt ,additional_context ); 111data .format = COMMON_CHAT_FORMAT_PEG_NATIVE ; 112data .supports_thinking = true; 113data .thinking_start_tag = THINK_START ; 114data .thinking_end_tags = {THINK_END ,FC_START }; 115data .preserved_tokens = { 116DSML , 117THINK_START , 118THINK_END , 119 }; 120 121if (inputs .has_continuation ()) { 122const auto & msg = inputs .continue_msg ; 123 124if (is_v4 && msg .reasoning_content .empty ()) { 125data .generation_prompt = GEN_PROMPT + THINK_END ; 126if (inputs .continue_final_message == COMMON_CHAT_CONTINUATION_CONTENT ) { 127data .generation_prompt += msg .render_content (); 128 } 129 }else { 130data .generation_prompt = GEN_PROMPT + THINK_START + msg .reasoning_content ; 131if (inputs .continue_final_message == COMMON_CHAT_CONTINUATION_CONTENT ) { 132data .generation_prompt += THINK_END + msg .render_content (); 133 } 134 } 135 136data .prompt += data .generation_prompt ; 137 } 138 139bool require_tools = inputs .tool_choice == COMMON_CHAT_TOOL_CHOICE_REQUIRED ; 140bool has_tool_calls = has_tools && inputs .tool_choice != COMMON_CHAT_TOOL_CHOICE_NONE ; 141 142auto parser = build_chat_peg_parser ([& ](common_chat_peg_builder & p ) { 143auto generation_prompt = p .literal (GEN_PROMPT ); 144auto end = p .end (); 145 146// build tool call section first since we might need it in reasoning 147auto tool_choice = p .choice (); 148if (has_tool_calls ) { 149foreach_function (inputs .tools , [& ](const json & tool ) { 150const auto & function = tool .at ("function" ); 151 std::string name = function .at ("name" ); 152 153 std::vector < common_peg_parser > required_parsers ; 154 std::vector < common_peg_parser > optional_parsers ; 155foreach_parameter (function , [& ](const common_chat_schema_property & param ,const common_chat_schema_document_ptr & doc ) { 156bool is_string = param .schema -> may_be_string (); 157 158auto arg = p .tool_arg ( 159p .tool_arg_open (p .literal (PARAM_START + " name=\"" )+ p .tool_arg_name (p .literal (param .name ))+ 160p .literal ("\" string=\"" + std::string (is_string ?"true" :"false" )+ "\">" ))+ 161 (is_string ? 162p .tool_arg_string_value (p .until (PARAM_END )) : 163p .tool_arg_json_value (p .schema (p .json (),"tool-" + name + "-arg-" + param .name + "-schema" , 164doc ,* param .schema )))+ 165p .tool_arg_close (p .literal (PARAM_END ))); 166 167auto named_arg = p .rule ("tool-" + name + "-arg-" + param .name ,arg ); 168if (param .required ) { 169required_parsers .push_back (named_arg ); 170 }else { 171optional_parsers .push_back (named_arg ); 172 } 173 }); 174 175common_peg_parser args_seq = p .eps (); 176for (size_t i = 0 ;i < required_parsers .size ();i ++ ) { 177if (i > 0 ) { 178args_seq = args_seq + p .space (); 179 } 180args_seq = args_seq + required_parsers [i ]; 181 } 182 183if (!optional_parsers .empty ()) { 184common_peg_parser any_opt = p .choice (); 185for (const auto & opt :optional_parsers ) { 186any_opt |=opt ; 187 } 188args_seq = args_seq + p .repeat (p .space ()+ any_opt ,0 ,-1 ); 189 } 190 191common_peg_parser invoke_body = args_seq ; 192auto func_parser = p .tool (p .tool_open (p .literal (INVOKE_START + " name=\"" )+ 193p .tool_name (p .literal (name ))+ p .literal ("\">\n" ))+ 194invoke_body + p .space ()+ p .tool_close (p .literal (INVOKE_END ))); 195 196tool_choice |=p .rule ("tool-" + name ,func_parser ); 197 }); 198 } 199 200common_peg_parser tool_calls = p .eps (); 201if (inputs .parallel_tool_calls ) { 202tool_calls = p .trigger_rule ("tool-call" , 203p .literal (FC_START )+ p .space ()+ tool_choice + 204p .zero_or_more (p .space ()+ tool_choice )+ p .space ()+ p .literal (FC_END )); 205 }else { 206tool_calls = p .trigger_rule ("tool-call" , 207p .literal (FC_START )+ p .space ()+ tool_choice + p .space ()+ p .literal (FC_END )); 208 } 209 210auto reasoning = p .eps (); 211auto reasoning_with_tc = p .eps (); 212auto obligatory_tool_calls = tool_calls ; 213bool allow_reasoning_with_tc = false; 214 215if (!require_tools ) { 216tool_calls = p .optional (tool_calls ); 217 } 218 219if (extract_reasoning && inputs .enable_thinking ) { 220reasoning = p .optional (THINK_START + p .reasoning (p .until (THINK_END ))+ THINK_END ); 221reasoning_with_tc = THINK_START + 222p .reasoning (p .until_one_of ({TC_SEPARATOR + FC_START ,FC_START ,THINK_END }))+ 223p .space ()+ obligatory_tool_calls ; 224allow_reasoning_with_tc = true; 225 }else if (extract_reasoning ) { 226// Thinking disabled but reasoning extraction requested: the generation prompt 227// contains an empty <think></think> pair (V3.2) or a bare </think> (V4) that 228// must still be consumed. 229reasoning = is_v4 230 ?p .optional (p .literal (THINK_END )) 231 :p .optional (p .literal (THINK_START )+ p .until (THINK_END )+ p .literal (THINK_END )); 232 } 233 234if (has_response_format ) { 235auto response_format = p .rule ("response-format" , 236p .literal ("```json" )+ p .space ()+ 237p .content (p .schema (p .json (),"response-format-schema" ,inputs .json_schema ))+ 238p .space ()+ p .literal ("```" )); 239return generation_prompt + reasoning + response_format + end ; 240 } 241 242if (!has_tool_calls ) { 243return generation_prompt + reasoning + p .content (p .rest ())+ end ; 244 } 245 246auto content_before_tools = p .negate (p .literal (THINK_START ))+ 247p .content (p .until_one_of ({TC_SEPARATOR + FC_START ,FC_START }))+ 248p .space (); 249return allow_reasoning_with_tc ?generation_prompt + (reasoning_with_tc | (reasoning + content_before_tools + tool_calls ))+ end : 250generation_prompt + reasoning + content_before_tools + tool_calls + end ; 251 }); 252 253data .parser = parser .save (); 254 255if (include_grammar ) { 256data .grammar_lazy = has_tools && !require_tools ; 257data .grammar = build_grammar ([& ](const common_grammar_builder & builder ) { 258parser .build_grammar (builder ,data .grammar_lazy ); 259 }); 260 261data .grammar_triggers = { 262 {COMMON_GRAMMAR_TRIGGER_TYPE_WORD ,FC_START }, 263 }; 264 } 265 266return data ; 267}