char-fork/llama.cpp
git clone https://git.t4t.associates/char-fork/llama.cpp
d9f918d2d
char/aliceai-t5-moe
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 { 20json data = json::array (); 21for (const auto & lb :logit_bias ) { 22data .push_back (json { 23 {"bias" ,lb .bias }, 24 {"token" ,lb .token }, 25 }); 26 } 27return data ; 28} 29 30json task_params::to_json (bool only_metrics )const { 31 std::vector < std::string > samplers ; 32samplers .reserve (sampling .samplers .size ()); 33for (const auto & sampler :sampling .samplers ) { 34samplers .emplace_back (common_sampler_type_to_str (sampler )); 35 } 36 37json lora = json::array (); 38for (auto & it :this -> lora ) { 39lora .push_back ({{"id" ,it .first }, {"scale" ,it .second }}); 40 } 41 42if (only_metrics ) { 43return 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 89auto grammar_triggers = json::array (); 90for (const auto & trigger :sampling .grammar_triggers ) { 91server_grammar_trigger ct (trigger ); 92grammar_triggers .push_back (ct .to_json ()); 93 } 94 95return 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 ()) { 156if (chat_parser_params .is_continuation && !chat_parser_params .echo ) { 157// initialize chat_msg to avoid emitting a delta containing the assistant prefill 158chat_msg = common_chat_parse ("" , true,chat_parser_params ); 159 } 160} 161 162common_chat_msg task_result_state::update_chat_msg ( 163const std::string & text_added , 164bool is_partial , 165 std::vector < common_chat_msg_diff > & diffs , 166bool filter_tool_calls ) { 167generated_text += text_added ; 168auto msg_prv_copy = chat_msg ; 169//SRV_DBG("Parsing chat message: %s\n", generated_text.c_str()); 170auto new_msg = common_chat_parse ( 171generated_text , 172is_partial , 173chat_parser_params ); 174if (!new_msg .empty ()) { 175new_msg .set_tool_call_ids (generated_tool_call_ids ,gen_tool_call_id ); 176chat_msg = new_msg ; 177auto all_diffs = common_chat_msg_diff::compute_diffs (msg_prv_copy ,chat_msg ); 178 179if (!filter_tool_calls ) { 180diffs = std::move (all_diffs ); 181 }else { 182for (auto & d :all_diffs ) { 183// If this is a new type of delta, flush all currently pending tool call names 184for (size_t i = 0 ;i < chat_msg .tool_calls .size ();++ i ) { 185if (sent_tool_call_names .count (i )|| chat_msg .tool_calls [i ].name .empty ()) { 186continue ; 187 } 188if (d .tool_call_index != i || !d .tool_call_delta .arguments .empty ()) { 189common_chat_msg_diff header ; 190header .tool_call_index = i ; 191header .tool_call_delta .id = chat_msg .tool_calls [i ].id ; 192header .tool_call_delta .name = chat_msg .tool_calls [i ].name ; 193diffs .push_back (std::move (header )); 194sent_tool_call_names .insert (i ); 195 } 196 } 197 198if (d .tool_call_index == std::string::npos ) { 199diffs .push_back (std::move (d )); 200 }else { 201size_t i = d .tool_call_index ; 202if (sent_tool_call_names .count (i )) { 203if (!d .tool_call_delta .arguments .empty ()) { 204d .tool_call_delta .name = "" ; 205d .tool_call_delta .id = "" ; 206diffs .push_back (std::move (d )); 207 } 208 }else { 209// Not sent yet. 210if (!d .tool_call_delta .arguments .empty ()|| !is_partial ) { 211d .tool_call_delta .name = chat_msg .tool_calls [i ].name ; 212d .tool_call_delta .id = chat_msg .tool_calls [i ].id ; 213diffs .push_back (std::move (d )); 214sent_tool_call_names .insert (i ); 215 }else { 216// Suppress 217 } 218 } 219 } 220 } 221// Final check at EOF 222if (!is_partial ) { 223for (size_t i = 0 ;i < chat_msg .tool_calls .size ();++ i ) { 224if (!sent_tool_call_names .count (i )&& !chat_msg .tool_calls [i ].name .empty ()) { 225common_chat_msg_diff header ; 226header .tool_call_index = i ; 227header .tool_call_delta .id = chat_msg .tool_calls [i ].id ; 228header .tool_call_delta .name = chat_msg .tool_calls [i ].name ; 229diffs .push_back (std::move (header )); 230sent_tool_call_names .insert (i ); 231 } 232 } 233 } 234 } 235 } 236return chat_msg ; 237} 238 239// 240// result_prompt_progress 241// 242json result_prompt_progress::to_json ()const { 243return 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 ) { 252switch (type ) { 253case STOP_TYPE_EOS :return "eos" ; 254case STOP_TYPE_WORD :return "word" ; 255case STOP_TYPE_LIMIT :return "limit" ; 256default :return "none" ; 257 } 258} 259 260// 261// completion_token_output 262// 263 264json completion_token_output::to_json (bool post_sampling_probs )const { 265json probs_for_token = json::array (); 266for (const auto & p :probs ) { 267 std::string txt (p .txt ); 268txt .resize (validate_utf8 (txt )); 269probs_for_token .push_back (json { 270 {"id" ,p .tok }, 271 {"token" ,txt }, 272 {"bytes" ,str_to_bytes (p .txt )}, 273 { 274post_sampling_probs ?"prob" :"logprob" , 275post_sampling_probs ?p .prob :logarithm (p .prob ) 276 }, 277 }); 278 } 279return probs_for_token ; 280} 281 282json completion_token_output::probs_vector_to_json (const std::vector < completion_token_output > & probs ,bool post_sampling_probs ) { 283json out = json::array (); 284for (const auto & p :probs ) { 285 std::string txt (p .text_to_send ); 286txt .resize (validate_utf8 (txt )); 287out .push_back (json { 288 {"id" ,p .tok }, 289 {"token" ,txt }, 290 {"bytes" ,str_to_bytes (p .text_to_send )}, 291 { 292post_sampling_probs ?"prob" :"logprob" , 293post_sampling_probs ?p .prob :logarithm (p .prob ) 294 }, 295 { 296post_sampling_probs ?"top_probs" :"top_logprobs" , 297p .to_json (post_sampling_probs ) 298 }, 299 }); 300 } 301return out ; 302} 303 304float completion_token_output::logarithm (float x ) { 305// the JSON library converts -inf to null, so we need to prevent that 306return 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 ; 311for (unsigned char c :str ) { 312bytes .push_back (c ); 313 } 314return bytes ; 315} 316 317// 318// server_task_result_cmpl_final 319// 320json server_task_result_cmpl_final::to_json () { 321GGML_ASSERT (is_updated && "update() must be called before to_json()" ); 322switch (res_type ) { 323case TASK_RESPONSE_TYPE_NONE : 324return to_json_non_oaicompat (); 325case TASK_RESPONSE_TYPE_OAI_CMPL : 326return to_json_oaicompat (); 327case TASK_RESPONSE_TYPE_OAI_CHAT : 328return stream ?to_json_oaicompat_chat_stream () :to_json_oaicompat_chat (); 329case TASK_RESPONSE_TYPE_OAI_RESP : 330return stream ?to_json_oaicompat_resp_stream () :to_json_oaicompat_resp (); 331case TASK_RESPONSE_TYPE_OAI_ASR : 332return to_json_oaicompat_asr (); 333case TASK_RESPONSE_TYPE_ANTHROPIC : 334return stream ?to_json_anthropic_stream () :to_json_anthropic (); 335default : 336GGML_ASSERT (false&& "Invalid task_response_type" ); 337 } 338} 339 340json server_task_result_cmpl_final::to_json_non_oaicompat () { 341json 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 }; 359if (!stream && !probs_output .empty ()) { 360res ["completion_probabilities" ]= completion_token_output::probs_vector_to_json (probs_output ,post_sampling_probs ); 361 } 362return response_fields .empty () ?res :json_get_nested_values (response_fields ,res ); 363} 364 365json server_task_result_cmpl_final::usage_json_oaicompat () { 366return 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 ); 376json logprobs = json (nullptr );// OAI default to null 377if (!stream && probs_output .size ()> 0 ) { 378logprobs = json { 379 {"content" , completion_token_output::probs_vector_to_json (probs_output ,post_sampling_probs )}, 380 }; 381 } 382json finish_reason = "length" ; 383if (stop == STOP_TYPE_WORD || stop == STOP_TYPE_EOS ) { 384finish_reason = "stop" ; 385 } 386json res = json { 387 {"choices" , json::array ({ 388json { 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 404if (verbose ) { 405res ["__verbose" ]= to_json_non_oaicompat (); 406 } 407if (stats .is_set ()) { 408res ["timings" ]= stats .to_json (); 409 } 410 411return res ; 412} 413 414json server_task_result_cmpl_final::to_json_oaicompat_chat () { 415 std::string finish_reason = "length" ; 416common_chat_msg msg ; 417if (!oaicompat_msg .empty ()) { 418msg = oaicompat_msg ; 419 }else { 420msg .role = "assistant" ; 421msg .content = content ; 422 } 423if (stop == STOP_TYPE_WORD || stop == STOP_TYPE_EOS ) { 424finish_reason = msg .tool_calls .empty () ?"stop" :"tool_calls" ; 425 } 426 427json choice { 428 {"finish_reason" ,finish_reason }, 429 {"index" ,index }, 430 {"message" ,msg .to_json_oaicompat ()}, 431 }; 432 433if (!stream && probs_output .size ()> 0 ) { 434choice ["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 441json 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 452if (verbose ) { 453res ["__verbose" ]= to_json_non_oaicompat (); 454 } 455if (stats .is_set ()) { 456res ["timings" ]= stats .to_json (); 457 } 458 459return 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" ; 465if (stop == STOP_TYPE_WORD || stop == STOP_TYPE_EOS ) { 466finish_reason = oaicompat_msg .tool_calls .empty () ?"stop" :"tool_calls" ; 467 } 468 469json deltas = json::array (); 470for (const auto & diff :oaicompat_msg_diffs ) { 471deltas .push_back ({ 472 {"choices" , json::array ({ 473json { 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 487deltas .push_back ({ 488 {"choices" , json::array ({ 489json { 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 502if (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 505deltas .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 516if (stats .is_set ()) { 517deltas .back ()["timings" ]= stats .to_json (); 518 } 519 520// extra fields for debugging purposes 521if (verbose && !deltas .empty ()) { 522deltas .front ()["__verbose" ]= to_json_non_oaicompat (); 523 } 524 525return deltas ; 526} 527 528json server_task_result_cmpl_final::to_json_oaicompat_resp () { 529common_chat_msg msg ; 530if (!oaicompat_msg .empty ()) { 531msg = oaicompat_msg ; 532 }else { 533msg .role = "assistant" ; 534msg .content = content ; 535 } 536 537 std::vector < json > output ; 538 539if (msg .reasoning_content != "" ) { 540output .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 553if (msg .content != "" ) { 554output .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 568for (const common_chat_tool_call & tool_call :oaicompat_msg .tool_calls ) { 569output .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 ); 580json 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 596return 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 603if (oaicompat_msg .reasoning_content != "" ) { 604const 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 615server_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 }); 622output .push_back (output_item ); 623 } 624 625if (oaicompat_msg .content != "" ) { 626server_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 635const json content_part = { 636 {"type" ,"output_text" }, 637 {"annotations" , json::array ()}, 638 {"logprobs" , json::array ()}, 639 {"text" ,oaicompat_msg .content } 640 }; 641 642server_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 }); 650const 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 658server_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 }); 665output .push_back (output_item ); 666 } 667 668for (const common_chat_tool_call & tool_call :oaicompat_msg .tool_calls ) { 669const 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 }; 677server_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 }); 684output .push_back (output_item ); 685 } 686 687 std::time_t t = std::time (0 ); 688server_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 709if (stats .is_set ()) { 710server_sent_events .back ().at ("data" )["timings" ]= stats .to_json (); 711 } 712 713return server_sent_events ; 714} 715 716json server_task_result_cmpl_final::to_json_oaicompat_asr () { 717json 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 }; 728return event ; 729} 730 731json server_task_result_cmpl_final::to_json_anthropic () { 732 std::string stop_reason = "max_tokens" ; 733if (stop == STOP_TYPE_WORD || stop == STOP_TYPE_EOS ) { 734stop_reason = oaicompat_msg .tool_calls .empty () ?"end_turn" :"tool_use" ; 735 } 736 737json content_blocks = json::array (); 738 739common_chat_msg msg ; 740if (!oaicompat_msg .empty ()) { 741msg = oaicompat_msg ; 742 }else { 743msg .role = "assistant" ; 744msg .content = content ; 745 } 746 747// thinking block comes first (Anthropic extended thinking format) 748if (!msg .reasoning_content .empty ()) { 749content_blocks .push_back ({ 750 {"type" ,"thinking" }, 751 {"thinking" ,msg .reasoning_content }, 752 {"signature" ,"" }// empty signature for local models (no cryptographic verification) 753 }); 754 } 755 756if (!msg .content .empty ()) { 757content_blocks .push_back ({ 758 {"type" ,"text" }, 759 {"text" ,msg .content } 760 }); 761 } 762 763for (const auto & tool_call :msg .tool_calls ) { 764json tool_use_block = { 765 {"type" ,"tool_use" }, 766 {"id" ,tool_call .id }, 767 {"name" ,tool_call .name } 768 }; 769 770try { 771tool_use_block ["input" ]= json::parse (tool_call .arguments ); 772 }catch (const std::exception & ) { 773tool_use_block ["input" ]= json::object (); 774 } 775 776content_blocks .push_back (tool_use_block ); 777 } 778 779json 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 794return res ; 795} 796 797json server_task_result_cmpl_final::to_json_anthropic_stream () { 798json events = json::array (); 799 800 std::string stop_reason = "max_tokens" ; 801if (stop == STOP_TYPE_WORD || stop == STOP_TYPE_EOS ) { 802stop_reason = oaicompat_msg .tool_calls .empty () ?"end_turn" :"tool_use" ; 803 } 804 805bool has_thinking = !oaicompat_msg .reasoning_content .empty (); 806bool has_text = !oaicompat_msg .content .empty (); 807size_t num_tool_calls = oaicompat_msg .tool_calls .size (); 808 809// content block indices: thinking (0) -> text (0 or 1) -> tool_use (n+) 810size_t thinking_block_index = 0 ; 811size_t text_block_index = has_thinking ?1 :0 ; 812 813bool thinking_block_started = false; 814bool text_block_started = false; 815 std::unordered_set < size_t > tool_calls_started ; 816 817for (const auto & diff :oaicompat_msg_diffs ) { 818// handle thinking/reasoning content 819if (!diff .reasoning_content_delta .empty ()) { 820if (!thinking_block_started ) { 821events .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 }); 832thinking_block_started = true; 833 } 834 835events .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 849if (!diff .content_delta .empty ()) { 850if (!text_block_started ) { 851events .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 }); 862text_block_started = true; 863 } 864 865events .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 879if (diff .tool_call_index != std::string::npos ) { 880size_t content_block_index = (has_thinking ?1 :0 )+ (has_text ?1 :0 )+ diff .tool_call_index ; 881 882if (tool_calls_started .find (diff .tool_call_index )== tool_calls_started .end ()) { 883const auto & full_tool_call = oaicompat_msg .tool_calls [diff .tool_call_index ]; 884 885events .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 }); 897tool_calls_started .insert (diff .tool_call_index ); 898 } 899 900if (!diff .tool_call_delta .arguments .empty ()) { 901events .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 917if (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 920events .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 }); 931events .push_back ({ 932 {"event" ,"content_block_stop" }, 933 {"data" , { 934 {"type" ,"content_block_stop" }, 935 {"index" ,thinking_block_index } 936 }} 937 }); 938 } 939 940if (has_text ) { 941events .push_back ({ 942 {"event" ,"content_block_stop" }, 943 {"data" , { 944 {"type" ,"content_block_stop" }, 945 {"index" ,text_block_index } 946 }} 947 }); 948 } 949 950for (size_t i = 0 ;i < num_tool_calls ;i ++ ) { 951size_t content_block_index = (has_thinking ?1 :0 )+ (has_text ?1 :0 )+ i ; 952events .push_back ({ 953 {"event" ,"content_block_stop" }, 954 {"data" , { 955 {"type" ,"content_block_stop" }, 956 {"index" ,content_block_index } 957 }} 958 }); 959 } 960 961events .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 975events .push_back ({ 976 {"event" ,"message_stop" }, 977 {"data" , { 978 {"type" ,"message_stop" } 979 }} 980 }); 981 982return events ; 983} 984 985// 986// server_task_result_cmpl_partial 987// 988void server_task_result_cmpl_partial::update (task_result_state & state ) { 989is_updated = true; 990if (is_begin ) { 991return ;// begin marker only flushes headers, skip parsing 992 } 993state .update_chat_msg (content , true,oaicompat_msg_diffs ); 994 995// Copy current state for use in to_json_*() (reflects state BEFORE this chunk) 996thinking_block_started = state .thinking_block_started ; 997text_block_started = state .text_block_started ; 998 999oai_resp_created = state .oai_resp_created ; 1000oai_resp_id = state .oai_resp_id ; 1001oai_resp_reasoning_id = state .oai_resp_reasoning_id ; 1002oai_resp_message_id = state .oai_resp_message_id ; 1003oai_resp_fc_id = state .oai_resp_fc_id ; 1004 1005// track if the accumulated message has any reasoning content 1006anthropic_has_reasoning = !state .chat_msg .reasoning_content .empty (); 1007 1008if (res_type == TASK_RESPONSE_TYPE_OAI_RESP && !state .oai_resp_created && (is_progress || n_decoded == 1 )) { 1009state .oai_resp_created = true; 1010 } 1011 1012// Pre-compute state updates based on diffs (for next chunk) 1013for (const common_chat_msg_diff & diff :oaicompat_msg_diffs ) { 1014if (!diff .reasoning_content_delta .empty ()&& !state .thinking_block_started ) { 1015state .thinking_block_started = true; 1016 } 1017if (!diff .content_delta .empty ()&& !state .text_block_started ) { 1018state .text_block_started = true; 1019 } 1020if (!diff .tool_call_delta .name .empty ()) { 1021state .oai_resp_fc_id = diff .tool_call_delta .id ; 1022 } 1023 } 1024} 1025 1026json server_task_result_cmpl_partial::to_json () { 1027GGML_ASSERT (is_updated && "update() must be called before to_json()" ); 1028if (is_begin ) { 1029return nullptr ;// simply signal to HTTP handler to send the headers and status code 1030 } 1031switch (res_type ) { 1032case TASK_RESPONSE_TYPE_NONE : 1033return to_json_non_oaicompat (); 1034case TASK_RESPONSE_TYPE_OAI_CMPL : 1035return to_json_oaicompat (); 1036case TASK_RESPONSE_TYPE_OAI_CHAT : 1037return to_json_oaicompat_chat (); 1038case TASK_RESPONSE_TYPE_OAI_RESP : 1039return to_json_oaicompat_resp (); 1040case TASK_RESPONSE_TYPE_OAI_ASR : 1041return to_json_oaicompat_asr (); 1042case TASK_RESPONSE_TYPE_ANTHROPIC : 1043return to_json_anthropic (); 1044default : 1045GGML_ASSERT (false&& "Invalid task_response_type" ); 1046 } 1047} 1048 1049json server_task_result_cmpl_partial::to_json_non_oaicompat () { 1050// non-OAI-compat JSON 1051json 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) 1061if (stats .is_set ()) { 1062res ["timings" ]= stats .to_json (); 1063 } 1064if (is_progress ) { 1065res ["prompt_progress" ]= progress .to_json (); 1066 } 1067if (!prob_output .probs .empty ()) { 1068res ["completion_probabilities" ]= completion_token_output::probs_vector_to_json ({prob_output },post_sampling_probs ); 1069 } 1070return res ; 1071} 1072 1073json server_task_result_cmpl_partial::to_json_oaicompat () { 1074 std::time_t t = std::time (0 ); 1075json logprobs = json (nullptr );// OAI default to null 1076if (prob_output .probs .size ()> 0 ) { 1077logprobs = json { 1078 {"content" , completion_token_output::probs_vector_to_json ({prob_output },post_sampling_probs )}, 1079 }; 1080 } 1081json res = json { 1082 {"choices" , json::array ({ 1083json { 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 1098if (verbose ) { 1099res ["__verbose" ]= to_json_non_oaicompat (); 1100 } 1101if (stats .is_set ()) { 1102res ["timings" ]= stats .to_json (); 1103 } 1104if (is_progress ) { 1105res ["prompt_progress" ]= progress .to_json (); 1106 } 1107 1108return res ; 1109} 1110 1111json server_task_result_cmpl_partial::to_json_oaicompat_chat () { 1112bool first = n_decoded == 1 ; 1113 std::time_t t = std::time (0 ); 1114json choices ; 1115 1116 std::vector < json > deltas ; 1117auto add_delta = [& ](const json & delta ) { 1118deltas .push_back ({ 1119 {"choices" , json::array ({ 1120json { 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 1134if (first || is_progress ) { 1135add_delta ({ 1136 {"role" ,"assistant" }, 1137 {"content" ,nullptr }, 1138 }); 1139 } 1140 1141for (const auto & diff :oaicompat_msg_diffs ) { 1142add_delta (server_chat_msg_diff_to_json_oaicompat (diff )); 1143 } 1144 1145if (!deltas .empty ()) { 1146auto & last_json = deltas [deltas .size ()- 1 ]; 1147GGML_ASSERT (last_json .at ("choices" ).size () >=1 ); 1148 1149if (prob_output .probs .size ()> 0 ) { 1150last_json .at ("choices" ).at (0 )["logprobs" ]= json { 1151 {"content" , completion_token_output::probs_vector_to_json ({prob_output },post_sampling_probs )}, 1152 }; 1153 } 1154 1155if (stats .is_set ()) { 1156last_json ["timings" ]= stats .to_json (); 1157 } 1158if (is_progress ) { 1159last_json ["prompt_progress" ]= progress .to_json (); 1160 } 1161 } 1162 1163return deltas ; 1164} 1165 1166json server_task_result_cmpl_partial::to_json_oaicompat_resp () { 1167 std::vector < json > events ; 1168 1169if (!oai_resp_created ) { 1170events .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 }); 1181events .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 ) { 1193events .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 1206for (const common_chat_msg_diff & diff :oaicompat_msg_diffs ) { 1207if (!diff .reasoning_content_delta .empty ()) { 1208if (!thinking_block_started ) { 1209events .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 }); 1223thinking_block_started = true; 1224 } 1225events .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 1235if (!diff .content_delta .empty ()) { 1236if (!text_block_started ) { 1237events .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 }); 1250events .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 }); 1261text_block_started = true; 1262 } 1263events .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 1273if (!diff .tool_call_delta .name .empty ()) { 1274events .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 }); 1288oai_resp_fc_id = diff .tool_call_delta .id ; 1289 } 1290 1291if (!diff .tool_call_delta .arguments .empty ()) { 1292events .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 1303if (!events .empty ()) { 1304json & data = events .back ().at ("data" ); 1305if (stats .is_set ()) { 1306data ["timings" ]= stats .to_json (); 1307 } 1308if (is_progress ) { 1309data ["prompt_progress" ]= progress .to_json (); 1310 } 1311 } 1312 1313return events ; 1314} 1315 1316json server_task_result_cmpl_partial::to_json_oaicompat_asr () { 1317json event = json { 1318 {"type" ,"transcript.text.delta" }, 1319 {"delta" ,content }, 1320 }; 1321return event ; 1322} 1323 1324json server_task_result_cmpl_partial::to_json_anthropic () { 1325json events = json::array (); 1326bool 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 1330if (first ) { 1331events .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+) 1354size_t thinking_block_index = 0 ; 1355// use anthropic_has_reasoning (set in update()) to know if ANY reasoning was generated 1356size_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 1360bool thinking_started = thinking_block_started ; 1361bool text_started = text_block_started ; 1362 1363for (const auto & diff :oaicompat_msg_diffs ) { 1364// handle thinking/reasoning content 1365if (!diff .reasoning_content_delta .empty ()) { 1366if (!thinking_started ) { 1367events .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 }); 1378thinking_started = true; 1379 } 1380 1381events .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 1395if (!diff .content_delta .empty ()) { 1396if (!text_started ) { 1397events .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 }); 1408text_started = true; 1409 } 1410 1411events .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 1425if (diff .tool_call_index != std::string::npos ) { 1426// use anthropic_has_reasoning for thinking block count (persists across calls) 1427size_t content_block_index = (anthropic_has_reasoning ?1 :0 )+ (text_started ?1 :0 )+ diff .tool_call_index ; 1428 1429if (!diff .tool_call_delta .name .empty ()) { 1430events .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 1444if (!diff .tool_call_delta .arguments .empty ()) { 1445events .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 1460return events ; 1461} 1462 1463// 1464// server_task_result_embd 1465// 1466json server_task_result_embd::to_json () { 1467return 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 () { 1473return json { 1474 {"index" ,index }, 1475 {"embedding" ,embedding }, 1476 }; 1477} 1478 1479json server_task_result_embd::to_json_oaicompat () { 1480return 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 () { 1491return 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 () { 1502json res = format_error_response (err_msg ,err_type ); 1503if (err_type == ERROR_TYPE_EXCEED_CONTEXT_SIZE ) { 1504res ["n_prompt_tokens" ]= n_prompt_tokens ; 1505res ["n_ctx" ]= n_ctx ; 1506 } 1507return res ; 1508} 1509 1510// 1511// server_task_result_metrics 1512// 1513json server_task_result_slots::to_json () { 1514return slots_data ; 1515} 1516 1517json server_task_result_metrics::to_json () { 1518// not used, /metrics renders prometheus text via to_metrics() 1519return json {}; 1520} 1521 1522// metrics definition: https://prometheus.io/docs/practices/naming/#metric-names 1523std::string server_task_result_metrics::to_metrics () { 1524const 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" , 1536metrics .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" , 1544metrics .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 1568const std::vector < metric_item > gauges = { 1569 { 1570"prompt_tokens_seconds" , 1571"Average prompt throughput in tokens/s" , 1572metrics .prompt_bucket .n_per_second () 1573 }, { 1574"predicted_tokens_seconds" , 1575"Average generation throughput in tokens/s" , 1576metrics .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 1594auto add_items = [& prometheus ](const char * type ,const std::vector < metric_item > & items ) { 1595for (const auto & item :items ) { 1596prometheus <<"# HELP llamacpp:" <<item .name <<" " <<item .description <<"\n" 1597 <<"# TYPE llamacpp:" <<item .name <<" " <<type <<"\n" 1598 <<"llamacpp:" <<item .name <<" " <<item .value <<"\n" ; 1599 } 1600 }; 1601 1602add_items ("counter" ,counters ); 1603add_items ("gauge" ,gauges ); 1604 1605// labeled counter: one time series per draft position 1606if (!metrics .n_accepted_per_pos .empty ()) { 1607prometheus <<"# 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" ; 1610for (size_t i = 0 ;i < metrics .n_accepted_per_pos .size ();i ++ ) { 1611prometheus <<"llamacpp:spec_decode_num_accepted_tokens_per_pos_total{position=\"" 1612 <<i <<"\"} " <<metrics .n_accepted_per_pos [i ] <<"\n" ; 1613 } 1614 } 1615 1616return prometheus .str (); 1617} 1618 1619// 1620// server_task_result_slot_save_load 1621// 1622json server_task_result_slot_save_load::to_json () { 1623if (is_save ) { 1624return 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 1635return 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 () { 1650return 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 () { 1661json result = json::array (); 1662for (size_t i = 0 ;i < loras .size ();++ i ) { 1663auto & lora = loras [i ]; 1664json 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 }; 1671if (!lora .alora_invocation_tokens .empty ()) { 1672entry ["alora_invocation_string" ]= lora .alora_invocation_string ; 1673entry ["alora_invocation_tokens" ]= lora .alora_invocation_tokens ; 1674 } 1675result .push_back (std::move (entry )); 1676 } 1677return result ; 1678} 1679 1680// 1681// server_task_result_apply_lora 1682// 1683 1684json server_task_result_apply_lora::to_json () { 1685return json {{"success" , true }}; 1686} 1687 1688// 1689// server_prompt_cache 1690// 1691size_t server_prompt_cache::size ()const { 1692size_t res = 0 ; 1693 1694for (const auto & state :states ) { 1695res += state .size (); 1696 } 1697 1698return res ; 1699} 1700 1701size_t server_prompt_cache::n_tokens ()const { 1702size_t res = 0 ; 1703 1704for (const auto & state :states ) { 1705res += state .prompt .n_tokens (); 1706 } 1707 1708return 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 1713for (auto it = states .begin ();it != states .end ();++ it ) { 1714const int cur_lcp_len = it -> prompt .tokens .get_common_prefix (prompt .tokens ); 1715 1716if (cur_lcp_len == (int )prompt .tokens .size ()) { 1717SRV_TRC ("%s" ," - prompt is already in the cache, skipping\n" ); 1718return nullptr ; 1719 } 1720 } 1721 1722// calculate checkpoints size to see if it will fit with the prompt 1723size_t checkpoints_size = 0 ; 1724for (const auto & ckpt :prompt .checkpoints ) { 1725checkpoints_size += ckpt .size (); 1726 } 1727 1728const size_t state_size_new = state_size_tgt + state_size_dft + checkpoints_size ; 1729 1730// skip over-limit entries to avoid disturbing the cache 1731if (limit_size > 0 && state_size_new > limit_size ) { 1732SRV_WRN (" - prompt state size %.3f MiB exceeds cache size limit %.3f MiB, skipping\n" , 1733state_size_new / (1024.0 * 1024.0 ),limit_size / (1024.0 * 1024.0 )); 1734return nullptr ; 1735 } 1736 1737// remove any cached prompts that are fully contained in the current prompt 1738for (auto it = states .begin ();it != states .end ();) { 1739const int len = it -> prompt .tokens .get_common_prefix (prompt .tokens ); 1740 1741if (len == (int )it -> prompt .tokens .size ()) { 1742SRV_TRC (" - removing obsolete cached prompt with length %d\n" ,len ); 1743 1744it = states .erase (it ); 1745 }else { 1746++ it ; 1747 } 1748 } 1749 1750if (limit_size > 0 ) { 1751// make room before allocating the new vectors to avoid breaching the limit 1752while (!states .empty ()&& size ()+ state_size_new > limit_size ) { 1753SRV_WRN (" - making room for prompt cache entry, removing oldest entry (size = %.3f MiB)\n" , 1754states .front ().size () / (1024.0 * 1024.0 )); 1755 1756states .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 1764try { 1765state_data_tgt .resize (state_size_tgt ); 1766state_data_dft .resize (state_size_dft ); 1767 }catch (const std::bad_alloc & e ) { 1768SRV_ERR ("failed to allocate memory for prompt cache state: %s\n" ,e .what ()); 1769 1770limit_size = std::max < size_t > (1 ,0.4 * size ()); 1771 1772SRV_WRN (" - cache size limit reduced to %.3f MiB\n" ,limit_size / (1024.0 * 1024.0 )); 1773 1774update (); 1775 1776return nullptr ; 1777 } 1778 1779states .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 1790return & 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 ) { 1794const int lcp_best = prompt .tokens .get_common_prefix (tokens_new ); 1795 1796float f_keep_best = prompt .tokens .size ()> 0 ?float (lcp_best ) /prompt .tokens .size () :-1.0f ;// empty slot: any cache entry wins 1797float f_sim_best = float (lcp_best ) /tokens_new .size (); 1798 1799SRV_TRC (" - looking for better prompt, base f_keep = %.3f, f_sim = %.3f\n" ,f_keep_best ,f_sim_best ); 1800 1801auto it_best = states .end (); 1802 1803// find the most similar cached prompt, that would also preserve the most context 1804for (auto it = states .begin ();it != states .end ();++ it ) { 1805const int lcp_cur = it -> prompt .tokens .get_common_prefix (tokens_new ); 1806 1807const float f_keep_cur = float (lcp_cur ) /it -> prompt .tokens .size (); 1808const float f_sim_cur = float (lcp_cur ) /tokens_new .size (); 1809 1810SRV_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 1813if (f_keep_cur < 0.25f ) { 1814continue ; 1815 } 1816 1817if (f_keep_best < f_keep_cur && f_sim_best < f_sim_cur ) { 1818f_keep_best = f_keep_cur ; 1819f_sim_best = f_sim_cur ; 1820 1821it_best = it ; 1822 } 1823 } 1824 1825if (it_best != states .end ()) { 1826SRV_TRC (" - found better prompt with f_keep = %.3f, f_sim = %.3f\n" ,f_keep_best ,f_sim_best ); 1827 1828 { 1829auto & data = it_best -> data .main ; 1830 1831const size_t size = data .size (); 1832const size_t n = llama_state_seq_set_data_ext (ctx_tgt ,data .data (),size ,id_slot ,0 ); 1833if (n != size ) { 1834SRV_ERR ("failed to restore state with size %zu\n" ,size ); 1835 1836return false; 1837 } 1838 1839data .clear (); 1840data .shrink_to_fit (); 1841 } 1842 1843 { 1844auto & data = it_best -> data .drft ; 1845 1846if (!data .empty ()) { 1847GGML_ASSERT (ctx_dft ); 1848 1849const size_t size = data .size (); 1850const size_t n = llama_state_seq_set_data_ext (ctx_dft ,data .data (),size ,id_slot ,0 ); 1851if (n != size ) { 1852SRV_WRN ("failed to restore state with size %zu\n" ,size ); 1853 1854return false; 1855 } 1856 1857data .clear (); 1858data .shrink_to_fit (); 1859 } 1860 } 1861 1862prompt = std::move (it_best -> prompt ); 1863 1864states .erase (it_best ); 1865 } 1866 1867return true; 1868} 1869 1870void server_prompt_cache::update () { 1871if (limit_size > 0 ) { 1872while (!states .empty ()&& size ()> limit_size ) { 1873SRV_WRN (" - cache size limit reached, removing oldest entry (size = %.3f MiB)\n" ,states .front ().size () / (1024.0 * 1024.0 )); 1874 1875states .pop_front (); 1876 } 1877 } 1878 1879// average size per token 1880const 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 1883const size_t limit_tokens_cur = limit_size > 0 ? std::max < size_t > (limit_tokens ,limit_size /size_per_token ) :limit_tokens ; 1884 1885if (limit_tokens > 0 ) { 1886while (!states .empty ()&& n_tokens ()> limit_tokens_cur ) { 1887SRV_WRN (" - cache token limit (%zu, est: %zu) reached, removing oldest entry (size = %.3f MiB)\n" , 1888limit_tokens ,limit_tokens_cur ,states .front ().size () / (1024.0 * 1024.0 )); 1889 1890states .pop_front (); 1891 } 1892 } 1893 1894SRV_TRC (" - cache state: %zu prompts, %.3f MiB (limits: %.3f MiB, %zu tokens, %zu est)\n" , 1895states .size (),size () / (1024.0 * 1024.0 ),limit_size / (1024.0 * 1024.0 ),limit_tokens ,limit_tokens_cur ); 1896 1897for (const auto & state :states ) { 1898SRV_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}