char-fork/llama.cpp
git clone https://git.t4t.associates/char-fork/llama.cpp
42f0225fe
char/aliceai-t5-moe
1#!/usr/bin/env python 2import pytest 3 4# ensure grandparent path is in sys.path 5from pathlib import Path 6import sys 7path = Path (__file__ ).resolve ().parents [1 ] 8sys .path .insert (0 ,str (path )) 9 10from utils import * 11from enum import Enum 12from typing import TypedDict 13 14server :ServerProcess 15 16TIMEOUT_START_SLOW = 15 * 60 # this is needed for real model tests 17TIMEOUT_HTTP_REQUEST = 60 18 19@ pytest . fixture ( autouse = True ) 20def create_server (): 21global server 22server = ServerPreset .tinyllama2 () 23server .model_alias = "tinyllama-2-tool-call" 24server .n_slots = 1 25server .n_ctx = 8192 26server .n_batch = 2048 27 28class CompletionMode (Enum ): 29NORMAL = "normal" 30STREAMED = "streamed" 31 32class ToolParameters (TypedDict ): 33type :str 34properties :dict [str ,dict ] 35required :list [str ] 36 37class ToolFunction (TypedDict ): 38name :str 39description :str 40parameters :ToolParameters 41 42class ToolDefinition (TypedDict ): 43type :str 44function :ToolFunction 45 46TEST_TOOL = ToolDefinition ( 47type = "function" , 48function = ToolFunction ( 49name = "test" , 50description = "" , 51parameters = ToolParameters ( 52type = "object" , 53properties = { 54"success" : { 55"type" :"boolean" , 56"const" :True , 57 }, 58 }, 59required = ["success" ], 60 ), 61 ), 62) 63 64PYTHON_TOOL = ToolDefinition ( 65type = "function" , 66function = ToolFunction ( 67name = "python" , 68description = "Runs code in an ipython interpreter and returns the result of the execution after 60 seconds." , 69parameters = ToolParameters ( 70type = "object" , 71properties = { 72"code" : { 73"type" :"string" , 74"description" :"The code to run in the ipython interpreter." , 75 }, 76 }, 77required = ["code" ], 78 ), 79 ), 80) 81 82WEATHER_TOOL = ToolDefinition ( 83type = "function" , 84function = ToolFunction ( 85name = "get_current_weather" , 86description = "Get the current weather in a given location" , 87parameters = ToolParameters ( 88type = "object" , 89properties = { 90"location" : { 91"type" :"string" , 92"description" :"The city and country/state, e.g. 'San Francisco, CA', or 'Paris, France'" , 93 }, 94 }, 95required = ["location" ], 96 ), 97 ), 98) 99 100def do_test_completion_with_required_tool_tiny (server :ServerProcess ,tool :dict ,argument_key :str | None ,n_predict ,** kwargs ): 101body = server .make_any_request ("POST" ,"/v1/chat/completions" ,data = { 102"max_tokens" :n_predict , 103"messages" : [ 104 {"role" :"system" ,"content" :"You are a coding assistant." }, 105 {"role" :"user" ,"content" :"Write an example" }, 106 ], 107"tool_choice" :"required" , 108"tools" : [tool ], 109"parallel_tool_calls" :False , 110** kwargs , 111 }) 112# assert res.status_code == 200, f"Expected status code 200, got {res.status_code}" 113choice = body ["choices" ][0 ] 114tool_calls = choice ["message" ].get ("tool_calls" ) 115assert tool_calls and len (tool_calls )== 1 ,f'Expected 1 tool call in { choice [ "message" ] } ' 116tool_call = tool_calls [0 ] 117assert choice ["message" ].get ("content" )in (None ,"" ),f'Expected no content in { choice [ "message" ] } ' 118# assert len(tool_call.get("id", "")) > 0, f'Expected non empty tool call id in {tool_call}' 119expected_function_name = "python" if tool ["type" ]== "code_interpreter" else tool ["function" ]["name" ] 120assert expected_function_name == tool_call ["function" ]["name" ],f'Expected tool name to be { tool_call [ "function" ][ "name" ] } in { choice [ "message" ] } ' 121actual_arguments = tool_call ["function" ]["arguments" ] 122assert isinstance (actual_arguments ,dict )or isinstance (actual_arguments ,str ),f'Expected arguments to be a dict or str, got: { actual_arguments } ' 123if argument_key is not None : 124if (isinstance (actual_arguments ,str )): 125actual_arguments = json .loads (actual_arguments ) 126assert argument_key in actual_arguments ,f"tool arguments: { actual_arguments } , expected: { argument_key } " 127 128# PR #22654: commented out since we're now allowing content before tool calls in tool_call: required, so we can't force this 129# in the tiny model just by using the grammar 130# 131# @pytest.mark.parametrize("stream", [CompletionMode.NORMAL, CompletionMode.STREAMED]) 132# @pytest.mark.parametrize("template_name,tool,argument_key", [ 133# ("Qwen3-Coder", TEST_TOOL, "success"), 134# ("Qwen3-Coder", TEST_TOOL, "success"), 135# ("meta-llama-Llama-3.3-70B-Instruct", TEST_TOOL, "success"), 136# ("meta-llama-Llama-3.3-70B-Instruct", TEST_TOOL, "success"), 137# ("meta-llama-Llama-3.3-70B-Instruct", PYTHON_TOOL, "code"), 138# ("meta-llama-Llama-3.3-70B-Instruct", PYTHON_TOOL, "code"), 139# ]) 140# def test_completion_with_required_tool_tiny_fast(template_name: str, tool: dict, argument_key: str | None, stream: CompletionMode): 141# global server 142# n_predict = 1024 143# # server = ServerPreset.stories15m_moe() 144# server.jinja = True 145# server.n_predict = n_predict 146# server.chat_template_file = f'../../../models/templates/{template_name}.jinja' 147# server.start() 148# do_test_completion_with_required_tool_tiny(server, tool, argument_key, n_predict, stream=stream == CompletionMode.STREAMED, temperature=0.0, top_k=1, top_p=1.0) 149 150# @pytest.mark.slow 151# @pytest.mark.parametrize("stream", [CompletionMode.NORMAL, CompletionMode.STREAMED]) 152# @pytest.mark.parametrize("template_name,tool,argument_key", [ 153# ("meta-llama-Llama-3.1-8B-Instruct", TEST_TOOL, "success"), 154# ("meta-llama-Llama-3.1-8B-Instruct", PYTHON_TOOL, "code"), 155 156# ("meetkai-functionary-medium-v3.1", TEST_TOOL, "success"), 157# ("meetkai-functionary-medium-v3.1", PYTHON_TOOL, "code"), 158 159# ("meetkai-functionary-medium-v3.2", TEST_TOOL, "success"), 160# # Functionary v3.2 format supports raw python content, which w/ a dummy stories model will never end on its own. 161# # ("meetkai-functionary-medium-v3.2", PYTHON_TOOL, "code"), 162 163# ("NousResearch-Hermes-2-Pro-Llama-3-8B-tool_use", TEST_TOOL, "success"), 164# ("NousResearch-Hermes-2-Pro-Llama-3-8B-tool_use", PYTHON_TOOL, "code"), 165 166# ("meta-llama-Llama-3.2-3B-Instruct", TEST_TOOL, "success"), 167# ("meta-llama-Llama-3.2-3B-Instruct", PYTHON_TOOL, "code"), 168 169# ("mistralai-Mistral-Nemo-Instruct-2407", TEST_TOOL, "success"), 170# ("mistralai-Mistral-Nemo-Instruct-2407", PYTHON_TOOL, "code"), 171 172# ("NousResearch-Hermes-3-Llama-3.1-8B-tool_use", TEST_TOOL, "success"), 173# ("NousResearch-Hermes-3-Llama-3.1-8B-tool_use", PYTHON_TOOL, "code"), 174 175# ("deepseek-ai-DeepSeek-R1-Distill-Llama-8B", TEST_TOOL, "success"), 176# ("deepseek-ai-DeepSeek-R1-Distill-Llama-8B", PYTHON_TOOL, "code"), 177 178# ("fireworks-ai-llama-3-firefunction-v2", TEST_TOOL, "success"), 179# # ("fireworks-ai-llama-3-firefunction-v2", PYTHON_TOOL, "codeFalse), True), 180# # ("fireworks-ai-llama-3-firefunction-v2", PYTHON_TOOL, "code"), 181 182# ]) 183# def test_completion_with_required_tool_tiny_slow(template_name: str, tool: dict, argument_key: str | None, stream: CompletionMode): 184# global server 185# n_predict = 512 186# # server = ServerPreset.stories15m_moe() 187# server.jinja = True 188# server.n_predict = n_predict 189# server.chat_template_file = f'../../../models/templates/{template_name}.jinja' 190# server.start(timeout_seconds=TIMEOUT_START_SLOW) 191# do_test_completion_with_required_tool_tiny(server, tool, argument_key, n_predict, stream=stream == CompletionMode.STREAMED) 192 193 194@ pytest . mark . slow 195@ pytest . mark . parametrize ( "stream" , [ CompletionMode . NORMAL , CompletionMode . STREAMED ]) 196@ pytest . mark . parametrize ( "tool,argument_key,hf_repo,template_override" , [ 197( TEST_TOOL , "success" , "bartowski/Meta-Llama-3.1-8B-Instruct-GGUF:Q4_K_M" , None ), 198( PYTHON_TOOL , "code" , "bartowski/Meta-Llama-3.1-8B-Instruct-GGUF:Q4_K_M" , None ), 199( PYTHON_TOOL , "code" , "bartowski/Meta-Llama-3.1-8B-Instruct-GGUF:Q4_K_M" , "chatml" ), 200 201( TEST_TOOL , "success" , "bartowski/gemma-2-2b-it-GGUF:Q4_K_M" , None ), 202( PYTHON_TOOL , "code" , "bartowski/gemma-2-2b-it-GGUF:Q4_K_M" , None ), 203( PYTHON_TOOL , "code" , "bartowski/gemma-2-2b-it-GGUF:Q4_K_M" , "chatml" ), 204 205( TEST_TOOL , "success" , "bartowski/Phi-3.5-mini-instruct-GGUF:Q4_K_M" , None ), 206( PYTHON_TOOL , "code" , "bartowski/Phi-3.5-mini-instruct-GGUF:Q4_K_M" , None ), 207( PYTHON_TOOL , "code" , "bartowski/Phi-3.5-mini-instruct-GGUF:Q4_K_M" , "chatml" ), 208 209( TEST_TOOL , "success" , "bartowski/Qwen2.5-1.5B-Instruct-GGUF:Q4_K_M" , None ), 210( PYTHON_TOOL , "code" , "bartowski/Qwen2.5-1.5B-Instruct-GGUF:Q4_K_M" , None ), 211( PYTHON_TOOL , "code" , "bartowski/Qwen2.5-1.5B-Instruct-GGUF:Q4_K_M" , "chatml" ), 212 213( TEST_TOOL , "success" , "bartowski/Qwen2.5-Coder-3B-Instruct-GGUF:Q4_K_M" , None ), 214( PYTHON_TOOL , "code" , "bartowski/Qwen2.5-Coder-3B-Instruct-GGUF:Q4_K_M" , None ), 215( PYTHON_TOOL , "code" , "bartowski/Qwen2.5-Coder-3B-Instruct-GGUF:Q4_K_M" , "chatml" ), 216 217( TEST_TOOL , "success" , "bartowski/Qwen2.5-7B-Instruct-GGUF:Q4_K_M" , None ), 218( PYTHON_TOOL , "code" , "bartowski/Qwen2.5-7B-Instruct-GGUF:Q4_K_M" , None ), 219( PYTHON_TOOL , "code" , "bartowski/Qwen2.5-7B-Instruct-GGUF:Q4_K_M" , "chatml" ), 220 221( TEST_TOOL , "success" , "bartowski/Hermes-2-Pro-Llama-3-8B-GGUF:Q4_K_M" , ( "NousResearch/Hermes-2-Pro-Llama-3-8B" , "tool_use" )), 222( PYTHON_TOOL , "code" , "bartowski/Hermes-2-Pro-Llama-3-8B-GGUF:Q4_K_M" , ( "NousResearch/Hermes-2-Pro-Llama-3-8B" , "tool_use" )), 223( PYTHON_TOOL , "code" , "bartowski/Hermes-2-Pro-Llama-3-8B-GGUF:Q4_K_M" , "chatml" ), 224 225( TEST_TOOL , "success" , "bartowski/Hermes-3-Llama-3.1-8B-GGUF:Q4_K_M" , ( "NousResearch/Hermes-3-Llama-3.1-8B" , "tool_use" )), 226( PYTHON_TOOL , "code" , "bartowski/Hermes-3-Llama-3.1-8B-GGUF:Q4_K_M" , ( "NousResearch/Hermes-3-Llama-3.1-8B" , "tool_use" )), 227( PYTHON_TOOL , "code" , "bartowski/Hermes-3-Llama-3.1-8B-GGUF:Q4_K_M" , "chatml" ), 228 229# (TEST_TOOL, "success", "bartowski/Mistral-Nemo-Instruct-2407-GGUF:Q4_K_M", None), 230# (PYTHON_TOOL, "code", "bartowski/Mistral-Nemo-Instruct-2407-GGUF:Q4_K_M", None), 231# (PYTHON_TOOL, "code", "bartowski/Mistral-Nemo-Instruct-2407-GGUF:Q4_K_M", "chatml"), 232 233( TEST_TOOL , "success" , "bartowski/functionary-small-v3.2-GGUF:Q4_K_M" , ( "meetkai/functionary-medium-v3.2" , None )), 234( PYTHON_TOOL , "code" , "bartowski/functionary-small-v3.2-GGUF:Q4_K_M" , ( "meetkai/functionary-medium-v3.2" , None )), 235( PYTHON_TOOL , "code" , "bartowski/functionary-small-v3.2-GGUF:Q4_K_M" , "chatml" ), 236 237( TEST_TOOL , "success" , "bartowski/Llama-3.2-3B-Instruct-GGUF:Q4_K_M" , ( "meta-llama/Llama-3.2-3B-Instruct" , None )), 238( PYTHON_TOOL , "code" , "bartowski/Llama-3.2-3B-Instruct-GGUF:Q4_K_M" , ( "meta-llama/Llama-3.2-3B-Instruct" , None )), 239( PYTHON_TOOL , "code" , "bartowski/Llama-3.2-3B-Instruct-GGUF:Q4_K_M" , "chatml" ), 240 241( TEST_TOOL , "success" , "bartowski/Llama-3.2-1B-Instruct-GGUF:Q4_K_M" , ( "meta-llama/Llama-3.2-3B-Instruct" , None )), 242( PYTHON_TOOL , "code" , "bartowski/Llama-3.2-1B-Instruct-GGUF:Q4_K_M" , ( "meta-llama/Llama-3.2-3B-Instruct" , None )), 243( PYTHON_TOOL , "code" , "bartowski/Llama-3.2-1B-Instruct-GGUF:Q4_K_M" , "chatml" ), 244 245( TEST_TOOL , "success" , "bartowski/DeepSeek-R1-Distill-Qwen-7B-GGUF:Q4_K_M" , None ), 246( PYTHON_TOOL , "code" , "bartowski/DeepSeek-R1-Distill-Qwen-7B-GGUF:Q4_K_M" , None ), 247]) 248def test_completion_with_required_tool_real_model (tool :dict ,argument_key :str | None ,hf_repo :str ,template_override :str | Tuple [str ,str | None ]| None ,stream :CompletionMode ): 249global server 250n_predict = 512 251server .jinja = True 252server .n_ctx = 8192 253server .n_predict = n_predict 254server .model_hf_repo = hf_repo 255server .model_hf_file = None 256if isinstance (template_override ,tuple ): 257 (template_hf_repo ,template_variant )= template_override 258server .chat_template_file = f"../../../models/templates/ { template_hf_repo . replace ( '/' , '-' ) + ( '-' + template_variant if template_variant else '' ) } .jinja" 259assert os .path .exists (server .chat_template_file ),f"Template file { server . chat_template_file } does not exist. Run `python scripts/get_chat_template.py { template_hf_repo } { template_variant } > { server . chat_template_file } ` to download the template." 260elif isinstance (template_override ,str ): 261server .chat_template = template_override 262server .start (timeout_seconds = TIMEOUT_START_SLOW ) 263body = server .make_any_request ("POST" ,"/v1/chat/completions" ,data = { 264"max_tokens" :n_predict , 265"messages" : [ 266 {"role" :"system" ,"content" :"You are a coding assistant." }, 267 {"role" :"user" ,"content" :"Write an example" }, 268 ], 269"tool_choice" :"required" , 270"tools" : [tool ], 271"parallel_tool_calls" :False , 272"stream" :stream == CompletionMode .STREAMED , 273"temperature" :0.0 , 274"top_k" :1 , 275"top_p" :1.0 , 276 },timeout = TIMEOUT_HTTP_REQUEST ) 277choice = body ["choices" ][0 ] 278tool_calls = choice ["message" ].get ("tool_calls" ) 279assert tool_calls and len (tool_calls )== 1 ,f'Expected 1 tool call in { choice [ "message" ] } ' 280tool_call = tool_calls [0 ] 281# assert choice["message"].get("content") in (None, ""), f'Expected no content in {choice["message"]}' 282expected_function_name = "python" if tool ["type" ]== "code_interpreter" else tool ["function" ]["name" ] 283assert expected_function_name == tool_call ["function" ]["name" ] 284actual_arguments = tool_call ["function" ]["arguments" ] 285assert isinstance (actual_arguments ,str ) 286if argument_key is not None : 287actual_arguments = json .loads (actual_arguments ) 288assert argument_key in actual_arguments ,f"tool arguments: { json . dumps ( actual_arguments ) } , expected: { argument_key } " 289 290 291def do_test_completion_without_tool_call (server :ServerProcess ,n_predict :int ,tools :list [dict ],tool_choice :str | None ,** kwargs ): 292body = server .make_any_request ("POST" ,"/v1/chat/completions" ,data = { 293"max_tokens" :n_predict , 294"messages" : [ 295 {"role" :"system" ,"content" :"You are a coding assistant." }, 296 {"role" :"user" ,"content" :"say hello world with python" }, 297 ], 298"tools" :tools if tools else None , 299"tool_choice" :tool_choice , 300** kwargs , 301 },timeout = TIMEOUT_HTTP_REQUEST ) 302choice = body ["choices" ][0 ] 303assert choice ["message" ].get ("tool_calls" )is None ,f'Expected no tool call in { choice [ "message" ] } ' 304 305 306@ pytest . mark . parametrize ( "stream" , [ CompletionMode . NORMAL , CompletionMode . STREAMED ]) 307@ pytest . mark . parametrize ( "template_name,n_predict,tools,tool_choice" , [ 308( "meta-llama-Llama-3.3-70B-Instruct" , 128 , [], None ), 309( "meta-llama-Llama-3.3-70B-Instruct" , 128 , [ TEST_TOOL ], None ), 310( "meta-llama-Llama-3.3-70B-Instruct" , 128 , [ PYTHON_TOOL ], 'none' ), 311]) 312def test_completion_without_tool_call_fast (template_name :str ,n_predict :int ,tools :list [dict ],tool_choice :str | None ,stream :CompletionMode ): 313global server 314server .n_predict = n_predict 315server .jinja = True 316server .chat_template_file = f'../../../models/templates/ { template_name } .jinja' 317server .start () 318do_test_completion_without_tool_call (server ,n_predict ,tools ,tool_choice ,stream = stream == CompletionMode .STREAMED ) 319 320 321@ pytest . mark . slow 322@ pytest . mark . parametrize ( "stream" , [ CompletionMode . NORMAL , CompletionMode . STREAMED ]) 323@ pytest . mark . parametrize ( "template_name,n_predict,tools,tool_choice" , [ 324( "meetkai-functionary-medium-v3.2" , 256 , [], None ), 325( "meetkai-functionary-medium-v3.2" , 256 , [ TEST_TOOL ], None ), 326( "meetkai-functionary-medium-v3.2" , 256 , [ PYTHON_TOOL ], 'none' ), 327( "meetkai-functionary-medium-v3.1" , 256 , [], None ), 328( "meetkai-functionary-medium-v3.1" , 256 , [ TEST_TOOL ], None ), 329( "meetkai-functionary-medium-v3.1" , 256 , [ PYTHON_TOOL ], 'none' ), 330( "meta-llama-Llama-3.2-3B-Instruct" , 256 , [], None ), 331( "meta-llama-Llama-3.2-3B-Instruct" , 256 , [ TEST_TOOL ], None ), 332( "meta-llama-Llama-3.2-3B-Instruct" , 256 , [ PYTHON_TOOL ], 'none' ), 333]) 334def test_completion_without_tool_call_slow (template_name :str ,n_predict :int ,tools :list [dict ],tool_choice :str | None ,stream :CompletionMode ): 335global server 336server .n_predict = n_predict 337server .jinja = True 338server .chat_template_file = f'../../../models/templates/ { template_name } .jinja' 339server .start (timeout_seconds = TIMEOUT_START_SLOW ) 340do_test_completion_without_tool_call (server ,n_predict ,tools ,tool_choice ,stream = stream == CompletionMode .STREAMED ) 341 342 343@ pytest . mark . slow 344@ pytest . mark . parametrize ( "stream" , [ CompletionMode . NORMAL , CompletionMode . STREAMED ]) 345@ pytest . mark . parametrize ( "hf_repo,template_override" , [ 346( "bartowski/Meta-Llama-3.1-8B-Instruct-GGUF:Q4_K_M" , None ), 347( "bartowski/Meta-Llama-3.1-8B-Instruct-GGUF:Q4_K_M" , "chatml" ), 348 349( "bartowski/Phi-3.5-mini-instruct-GGUF:Q4_K_M" , None ), 350( "bartowski/Phi-3.5-mini-instruct-GGUF:Q4_K_M" , "chatml" ), 351 352( "bartowski/Qwen2.5-1.5B-Instruct-GGUF:Q4_K_M" , None ), 353( "bartowski/Qwen2.5-1.5B-Instruct-GGUF:Q4_K_M" , "chatml" ), 354 355( "bartowski/Qwen2.5-Coder-3B-Instruct-GGUF:Q4_K_M" , None ), 356( "bartowski/Qwen2.5-Coder-3B-Instruct-GGUF:Q4_K_M" , "chatml" ), 357 358( "bartowski/Qwen2.5-7B-Instruct-GGUF:Q4_K_M" , None ), 359( "bartowski/Qwen2.5-7B-Instruct-GGUF:Q4_K_M" , "chatml" ), 360 361( "bartowski/Hermes-2-Pro-Llama-3-8B-GGUF:Q4_K_M" , ( "NousResearch/Hermes-2-Pro-Llama-3-8B" , "tool_use" )), 362( "bartowski/Hermes-2-Pro-Llama-3-8B-GGUF:Q4_K_M" , "chatml" ), 363 364( "bartowski/Hermes-3-Llama-3.1-8B-GGUF:Q4_K_M" , ( "NousResearch/Hermes-3-Llama-3.1-8B" , "tool_use" )), 365( "bartowski/Hermes-3-Llama-3.1-8B-GGUF:Q4_K_M" , "chatml" ), 366 367# ("bartowski/Mistral-Nemo-Instruct-2407-GGUF:Q4_K_M", None), 368# ("bartowski/Mistral-Nemo-Instruct-2407-GGUF:Q4_K_M", "chatml"), 369 370# ("bartowski/functionary-small-v3.2-GGUF:Q8_0", ("meetkai/functionary-medium-v3.2", None)), 371# ("bartowski/functionary-small-v3.2-GGUF:Q8_0", "chatml"), 372 373( "bartowski/Llama-3.2-3B-Instruct-GGUF:Q4_K_M" , ( "meta-llama/Llama-3.2-3B-Instruct" , None )), 374( "bartowski/Llama-3.2-3B-Instruct-GGUF:Q4_K_M" , "chatml" ), 375 376( "bartowski/c4ai-command-r7b-12-2024-GGUF:Q6_K_L" , ( "CohereForAI/c4ai-command-r7b-12-2024" , "tool_use" )), 377 378( "bartowski/DeepSeek-R1-Distill-Qwen-7B-GGUF:Q4_K_M" , None ), 379 380# Note: gemma-2-2b-it knows itself as "model", not "assistant", so we don't test the ill-suited chatml on it. 381( "bartowski/gemma-2-2b-it-GGUF:Q4_K_M" , None ), 382 383# ("bartowski/Llama-3.2-1B-Instruct-GGUF:Q4_K_M", ("meta-llama/Llama-3.2-3B-Instruct", None)), 384]) 385def test_weather (hf_repo :str ,template_override :str | Tuple [str ,str | None ]| None ,stream :CompletionMode ): 386global server 387n_predict = 512 388server .jinja = True 389server .n_ctx = 8192 390server .n_predict = n_predict 391server .model_hf_repo = hf_repo 392server .model_hf_file = None 393if isinstance (template_override ,tuple ): 394 (template_hf_repo ,template_variant )= template_override 395server .chat_template_file = f"../../../models/templates/ { template_hf_repo . replace ( '/' , '-' ) + ( '-' + template_variant if template_variant else '' ) } .jinja" 396assert os .path .exists (server .chat_template_file ),f"Template file { server . chat_template_file } does not exist. Run `python scripts/get_chat_template.py { template_hf_repo } { template_variant } > { server . chat_template_file } ` to download the template." 397elif isinstance (template_override ,str ): 398server .chat_template = template_override 399server .start () 400do_test_weather (server ,stream = stream == CompletionMode .STREAMED ,max_tokens = n_predict ) 401 402 403def do_test_weather (server :ServerProcess ,** kwargs ): 404body = server .make_any_request ("POST" ,"/v1/chat/completions" ,data = { 405"messages" : [ 406 {"role" :"system" ,"content" :"You are a chatbot that uses tools/functions. Dont overthink things." }, 407 {"role" :"user" ,"content" :"What is the weather in Istanbul?" }, 408 ], 409"tools" : [WEATHER_TOOL ], 410** kwargs , 411 },timeout = TIMEOUT_HTTP_REQUEST ) 412choice = body ["choices" ][0 ] 413tool_calls = choice ["message" ].get ("tool_calls" ) 414assert tool_calls and len (tool_calls )== 1 ,f'Expected 1 tool call in { choice [ "message" ] } ' 415tool_call = tool_calls [0 ] 416# assert choice["message"].get("content") in (None, ""), f'Expected no content in {choice["message"]}' 417assert tool_call ["function" ]["name" ]== WEATHER_TOOL ["function" ]["name" ],f'Expected weather tool call, got { tool_call [ "function" ][ "name" ] } ' 418# assert len(tool_call.get("id", "")) > 0, f'Expected non empty tool call id in {tool_call}' 419actual_arguments = json .loads (tool_call ["function" ]["arguments" ]) 420assert 'location' in actual_arguments ,f"location not found in { json . dumps ( actual_arguments ) } " 421location = actual_arguments ["location" ] 422assert isinstance (location ,str ),f"Expected location to be a string, got { type ( location ) } : { json . dumps ( location ) } " 423assert re .match ('^Istanbul(( |, ?)(TR|Turkey|Türkiye))?$' ,location ),f'Expected Istanbul for location, got { location } ' 424 425 426@ pytest . mark . slow 427@ pytest . mark . parametrize ( "stream" , [ CompletionMode . NORMAL , CompletionMode . STREAMED ]) 428@ pytest . mark . parametrize ( "result_override,n_predict,hf_repo,template_override" , [ 429( None , 128 , "bartowski/Phi-3.5-mini-instruct-GGUF:Q4_K_M" , "chatml" ), 430( None , 128 , "bartowski/Qwen2.5-Coder-3B-Instruct-GGUF:Q4_K_M" , None ), 431( None , 128 , "bartowski/Qwen2.5-Coder-3B-Instruct-GGUF:Q4_K_M" , "chatml" ), 432( None , 128 , "bartowski/Qwen2.5-7B-Instruct-GGUF:Q4_K_M" , "chatml" ), 433( None , 128 , "bartowski/Hermes-2-Pro-Llama-3-8B-GGUF:Q4_K_M" , ( "NousResearch/Hermes-2-Pro-Llama-3-8B" , "tool_use" )), 434( None , 128 , "bartowski/Hermes-3-Llama-3.1-8B-GGUF:Q4_K_M" , ( "NousResearch/Hermes-3-Llama-3.1-8B" , "tool_use" )), 435( None , 128 , "bartowski/functionary-small-v3.2-GGUF:Q8_0" , ( "meetkai/functionary-medium-v3.2" , None )), 436( None , 128 , "bartowski/Mistral-Nemo-Instruct-2407-GGUF:Q4_K_M" , None ), 437( None , 128 , "bartowski/Mistral-Nemo-Instruct-2407-GGUF:Q4_K_M" , "chatml" ), 438( None , 128 , "bartowski/Phi-3.5-mini-instruct-GGUF:Q4_K_M" , None ), 439( "[\\s\\S]*?\\*\\*\\s*0.5($|\\*\\*)" , 8192 , "bartowski/DeepSeek-R1-Distill-Qwen-7B-GGUF:Q4_K_M" , ( "llama-cpp-deepseek-r1" , None )), 440 441# TODO: fix these (wrong results, either didn't respect decimal instruction or got wrong value) 442# (None, 128, "bartowski/Meta-Llama-3.1-8B-Instruct-GGUF:Q4_K_M", None), 443# ("[\\s\\S]*?\\*\\*\\s*0.5($|\\*\\*)", 8192, "bartowski/DeepSeek-R1-Distill-Qwen-7B-GGUF:Q4_K_M", None), 444]) 445def test_calc_result (result_override :str | None ,n_predict :int ,hf_repo :str ,template_override :str | Tuple [str ,str | None ]| None ,stream :CompletionMode ): 446global server 447server .jinja = True 448server .n_ctx = 8192 * 2 449server .n_predict = n_predict 450server .model_hf_repo = hf_repo 451server .model_hf_file = None 452if isinstance (template_override ,tuple ): 453 (template_hf_repo ,template_variant )= template_override 454server .chat_template_file = f"../../../models/templates/ { template_hf_repo . replace ( '/' , '-' ) + ( '-' + template_variant if template_variant else '' ) } .jinja" 455assert os .path .exists (server .chat_template_file ),f"Template file { server . chat_template_file } does not exist. Run `python scripts/get_chat_template.py { template_hf_repo } { template_variant } > { server . chat_template_file } ` to download the template." 456elif isinstance (template_override ,str ): 457server .chat_template = template_override 458server .start (timeout_seconds = TIMEOUT_START_SLOW ) 459do_test_calc_result (server ,result_override ,n_predict ,stream = stream == CompletionMode .STREAMED ) 460 461 462def do_test_calc_result (server :ServerProcess ,result_override :str | None ,n_predict :int ,** kwargs ): 463body = server .make_any_request ("POST" ,"/v1/chat/completions" ,data = { 464"max_tokens" :n_predict , 465"messages" : [ 466 {"role" :"system" ,"content" :"You are a tools-calling assistant. You express numerical values with at most two decimals." }, 467 {"role" :"user" ,"content" :"What's the y coordinate of a point on the unit sphere at angle 30 degrees?" }, 468 { 469"role" :"assistant" , 470"content" :None , 471"tool_calls" : [ 472 { 473"id" :"call_6789" , 474"type" :"function" , 475"function" : { 476"name" :"calculate" , 477"arguments" :"{\"expression\":\"sin(30 * pi / 180)\"}" 478 } 479 } 480 ] 481 }, 482 { 483"role" :"tool" , 484"name" :"calculate" , 485"content" :"0.55644242476" , 486"tool_call_id" :"call_6789" 487 } 488 ], 489"tools" : [ 490 { 491"type" :"function" , 492"function" :{ 493"name" :"calculate" , 494"description" :"A calculator function that computes values of arithmetic expressions in the Python syntax" , 495"parameters" :{ 496"type" :"object" , 497"properties" :{ 498"expression" :{ 499"type" :"string" , 500"description" :"An arithmetic expression to compute the value of (Python syntad, assuming all floats)" 501 } 502 }, 503"required" :["expression" ] 504 } 505 } 506 } 507 ], 508** kwargs , 509 },timeout = TIMEOUT_HTTP_REQUEST ) 510choice = body ["choices" ][0 ] 511tool_calls = choice ["message" ].get ("tool_calls" ) 512assert tool_calls is None ,f'Expected no tool call in { choice [ "message" ] } ' 513content = choice ["message" ].get ("content" ) 514assert content is not None ,f'Expected content in { choice [ "message" ] } ' 515if result_override is not None : 516assert re .match (result_override ,content ),f'Expected { result_override } , got { content } ' 517else : 518assert re .match ('^[\\s\\S]*?((That\'s|\\bis) (approximately )?)?\\b0\\.(5\\b|56\\b|556)' ,content ), \ 519f'Expected something like "The y coordinate is 0.56.", got { content } ' 520 521 522@ pytest . mark . slow 523@ pytest . mark . parametrize ( "stream" , [ CompletionMode . NORMAL , CompletionMode . STREAMED ]) 524@ pytest . mark . parametrize ( "n_predict,reasoning_format,expect_reasoning_content,expect_content,hf_repo,template_override" , [ 525( 128 , 'deepseek' , None , "^The sum of 102 and 7 is 109[\\s\\S]*" , "bartowski/Phi-3.5-mini-instruct-GGUF:Q4_K_M" , None ), 526( 128 , None , None , "^The sum of 102 and 7 is 109[\\s\\S]*" , "bartowski/Phi-3.5-mini-instruct-GGUF:Q4_K_M" , None ), 527( 1024 , 'deepseek' , "I need to calculate the sum of 102 and 7[\\s\\S]*" , "To find the sum of[\\s\\S]*" , "bartowski/DeepSeek-R1-Distill-Qwen-7B-GGUF:Q4_K_M" , None ), 528( 1024 , 'deepseek' , "First, I [\\s\\S]*" , "To find the sum of[\\s\\S]*" , "bartowski/DeepSeek-R1-Distill-Qwen-7B-GGUF:Q4_K_M" , ( "llama-cpp-deepseek-r1" , None )), 529# (1024, 'none', CompletionMode.NORMAL, None, "^(<think>\\s*)?I need[\\s\\S]*?</think>\\s*To find[\\s\\S]*", "bartowski/DeepSeek-R1-Distill-Qwen-7B-GGUF:Q4_K_M", None), 530# (128, 'deepseek', None, "^Okay, let me figure out the sum of 102 and 7[\\s\\S]*", "bartowski/Qwen_QwQ-32B-GGUF:Q4_K_M", None), 531]) 532def test_thoughts (n_predict :int ,reasoning_format :Literal ['deepseek' ,'none' ]| None ,expect_content :str | None ,expect_reasoning_content :str | None ,hf_repo :str ,template_override :str | Tuple [str ,str | None ]| None ,stream :CompletionMode ): 533global server 534server .reasoning_format = reasoning_format 535server .jinja = True 536server .n_ctx = 8192 * 2 537server .n_predict = n_predict 538server .model_hf_repo = hf_repo 539server .model_hf_file = None 540if isinstance (template_override ,tuple ): 541 (template_hf_repo ,template_variant )= template_override 542server .chat_template_file = f"../../../models/templates/ { template_hf_repo . replace ( '/' , '-' ) + ( '-' + template_variant if template_variant else '' ) } .jinja" 543assert os .path .exists (server .chat_template_file ),f"Template file { server . chat_template_file } does not exist. Run `python scripts/get_chat_template.py { template_hf_repo } { template_variant } > { server . chat_template_file } ` to download the template." 544elif isinstance (template_override ,str ): 545server .chat_template = template_override 546server .start () 547body = server .make_any_request ("POST" ,"/v1/chat/completions" ,data = { 548"max_tokens" :n_predict , 549"messages" : [ 550 {"role" :"user" ,"content" :"What's the sum of 102 and 7?" }, 551 ], 552"stream" :stream == CompletionMode .STREAMED , 553 },timeout = TIMEOUT_HTTP_REQUEST ) 554choice = body ["choices" ][0 ] 555assert choice ["message" ].get ("tool_calls" )is None ,f'Expected no tool call in { choice [ "message" ] } ' 556 557content = choice ["message" ].get ("content" ) 558if expect_content is None : 559assert choice ["message" ].get ("content" )in (None ,"" ),f'Expected no content in { choice [ "message" ] } ' 560else : 561assert re .match (expect_content ,content ),f'Expected { expect_content } , got { content } ' 562 563reasoning_content = choice ["message" ].get ("reasoning_content" ) 564if expect_reasoning_content is None : 565assert reasoning_content is None ,f'Expected no reasoning content in { choice [ "message" ] } ' 566else : 567assert re .match (expect_reasoning_content ,reasoning_content ),f'Expected { expect_reasoning_content } , got { reasoning_content } ' 568 569 570@ pytest . mark . slow 571@ pytest . mark . parametrize ( "stream" , [ CompletionMode . NORMAL , CompletionMode . STREAMED ]) 572@ pytest . mark . parametrize ( "hf_repo,template_override" , [ 573( "bartowski/DeepSeek-R1-Distill-Qwen-7B-GGUF:Q4_K_M" , None ), 574 575( "bartowski/Phi-3.5-mini-instruct-GGUF:Q4_K_M" , None ), 576( "bartowski/Phi-3.5-mini-instruct-GGUF:Q4_K_M" , "chatml" ), 577 578( "bartowski/functionary-small-v3.2-GGUF:Q8_0" , ( "meetkai-functionary-medium-v3.2" , None )), 579( "bartowski/functionary-small-v3.2-GGUF:Q8_0" , "chatml" ), 580 581# ("bartowski/Meta-Llama-3.1-8B-Instruct-GGUF:Q4_K_M", None), 582( "bartowski/Meta-Llama-3.1-8B-Instruct-GGUF:Q4_K_M" , "chatml" ), 583 584( "bartowski/Llama-3.2-1B-Instruct-GGUF:Q4_K_M" , ( "meta-llama-Llama-3.2-3B-Instruct" , None )), 585( "bartowski/Llama-3.2-1B-Instruct-GGUF:Q4_K_M" , None ), 586 587( "bartowski/Llama-3.2-3B-Instruct-GGUF:Q4_K_M" , ( "meta-llama-Llama-3.2-3B-Instruct" , None )), 588( "bartowski/Llama-3.2-3B-Instruct-GGUF:Q4_K_M" , None ), 589 590( "bartowski/Qwen2.5-7B-Instruct-GGUF:Q4_K_M" , None ), 591( "bartowski/Qwen2.5-7B-Instruct-GGUF:Q4_K_M" , "chatml" ), 592 593( "bartowski/Hermes-2-Pro-Llama-3-8B-GGUF:Q4_K_M" , ( "NousResearch/Hermes-2-Pro-Llama-3-8B" , "tool_use" )), 594( "bartowski/Hermes-2-Pro-Llama-3-8B-GGUF:Q4_K_M" , "chatml" ), 595 596( "bartowski/Hermes-3-Llama-3.1-8B-GGUF:Q4_K_M" , ( "NousResearch-Hermes-3-Llama-3.1-8B" , "tool_use" )), 597( "bartowski/Hermes-3-Llama-3.1-8B-GGUF:Q4_K_M" , "chatml" ), 598 599( "bartowski/Mistral-Nemo-Instruct-2407-GGUF:Q4_K_M" , None ), 600( "bartowski/Mistral-Nemo-Instruct-2407-GGUF:Q4_K_M" , "chatml" ), 601 602( "bartowski/gemma-2-2b-it-GGUF:Q4_K_M" , None ), 603( "bartowski/gemma-2-2b-it-GGUF:Q4_K_M" , "chatml" ), 604]) 605def test_hello_world (hf_repo :str ,template_override :str | Tuple [str ,str | None ]| None ,stream :CompletionMode ): 606global server 607n_predict = 512 # High because of DeepSeek R1 608server .jinja = True 609server .n_ctx = 8192 610server .n_predict = n_predict 611server .model_hf_repo = hf_repo 612server .model_hf_file = None 613if isinstance (template_override ,tuple ): 614 (template_hf_repo ,template_variant )= template_override 615server .chat_template_file = f"../../../models/templates/ { template_hf_repo . replace ( '/' , '-' ) + ( '-' + template_variant if template_variant else '' ) } .jinja" 616assert os .path .exists (server .chat_template_file ),f"Template file { server . chat_template_file } does not exist. Run `python scripts/get_chat_template.py { template_hf_repo } { template_variant } > { server . chat_template_file } ` to download the template." 617elif isinstance (template_override ,str ): 618server .chat_template = template_override 619server .start (timeout_seconds = TIMEOUT_START_SLOW ) 620 621do_test_hello_world (server ,stream = stream == CompletionMode .STREAMED ,max_tokens = n_predict ) 622 623 624def do_test_hello_world (server :ServerProcess ,** kwargs ): 625body = server .make_any_request ("POST" ,"/v1/chat/completions" ,data = { 626"messages" : [ 627 {"role" :"system" ,"content" :"You are a tool-calling agent." }, 628 {"role" :"user" ,"content" :"say hello world with python" }, 629 ], 630"tools" : [PYTHON_TOOL ], 631** kwargs , 632 },timeout = TIMEOUT_HTTP_REQUEST ) 633choice = body ["choices" ][0 ] 634tool_calls = choice ["message" ].get ("tool_calls" ) 635assert tool_calls and len (tool_calls )== 1 ,f'Expected 1 tool call in { choice [ "message" ] } ' 636tool_call = tool_calls [0 ] 637# assert choice["message"].get("content") in (None, ""), f'Expected no content in {choice["message"]}' 638assert tool_call ["function" ]["name" ]== PYTHON_TOOL ["function" ]["name" ] 639# assert len(tool_call.get("id", "")) > 0, f'Expected non empty tool call id in {tool_call}' 640actual_arguments = json .loads (tool_call ["function" ]["arguments" ]) 641assert 'code' in actual_arguments ,f"code not found in { json . dumps ( actual_arguments ) } " 642code = actual_arguments ["code" ] 643assert isinstance (code ,str ),f"Expected code to be a string, got { type ( code ) } : { json . dumps ( code ) } " 644assert re .match (r'''print\(("[Hh]ello,? [Ww]orld!?"|'[Hh]ello,? [Ww]orld!?')\)''' ,re .sub (r'#.*\n?' ,'' ,code )),f'Expected hello world, got { code } '