char-fork/llama.cpp

git clone https://git.t4t.associates/char-fork/llama.cpp

Xuan-Son Nguyencommon: add json.h abstraction (#27511)d9f918d2d

char/aliceai-t5-moe
65.8 KiB1901 linesraw
1#include "server-task.h"
2
3#include "build-info.h"
4#include "server-chat.h"
5#include "chat.h"
6#include "common.h"
7#include "json-schema-to-grammar.h"
8#include "llama.h"
9#include "sampling.h"
10#include "speculative.h"
11#include "server-common.h"
12
13#include <sstream>
14
15//
16// task_params
17//
18
19json task_params::format_logit_bias(const std::vector<llama_logit_bias> & logit_bias) const {
20    json data = json::array();
21    for (const auto & lb : logit_bias) {
22        data.push_back(json{
23            {"bias", lb.bias},
24            {"token", lb.token},
25        });
26    }
27    return data;
28}
29
30json task_params::to_json(bool only_metrics) const {
31    std::vector<std::string> samplers;
32    samplers.reserve(sampling.samplers.size());
33    for (const auto & sampler : sampling.samplers) {
34        samplers.emplace_back(common_sampler_type_to_str(sampler));
35    }
36
37    json lora = json::array();
38    for (auto & it : this->lora) {
39        lora.push_back({{"id", it.first}, {"scale", it.second}});
40    }
41
42    if (only_metrics) {
43        return json {
44            {"seed",                      sampling.seed},
45            {"temperature",               sampling.temp},
46            {"dynatemp_range",            sampling.dynatemp_range},
47            {"dynatemp_exponent",         sampling.dynatemp_exponent},
48            {"top_k",                     sampling.top_k},
49            {"top_p",                     sampling.top_p},
50            {"min_p",                     sampling.min_p},
51            {"top_n_sigma",               sampling.top_n_sigma},
52            {"xtc_probability",           sampling.xtc_probability},
53            {"xtc_threshold",             sampling.xtc_threshold},
54            {"typical_p",                 sampling.typ_p},
55            {"repeat_last_n",             sampling.penalty_last_n},
56            {"repeat_penalty",            sampling.penalty_repeat},
57            {"presence_penalty",          sampling.penalty_present},
58            {"frequency_penalty",         sampling.penalty_freq},
59            {"dry_multiplier",            sampling.dry_multiplier},
60            {"dry_base",                  sampling.dry_base},
61            {"dry_allowed_length",        sampling.dry_allowed_length},
62            {"dry_penalty_last_n",        sampling.dry_penalty_last_n},
63            {"mirostat",                  sampling.mirostat},
64            {"mirostat_tau",              sampling.mirostat_tau},
65            {"mirostat_eta",              sampling.mirostat_eta},
66            {"adaptive_target",           sampling.adaptive_target},
67            {"adaptive_decay",            sampling.adaptive_decay},
68            {"max_tokens",                n_predict},
69            {"n_predict",                 n_predict}, // TODO: deduplicate?
70            {"n_keep",                    n_keep},
71            {"n_discard",                 n_discard},
72            {"ignore_eos",                sampling.ignore_eos},
73            {"stream",                    stream},
74            {"n_probs",                   sampling.n_probs},
75            {"min_keep",                  sampling.min_keep},
76            {"chat_format",               common_chat_format_name(chat_parser_params.format)},
77            {"reasoning_format",          common_reasoning_format_name(chat_parser_params.reasoning_format)},
78            {"reasoning_in_content",      chat_parser_params.reasoning_in_content},
79            {"generation_prompt",         chat_parser_params.generation_prompt},
80            {"samplers",                  samplers},
81            {"speculative.types",         common_speculative_type_name_str(speculative.types)},
82            {"timings_per_token",         timings_per_token},
83            {"post_sampling_probs",       post_sampling_probs},
84            {"backend_sampling",          sampling.backend_sampling},
85            {"lora",                      lora},
86        };
87    }
88
89    auto grammar_triggers = json::array();
90    for (const auto & trigger : sampling.grammar_triggers) {
91        server_grammar_trigger ct(trigger);
92        grammar_triggers.push_back(ct.to_json());
93    }
94
95    return json {
96        {"seed",                      sampling.seed},
97        {"temperature",               sampling.temp},
98        {"dynatemp_range",            sampling.dynatemp_range},
99        {"dynatemp_exponent",         sampling.dynatemp_exponent},
100        {"top_k",                     sampling.top_k},
101        {"top_p",                     sampling.top_p},
102        {"min_p",                     sampling.min_p},
103        {"top_n_sigma",               sampling.top_n_sigma},
104        {"xtc_probability",           sampling.xtc_probability},
105        {"xtc_threshold",             sampling.xtc_threshold},
106        {"typical_p",                 sampling.typ_p},
107        {"repeat_last_n",             sampling.penalty_last_n},
108        {"repeat_penalty",            sampling.penalty_repeat},
109        {"presence_penalty",          sampling.penalty_present},
110        {"frequency_penalty",         sampling.penalty_freq},
111        {"dry_multiplier",            sampling.dry_multiplier},
112        {"dry_base",                  sampling.dry_base},
113        {"dry_allowed_length",        sampling.dry_allowed_length},
114        {"dry_penalty_last_n",        sampling.dry_penalty_last_n},
115        {"dry_sequence_breakers",     sampling.dry_sequence_breakers},
116        {"mirostat",                  sampling.mirostat},
117        {"mirostat_tau",              sampling.mirostat_tau},
118        {"mirostat_eta",              sampling.mirostat_eta},
119        {"adaptive_target",           sampling.adaptive_target},
120        {"adaptive_decay",            sampling.adaptive_decay},
121        {"stop",                      antiprompt},
122        {"max_tokens",                n_predict},
123        {"n_predict",                 n_predict}, // TODO: deduplicate?
124        {"n_keep",                    n_keep},
125        {"n_discard",                 n_discard},
126        {"ignore_eos",                sampling.ignore_eos},
127        {"stream",                    stream},
128        {"logit_bias",                format_logit_bias(sampling.logit_bias)},
129        {"n_probs",                   sampling.n_probs},
130        {"min_keep",                  sampling.min_keep},
131        {"grammar",                   common_grammar_value(sampling.grammar)},
132        {"grammar_lazy",              sampling.grammar_lazy},
133        {"grammar_triggers",          grammar_triggers},
134        {"preserved_tokens",          sampling.preserved_tokens},
135        {"chat_format",               common_chat_format_name(chat_parser_params.format)},
136        {"reasoning_format",          common_reasoning_format_name(chat_parser_params.reasoning_format)},
137        {"reasoning_in_content",      chat_parser_params.reasoning_in_content},
138        {"generation_prompt",         chat_parser_params.generation_prompt},
139        {"samplers",                  samplers},
140        {"speculative.types",         common_speculative_type_name_str(speculative.types)},
141        {"timings_per_token",         timings_per_token},
142        {"post_sampling_probs",       post_sampling_probs},
143        {"backend_sampling",          sampling.backend_sampling},
144        {"lora",                      lora},
145    };
146}
147
148//
149// task_result_state
150//
151task_result_state::task_result_state(const common_chat_parser_params & chat_parser_params)
152    : chat_parser_params(chat_parser_params)
153    , oai_resp_id("resp_" + random_string())
154    , oai_resp_reasoning_id("rs_" + random_string())
155    , oai_resp_message_id("msg_" + random_string()) {
156    if (chat_parser_params.is_continuation && !chat_parser_params.echo) {
157        // initialize chat_msg to avoid emitting a delta containing the assistant prefill
158        chat_msg = common_chat_parse("", true, chat_parser_params);
159    }
160}
161
162common_chat_msg task_result_state::update_chat_msg(
163        const std::string & text_added,
164        bool is_partial,
165        std::vector<common_chat_msg_diff> & diffs,
166        bool filter_tool_calls) {
167    generated_text += text_added;
168    auto msg_prv_copy = chat_msg;
169    //SRV_DBG("Parsing chat message: %s\n", generated_text.c_str());
170    auto new_msg = common_chat_parse(
171        generated_text,
172        is_partial,
173        chat_parser_params);
174    if (!new_msg.empty()) {
175        new_msg.set_tool_call_ids(generated_tool_call_ids, gen_tool_call_id);
176        chat_msg = new_msg;
177        auto all_diffs = common_chat_msg_diff::compute_diffs(msg_prv_copy, chat_msg);
178
179        if (!filter_tool_calls) {
180            diffs = std::move(all_diffs);
181        } else {
182            for (auto & d : all_diffs) {
183                // If this is a new type of delta, flush all currently pending tool call names
184                for (size_t i = 0; i < chat_msg.tool_calls.size(); ++i) {
185                    if (sent_tool_call_names.count(i) || chat_msg.tool_calls[i].name.empty()) {
186                        continue;
187                    }
188                    if (d.tool_call_index != i || !d.tool_call_delta.arguments.empty()) {
189                        common_chat_msg_diff header;
190                        header.tool_call_index      = i;
191                        header.tool_call_delta.id   = chat_msg.tool_calls[i].id;
192                        header.tool_call_delta.name = chat_msg.tool_calls[i].name;
193                        diffs.push_back(std::move(header));
194                        sent_tool_call_names.insert(i);
195                    }
196                }
197
198                if (d.tool_call_index == std::string::npos) {
199                    diffs.push_back(std::move(d));
200                } else {
201                    size_t i = d.tool_call_index;
202                    if (sent_tool_call_names.count(i)) {
203                        if (!d.tool_call_delta.arguments.empty()) {
204                            d.tool_call_delta.name = "";
205                            d.tool_call_delta.id   = "";
206                            diffs.push_back(std::move(d));
207                        }
208                    } else {
209                        // Not sent yet.
210                        if (!d.tool_call_delta.arguments.empty() || !is_partial) {
211                            d.tool_call_delta.name = chat_msg.tool_calls[i].name;
212                            d.tool_call_delta.id   = chat_msg.tool_calls[i].id;
213                            diffs.push_back(std::move(d));
214                            sent_tool_call_names.insert(i);
215                        } else {
216                            // Suppress
217                        }
218                    }
219                }
220            }
221            // Final check at EOF
222            if (!is_partial) {
223                for (size_t i = 0; i < chat_msg.tool_calls.size(); ++i) {
224                    if (!sent_tool_call_names.count(i) && !chat_msg.tool_calls[i].name.empty()) {
225                        common_chat_msg_diff header;
226                        header.tool_call_index      = i;
227                        header.tool_call_delta.id   = chat_msg.tool_calls[i].id;
228                        header.tool_call_delta.name = chat_msg.tool_calls[i].name;
229                        diffs.push_back(std::move(header));
230                        sent_tool_call_names.insert(i);
231                    }
232                }
233            }
234        }
235    }
236    return chat_msg;
237}
238
239//
240// result_prompt_progress
241//
242json result_prompt_progress::to_json() const {
243    return json {
244        {"total",     total},
245        {"cache",     cache},
246        {"processed", processed},
247        {"time_ms",   time_ms},
248    };
249}
250
251static inline std::string stop_type_to_str(stop_type type) {
252    switch (type) {
253        case STOP_TYPE_EOS:   return "eos";
254        case STOP_TYPE_WORD:  return "word";
255        case STOP_TYPE_LIMIT: return "limit";
256        default:              return "none";
257    }
258}
259
260//
261// completion_token_output
262//
263
264json completion_token_output::to_json(bool post_sampling_probs) const {
265    json probs_for_token = json::array();
266    for (const auto & p : probs) {
267        std::string txt(p.txt);
268        txt.resize(validate_utf8(txt));
269        probs_for_token.push_back(json {
270            {"id",      p.tok},
271            {"token",   txt},
272            {"bytes",   str_to_bytes(p.txt)},
273            {
274                post_sampling_probs ? "prob" : "logprob",
275                post_sampling_probs ? p.prob : logarithm(p.prob)
276            },
277        });
278    }
279    return probs_for_token;
280}
281
282json completion_token_output::probs_vector_to_json(const std::vector<completion_token_output> & probs, bool post_sampling_probs) {
283    json out = json::array();
284    for (const auto & p : probs) {
285        std::string txt(p.text_to_send);
286        txt.resize(validate_utf8(txt));
287        out.push_back(json {
288            {"id",           p.tok},
289            {"token",        txt},
290            {"bytes",        str_to_bytes(p.text_to_send)},
291            {
292                post_sampling_probs ? "prob" : "logprob",
293                post_sampling_probs ? p.prob : logarithm(p.prob)
294            },
295            {
296                post_sampling_probs ? "top_probs" : "top_logprobs",
297                p.to_json(post_sampling_probs)
298            },
299        });
300    }
301    return out;
302}
303
304float completion_token_output::logarithm(float x) {
305    // the JSON library converts -inf to null, so we need to prevent that
306    return x == 0.0f ? std::numeric_limits<float>::lowest() : std::log(x);
307}
308
309std::vector<unsigned char> completion_token_output::str_to_bytes(const std::string & str) {
310    std::vector<unsigned char> bytes;
311    for (unsigned char c : str) {
312        bytes.push_back(c);
313    }
314    return bytes;
315}
316
317//
318// server_task_result_cmpl_final
319//
320json server_task_result_cmpl_final::to_json() {
321    GGML_ASSERT(is_updated && "update() must be called before to_json()");
322    switch (res_type) {
323        case TASK_RESPONSE_TYPE_NONE:
324            return to_json_non_oaicompat();
325        case TASK_RESPONSE_TYPE_OAI_CMPL:
326            return to_json_oaicompat();
327        case TASK_RESPONSE_TYPE_OAI_CHAT:
328            return stream ? to_json_oaicompat_chat_stream() : to_json_oaicompat_chat();
329        case TASK_RESPONSE_TYPE_OAI_RESP:
330            return stream ? to_json_oaicompat_resp_stream() : to_json_oaicompat_resp();
331        case TASK_RESPONSE_TYPE_OAI_ASR:
332            return to_json_oaicompat_asr();
333        case TASK_RESPONSE_TYPE_ANTHROPIC:
334            return stream ? to_json_anthropic_stream() : to_json_anthropic();
335        default:
336            GGML_ASSERT(false && "Invalid task_response_type");
337    }
338}
339
340json server_task_result_cmpl_final::to_json_non_oaicompat() {
341    json res = json {
342        {"index",               index},
343        {"content",             content},
344        {"tokens",              tokens},
345        {"id_slot",             id_slot},
346        {"stop",                true},
347        {"model",               oaicompat_model},
348        {"tokens_predicted",    n_decoded},
349        {"tokens_evaluated",    n_prompt_tokens},
350        {"generation_settings", generation_params.to_json()},
351        {"prompt",              prompt},
352        {"has_new_line",        has_new_line},
353        {"truncated",           truncated},
354        {"stop_type",           stop_type_to_str(stop)},
355        {"stopping_word",       stopping_word},
356        {"tokens_cached",       n_tokens_cached},
357        {"timings",             stats.to_json()},
358    };
359    if (!stream && !probs_output.empty()) {
360        res["completion_probabilities"] = completion_token_output::probs_vector_to_json(probs_output, post_sampling_probs);
361    }
362    return response_fields.empty() ? res : json_get_nested_values(response_fields, res);
363}
364
365json server_task_result_cmpl_final::usage_json_oaicompat() {
366    return json {
367        {"completion_tokens", n_decoded},
368        {"prompt_tokens",     n_prompt_tokens},
369        {"total_tokens",      n_decoded + n_prompt_tokens},
370        {"prompt_tokens_details", json { {"cached_tokens", n_prompt_tokens_cache} }},
371    };
372}
373
374json server_task_result_cmpl_final::to_json_oaicompat() {
375    std::time_t t = std::time(0);
376    json logprobs = json(nullptr); // OAI default to null
377    if (!stream && probs_output.size() > 0) {
378        logprobs = json{
379            {"content", completion_token_output::probs_vector_to_json(probs_output, post_sampling_probs)},
380        };
381    }
382    json finish_reason = "length";
383    if (stop == STOP_TYPE_WORD || stop == STOP_TYPE_EOS) {
384        finish_reason = "stop";
385    }
386    json res = json {
387        {"choices",            json::array({
388            json{
389                {"text",          content},
390                {"index",         index},
391                {"logprobs",      logprobs},
392                {"finish_reason", finish_reason},
393            }
394        })},
395        {"created",            t},
396        {"model",              oaicompat_model},
397        {"system_fingerprint", std::string(llama_build_info())},
398        {"object",             "text_completion"},
399        {"usage",              usage_json_oaicompat()},
400        {"id", oaicompat_cmpl_id}
401    };
402
403    // extra fields for debugging purposes
404    if (verbose) {
405        res["__verbose"] = to_json_non_oaicompat();
406    }
407    if (stats.is_set()) {
408        res["timings"] = stats.to_json();
409    }
410
411    return res;
412}
413
414json server_task_result_cmpl_final::to_json_oaicompat_chat() {
415    std::string finish_reason = "length";
416    common_chat_msg msg;
417    if (!oaicompat_msg.empty()) {
418        msg = oaicompat_msg;
419    } else {
420        msg.role = "assistant";
421        msg.content = content;
422    }
423    if (stop == STOP_TYPE_WORD || stop == STOP_TYPE_EOS) {
424        finish_reason = msg.tool_calls.empty() ? "stop" : "tool_calls";
425    }
426
427    json choice {
428        {"finish_reason", finish_reason},
429        {"index", index},
430        {"message", msg.to_json_oaicompat()},
431    };
432
433    if (!stream && probs_output.size() > 0) {
434        choice["logprobs"] = json{
435            {"content", completion_token_output::probs_vector_to_json(probs_output, post_sampling_probs)},
436        };
437    }
438
439    std::time_t t = std::time(0);
440
441    json res = json {
442        {"choices",            json::array({choice})},
443        {"created",            t},
444        {"model",              oaicompat_model},
445        {"system_fingerprint", std::string(llama_build_info())},
446        {"object",             "chat.completion"},
447        {"usage",              usage_json_oaicompat()},
448        {"id", oaicompat_cmpl_id}
449    };
450
451    // extra fields for debugging purposes
452    if (verbose) {
453        res["__verbose"] = to_json_non_oaicompat();
454    }
455    if (stats.is_set()) {
456        res["timings"] = stats.to_json();
457    }
458
459    return res;
460}
461
462json server_task_result_cmpl_final::to_json_oaicompat_chat_stream() {
463    std::time_t t = std::time(0);
464    std::string finish_reason = "length";
465    if (stop == STOP_TYPE_WORD || stop == STOP_TYPE_EOS) {
466        finish_reason = oaicompat_msg.tool_calls.empty() ? "stop" : "tool_calls";
467    }
468
469    json deltas = json::array();
470    for (const auto & diff : oaicompat_msg_diffs) {
471        deltas.push_back({
472            {"choices", json::array({
473                json {
474                    {"finish_reason", nullptr},
475                    {"index", index},
476                    {"delta", server_chat_msg_diff_to_json_oaicompat(diff)},
477                },
478            })},
479            {"created", t},
480            {"id", oaicompat_cmpl_id},
481            {"model", oaicompat_model},
482            {"system_fingerprint", std::string(llama_build_info())},
483            {"object", "chat.completion.chunk"},
484        });
485    }
486
487    deltas.push_back({
488        {"choices", json::array({
489            json {
490                {"finish_reason", finish_reason},
491                {"index", index},
492                {"delta", json::object()},
493            },
494        })},
495        {"created",            t},
496        {"id",                 oaicompat_cmpl_id},
497        {"model",              oaicompat_model},
498        {"system_fingerprint", std::string(llama_build_info())},
499        {"object",             "chat.completion.chunk"},
500    });
501
502    if (include_usage) {
503        // OpenAI API spec for chat.completion.chunks specifies an empty `choices` array for the last chunk when including usage
504        // https://platform.openai.com/docs/api-reference/chat_streaming/streaming#chat_streaming/streaming-choices
505        deltas.push_back({
506            {"choices", json::array()},
507            {"created",            t},
508            {"id",                 oaicompat_cmpl_id},
509            {"model",              oaicompat_model},
510            {"system_fingerprint", std::string(llama_build_info())},
511            {"object",             "chat.completion.chunk"},
512            {"usage",              usage_json_oaicompat()},
513        });
514    }
515
516    if (stats.is_set()) {
517        deltas.back()["timings"] = stats.to_json();
518    }
519
520    // extra fields for debugging purposes
521    if (verbose && !deltas.empty()) {
522        deltas.front()["__verbose"] = to_json_non_oaicompat();
523    }
524
525    return deltas;
526}
527
528json server_task_result_cmpl_final::to_json_oaicompat_resp() {
529    common_chat_msg msg;
530    if (!oaicompat_msg.empty()) {
531        msg = oaicompat_msg;
532    } else {
533        msg.role = "assistant";
534        msg.content = content;
535    }
536
537    std::vector<json> output;
538
539    if (msg.reasoning_content != "") {
540        output.push_back(json {
541            {"id",      "rs_" + random_string()},
542            {"summary", json::array()},
543            {"type",    "reasoning"},
544            {"content", json::array({ json {
545                {"text", msg.reasoning_content},
546                {"type", "reasoning_text"},
547            }})},
548            {"encrypted_content", ""},
549            {"status",            "completed"},
550        });
551    }
552
553    if (msg.content != "") {
554        output.push_back(json {
555            {"content", json::array({ json {
556                {"type",        "output_text"},
557                {"annotations", json::array()},
558                {"logprobs",    json::array()},
559                {"text",        msg.content},
560            }})},
561            {"id",     "msg_" + random_string()},
562            {"role",   msg.role},
563            {"status", "completed"},
564            {"type",   "message"},
565        });
566    }
567
568    for (const common_chat_tool_call & tool_call : oaicompat_msg.tool_calls) {
569        output.push_back(json {
570            {"id",        "fc_" + tool_call.id},
571            {"type",      "function_call"},
572            {"status",    "completed"},
573            {"arguments", tool_call.arguments},
574            {"call_id",   "call_" + tool_call.id},
575            {"name",      tool_call.name},
576        });
577    }
578
579    std::time_t t = std::time(0);
580    json res = {
581        {"completed_at", t},
582        {"created_at",   t},
583        {"id",           oai_resp_id},
584        {"model",        oaicompat_model},
585        {"object",       "response"},
586        {"output",       output},
587        {"status",       "completed"},
588        {"usage",        json {
589            {"input_tokens",  n_prompt_tokens},
590            {"output_tokens", n_decoded},
591            {"total_tokens",  n_decoded + n_prompt_tokens},
592            {"input_tokens_details", json { {"cached_tokens", n_prompt_tokens_cache} }},
593        }},
594    };
595
596    return res;
597}
598
599json server_task_result_cmpl_final::to_json_oaicompat_resp_stream() {
600    std::vector<json> server_sent_events;
601    std::vector<json> output;
602
603    if (oaicompat_msg.reasoning_content != "") {
604        const json output_item = json {
605            {"id",      oai_resp_reasoning_id},
606            {"summary", json::array()},
607            {"type",    "reasoning"},
608            {"content", json::array({ json {
609                {"text", oaicompat_msg.reasoning_content},
610                {"type", "reasoning_text"},
611            }})},
612            {"encrypted_content", ""},
613        };
614
615        server_sent_events.push_back(json {
616            {"event", "response.output_item.done"},
617            {"data", json {
618                {"type", "response.output_item.done"},
619                {"item", output_item}
620            }}
621        });
622        output.push_back(output_item);
623    }
624
625    if (oaicompat_msg.content != "") {
626        server_sent_events.push_back(json {
627            {"event", "response.output_text.done"},
628            {"data", json {
629                {"type",    "response.output_text.done"},
630                {"item_id", oai_resp_message_id},
631                {"text",    oaicompat_msg.content}
632            }}
633        });
634
635        const json content_part = {
636            {"type",        "output_text"},
637            {"annotations", json::array()},
638            {"logprobs",    json::array()},
639            {"text",        oaicompat_msg.content}
640        };
641
642        server_sent_events.push_back(json {
643            {"event", "response.content_part.done"},
644            {"data", json {
645                {"type",    "response.content_part.done"},
646                {"item_id", oai_resp_message_id},
647                {"part",    content_part}
648            }}
649        });
650        const json output_item = {
651            {"type",    "message"},
652            {"status",  "completed"},
653            {"id",      oai_resp_message_id},
654            {"content", json::array({content_part})},
655            {"role",    "assistant"}
656        };
657
658        server_sent_events.push_back(json {
659            {"event", "response.output_item.done"},
660            {"data", json {
661                {"type", "response.output_item.done"},
662                {"item", output_item}
663            }}
664        });
665        output.push_back(output_item);
666    }
667
668    for (const common_chat_tool_call & tool_call : oaicompat_msg.tool_calls) {
669        const json output_item = {
670            {"id",        "fc_" + tool_call.id},
671            {"type",      "function_call"},
672            {"status",    "completed"},
673            {"arguments", tool_call.arguments},
674            {"call_id",   "call_" + tool_call.id},
675            {"name",      tool_call.name}
676        };
677        server_sent_events.push_back(json {
678            {"event", "response.output_item.done"},
679            {"data", json {
680                {"type", "response.output_item.done"},
681                {"item", output_item}
682            }}
683        });
684        output.push_back(output_item);
685    }
686
687    std::time_t t = std::time(0);
688    server_sent_events.push_back(json {
689        {"event", "response.completed"},
690        {"data", json {
691            {"type", "response.completed"},
692            {"response", json {
693                {"id",         oai_resp_id},
694                {"object",     "response"},
695                {"created_at", t},
696                {"status",     "completed"},
697                {"model",      oaicompat_model},
698                {"output",     output},
699                {"usage",      json {
700                    {"input_tokens",  n_prompt_tokens},
701                    {"output_tokens", n_decoded},
702                    {"total_tokens",  n_decoded + n_prompt_tokens},
703                    {"input_tokens_details", json { {"cached_tokens", n_prompt_tokens_cache} }},
704                }}
705            }},
706        }}
707    });
708
709    if (stats.is_set()) {
710        server_sent_events.back().at("data")["timings"] = stats.to_json();
711    }
712
713    return server_sent_events;
714}
715
716json server_task_result_cmpl_final::to_json_oaicompat_asr() {
717    json event = json {
718        {"type",  "transcript.text.done"},
719        {"text",  oaicompat_msg.content},
720        {"usage", json {
721            {"type",         "tokens"},
722            {"input_tokens",  n_prompt_tokens},
723            {"output_tokens", n_decoded},
724            {"total_tokens",  n_decoded + n_prompt_tokens},
725            {"input_tokens_details", json { {"cached_tokens", n_prompt_tokens_cache} }},
726        }},
727    };
728    return event;
729}
730
731json server_task_result_cmpl_final::to_json_anthropic() {
732    std::string stop_reason = "max_tokens";
733    if (stop == STOP_TYPE_WORD || stop == STOP_TYPE_EOS) {
734        stop_reason = oaicompat_msg.tool_calls.empty() ? "end_turn" : "tool_use";
735    }
736
737    json content_blocks = json::array();
738
739    common_chat_msg msg;
740    if (!oaicompat_msg.empty()) {
741        msg = oaicompat_msg;
742    } else {
743        msg.role = "assistant";
744        msg.content = content;
745    }
746
747    // thinking block comes first (Anthropic extended thinking format)
748    if (!msg.reasoning_content.empty()) {
749        content_blocks.push_back({
750            {"type", "thinking"},
751            {"thinking", msg.reasoning_content},
752            {"signature", ""}  // empty signature for local models (no cryptographic verification)
753        });
754    }
755
756    if (!msg.content.empty()) {
757        content_blocks.push_back({
758            {"type", "text"},
759            {"text", msg.content}
760        });
761    }
762
763    for (const auto & tool_call : msg.tool_calls) {
764        json tool_use_block = {
765            {"type", "tool_use"},
766            {"id", tool_call.id},
767            {"name", tool_call.name}
768        };
769
770        try {
771            tool_use_block["input"] = json::parse(tool_call.arguments);
772        } catch (const std::exception &) {
773            tool_use_block["input"] = json::object();
774        }
775
776        content_blocks.push_back(tool_use_block);
777    }
778
779    json res = {
780        {"id", oaicompat_cmpl_id},
781        {"type", "message"},
782        {"role", "assistant"},
783        {"content", content_blocks},
784        {"model", oaicompat_model},
785        {"stop_reason", stop_reason},
786        {"stop_sequence", stopping_word.empty() ? nullptr : json(stopping_word)},
787        {"usage", {
788            {"cache_read_input_tokens", n_prompt_tokens_cache},
789            {"input_tokens", n_prompt_tokens - n_prompt_tokens_cache},
790            {"output_tokens", n_decoded}
791        }}
792    };
793
794    return res;
795}
796
797json server_task_result_cmpl_final::to_json_anthropic_stream() {
798    json events = json::array();
799
800    std::string stop_reason = "max_tokens";
801    if (stop == STOP_TYPE_WORD || stop == STOP_TYPE_EOS) {
802        stop_reason = oaicompat_msg.tool_calls.empty() ? "end_turn" : "tool_use";
803    }
804
805    bool has_thinking = !oaicompat_msg.reasoning_content.empty();
806    bool has_text     = !oaicompat_msg.content.empty();
807    size_t num_tool_calls = oaicompat_msg.tool_calls.size();
808
809    // content block indices: thinking (0) -> text (0 or 1) -> tool_use (n+)
810    size_t thinking_block_index = 0;
811    size_t text_block_index     = has_thinking ? 1 : 0;
812
813    bool thinking_block_started = false;
814    bool text_block_started     = false;
815    std::unordered_set<size_t> tool_calls_started;
816
817    for (const auto & diff : oaicompat_msg_diffs) {
818        // handle thinking/reasoning content
819        if (!diff.reasoning_content_delta.empty()) {
820            if (!thinking_block_started) {
821                events.push_back({
822                    {"event", "content_block_start"},
823                    {"data", {
824                        {"type", "content_block_start"},
825                        {"index", thinking_block_index},
826                        {"content_block", {
827                            {"type", "thinking"},
828                            {"thinking", ""}
829                        }}
830                    }}
831                });
832                thinking_block_started = true;
833            }
834
835            events.push_back({
836                {"event", "content_block_delta"},
837                {"data", {
838                    {"type", "content_block_delta"},
839                    {"index", thinking_block_index},
840                    {"delta", {
841                        {"type", "thinking_delta"},
842                        {"thinking", diff.reasoning_content_delta}
843                    }}
844                }}
845            });
846        }
847
848        // handle regular text content
849        if (!diff.content_delta.empty()) {
850            if (!text_block_started) {
851                events.push_back({
852                    {"event", "content_block_start"},
853                    {"data", {
854                        {"type", "content_block_start"},
855                        {"index", text_block_index},
856                        {"content_block", {
857                            {"type", "text"},
858                            {"text", ""}
859                        }}
860                    }}
861                });
862                text_block_started = true;
863            }
864
865            events.push_back({
866                {"event", "content_block_delta"},
867                {"data", {
868                    {"type", "content_block_delta"},
869                    {"index", text_block_index},
870                    {"delta", {
871                        {"type", "text_delta"},
872                        {"text", diff.content_delta}
873                    }}
874                }}
875            });
876        }
877
878        // handle tool calls
879        if (diff.tool_call_index != std::string::npos) {
880            size_t content_block_index = (has_thinking ? 1 : 0) + (has_text ? 1 : 0) + diff.tool_call_index;
881
882            if (tool_calls_started.find(diff.tool_call_index) == tool_calls_started.end()) {
883                const auto & full_tool_call = oaicompat_msg.tool_calls[diff.tool_call_index];
884
885                events.push_back({
886                    {"event", "content_block_start"},
887                    {"data", {
888                        {"type", "content_block_start"},
889                        {"index", content_block_index},
890                        {"content_block", {
891                            {"type", "tool_use"},
892                            {"id", full_tool_call.id},
893                            {"name", full_tool_call.name}
894                        }}
895                    }}
896                });
897                tool_calls_started.insert(diff.tool_call_index);
898            }
899
900            if (!diff.tool_call_delta.arguments.empty()) {
901                events.push_back({
902                    {"event", "content_block_delta"},
903                    {"data", {
904                        {"type", "content_block_delta"},
905                        {"index", content_block_index},
906                        {"delta", {
907                            {"type", "input_json_delta"},
908                            {"partial_json", diff.tool_call_delta.arguments}
909                        }}
910                    }}
911                });
912            }
913        }
914    }
915
916    // close content blocks in order
917    if (has_thinking) {
918        // Anthropic API requires a signature_delta before closing thinking blocks
919        // We use an empty signature since we can't generate a cryptographic signature for local models
920        events.push_back({
921            {"event", "content_block_delta"},
922            {"data", {
923                {"type", "content_block_delta"},
924                {"index", thinking_block_index},
925                {"delta", {
926                    {"type", "signature_delta"},
927                    {"signature", ""}
928                }}
929            }}
930        });
931        events.push_back({
932            {"event", "content_block_stop"},
933            {"data", {
934                {"type", "content_block_stop"},
935                {"index", thinking_block_index}
936            }}
937        });
938    }
939
940    if (has_text) {
941        events.push_back({
942            {"event", "content_block_stop"},
943            {"data", {
944                {"type", "content_block_stop"},
945                {"index", text_block_index}
946            }}
947        });
948    }
949
950    for (size_t i = 0; i < num_tool_calls; i++) {
951        size_t content_block_index = (has_thinking ? 1 : 0) + (has_text ? 1 : 0) + i;
952        events.push_back({
953            {"event", "content_block_stop"},
954            {"data", {
955                {"type", "content_block_stop"},
956                {"index", content_block_index}
957            }}
958        });
959    }
960
961    events.push_back({
962        {"event", "message_delta"},
963        {"data", {
964            {"type", "message_delta"},
965            {"delta", {
966                {"stop_reason", stop_reason},
967                {"stop_sequence", stopping_word.empty() ? nullptr : json(stopping_word)}
968            }},
969            {"usage", {
970                {"output_tokens", n_decoded}
971            }}
972        }}
973    });
974
975    events.push_back({
976        {"event", "message_stop"},
977        {"data", {
978            {"type", "message_stop"}
979        }}
980    });
981
982    return events;
983}
984
985//
986// server_task_result_cmpl_partial
987//
988void server_task_result_cmpl_partial::update(task_result_state & state) {
989    is_updated = true;
990    if (is_begin) {
991        return; // begin marker only flushes headers, skip parsing
992    }
993    state.update_chat_msg(content, true, oaicompat_msg_diffs);
994
995    // Copy current state for use in to_json_*() (reflects state BEFORE this chunk)
996    thinking_block_started = state.thinking_block_started;
997    text_block_started     = state.text_block_started;
998
999    oai_resp_created       = state.oai_resp_created;
1000    oai_resp_id            = state.oai_resp_id;
1001    oai_resp_reasoning_id  = state.oai_resp_reasoning_id;
1002    oai_resp_message_id    = state.oai_resp_message_id;
1003    oai_resp_fc_id         = state.oai_resp_fc_id;
1004
1005    // track if the accumulated message has any reasoning content
1006    anthropic_has_reasoning = !state.chat_msg.reasoning_content.empty();
1007
1008    if (res_type == TASK_RESPONSE_TYPE_OAI_RESP && !state.oai_resp_created && (is_progress || n_decoded == 1)) {
1009        state.oai_resp_created = true;
1010    }
1011
1012    // Pre-compute state updates based on diffs (for next chunk)
1013    for (const common_chat_msg_diff & diff : oaicompat_msg_diffs) {
1014        if (!diff.reasoning_content_delta.empty() && !state.thinking_block_started) {
1015            state.thinking_block_started = true;
1016        }
1017        if (!diff.content_delta.empty() && !state.text_block_started) {
1018            state.text_block_started = true;
1019        }
1020        if (!diff.tool_call_delta.name.empty()) {
1021            state.oai_resp_fc_id = diff.tool_call_delta.id;
1022        }
1023    }
1024}
1025
1026json server_task_result_cmpl_partial::to_json() {
1027    GGML_ASSERT(is_updated && "update() must be called before to_json()");
1028    if (is_begin) {
1029        return nullptr; // simply signal to HTTP handler to send the headers and status code
1030    }
1031    switch (res_type) {
1032        case TASK_RESPONSE_TYPE_NONE:
1033            return to_json_non_oaicompat();
1034        case TASK_RESPONSE_TYPE_OAI_CMPL:
1035            return to_json_oaicompat();
1036        case TASK_RESPONSE_TYPE_OAI_CHAT:
1037            return to_json_oaicompat_chat();
1038        case TASK_RESPONSE_TYPE_OAI_RESP:
1039            return to_json_oaicompat_resp();
1040        case TASK_RESPONSE_TYPE_OAI_ASR:
1041            return to_json_oaicompat_asr();
1042        case TASK_RESPONSE_TYPE_ANTHROPIC:
1043            return to_json_anthropic();
1044        default:
1045            GGML_ASSERT(false && "Invalid task_response_type");
1046    }
1047}
1048
1049json server_task_result_cmpl_partial::to_json_non_oaicompat() {
1050    // non-OAI-compat JSON
1051    json res = json {
1052        {"index",            index},
1053        {"content",          content},
1054        {"tokens",           tokens},
1055        {"stop",             false},
1056        {"id_slot",          id_slot},
1057        {"tokens_predicted", n_decoded},
1058        {"tokens_evaluated", n_prompt_tokens},
1059    };
1060    // populate the timings object when needed (usually for the last response or with timings_per_token enabled)
1061    if (stats.is_set()) {
1062        res["timings"] = stats.to_json();
1063    }
1064    if (is_progress) {
1065        res["prompt_progress"] = progress.to_json();
1066    }
1067    if (!prob_output.probs.empty()) {
1068        res["completion_probabilities"] = completion_token_output::probs_vector_to_json({prob_output}, post_sampling_probs);
1069    }
1070    return res;
1071}
1072
1073json server_task_result_cmpl_partial::to_json_oaicompat() {
1074    std::time_t t = std::time(0);
1075    json logprobs = json(nullptr); // OAI default to null
1076    if (prob_output.probs.size() > 0) {
1077        logprobs = json{
1078            {"content", completion_token_output::probs_vector_to_json({prob_output}, post_sampling_probs)},
1079        };
1080    }
1081    json res = json {
1082        {"choices",            json::array({
1083            json{
1084                {"text",          content},
1085                {"index",         index},
1086                {"logprobs",      logprobs},
1087                {"finish_reason", nullptr},
1088            }
1089        })},
1090        {"created",            t},
1091        {"model",              oaicompat_model},
1092        {"system_fingerprint", std::string(llama_build_info())},
1093        {"object",             "text_completion"},
1094        {"id",                 oaicompat_cmpl_id}
1095    };
1096
1097    // extra fields for debugging purposes
1098    if (verbose) {
1099        res["__verbose"] = to_json_non_oaicompat();
1100    }
1101    if (stats.is_set()) {
1102        res["timings"] = stats.to_json();
1103    }
1104    if (is_progress) {
1105        res["prompt_progress"] = progress.to_json();
1106    }
1107
1108    return res;
1109}
1110
1111json server_task_result_cmpl_partial::to_json_oaicompat_chat() {
1112    bool first = n_decoded == 1;
1113    std::time_t t = std::time(0);
1114    json choices;
1115
1116    std::vector<json> deltas;
1117    auto add_delta = [&](const json & delta) {
1118        deltas.push_back({
1119            {"choices", json::array({
1120                json {
1121                    {"finish_reason", nullptr},
1122                    {"index", index},
1123                    {"delta", delta},
1124                },
1125            })},
1126            {"created", t},
1127            {"id", oaicompat_cmpl_id},
1128            {"model", oaicompat_model},
1129            {"system_fingerprint", std::string(llama_build_info())},
1130            {"object", "chat.completion.chunk"},
1131        });
1132    };
1133    // We have to send an initial update to conform to openai behavior
1134    if (first || is_progress) {
1135        add_delta({
1136            {"role", "assistant"},
1137            {"content", nullptr},
1138        });
1139    }
1140
1141    for (const auto & diff : oaicompat_msg_diffs) {
1142        add_delta(server_chat_msg_diff_to_json_oaicompat(diff));
1143    }
1144
1145    if (!deltas.empty()) {
1146        auto & last_json = deltas[deltas.size() - 1];
1147        GGML_ASSERT(last_json.at("choices").size() >= 1);
1148
1149        if (prob_output.probs.size() > 0) {
1150            last_json.at("choices").at(0)["logprobs"] = json {
1151                {"content", completion_token_output::probs_vector_to_json({prob_output}, post_sampling_probs)},
1152            };
1153        }
1154
1155        if (stats.is_set()) {
1156            last_json["timings"] = stats.to_json();
1157        }
1158        if (is_progress) {
1159            last_json["prompt_progress"] = progress.to_json();
1160        }
1161    }
1162
1163    return deltas;
1164}
1165
1166json server_task_result_cmpl_partial::to_json_oaicompat_resp() {
1167    std::vector<json> events;
1168
1169    if (!oai_resp_created) {
1170        events.push_back(json {
1171            {"event", "response.created"},
1172            {"data", json {
1173                {"type", "response.created"},
1174                {"response", json {
1175                    {"id",     oai_resp_id},
1176                    {"object", "response"},
1177                    {"status", "in_progress"},
1178                }},
1179            }},
1180        });
1181        events.push_back(json {
1182            {"event", "response.in_progress"},
1183            {"data", json {
1184                {"type", "response.in_progress"},
1185                {"response", json {
1186                    {"id",     oai_resp_id},
1187                    {"object", "response"},
1188                    {"status", "in_progress"},
1189                }},
1190            }},
1191        });
1192    } else if (is_progress) {
1193        events.push_back(json {
1194            {"event", "response.in_progress"},
1195            {"data", json {
1196                {"type", "response.in_progress"},
1197                {"response", json {
1198                    {"id",     oai_resp_id},
1199                    {"object", "response"},
1200                    {"status", "in_progress"},
1201                }},
1202            }},
1203        });
1204    }
1205
1206    for (const common_chat_msg_diff & diff : oaicompat_msg_diffs) {
1207        if (!diff.reasoning_content_delta.empty()) {
1208            if (!thinking_block_started) {
1209                events.push_back(json {
1210                    {"event", "response.output_item.added"},
1211                    {"data", json {
1212                        {"type", "response.output_item.added"},
1213                        {"item", json {
1214                            {"id",                oai_resp_reasoning_id},
1215                            {"summary",           json::array()},
1216                            {"type",              "reasoning"},
1217                            {"content",           json::array()},
1218                            {"encrypted_content", ""},
1219                            {"status",            "in_progress"},
1220                        }},
1221                    }},
1222                });
1223                thinking_block_started = true;
1224            }
1225            events.push_back(json {
1226                {"event", "response.reasoning_text.delta"},
1227                {"data", json {
1228                    {"type",    "response.reasoning_text.delta"},
1229                    {"delta",   diff.reasoning_content_delta},
1230                    {"item_id", oai_resp_reasoning_id},
1231                }},
1232            });
1233        }
1234
1235        if (!diff.content_delta.empty()) {
1236            if (!text_block_started) {
1237                events.push_back(json {
1238                    {"event", "response.output_item.added"},
1239                    {"data", json {
1240                        {"type", "response.output_item.added"},
1241                        {"item", json {
1242                            {"content", json::array()},
1243                            {"id",      oai_resp_message_id},
1244                            {"role",    "assistant"},
1245                            {"status",  "in_progress"},
1246                            {"type",    "message"},
1247                        }},
1248                    }},
1249                });
1250                events.push_back(json {
1251                    {"event", "response.content_part.added"},
1252                    {"data", json {
1253                        {"type",    "response.content_part.added"},
1254                        {"item_id", oai_resp_message_id},
1255                        {"part", json {
1256                            {"type", "output_text"},
1257                            {"text", ""},
1258                        }},
1259                    }},
1260                });
1261                text_block_started = true;
1262            }
1263            events.push_back(json {
1264                {"event", "response.output_text.delta"},
1265                {"data", json {
1266                    {"type",    "response.output_text.delta"},
1267                    {"item_id", oai_resp_message_id},
1268                    {"delta",   diff.content_delta},
1269                }},
1270            });
1271        }
1272
1273        if (!diff.tool_call_delta.name.empty()) {
1274            events.push_back(json {
1275                {"event", "response.output_item.added"},
1276                {"data", json {
1277                    {"type",  "response.output_item.added"},
1278                    {"item", json {
1279                        {"id",        "fc_" + diff.tool_call_delta.id},
1280                        {"arguments", ""},
1281                        {"call_id",   "call_" + diff.tool_call_delta.id},
1282                        {"name",      diff.tool_call_delta.name},
1283                        {"type",      "function_call"},
1284                        {"status",    "in_progress"},
1285                    }},
1286                }},
1287            });
1288            oai_resp_fc_id = diff.tool_call_delta.id;
1289        }
1290
1291        if (!diff.tool_call_delta.arguments.empty()) {
1292            events.push_back(json {
1293                {"event", "response.function_call_arguments.delta"},
1294                {"data", json {
1295                    {"type",    "response.function_call_arguments.delta"},
1296                    {"delta",   diff.tool_call_delta.arguments},
1297                    {"item_id", "fc_" + oai_resp_fc_id},
1298                }},
1299            });
1300        }
1301    }
1302
1303    if (!events.empty()) {
1304        json & data = events.back().at("data");
1305        if (stats.is_set()) {
1306            data["timings"] = stats.to_json();
1307        }
1308        if (is_progress) {
1309            data["prompt_progress"] = progress.to_json();
1310        }
1311    }
1312
1313    return events;
1314}
1315
1316json server_task_result_cmpl_partial::to_json_oaicompat_asr() {
1317    json event = json {
1318        {"type", "transcript.text.delta"},
1319        {"delta", content},
1320    };
1321    return event;
1322}
1323
1324json server_task_result_cmpl_partial::to_json_anthropic() {
1325    json events = json::array();
1326    bool first = (n_decoded == 1);
1327    // use member variables to track block state across streaming calls
1328    // (anthropic_thinking_block_started, anthropic_text_block_started)
1329
1330    if (first) {
1331        events.push_back({
1332            {"event", "message_start"},
1333            {"data", {
1334                {"type", "message_start"},
1335                {"message", {
1336                    {"id", oaicompat_cmpl_id},
1337                    {"type", "message"},
1338                    {"role", "assistant"},
1339                    {"content", json::array()},
1340                    {"model", oaicompat_model},
1341                    {"stop_reason", nullptr},
1342                    {"stop_sequence", nullptr},
1343                    {"usage", {
1344                        {"cache_read_input_tokens", n_prompt_tokens_cache},
1345                        {"input_tokens", n_prompt_tokens - n_prompt_tokens_cache},
1346                        {"output_tokens", 0}
1347                    }}
1348                }}
1349            }}
1350        });
1351    }
1352
1353    // content block indices: thinking (0) -> text (0 or 1) -> tool_use (n+)
1354    size_t thinking_block_index = 0;
1355    // use anthropic_has_reasoning (set in update()) to know if ANY reasoning was generated
1356    size_t text_block_index     = anthropic_has_reasoning ? 1 : 0;
1357
1358    // use local copies of streaming state (copied from task_result_state in update())
1359    // these reflect the state BEFORE this chunk was processed
1360    bool thinking_started = thinking_block_started;
1361    bool text_started     = text_block_started;
1362
1363    for (const auto & diff : oaicompat_msg_diffs) {
1364        // handle thinking/reasoning content
1365        if (!diff.reasoning_content_delta.empty()) {
1366            if (!thinking_started) {
1367                events.push_back({
1368                    {"event", "content_block_start"},
1369                    {"data", {
1370                        {"type", "content_block_start"},
1371                        {"index", thinking_block_index},
1372                        {"content_block", {
1373                            {"type", "thinking"},
1374                            {"thinking", ""}
1375                        }}
1376                    }}
1377                });
1378                thinking_started = true;
1379            }
1380
1381            events.push_back({
1382                {"event", "content_block_delta"},
1383                {"data", {
1384                    {"type", "content_block_delta"},
1385                    {"index", thinking_block_index},
1386                    {"delta", {
1387                        {"type", "thinking_delta"},
1388                        {"thinking", diff.reasoning_content_delta}
1389                    }}
1390                }}
1391            });
1392        }
1393
1394        // handle regular text content
1395        if (!diff.content_delta.empty()) {
1396            if (!text_started) {
1397                events.push_back({
1398                    {"event", "content_block_start"},
1399                    {"data", {
1400                        {"type", "content_block_start"},
1401                        {"index", text_block_index},
1402                        {"content_block", {
1403                            {"type", "text"},
1404                            {"text", ""}
1405                        }}
1406                    }}
1407                });
1408                text_started = true;
1409            }
1410
1411            events.push_back({
1412                {"event", "content_block_delta"},
1413                {"data", {
1414                    {"type", "content_block_delta"},
1415                    {"index", text_block_index},
1416                    {"delta", {
1417                        {"type", "text_delta"},
1418                        {"text", diff.content_delta}
1419                    }}
1420                }}
1421            });
1422        }
1423
1424        // handle tool calls
1425        if (diff.tool_call_index != std::string::npos) {
1426            // use anthropic_has_reasoning for thinking block count (persists across calls)
1427            size_t content_block_index = (anthropic_has_reasoning ? 1 : 0) + (text_started ? 1 : 0) + diff.tool_call_index;
1428
1429            if (!diff.tool_call_delta.name.empty()) {
1430                events.push_back({
1431                    {"event", "content_block_start"},
1432                    {"data", {
1433                        {"type", "content_block_start"},
1434                        {"index", content_block_index},
1435                        {"content_block", {
1436                            {"type", "tool_use"},
1437                            {"id", diff.tool_call_delta.id},
1438                            {"name", diff.tool_call_delta.name}
1439                        }}
1440                    }}
1441                });
1442            }
1443
1444            if (!diff.tool_call_delta.arguments.empty()) {
1445                events.push_back({
1446                    {"event", "content_block_delta"},
1447                    {"data", {
1448                        {"type", "content_block_delta"},
1449                        {"index", content_block_index},
1450                        {"delta", {
1451                            {"type", "input_json_delta"},
1452                            {"partial_json", diff.tool_call_delta.arguments}
1453                        }}
1454                    }}
1455                });
1456            }
1457        }
1458    }
1459
1460    return events;
1461}
1462
1463//
1464// server_task_result_embd
1465//
1466json server_task_result_embd::to_json() {
1467    return res_type == TASK_RESPONSE_TYPE_OAI_EMBD
1468        ? to_json_oaicompat()
1469        : to_json_non_oaicompat();
1470}
1471
1472json server_task_result_embd::to_json_non_oaicompat() {
1473    return json {
1474        {"index",     index},
1475        {"embedding", embedding},
1476    };
1477}
1478
1479json server_task_result_embd::to_json_oaicompat() {
1480    return json {
1481        {"index",            index},
1482        {"embedding",        embedding[0]},
1483        {"tokens_evaluated", n_tokens},
1484    };
1485}
1486
1487//
1488// server_task_result_rerank
1489//
1490json server_task_result_rerank::to_json() {
1491    return json {
1492        {"index",            index},
1493        {"score",            score},
1494        {"tokens_evaluated", n_tokens},
1495    };
1496}
1497
1498//
1499// server_task_result_error
1500//
1501json server_task_result_error::to_json() {
1502    json res = format_error_response(err_msg, err_type);
1503    if (err_type == ERROR_TYPE_EXCEED_CONTEXT_SIZE) {
1504        res["n_prompt_tokens"] = n_prompt_tokens;
1505        res["n_ctx"]           = n_ctx;
1506    }
1507    return res;
1508}
1509
1510//
1511// server_task_result_metrics
1512//
1513json server_task_result_slots::to_json() {
1514    return slots_data;
1515}
1516
1517json server_task_result_metrics::to_json() {
1518    // not used, /metrics renders prometheus text via to_metrics()
1519    return json{};
1520}
1521
1522// metrics definition: https://prometheus.io/docs/practices/naming/#metric-names
1523std::string server_task_result_metrics::to_metrics() {
1524    const std::vector<metric_item> counters = {
1525        {
1526            "prompt_tokens_total",
1527            "Number of prompt tokens processed, excluding cached tokens",
1528            (double) metrics.prompt.count
1529        }, {
1530            "prompt_tokens_cached_total",
1531            "Number of prompt tokens reused from the cache",
1532            (double) metrics.n_prompt_cached
1533        }, {
1534            "prompt_seconds_total",
1535            "Total time spent processing prompts",
1536            metrics.prompt.time / 1.e6
1537        }, {
1538            "tokens_predicted_total",
1539            "Number of generation tokens processed",
1540            (double) metrics.predict.count
1541        }, {
1542            "tokens_predicted_seconds_total",
1543            "Total time spent generating tokens",
1544            metrics.predict.time / 1.e6
1545        }, {
1546            "n_decode_total",
1547            "Total number of llama_decode() calls, excluding speculative decoding and multimodal decoding",
1548            (double) metrics.n_decode
1549        }, {
1550            "n_tokens_max",
1551            "Largest observed sequence length (prompt + generation)",
1552            (double) metrics.n_tokens_max
1553        }, {
1554            "spec_decode_num_draft_tokens_total",
1555            "Speculative: Total draft tokens generated",
1556            (double) metrics.n_draft_tokens
1557        }, {
1558            "spec_decode_num_accepted_tokens_total",
1559            "Speculative: Total draft tokens accepted by the target model",
1560            (double) metrics.n_draft_accepted
1561        }, {
1562            "spec_decode_num_drafts_total",
1563            "Speculative: Total speculative decoding verification steps",
1564            (double) metrics.n_draft_verif_steps
1565        },
1566    };
1567
1568    const std::vector<metric_item> gauges = {
1569        {
1570            "prompt_tokens_seconds",
1571            "Average prompt throughput in tokens/s",
1572            metrics.prompt_bucket.n_per_second()
1573        }, {
1574            "predicted_tokens_seconds",
1575            "Average generation throughput in tokens/s",
1576            metrics.predict_bucket.n_per_second()
1577        }, {
1578            "requests_processing",
1579            "Number of requests processing",
1580            (double) n_processing_slots
1581        }, {
1582            "requests_deferred",
1583            "Number of requests deferred",
1584            (double) n_tasks_deferred
1585        }, {
1586            "n_busy_slots_per_decode",
1587            "Average number of busy slots per llama_decode() call",
1588            (double) metrics.n_busy_slots / std::max((double) metrics.n_decode, 1.0)
1589        },
1590    };
1591
1592    std::stringstream prometheus;
1593
1594    auto add_items = [&prometheus](const char * type, const std::vector<metric_item> & items) {
1595        for (const auto & item : items) {
1596            prometheus << "# HELP llamacpp:" << item.name << " " << item.description << "\n"
1597                       << "# TYPE llamacpp:" << item.name << " " << type             << "\n"
1598                       << "llamacpp:"        << item.name << " " << item.value       << "\n";
1599        }
1600    };
1601
1602    add_items("counter", counters);
1603    add_items("gauge",   gauges);
1604
1605    // labeled counter: one time series per draft position
1606    if (!metrics.n_accepted_per_pos.empty()) {
1607        prometheus << "# HELP llamacpp:spec_decode_num_accepted_tokens_per_pos_total"
1608                      " Accepted tokens per draft position\n"
1609                   << "# TYPE llamacpp:spec_decode_num_accepted_tokens_per_pos_total counter\n";
1610        for (size_t i = 0; i < metrics.n_accepted_per_pos.size(); i++) {
1611            prometheus << "llamacpp:spec_decode_num_accepted_tokens_per_pos_total{position=\""
1612                       << i << "\"} " << metrics.n_accepted_per_pos[i] << "\n";
1613        }
1614    }
1615
1616    return prometheus.str();
1617}
1618
1619//
1620// server_task_result_slot_save_load
1621//
1622json server_task_result_slot_save_load::to_json() {
1623    if (is_save) {
1624        return json {
1625            { "id_slot",   id_slot },
1626            { "filename",  filename },
1627            { "n_saved",   n_tokens },
1628            { "n_written", n_bytes },
1629            { "timings", {
1630                { "save_ms", t_ms }
1631            }},
1632        };
1633    }
1634
1635    return json {
1636        { "id_slot",    id_slot },
1637        { "filename",   filename },
1638        { "n_restored", n_tokens },
1639        { "n_read",     n_bytes },
1640        { "timings", {
1641            { "restore_ms", t_ms }
1642        }},
1643    };
1644}
1645
1646//
1647// server_task_result_slot_erase
1648//
1649json server_task_result_slot_erase::to_json() {
1650    return json {
1651        { "id_slot",  id_slot },
1652        { "n_erased", n_erased },
1653    };
1654}
1655
1656//
1657// server_task_result_get_lora
1658//
1659
1660json server_task_result_get_lora::to_json() {
1661    json result = json::array();
1662    for (size_t i = 0; i < loras.size(); ++i) {
1663        auto & lora = loras[i];
1664        json entry = {
1665            {"id",            i},
1666            {"path",          lora.info.path},
1667            {"scale",         lora.info.scale},
1668            {"task_name",     lora.info.task_name},
1669            {"prompt_prefix", lora.info.prompt_prefix},
1670        };
1671        if (!lora.alora_invocation_tokens.empty()) {
1672            entry["alora_invocation_string"] = lora.alora_invocation_string;
1673            entry["alora_invocation_tokens"] = lora.alora_invocation_tokens;
1674        }
1675        result.push_back(std::move(entry));
1676    }
1677    return result;
1678}
1679
1680//
1681// server_task_result_apply_lora
1682//
1683
1684json server_task_result_apply_lora::to_json() {
1685    return json {{ "success", true }};
1686}
1687
1688//
1689// server_prompt_cache
1690//
1691size_t server_prompt_cache::size() const {
1692    size_t res = 0;
1693
1694    for (const auto & state : states) {
1695        res += state.size();
1696    }
1697
1698    return res;
1699}
1700
1701size_t server_prompt_cache::n_tokens() const {
1702    size_t res = 0;
1703
1704    for (const auto & state : states) {
1705        res += state.prompt.n_tokens();
1706    }
1707
1708    return res;
1709}
1710
1711server_prompt_cache_state * server_prompt_cache::alloc(const server_prompt & prompt, size_t state_size_tgt, size_t state_size_dft) {
1712    // first check if the current state is contained fully in the cache
1713    for (auto it = states.begin(); it != states.end(); ++it) {
1714        const int cur_lcp_len = it->prompt.tokens.get_common_prefix(prompt.tokens);
1715
1716        if (cur_lcp_len == (int) prompt.tokens.size()) {
1717            SRV_TRC("%s", " - prompt is already in the cache, skipping\n");
1718            return nullptr;
1719        }
1720    }
1721
1722    // calculate checkpoints size to see if it will fit with the prompt
1723    size_t checkpoints_size = 0;
1724    for (const auto & ckpt : prompt.checkpoints) {
1725        checkpoints_size += ckpt.size();
1726    }
1727
1728    const size_t state_size_new = state_size_tgt + state_size_dft + checkpoints_size;
1729
1730    // skip over-limit entries to avoid disturbing the cache
1731    if (limit_size > 0 && state_size_new > limit_size) {
1732        SRV_WRN(" - prompt state size %.3f MiB exceeds cache size limit %.3f MiB, skipping\n",
1733                state_size_new / (1024.0 * 1024.0), limit_size / (1024.0 * 1024.0));
1734        return nullptr;
1735    }
1736
1737    // remove any cached prompts that are fully contained in the current prompt
1738    for (auto it = states.begin(); it != states.end();) {
1739        const int len = it->prompt.tokens.get_common_prefix(prompt.tokens);
1740
1741        if (len == (int) it->prompt.tokens.size()) {
1742            SRV_TRC(" - removing obsolete cached prompt with length %d\n", len);
1743
1744            it = states.erase(it);
1745        } else {
1746            ++it;
1747        }
1748    }
1749
1750    if (limit_size > 0) {
1751        // make room before allocating the new vectors to avoid breaching the limit
1752        while (!states.empty() && size() + state_size_new > limit_size) {
1753            SRV_WRN(" - making room for prompt cache entry, removing oldest entry (size = %.3f MiB)\n",
1754                    states.front().size() / (1024.0 * 1024.0));
1755
1756            states.pop_front();
1757        }
1758    }
1759
1760    std::vector<uint8_t> state_data_tgt;
1761    std::vector<uint8_t> state_data_dft;
1762
1763    // check if we can allocate enough memory for the new state
1764    try {
1765        state_data_tgt.resize(state_size_tgt);
1766        state_data_dft.resize(state_size_dft);
1767    } catch (const std::bad_alloc & e) {
1768        SRV_ERR("failed to allocate memory for prompt cache state: %s\n", e.what());
1769
1770        limit_size = std::max<size_t>(1, 0.4*size());
1771
1772        SRV_WRN(" - cache size limit reduced to %.3f MiB\n", limit_size / (1024.0 * 1024.0));
1773
1774        update();
1775
1776        return nullptr;
1777    }
1778
1779    states.push_back({
1780        /*.prompt =*/ {
1781            /*.tokens      =*/ prompt.tokens.clone(),
1782            /*.checkpoints =*/ prompt.checkpoints,
1783        },
1784        /*.data   =*/ {
1785            /*.main =*/ std::move(state_data_tgt),
1786            /*.drft =*/ std::move(state_data_dft),
1787        },
1788    });
1789
1790    return &states.back();
1791}
1792
1793bool server_prompt_cache::load(server_prompt & prompt, const server_tokens & tokens_new, llama_context * ctx_tgt, llama_context * ctx_dft, int32_t id_slot) {
1794    const int lcp_best = prompt.tokens.get_common_prefix(tokens_new);
1795
1796    float f_keep_best = prompt.tokens.size() > 0 ? float(lcp_best) / prompt.tokens.size() : -1.0f; // empty slot: any cache entry wins
1797    float f_sim_best  = float(lcp_best) / tokens_new.size();
1798
1799    SRV_TRC(" - looking for better prompt, base f_keep = %.3f, f_sim = %.3f\n", f_keep_best, f_sim_best);
1800
1801    auto it_best = states.end();
1802
1803    // find the most similar cached prompt, that would also preserve the most context
1804    for (auto it = states.begin(); it != states.end(); ++it) {
1805        const int lcp_cur = it->prompt.tokens.get_common_prefix(tokens_new);
1806
1807        const float f_keep_cur = float(lcp_cur) / it->prompt.tokens.size();
1808        const float f_sim_cur  = float(lcp_cur) / tokens_new.size();
1809
1810        SRV_TRC("   - prompt with length %7zu, lcp = %7d, f_keep = %.3f, f_sim = %.3f\n", it->prompt.tokens.size(), lcp_cur, f_keep_cur, f_sim_cur);
1811
1812        // don't trash large prompts
1813        if (f_keep_cur < 0.25f) {
1814            continue;
1815        }
1816
1817        if (f_keep_best < f_keep_cur && f_sim_best < f_sim_cur) {
1818            f_keep_best = f_keep_cur;
1819            f_sim_best  = f_sim_cur;
1820
1821            it_best = it;
1822        }
1823    }
1824
1825    if (it_best != states.end()) {
1826        SRV_TRC(" - found better prompt with f_keep = %.3f, f_sim = %.3f\n", f_keep_best, f_sim_best);
1827
1828        {
1829            auto & data = it_best->data.main;
1830
1831            const size_t size = data.size();
1832            const size_t n = llama_state_seq_set_data_ext(ctx_tgt, data.data(), size, id_slot, 0);
1833            if (n != size) {
1834                SRV_ERR("failed to restore state with size %zu\n", size);
1835
1836                return false;
1837            }
1838
1839            data.clear();
1840            data.shrink_to_fit();
1841        }
1842
1843        {
1844            auto & data = it_best->data.drft;
1845
1846            if (!data.empty()) {
1847                GGML_ASSERT(ctx_dft);
1848
1849                const size_t size = data.size();
1850                const size_t n = llama_state_seq_set_data_ext(ctx_dft, data.data(), size, id_slot, 0);
1851                if (n != size) {
1852                    SRV_WRN("failed to restore state with size %zu\n", size);
1853
1854                    return false;
1855                }
1856
1857                data.clear();
1858                data.shrink_to_fit();
1859            }
1860        }
1861
1862        prompt = std::move(it_best->prompt);
1863
1864        states.erase(it_best);
1865    }
1866
1867    return true;
1868}
1869
1870void server_prompt_cache::update() {
1871    if (limit_size > 0) {
1872        while (!states.empty() && size() > limit_size) {
1873            SRV_WRN(" - cache size limit reached, removing oldest entry (size = %.3f MiB)\n", states.front().size() / (1024.0 * 1024.0));
1874
1875            states.pop_front();
1876        }
1877    }
1878
1879    // average size per token
1880    const float size_per_token = std::max<float>(1.0f, float(size()) / (std::max<size_t>(1, n_tokens())));
1881
1882    // dynamically increase the token limit if it can fit in the memory limit
1883    const size_t limit_tokens_cur = limit_size > 0 ? std::max<size_t>(limit_tokens, limit_size/size_per_token) : limit_tokens;
1884
1885    if (limit_tokens > 0) {
1886        while (!states.empty() && n_tokens() > limit_tokens_cur) {
1887            SRV_WRN(" - cache token limit (%zu, est: %zu) reached, removing oldest entry (size = %.3f MiB)\n",
1888                    limit_tokens, limit_tokens_cur, states.front().size() / (1024.0 * 1024.0));
1889
1890            states.pop_front();
1891        }
1892    }
1893
1894    SRV_TRC(" - cache state: %zu prompts, %.3f MiB (limits: %.3f MiB, %zu tokens, %zu est)\n",
1895            states.size(), size() / (1024.0 * 1024.0), limit_size / (1024.0 * 1024.0), limit_tokens, limit_tokens_cur);
1896
1897    for (const auto & state : states) {
1898        SRV_TRC("   - prompt %p: %7d tokens, checkpoints: %2zu, %9.3f MiB\n",
1899                (const void *)&state, state.prompt.n_tokens(), state.prompt.checkpoints.size(), state.size() / (1024.0 * 1024.0));
1900    }
1901}