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