char-fork/llama.cpp
git clone https://git.t4t.associates/char-fork/llama.cpp
d9f918d2d
char/aliceai-t5-moe
1#include "server-chat.h" 2#include "server-common.h" 3 4#include <sstream> 5 6json server_chat_convert_responses_to_chatcmpl (const json & response_body ) { 7if (!response_body .contains ("input" )) { 8throw std::invalid_argument ("'input' is required" ); 9 } 10if (!json_value (response_body ,"previous_response_id" , std::string {}).empty ()) { 11throw std::invalid_argument ("llama.cpp does not support 'previous_response_id'." ); 12 } 13 14const json input_value = response_body .at ("input" ); 15json chatcmpl_body = response_body ; 16chatcmpl_body .erase ("input" ); 17 std::vector < json > chatcmpl_messages ; 18 19if (response_body .contains ("instructions" )) { 20chatcmpl_messages .push_back ({ 21 {"role" ,"system" }, 22 {"content" ,json_value (response_body ,"instructions" , std::string ())}, 23 }); 24chatcmpl_body .erase ("instructions" ); 25 } 26 27if (input_value .is_string ()) { 28// #responses_create-input-text_input 29chatcmpl_messages .push_back ({ 30 {"role" ,"user" }, 31 {"content" ,input_value }, 32 }); 33 }else if (input_value .is_array ()) { 34// #responses_create-input-input_item_list 35 36static auto exists_and_is_array = [](const json & j ,const char * key )-> bool { 37return j .contains (key )&& j .at (key ).is_array (); 38 }; 39static auto exists_and_is_string = [](const json & j ,const char * key )-> bool { 40return j .contains (key )&& j .at (key ).is_string (); 41 }; 42 43for (json item :input_value ) { 44bool merge_prev = !chatcmpl_messages .empty ()&& chatcmpl_messages .back ().value ("role" ,"" )== "assistant" ; 45 46if (exists_and_is_string (item ,"content" )) { 47// #responses_create-input-input_item_list-input_message-content-text_input 48// Only "Input message" contains item["content"]::string 49// After converting item["content"]::string to item["content"]::array, 50// we can treat "Input message" as sum of "Item-Input message" and "Item-Output message" 51item ["content" ]= json::array ({ 52json { 53 {"text" ,item .at ("content" )}, 54 {"type" ,"input_text" } 55 } 56 }); 57 } 58 59if (exists_and_is_array (item ,"content" )&& 60exists_and_is_string (item ,"role" )&& 61 (item .at ("role" )== "user" || 62item .at ("role" )== "system" || 63item .at ("role" )== "developer" ) 64 ) { 65// #responses_create-input-input_item_list-item-input_message 66 std::vector < json > chatcmpl_content ; 67 68for (const json & input_item :item .at ("content" )) { 69const std::string type = json_value (input_item ,"type" , std::string ()); 70 71if (type == "input_text" ) { 72if (!input_item .contains ("text" )) { 73throw std::invalid_argument ("'Input text' requires 'text'" ); 74 } 75chatcmpl_content .push_back ({ 76 {"text" ,input_item .at ("text" )}, 77 {"type" ,"text" }, 78 }); 79 }else if (type == "input_image" ) { 80// While `detail` is marked as required, 81// it has default value("auto") and can be omitted. 82 83if (!input_item .contains ("image_url" )) { 84throw std::invalid_argument ("'image_url' is required" ); 85 } 86chatcmpl_content .push_back ({ 87 {"image_url" ,json { 88 {"url" ,input_item .at ("image_url" )} 89 }}, 90 {"type" ,"image_url" }, 91 }); 92 }else if (type == "input_file" ) { 93throw std::invalid_argument ("'input_file' is not supported by llamacpp at this moment" ); 94 }else { 95throw std::invalid_argument ("'type' must be one of 'input_text', 'input_image', or 'input_file'" ); 96 } 97 } 98 99if (item .contains ("type" )) { 100item .erase ("type" ); 101 } 102if (item .contains ("status" )) { 103item .erase ("status" ); 104 } 105item ["content" ]= chatcmpl_content ; 106 107chatcmpl_messages .push_back (item ); 108 }else if (exists_and_is_string (item ,"role" )&& 109item .at ("role" )== "assistant" && 110exists_and_is_string (item ,"type" )&& 111item .at ("type" )== "message" 112 ) { 113// #responses_create-input-input_item_list-item-output_message 114auto chatcmpl_content = json::array (); 115 116// Handle both string content and array content 117if (item .contains ("content" )&& item .at ("content" ).is_string ()) { 118// String content - convert to text content part 119chatcmpl_content .push_back ({ 120 {"text" ,item .at ("content" )}, 121 {"type" ,"text" }, 122 }); 123 }else if (exists_and_is_array (item ,"content" )) { 124// Array content - process each item 125for (const auto & output_text :item .at ("content" )) { 126const std::string type = json_value (output_text ,"type" , std::string ()); 127if (type == "output_text" || type == "input_text" ) { 128// Accept both output_text and input_text (string content gets converted to input_text) 129if (!exists_and_is_string (output_text ,"text" )) { 130throw std::invalid_argument ("'Output text' requires 'text'" ); 131 } 132chatcmpl_content .push_back ({ 133 {"text" ,output_text .at ("text" )}, 134 {"type" ,"text" }, 135 }); 136 }else if (type == "refusal" ) { 137if (!exists_and_is_string (output_text ,"refusal" )) { 138throw std::invalid_argument ("'Refusal' requires 'refusal'" ); 139 } 140chatcmpl_content .push_back ({ 141 {"refusal" ,output_text .at ("refusal" )}, 142 {"type" ,"refusal" }, 143 }); 144 }else { 145throw std::invalid_argument ("'type' must be one of 'output_text' or 'refusal'" ); 146 } 147 } 148 } 149 150if (merge_prev ) { 151auto & prev_msg = chatcmpl_messages .back (); 152if (!exists_and_is_array (prev_msg ,"content" )) { 153prev_msg ["content" ]= json::array (); 154 } 155auto & prev_content = prev_msg ["content" ]; 156prev_content .insert (chatcmpl_content ); 157 }else { 158item .erase ("status" ); 159item .erase ("type" ); 160item ["content" ]= chatcmpl_content ; 161chatcmpl_messages .push_back (item ); 162 } 163 }else if (exists_and_is_string (item ,"arguments" )&& 164exists_and_is_string (item ,"call_id" )&& 165exists_and_is_string (item ,"name" )&& 166exists_and_is_string (item ,"type" )&& 167item .at ("type" )== "function_call" 168 ) { 169// #responses_create-input-input_item_list-item-function_tool_call 170json tool_call = { 171 {"function" ,json { 172 {"arguments" ,item .at ("arguments" )}, 173 {"name" ,item .at ("name" )}, 174 }}, 175 {"id" ,item .at ("call_id" )}, 176 {"type" ,"function" }, 177 }; 178 179if (merge_prev ) { 180auto & prev_msg = chatcmpl_messages .back (); 181if (!exists_and_is_array (prev_msg ,"tool_calls" )) { 182prev_msg ["tool_calls" ]= json::array (); 183 } 184prev_msg ["tool_calls" ].push_back (tool_call ); 185 }else { 186chatcmpl_messages .push_back (json { 187 {"role" ,"assistant" }, 188 {"tool_calls" , json::array ({tool_call })} 189 }); 190 } 191 }else if (exists_and_is_string (item ,"call_id" )&& 192 (exists_and_is_string (item ,"output" )|| exists_and_is_array (item ,"output" ))&& 193exists_and_is_string (item ,"type" )&& 194item .at ("type" )== "function_call_output" 195 ) { 196// #responses_create-input-input_item_list-item-function_tool_call_output 197if (item .at ("output" ).is_string ()) { 198chatcmpl_messages .push_back (json { 199 {"content" ,item .at ("output" )}, 200 {"role" ,"tool" }, 201 {"tool_call_id" ,item .at ("call_id" )}, 202 }); 203 }else { 204json chatcmpl_outputs = item .at ("output" ); 205for (json & chatcmpl_output :chatcmpl_outputs ) { 206if (!chatcmpl_output .contains ("type" )|| chatcmpl_output .at ("type" )!= "input_text" ) { 207throw std::invalid_argument ("Output of tool call should be 'Input text'" ); 208 } 209chatcmpl_output ["type" ]= "text" ; 210 } 211chatcmpl_messages .push_back (json { 212 {"content" ,chatcmpl_outputs }, 213 {"role" ,"tool" }, 214 {"tool_call_id" ,item .at ("call_id" )}, 215 }); 216 } 217 }else if (exists_and_is_array (item ,"summary" )&& 218exists_and_is_string (item ,"type" )&& 219item .at ("type" )== "reasoning" ) { 220// #responses_create-input-input_item_list-item-reasoning 221 222if (!exists_and_is_array (item ,"content" )) { 223throw std::invalid_argument ("item['content'] is not an array" ); 224 } 225if (item .at ("content" ).empty ()) { 226throw std::invalid_argument ("item['content'] is empty" ); 227 } 228if (!exists_and_is_string (item .at ("content" )[0 ],"text" )) { 229throw std::invalid_argument ("item['content']['text'] is not a string" ); 230 } 231 232if (merge_prev ) { 233auto & prev_msg = chatcmpl_messages .back (); 234prev_msg ["reasoning_content" ]= item .at ("content" )[0 ].at ("text" ); 235 }else { 236chatcmpl_messages .push_back (json { 237 {"role" ,"assistant" }, 238 {"content" , json::array ()}, 239 {"reasoning_content" ,item .at ("content" )[0 ].at ("text" )}, 240 }); 241 } 242 }else { 243throw std::invalid_argument ("Cannot determine type of 'item'" ); 244 } 245 } 246 }else { 247throw std::invalid_argument ("'input' must be a string or array of objects" ); 248 } 249 250chatcmpl_body ["messages" ]= chatcmpl_messages ; 251 252if (response_body .contains ("tools" )) { 253if (!response_body .at ("tools" ).is_array ()) { 254throw std::invalid_argument ("'tools' must be an array of objects" ); 255 } 256 std::vector < json > chatcmpl_tools ; 257for (json resp_tool :response_body .at ("tools" )) { 258json chatcmpl_tool ; 259 260const std::string type = json_value (resp_tool ,"type" , std::string ()); 261if (type != "function" ) { 262// Non-function Responses tools have no Chat Completions equivalent. 263SRV_WRN ("unsupported Responses tool type '%s' skipped\n" ,type .c_str ()); 264continue ; 265 } 266resp_tool .erase ("type" ); 267chatcmpl_tool ["type" ]= "function" ; 268 269if (!resp_tool .contains ("strict" )) { 270resp_tool ["strict" ]= true; 271 } 272chatcmpl_tool ["function" ]= resp_tool ; 273chatcmpl_tools .push_back (chatcmpl_tool ); 274 } 275chatcmpl_body .erase ("tools" ); 276if (!chatcmpl_tools .empty ()) { 277chatcmpl_body ["tools" ]= chatcmpl_tools ; 278 } 279 } 280 281if (response_body .contains ("max_output_tokens" )) { 282chatcmpl_body .erase ("max_output_tokens" ); 283chatcmpl_body ["max_tokens" ]= response_body ["max_output_tokens" ]; 284 } 285 286if (response_body .contains ("reasoning" )) { 287// Only "effort" is handled so far 288const json & reasoning = response_body .at ("reasoning" ); 289if (reasoning .contains ("effort" )) { 290chatcmpl_body ["reasoning_effort" ]= reasoning .at ("effort" ); 291 } 292chatcmpl_body .erase ("reasoning" ); 293 } 294 295return chatcmpl_body ; 296} 297 298// Edits the cch section of an "x-anthropic-billing-header" system prompt. 299// Does nothing to any other prompt. 300// 301// This is a claude message with a "cch=ef01a" attribute that breaks prefix caching. 302// The cch stamp is a whitebox end-to-end integrity hint. It's not meaningful as a 303// system prompt data, particularly to llama.cpp, but its presence means the prefix 304// cache will not get past it: It changes on each request. 305// 306// Reference: https://github.com/ggml-org/llama.cpp/pull/21793 307// Example header: 308// ``` 309// x-anthropic-billing-header: cc_version=2.1.101.e51; cc_entrypoint=cli; cch=a5145;You are Claude Code, Anthropic's official CLI for Claude. 310// ^^^^^ 311// ``` 312static void normalize_anthropic_billing_header (std::string & system_text ) { 313if (system_text .rfind ("x-anthropic-billing-header:" ,0 )!= 0 ) { 314return ; 315 } 316 317const size_t header_prefix_length = strlen ("x-anthropic-billing-header:" ); 318const size_t cch_length = 5 ; 319const size_t index_cch = system_text .find ("cch=" ,header_prefix_length ); 320if (index_cch == std::string::npos ) { 321return ; 322 } 323 324const size_t index_replace = index_cch + 4 ; 325if (index_replace + cch_length < system_text .length ()&& system_text [index_replace + cch_length ]== ';' ) { 326for (size_t i = 0 ;i < cch_length ;++ i ) { 327system_text [index_replace + i ]= 'f' ; 328 } 329 }else { 330LOG_ERR ("anthropic string not as expected: %s" ,system_text .c_str ()); 331 } 332} 333 334json server_chat_convert_anthropic_to_oai (const json & body ) { 335json oai_body ; 336 337// Convert system prompt 338json oai_messages = json::array (); 339auto system_param = json_value (body ,"system" ,json ()); 340if (!system_param .is_null ()) { 341 std::string system_content ; 342 343if (system_param .is_string ()) { 344system_content = system_param .get < std::string > (); 345normalize_anthropic_billing_header (system_content ); 346 }else if (system_param .is_array ()) { 347for (const auto & block :system_param ) { 348if (json_value (block ,"type" , std::string ())== "text" ) { 349auto system_text = json_value (block ,"text" , std::string ()); 350normalize_anthropic_billing_header (system_text ); 351system_content += system_text ; 352 } 353 } 354 } 355 356oai_messages .push_back ({ 357 {"role" ,"system" }, 358 {"content" ,system_content } 359 }); 360 } 361 362// Convert messages 363if (!body .contains ("messages" )) { 364throw std::runtime_error ("'messages' is required" ); 365 } 366const json & messages = body .at ("messages" ); 367if (messages .is_array ()) { 368for (const auto & msg :messages ) { 369 std::string role = json_value (msg ,"role" , std::string ()); 370 371if (!msg .contains ("content" )) { 372if (role == "assistant" ) { 373continue ; 374 } 375oai_messages .push_back (msg ); 376continue ; 377 } 378 379const json & content = msg .at ("content" ); 380 381if (content .is_string ()) { 382oai_messages .push_back (msg ); 383continue ; 384 } 385 386if (!content .is_array ()) { 387oai_messages .push_back (msg ); 388continue ; 389 } 390 391json tool_calls = json::array (); 392json converted_content = json::array (); 393json tool_results = json::array (); 394 std::string reasoning_content ; 395bool has_tool_calls = false; 396 397for (const auto & block :content ) { 398 std::string type = json_value (block ,"type" , std::string ()); 399 400if (type == "text" ) { 401converted_content .push_back (block ); 402 }else if (type == "thinking" ) { 403reasoning_content += json_value (block ,"thinking" , std::string ()); 404 }else if (type == "image" ) { 405json source = json_value (block ,"source" , json::object ()); 406 std::string source_type = json_value (source ,"type" , std::string ()); 407 408if (source_type == "base64" ) { 409 std::string media_type = json_value (source ,"media_type" , std::string ("image/jpeg" )); 410 std::string data = json_value (source ,"data" , std::string ()); 411 std::ostringstream ss ; 412ss <<"data:" <<media_type <<";base64," <<data ; 413 414converted_content .push_back ({ 415 {"type" ,"image_url" }, 416 {"image_url" , { 417 {"url" ,ss .str ()} 418 }} 419 }); 420 }else if (source_type == "url" ) { 421 std::string url = json_value (source ,"url" , std::string ()); 422converted_content .push_back ({ 423 {"type" ,"image_url" }, 424 {"image_url" , { 425 {"url" ,url } 426 }} 427 }); 428 } 429 }else if (type == "tool_use" ) { 430tool_calls .push_back ({ 431 {"id" ,json_value (block ,"id" , std::string ())}, 432 {"type" ,"function" }, 433 {"function" , { 434 {"name" ,json_value (block ,"name" , std::string ())}, 435 {"arguments" ,json_value (block ,"input" , json::object ()).dump ()} 436 }} 437 }); 438has_tool_calls = true; 439 }else if (type == "tool_result" ) { 440 std::string tool_use_id = json_value (block ,"tool_use_id" , std::string ()); 441 442auto result_content = json_value (block ,"content" ,json ()); 443if (result_content .is_string ()) { 444tool_results .push_back ({ 445 {"role" ,"tool" }, 446 {"tool_call_id" ,tool_use_id }, 447 {"content" ,result_content .get < std::string > ()} 448 }); 449 }else if (result_content .is_array ()) { 450// Single-pass: build both text and content_parts, decide format at the end 451 std::string result_text ; 452json content_parts = json::array (); 453bool has_images = false; 454 455for (const auto & c :result_content ) { 456 std::string c_type = json_value (c ,"type" , std::string ()); 457if (c_type == "text" ) { 458 std::string text = json_value (c ,"text" , std::string ()); 459result_text += text ; 460content_parts .push_back ({ 461 {"type" ,"text" }, 462 {"text" ,text } 463 }); 464 }else if (c_type == "image" ) { 465has_images = true; 466json source = json_value (c ,"source" , json::object ()); 467 std::string source_type = json_value (source ,"type" , std::string ()); 468if (source_type == "base64" ) { 469 std::string media_type = json_value (source ,"media_type" , std::string ("image/jpeg" )); 470 std::string data = json_value (source ,"data" , std::string ()); 471 std::string url = "data:" + media_type + ";base64," + data ; 472content_parts .push_back ({ 473 {"type" ,"image_url" }, 474 {"image_url" , {{"url" ,url }}} 475 }); 476 }else if (source_type == "url" ) { 477content_parts .push_back ({ 478 {"type" ,"image_url" }, 479 {"image_url" , {{"url" ,json_value (source ,"url" , std::string ())}}} 480 }); 481 } 482 } 483 } 484 485if (!has_images ) { 486// Text-only: collapse to a plain string for maximum compatibility 487tool_results .push_back ({ 488 {"role" ,"tool" }, 489 {"tool_call_id" ,tool_use_id }, 490 {"content" ,result_text } 491 }); 492 }else { 493// Mixed or image-only: use array content parts (OpenAI multimodal tool format) 494tool_results .push_back ({ 495 {"role" ,"tool" }, 496 {"tool_call_id" ,tool_use_id }, 497 {"content" ,content_parts } 498 }); 499 } 500 }else { 501tool_results .push_back ({ 502 {"role" ,"tool" }, 503 {"tool_call_id" ,tool_use_id }, 504 {"content" ,"" } 505 }); 506 } 507 } 508 } 509 510if (!converted_content .empty ()|| has_tool_calls || !reasoning_content .empty ()) { 511json new_msg = {{"role" ,role }}; 512if (!converted_content .empty ()) { 513new_msg ["content" ]= converted_content ; 514 }else if (has_tool_calls || !reasoning_content .empty ()) { 515new_msg ["content" ]= "" ; 516 } 517if (!tool_calls .empty ()) { 518new_msg ["tool_calls" ]= tool_calls ; 519 } 520if (!reasoning_content .empty ()) { 521new_msg ["reasoning_content" ]= reasoning_content ; 522 } 523oai_messages .push_back (new_msg ); 524 } 525 526for (const auto & tool_msg :tool_results ) { 527oai_messages .push_back (tool_msg ); 528 } 529 } 530 } 531 532oai_body ["messages" ]= oai_messages ; 533 534// Convert tools 535if (body .contains ("tools" )) { 536const json & tools = body .at ("tools" ); 537if (tools .is_array ()) { 538json oai_tools = json::array (); 539for (const auto & tool :tools ) { 540oai_tools .push_back ({ 541 {"type" ,"function" }, 542 {"function" , { 543 {"name" ,json_value (tool ,"name" , std::string ())}, 544 {"description" ,json_value (tool ,"description" , std::string ())}, 545 {"parameters" ,tool .contains ("input_schema" ) ?tool .at ("input_schema" ) : json::object ()} 546 }} 547 }); 548 } 549oai_body ["tools" ]= oai_tools ; 550 } 551 } 552 553// Convert tool_choice 554if (body .contains ("tool_choice" )) { 555const json & tc = body .at ("tool_choice" ); 556if (tc .is_object ()) { 557 std::string type = json_value (tc ,"type" , std::string ()); 558if (type == "auto" ) { 559oai_body ["tool_choice" ]= "auto" ; 560 }else if (type == "any" || type == "tool" ) { 561oai_body ["tool_choice" ]= "required" ; 562 } 563 } 564 } 565 566// Convert stop_sequences to stop 567if (body .contains ("stop_sequences" )) { 568oai_body ["stop" ]= body .at ("stop_sequences" ); 569 } 570 571// Handle max_tokens (required in Anthropic, but we're permissive) 572if (body .contains ("max_tokens" )) { 573oai_body ["max_tokens" ]= body .at ("max_tokens" ); 574 }else { 575oai_body ["max_tokens" ]= 4096 ; 576 } 577 578// Pass through common params 579for (const auto & key : {"temperature" ,"top_p" ,"top_k" ,"stream" ,"chat_template_kwargs" }) { 580if (body .contains (key )) { 581oai_body [key ]= body .at (key ); 582 } 583 } 584 585// Handle Anthropic-specific thinking param 586if (body .contains ("thinking" )) { 587json thinking = json_value (body ,"thinking" , json::object ()); 588 std::string thinking_type = json_value (thinking ,"type" , std::string ()); 589if (thinking_type == "enabled" ) { 590int budget_tokens = json_value (thinking ,"budget_tokens" ,10000 ); 591oai_body ["thinking_budget_tokens" ]= budget_tokens ; 592 } 593 } 594 595// Handle Anthropic-specific metadata param 596if (body .contains ("metadata" )) { 597json metadata = json_value (body ,"metadata" , json::object ()); 598 std::string user_id = json_value (metadata ,"user_id" , std::string ()); 599if (!user_id .empty ()) { 600oai_body ["__metadata_user_id" ]= user_id ; 601 } 602 } 603 604return oai_body ; 605} 606 607json server_chat_msg_diff_to_json_oaicompat (const common_chat_msg_diff & diff ) { 608json delta = json::object (); 609if (!diff .reasoning_content_delta .empty ()) { 610delta ["reasoning_content" ]= diff .reasoning_content_delta ; 611 } 612if (!diff .content_delta .empty ()) { 613delta ["content" ]= diff .content_delta ; 614 } 615if (diff .tool_call_index != std::string::npos ) { 616json tool_call ; 617tool_call ["index" ]= diff .tool_call_index ; 618if (!diff .tool_call_delta .id .empty ()) { 619tool_call ["id" ]= diff .tool_call_delta .id ; 620tool_call ["type" ]= "function" ; 621 } 622if (!diff .tool_call_delta .name .empty ()|| !diff .tool_call_delta .arguments .empty ()) { 623json function = json::object (); 624if (!diff .tool_call_delta .name .empty ()) { 625function ["name" ]= diff .tool_call_delta .name ; 626 } 627if (!diff .tool_call_delta .arguments .empty ()) { 628function ["arguments" ]= diff .tool_call_delta .arguments ; 629 } 630tool_call ["function" ]= function ; 631 } 632delta ["tool_calls" ]= json::array ({tool_call }); 633 } 634return delta ; 635} 636 637json convert_transcriptions_to_chatcmpl ( 638const json & inp_body , 639const common_chat_templates * tmpls , 640const std::map < std::string ,uploaded_file > & in_files , 641 std::vector < raw_buffer > & out_files ) { 642// TODO @ngxson : this function may need to be improved in the future 643// handle input files 644out_files .clear (); 645auto it = in_files .find ("file" ); 646if (it != in_files .end ()) { 647out_files .push_back (it -> second .data ); 648 }else { 649throw std::invalid_argument ("No input file found for transcription" ); 650 } 651 652// handle input data 653 std::string prompt = json_value (inp_body ,"prompt" , std::string ()); 654 std::string language = json_value (inp_body ,"language" , std::string ()); 655 std::string response_format = json_value (inp_body ,"response_format" , std::string ("json" )); 656if (response_format != "json" ) { 657throw std::invalid_argument ("Only 'json' response_format is supported for transcription" ); 658 } 659const common_chat_prompt_preset preset = common_chat_get_asr_prompt (tmpls ); 660if (prompt .empty ()) { 661prompt = preset .user ; 662 } 663if (!language .empty ()) { 664prompt += string_format (" (language: %s)" ,language .c_str ()); 665 } 666prompt += get_media_marker (); 667 668json messages = json::array (); 669if (!preset .system .empty ()) { 670messages .push_back ({{"role" ,"system" }, {"content" ,preset .system }}); 671 } 672messages .push_back ({{"role" ,"user" }, {"content" ,prompt }}); 673 674json chatcmpl_body = inp_body ;// copy all fields 675chatcmpl_body ["messages" ]= messages ; 676 677// because input from form-data, everything is string, we need to correct the types here 678 std::string stream = json_value (inp_body ,"stream" , std::string ("false" )); 679chatcmpl_body ["stream" ]= stream == "true" ; 680 681if (inp_body .contains ("max_tokens" )) { 682 std::string inp = inp_body ["max_tokens" ].get < std::string > (); 683chatcmpl_body ["max_tokens" ]= std::stoul (inp ); 684 } 685 686if (inp_body .contains ("temperature" )) { 687 std::string inp = inp_body ["temperature" ].get < std::string > (); 688chatcmpl_body ["temperature" ]= std::stof (inp ); 689 } 690 691return chatcmpl_body ; 692}