char-fork/llama.cpp
git clone https://git.t4t.associates/char-fork/llama.cpp
bfa321917
char/aliceai-t5-moe
1import pytest 2from openai import OpenAI 3from utils import * 4 5server :ServerProcess 6 7@ pytest . fixture ( autouse = True ) 8def create_server (): 9global server 10server = ServerPreset .tinyllama2 () 11 12 13@ pytest . mark . parametrize ( 14"model,system_prompt,user_prompt,max_tokens,re_content,n_prompt,n_predicted,finish_reason,jinja,chat_template" , 15[ 16( None , "Book" , "Hey" , 8 , "But she couldn't" , 69 , 8 , "length" , False , None ), 17( None , "Book" , "Hey" , 8 , "But she couldn't" , 69 , 8 , "length" , True , None ), 18( None , "Book" , "What is the best book" , 8 , "(Suddenly)+|\\{ \" Sarax." , 77 , 8 , "length" , False , None ), 19( None , "Book" , "What is the best book" , 8 , "(Suddenly)+|\\{ \" Sarax." , 77 , 8 , "length" , True , None ), 20( None , "Book" , "What is the best book" , 8 , "(Suddenly)+|\\{ \" Sarax." , 77 , 8 , "length" , True , 'chatml' ), 21( None , "Book" , "What is the best book" , 8 , "^ blue" , 23 , 8 , "length" , True , "This is not a chat template, it is" ), 22( "codellama70b" , "You are a coding assistant." , "Write the fibonacci function in c++." , 128 , "(Aside|she|felter|alonger)+" , 104 , 128 , "length" , False , None ), 23( "codellama70b" , "You are a coding assistant." , "Write the fibonacci function in c++." , 128 , "(Aside|she|felter|alonger)+" , 104 , 128 , "length" , True , None ), 24( None , "Book" , [{ "type" : "text" , "text" : "What is" }, { "type" : "text" , "text" : "the best book" }], 8 , "Whillicter" , 79 , 8 , "length" , False , None ), 25( None , "Book" , [{ "type" : "text" , "text" : "What is" }, { "type" : "text" , "text" : "the best book" }], 8 , "Whillicter" , 79 , 8 , "length" , True , None ), 26] 27) 28def test_chat_completion (model ,system_prompt ,user_prompt ,max_tokens ,re_content ,n_prompt ,n_predicted ,finish_reason ,jinja ,chat_template ): 29global server 30server .jinja = jinja 31server .chat_template = chat_template 32server .start () 33res = server .make_request ("POST" ,"/chat/completions" ,data = { 34"model" :model , 35"max_tokens" :max_tokens , 36"messages" : [ 37 {"role" :"system" ,"content" :system_prompt }, 38 {"role" :"user" ,"content" :user_prompt }, 39 ], 40 }) 41assert res .status_code == 200 42assert "cmpl" in res .body ["id" ]# make sure the completion id has the expected format 43assert res .body ["system_fingerprint" ].startswith ("b" ) 44# we no longer reflect back the model name, see https://github.com/ggml-org/llama.cpp/pull/17668 45# assert res.body["model"] == model if model is not None else server.model_alias 46assert res .body ["usage" ]["prompt_tokens" ]== n_prompt 47assert res .body ["usage" ]["completion_tokens" ]== n_predicted 48choice = res .body ["choices" ][0 ] 49assert "assistant" == choice ["message" ]["role" ] 50assert match_regex (re_content ,choice ["message" ]["content" ]),f'Expected { re_content } , got { choice [ "message" ][ "content" ] } ' 51assert choice ["finish_reason" ]== finish_reason 52 53 54def test_chat_completion_cached_tokens (): 55global server 56server .n_slots = 1 57server .start () 58seq = [ 59 ("1 2 3 4 5 6" ,77 ,0 ), 60 ("1 2 3 4 5 6" ,77 ,76 ), 61 ("1 2 3 4 5 9" ,77 ,51 ), 62 ("1 2 3 9 9 9" ,77 ,47 ), 63 ] 64for user_prompt ,n_prompt ,n_cache in seq : 65res = server .make_request ("POST" ,"/chat/completions" ,data = { 66"max_tokens" :8 , 67"messages" : [ 68 {"role" :"system" ,"content" :"Test" }, 69 {"role" :"user" ,"content" :user_prompt }, 70 ], 71 }) 72assert res .body ["usage" ]["prompt_tokens" ]== n_prompt 73assert res .body ["usage" ]["prompt_tokens_details" ]["cached_tokens" ]== n_cache 74 75@ pytest . mark . parametrize ( 76"system_prompt,user_prompt,max_tokens,re_content,n_prompt,n_predicted,finish_reason" , 77[ 78( "Book" , "What is the best book" , 8 , "(Suddenly)+" , 77 , 8 , "length" ), 79( "You are a coding assistant." , "Write the fibonacci function in c++." , 128 , "(Aside|she|felter|alonger)+" , 104 , 128 , "length" ), 80] 81) 82def test_chat_completion_stream (system_prompt ,user_prompt ,max_tokens ,re_content ,n_prompt ,n_predicted ,finish_reason ): 83global server 84server .model_alias = "llama-test-model" 85server .start () 86res = server .make_stream_request ("POST" ,"/chat/completions" ,data = { 87"max_tokens" :max_tokens , 88"messages" : [ 89 {"role" :"system" ,"content" :system_prompt }, 90 {"role" :"user" ,"content" :user_prompt }, 91 ], 92"stream" :True , 93 }) 94content = "" 95last_cmpl_id = None 96for i ,data in enumerate (res ): 97if data ["choices" ]: 98choice = data ["choices" ][0 ] 99if i == 0 : 100# Check first role message for stream=True 101assert choice ["delta" ]["content" ]is None 102assert choice ["delta" ]["role" ]== "assistant" 103else : 104assert "role" not in choice ["delta" ] 105assert data ["system_fingerprint" ].startswith ("b" ) 106assert data ["model" ]== "llama-test-model" 107if last_cmpl_id is None : 108last_cmpl_id = data ["id" ] 109assert last_cmpl_id == data ["id" ]# make sure the completion id is the same for all events in the stream 110if choice ["finish_reason" ]in ["stop" ,"length" ]: 111assert "content" not in choice ["delta" ] 112assert match_regex (re_content ,content ) 113assert choice ["finish_reason" ]== finish_reason 114else : 115assert choice ["finish_reason" ]is None 116content += choice ["delta" ]["content" ]or '' 117else : 118assert data ["usage" ]["prompt_tokens" ]== n_prompt 119assert data ["usage" ]["completion_tokens" ]== n_predicted 120 121 122def test_chat_completion_with_openai_library (): 123global server 124server .start () 125client = OpenAI (api_key = "dummy" ,base_url = f"http:// { server . server_host } : { server . server_port } /v1" ) 126res = client .chat .completions .create ( 127model = "gpt-3.5-turbo-instruct" , 128messages = [ 129 {"role" :"system" ,"content" :"Book" }, 130 {"role" :"user" ,"content" :"What is the best book" }, 131 ], 132max_tokens = 8 , 133seed = 42 , 134temperature = 0.8 , 135 ) 136assert res .system_fingerprint is not None and res .system_fingerprint .startswith ("b" ) 137assert res .choices [0 ].finish_reason == "length" 138assert res .choices [0 ].message .content is not None 139assert match_regex ("(Suddenly)+" ,res .choices [0 ].message .content ) 140 141 142def test_chat_template (): 143global server 144server .chat_template = "llama3" 145server .debug = True # to get the "__verbose" object in the response 146server .start () 147res = server .make_request ("POST" ,"/chat/completions" ,data = { 148"max_tokens" :8 , 149"messages" : [ 150 {"role" :"system" ,"content" :"Book" }, 151 {"role" :"user" ,"content" :"What is the best book" }, 152 ] 153 }) 154assert res .status_code == 200 155assert "__verbose" in res .body 156assert res .body ["__verbose" ]["prompt" ]== "<s> <|start_header_id|>system<|end_header_id|>\n\nBook<|eot_id|><|start_header_id|>user<|end_header_id|>\n\nWhat is the best book<|eot_id|><|start_header_id|>assistant<|end_header_id|>\n\n" 157 158 159@ pytest . mark . parametrize ( "prefill,re_prefill" , [ 160( "Whill" , "Whill" ), 161([{ "type" : "text" , "text" : "Wh" }, { "type" : "text" , "text" : "ill" }], "Wh\n\nill" ), 162]) 163def test_chat_template_assistant_prefill (prefill ,re_prefill ): 164global server 165server .jinja = True 166server .chat_template_file = "../../../models/templates/meta-llama-Llama-3.1-8B-Instruct.jinja" 167server .debug = True # to get the "__verbose" object in the response 168server .start () 169res = server .make_request ("POST" ,"/chat/completions" ,data = { 170"max_tokens" :8 , 171"messages" : [ 172 {"role" :"system" ,"content" :"Book" }, 173 {"role" :"user" ,"content" :"What is the best book" }, 174 {"role" :"assistant" ,"content" :prefill }, 175 ] 176 }) 177assert res .status_code == 200 178assert "__verbose" in res .body 179assert res .body ["__verbose" ]["prompt" ].endswith (f"<|start_header_id|>user<|end_header_id|>\n\nWhat is the best book<|eot_id|><|start_header_id|>assistant<|end_header_id|>\n\n { re_prefill } " ) 180 181 182def test_chat_template_continue_final_message_vllm_compat (): 183"""continue_final_message is the vLLM/transformers explicit alias for the prefill_assistant heuristic. 184Both must produce the same prompt.""" 185global server 186server .jinja = True 187server .chat_template_file = "../../../models/templates/meta-llama-Llama-3.1-8B-Instruct.jinja" 188server .debug = True 189server .start () 190res = server .make_request ("POST" ,"/chat/completions" ,data = { 191"max_tokens" :8 , 192"add_generation_prompt" :False , 193"continue_final_message" :True , 194"messages" : [ 195 {"role" :"system" ,"content" :"Book" }, 196 {"role" :"user" ,"content" :"What is the best book" }, 197 {"role" :"assistant" ,"content" :"Whill" }, 198 ] 199 }) 200assert res .status_code == 200 201assert "__verbose" in res .body 202assert res .body ["__verbose" ]["prompt" ].endswith ("<|start_header_id|>user<|end_header_id|>\n\nWhat is the best book<|eot_id|><|start_header_id|>assistant<|end_header_id|>\n\nWhill" ) 203 204 205def test_chat_template_continue_final_message_mutual_exclusion (): 206"""add_generation_prompt and continue_final_message both set to true must be rejected""" 207global server 208server .chat_template = "llama3" 209server .start () 210res = server .make_request ("POST" ,"/chat/completions" ,data = { 211"max_tokens" :8 , 212"add_generation_prompt" :True , 213"continue_final_message" :True , 214"messages" : [ 215 {"role" :"user" ,"content" :"Hi" }, 216 {"role" :"assistant" ,"content" :"Hello" }, 217 ] 218 }) 219assert res .status_code == 400 220 221 222def test_apply_chat_template (): 223global server 224server .chat_template = "command-r" 225server .start () 226res = server .make_request ("POST" ,"/apply-template" ,data = { 227"messages" : [ 228 {"role" :"system" ,"content" :"You are a test." }, 229 {"role" :"user" ,"content" :"Hi there" }, 230 ] 231 }) 232assert res .status_code == 200 233assert "prompt" in res .body 234assert res .body ["prompt" ]== "<|START_OF_TURN_TOKEN|><|SYSTEM_TOKEN|>You are a test.<|END_OF_TURN_TOKEN|><|START_OF_TURN_TOKEN|><|USER_TOKEN|>Hi there<|END_OF_TURN_TOKEN|><|START_OF_TURN_TOKEN|><|CHATBOT_TOKEN|>" 235 236 237@ pytest . mark . parametrize ( "response_format,n_predicted,re_content" , [ 238({ "type" : "json_object" , "schema" : { "const" : "42" }}, 6 , "\"42\"" ), 239({ "type" : "json_object" , "schema" : { "items" : [{ "type" : "integer" }]}}, 10 , "[ -3000 ]" ), 240({ "type" : "json_schema" , "json_schema" : { "schema" : { "const" : "foooooo" }}}, 10 , "\"foooooo\"" ), 241({ "type" : "json_object" }, 10 , "(\\{|John)+" ), 242({ "type" : "sound" }, 0 , None ), 243# invalid response format (expected to fail) 244({ "type" : "json_object" , "schema" : 123 }, 0 , None ), 245({ "type" : "json_object" , "schema" : { "type" : 123 }}, 0 , None ), 246({ "type" : "json_object" , "schema" : { "type" : "hiccup" }}, 0 , None ), 247]) 248def test_completion_with_response_format (response_format :dict ,n_predicted :int ,re_content :str | None ): 249global server 250server .start () 251res = server .make_request ("POST" ,"/chat/completions" ,data = { 252"max_tokens" :n_predicted , 253"messages" : [ 254 {"role" :"system" ,"content" :"You are a coding assistant." }, 255 {"role" :"user" ,"content" :"Write an example" }, 256 ], 257"response_format" :response_format , 258 }) 259if re_content is not None : 260assert res .status_code == 200 261choice = res .body ["choices" ][0 ] 262assert match_regex (re_content ,choice ["message" ]["content" ]) 263else : 264assert res .status_code == 400 265assert "error" in res .body 266 267 268@ pytest . mark . parametrize ( "jinja,json_schema,n_predicted,re_content" , [ 269( False , { "const" : "42" }, 6 , "\"42\"" ), 270( True , { "const" : "42" }, 6 , "\"42\"" ), 271]) 272def test_completion_with_json_schema (jinja :bool ,json_schema :dict ,n_predicted :int ,re_content :str ): 273global server 274server .jinja = jinja 275server .debug = True 276server .start () 277res = server .make_request ("POST" ,"/chat/completions" ,data = { 278"max_tokens" :n_predicted , 279"messages" : [ 280 {"role" :"system" ,"content" :"You are a coding assistant." }, 281 {"role" :"user" ,"content" :"Write an example" }, 282 ], 283"json_schema" :json_schema , 284 }) 285assert res .status_code == 200 ,f'Expected 200, got { res . status_code } ' 286choice = res .body ["choices" ][0 ] 287assert match_regex (re_content ,choice ["message" ]["content" ]),f'Expected { re_content } , got { choice [ "message" ][ "content" ] } ' 288 289 290@ pytest . mark . parametrize ( "jinja,grammar,n_predicted,re_content" , [ 291( False , 'root ::= "a"{5,5}' , 6 , "a{5,5}" ), 292( True , 'root ::= "a"{5,5}' , 6 , "a{5,5}" ), 293]) 294def test_completion_with_grammar (jinja :bool ,grammar :str ,n_predicted :int ,re_content :str ): 295global server 296server .jinja = jinja 297server .start () 298res = server .make_request ("POST" ,"/chat/completions" ,data = { 299"max_tokens" :n_predicted , 300"messages" : [ 301 {"role" :"user" ,"content" :"Does not matter what I say, does it?" }, 302 ], 303"grammar" :grammar , 304 }) 305assert res .status_code == 200 ,res .body 306choice = res .body ["choices" ][0 ] 307assert match_regex (re_content ,choice ["message" ]["content" ]),choice ["message" ]["content" ] 308 309 310def test_completion_with_invalid_grammar (): 311global server 312server .start () 313res = server .make_request ("POST" ,"/chat/completions" ,data = { 314"max_tokens" :8 , 315"messages" : [ 316 {"role" :"user" ,"content" :"Does not matter what I say, does it?" }, 317 ], 318"grammar" :"root ::= this is (not valid GBNF" , 319 }) 320assert res .status_code == 400 ,res .body 321assert "error" in res .body 322 323 324@ pytest . mark . parametrize ( "messages" , [ 325None , 326"string" , 327[ 123 ], 328[{}], 329[{ "role" : 123 }], 330[{ "role" : "system" , "content" : 123 }], 331# [{"content": "hello"}], # TODO: should not be a valid case 332[{ "role" : "system" , "content" : "test" }, {}], 333[{ "role" : "user" , "content" : "test" }, { "role" : "assistant" , "content" : "test" }, { "role" : "assistant" , "content" : "test" }], 334]) 335def test_invalid_chat_completion_req (messages ): 336global server 337server .start () 338res = server .make_request ("POST" ,"/chat/completions" ,data = { 339"messages" :messages , 340 }) 341assert res .status_code == 400 or res .status_code == 500 342assert "error" in res .body 343 344 345def test_chat_completion_with_timings_per_token (): 346global server 347server .start () 348res = server .make_stream_request ("POST" ,"/chat/completions" ,data = { 349"max_tokens" :10 , 350"messages" : [{"role" :"user" ,"content" :"test" }], 351"stream" :True , 352"stream_options" : {"include_usage" :True }, 353"timings_per_token" :True , 354 }) 355stats_received = False 356for i ,data in enumerate (res ): 357if i == 0 : 358# Check first role message for stream=True 359assert data ["choices" ][0 ]["delta" ]["content" ]is None 360assert data ["choices" ][0 ]["delta" ]["role" ]== "assistant" 361assert "timings" not in data ,f'First event should not have timings: { data } ' 362else : 363if data ["choices" ]: 364assert "role" not in data ["choices" ][0 ]["delta" ] 365else : 366assert "timings" in data 367assert "prompt_per_second" in data ["timings" ] 368assert "predicted_per_second" in data ["timings" ] 369assert "predicted_n" in data ["timings" ] 370assert data ["timings" ]["predicted_n" ]<= 10 371stats_received = True 372assert stats_received 373 374 375def test_logprobs (): 376global server 377server .start () 378client = OpenAI (api_key = "dummy" ,base_url = f"http:// { server . server_host } : { server . server_port } /v1" ) 379res = client .chat .completions .create ( 380model = "gpt-3.5-turbo-instruct" , 381temperature = 0.0 , 382messages = [ 383 {"role" :"system" ,"content" :"Book" }, 384 {"role" :"user" ,"content" :"What is the best book" }, 385 ], 386max_tokens = 5 , 387logprobs = True , 388top_logprobs = 10 , 389 ) 390output_text = res .choices [0 ].message .content 391aggregated_text = '' 392assert res .choices [0 ].logprobs is not None 393assert res .choices [0 ].logprobs .content is not None 394for token in res .choices [0 ].logprobs .content : 395aggregated_text += token .token 396assert token .logprob <= 0.0 397assert token .bytes is not None 398assert len (token .top_logprobs )> 0 399assert aggregated_text == output_text 400 401 402def test_logprobs_stream (): 403global server 404server .start () 405client = OpenAI (api_key = "dummy" ,base_url = f"http:// { server . server_host } : { server . server_port } /v1" ) 406res = client .chat .completions .create ( 407model = "gpt-3.5-turbo-instruct" , 408temperature = 0.0 , 409messages = [ 410 {"role" :"system" ,"content" :"Book" }, 411 {"role" :"user" ,"content" :"What is the best book" }, 412 ], 413max_tokens = 5 , 414logprobs = True , 415top_logprobs = 10 , 416stream = True , 417 ) 418output_text = '' 419aggregated_text = '' 420for i ,data in enumerate (res ): 421if data .choices : 422choice = data .choices [0 ] 423if i == 0 : 424# Check first role message for stream=True 425assert choice .delta .content is None 426assert choice .delta .role == "assistant" 427else : 428assert choice .delta .role is None 429if choice .finish_reason is None : 430if choice .delta .content : 431output_text += choice .delta .content 432assert choice .logprobs is not None 433assert choice .logprobs .content is not None 434for token in choice .logprobs .content : 435aggregated_text += token .token 436assert token .logprob <= 0.0 437assert token .bytes is not None 438assert token .top_logprobs is not None 439assert len (token .top_logprobs )> 0 440assert aggregated_text == output_text 441 442 443def test_logit_bias (): 444global server 445server .start () 446 447exclude = ["i" ,"I" ,"the" ,"The" ,"to" ,"a" ,"an" ,"be" ,"is" ,"was" ,"but" ,"But" ,"and" ,"And" ,"so" ,"So" ,"you" ,"You" ,"he" ,"He" ,"she" ,"She" ,"we" ,"We" ,"they" ,"They" ,"it" ,"It" ,"his" ,"His" ,"her" ,"Her" ,"book" ,"Book" ] 448 449res = server .make_request ("POST" ,"/tokenize" ,data = { 450"content" :" " + " " .join (exclude )+ " " , 451 }) 452assert res .status_code == 200 453tokens = res .body ["tokens" ] 454logit_bias = {tok :- 100 for tok in tokens } 455 456client = OpenAI (api_key = "dummy" ,base_url = f"http:// { server . server_host } : { server . server_port } /v1" ) 457res = client .chat .completions .create ( 458model = "gpt-3.5-turbo-instruct" , 459temperature = 0.0 , 460messages = [ 461 {"role" :"system" ,"content" :"Book" }, 462 {"role" :"user" ,"content" :"What is the best book" }, 463 ], 464max_tokens = 64 , 465logit_bias = logit_bias 466 ) 467output_text = res .choices [0 ].message .content 468assert output_text 469assert all (output_text .find (" " + tok + " " )== - 1 for tok in exclude ) 470 471def test_context_size_exceeded (): 472global server 473server .start () 474res = server .make_request ("POST" ,"/chat/completions" ,data = { 475"messages" : [ 476 {"role" :"system" ,"content" :"Book" }, 477 {"role" :"user" ,"content" :"What is the best book" }, 478 ]* 100 ,# make the prompt too long 479 }) 480assert res .status_code == 400 481assert "error" in res .body 482assert res .body ["error" ]["type" ]== "exceed_context_size_error" 483assert res .body ["error" ]["n_prompt_tokens" ]> 0 484assert server .n_ctx is not None 485assert server .n_slots is not None 486assert res .body ["error" ]["n_ctx" ]== server .n_ctx // server .n_slots 487 488 489def test_context_size_exceeded_stream (): 490global server 491server .start () 492try : 493for _ in server .make_stream_request ("POST" ,"/chat/completions" ,data = { 494"messages" : [ 495 {"role" :"system" ,"content" :"Book" }, 496 {"role" :"user" ,"content" :"What is the best book" }, 497 ]* 100 ,# make the prompt too long 498"stream" :True }): 499pass 500assert False ,"Should have failed" 501except ServerError as e : 502assert e .code == 400 503assert "error" in e .body 504assert e .body ["error" ]["type" ]== "exceed_context_size_error" 505assert e .body ["error" ]["n_prompt_tokens" ]> 0 506assert server .n_ctx is not None 507assert server .n_slots is not None 508assert e .body ["error" ]["n_ctx" ]== server .n_ctx // server .n_slots 509 510 511@ pytest . mark . parametrize ( 512"n_batch,batch_count,reuse_cache" , 513[ 514( 64 , 4 , False ), 515( 64 , 2 , True ), 516] 517) 518def test_return_progress (n_batch ,batch_count ,reuse_cache ): 519global server 520server .n_batch = n_batch 521server .n_ctx = 256 522server .n_slots = 1 523server .start () 524def make_cmpl_request (): 525return server .make_stream_request ("POST" ,"/chat/completions" ,data = { 526"max_tokens" :10 , 527"messages" : [ 528 {"role" :"user" ,"content" :"This is a test" * 10 }, 529 ], 530"stream" :True , 531"return_progress" :True , 532 }) 533if reuse_cache : 534# make a first request to populate the cache 535res0 = make_cmpl_request () 536for _ in res0 : 537pass # discard the output 538 539res = make_cmpl_request () 540last_progress = None 541total_batch_count = 0 542 543for data in res : 544cur_progress = data .get ("prompt_progress" ,None ) 545if cur_progress is None : 546continue 547if total_batch_count == 0 : 548# first progress report must have n_cache == n_processed 549assert cur_progress ["total" ]> 0 550assert cur_progress ["cache" ]== cur_progress ["processed" ] 551if reuse_cache : 552# when reusing cache, we expect some cached tokens 553assert cur_progress ["cache" ]> 0 554if last_progress is not None : 555assert cur_progress ["total" ]== last_progress ["total" ] 556assert cur_progress ["cache" ]== last_progress ["cache" ] 557assert cur_progress ["processed" ]> last_progress ["processed" ] 558total_batch_count += 1 559last_progress = cur_progress 560 561# last progress should indicate completion (all tokens processed) 562assert last_progress is not None 563assert last_progress ["total" ]> 0 564assert last_progress ["processed" ]== last_progress ["total" ] 565assert total_batch_count == batch_count 566 567 568def test_chat_completions_multiple_choices (): 569global server 570server .start () 571# make sure cache can be reused across multiple choices and multiple requests 572# ref: https://github.com/ggml-org/llama.cpp/pull/18663 573for _ in range (2 ): 574res = server .make_request ("POST" ,"/chat/completions" ,data = { 575"max_tokens" :8 , 576"n" :2 , 577"messages" : [ 578 {"role" :"system" ,"content" :"Book" }, 579 {"role" :"user" ,"content" :"What is the best book" }, 580 ], 581# test forcing the same slot to be used 582# the scheduler should not be locked up in this case 583"id_slot" :0 , 584 }) 585assert res .status_code == 200 586assert len (res .body ["choices" ])== 2 587for choice in res .body ["choices" ]: 588assert "assistant" == choice ["message" ]["role" ] 589assert choice ["finish_reason" ]== "length" 590 591 592def test_chat_completions_token_count (): 593global server 594server .start () 595# make sure cache can be reused across multiple choices and multiple requests 596# ref: https://github.com/ggml-org/llama.cpp/pull/18663 597for _ in range (2 ): 598res = server .make_request ("POST" ,"/chat/completions/input_tokens" ,data = { 599"messages" : [ 600 {"role" :"system" ,"content" :"Book" }, 601 {"role" :"user" ,"content" :"What is the best book" }, 602 ], 603 }) 604assert res .status_code == 200 605assert res .body ["input_tokens" ]> 5 606 607 608def test_verbose_debug (): 609global server 610server .start () 611for verbose in [True ,False ]: 612res = server .make_request ("POST" ,"/chat/completions" ,data = { 613"max_tokens" :2 , 614"messages" : [ 615 {"role" :"system" ,"content" :"Book" }, 616 {"role" :"user" ,"content" :"What is the best book" }, 617 ], 618"verbose" :verbose , 619 }) 620assert res .status_code == 200 621if verbose : 622assert "__verbose" in res .body 623assert "Book" in res .body ["__verbose" ]["prompt" ] 624else : 625assert "__verbose" not in res .body