2025-01-30 19:13:58 +00:00
// Tests chat handling, including grammar generation and parsing for tool calling, for various templates.
//
// Also acts as a CLI to generate a Markdown summary of the formats of Jinja templates,
// e.g. given Minja (http://github.com/google/minja) checked out in parent dir:
//
// cmake -B build && cmake --build build --parallel && ./build/bin/test-chat ../minja/build/tests/*.jinja 2>/dev/null
//
2025-02-18 18:03:23 +00:00
# include "chat.h"
2025-04-24 16:00:10 +03:00
2025-06-09 11:03:09 -07:00
# include "log.h"
2025-04-24 16:00:10 +03:00
# include "../src/unicode.h"
# include "../src/llama-grammar.h"
2025-01-30 19:13:58 +00:00
2025-05-30 16:25:45 +03:00
# include <nlohmann/json.hpp>
# include <fstream>
# include <iostream>
2025-10-27 18:54:01 -04:00
# include <functional>
2025-05-30 16:25:45 +03:00
# include <string>
2025-01-30 19:13:58 +00:00
using json = nlohmann : : ordered_json ;
2025-05-25 01:48:08 +01:00
static std : : ostream & operator < < ( std : : ostream & os , const common_chat_msg_diff & diff ) {
os < < " { content_delta: " < < diff . content_delta < < " ; " ;
2025-06-02 10:15:44 -07:00
os < < " reasoning_content_delta: " < < diff . reasoning_content_delta < < " ; " ;
2025-05-25 01:48:08 +01:00
if ( diff . tool_call_index ! = std : : string : : npos ) {
os < < " tool_call_index: " < < diff . tool_call_index < < " ; " ;
os < < " tool_call_delta.name: " < < diff . tool_call_delta . name < < " ; " ;
os < < " tool_call_delta.id: " < < diff . tool_call_delta . id < < " ; " ;
os < < " tool_call_delta.arguments: " < < diff . tool_call_delta . arguments < < " ; " ;
}
os < < " } " ;
return os ;
}
// operator<< for vector<common_chat_msg_diff>:
static std : : ostream & operator < < ( std : : ostream & os , const std : : vector < common_chat_msg_diff > & diffs ) {
os < < " [ \n " ;
for ( const auto & diff : diffs ) {
os < < " " < < diff < < " , \n " ;
}
os < < " ] " ;
return os ;
}
static std : : ostream & operator < < ( std : : ostream & os , const common_chat_msg & msg ) {
os < < " { role: " < < msg . role < < " ; " ;
os < < " content: " < < msg . content < < " ; " ;
os < < " content_parts: [ \n " ;
for ( const auto & part : msg . content_parts ) {
os < < " { type: " < < part . type < < " ; text: " < < part . text < < " }, \n " ;
}
os < < " ]; " ;
os < < " reasoning_content: " < < msg . reasoning_content < < " ; " ;
os < < " tool_calls: [ \n " ;
for ( const auto & tool_call : msg . tool_calls ) {
os < < " { name: " < < tool_call . name < < " ; arguments: " < < tool_call . arguments < < " ; id: " < < tool_call . id < < " }, \n " ;
}
os < < " ] " ;
os < < " } " ;
return os ;
}
template < class T > static bool equals ( const T & expected , const T & actual ) {
return expected = = actual ;
}
static common_chat_msg normalize ( const common_chat_msg & msg ) {
common_chat_msg normalized = msg ;
for ( auto & tool_call : normalized . tool_calls ) {
try {
tool_call . arguments = json : : parse ( tool_call . arguments ) . dump ( ) ;
} catch ( const std : : exception & ) {
// Do nothing
}
}
return normalized ;
}
template < >
bool equals ( const common_chat_msg & expected , const common_chat_msg & actual ) {
return normalize ( expected ) = = normalize ( actual ) ;
}
2025-01-30 19:13:58 +00:00
template < class T > static void assert_equals ( const T & expected , const T & actual ) {
2025-05-25 01:48:08 +01:00
if ( ! equals ( expected , actual ) ) {
2025-01-30 19:13:58 +00:00
std : : cerr < < " Expected: " < < expected < < std : : endl ;
std : : cerr < < " Actual: " < < actual < < std : : endl ;
std : : cerr < < std : : flush ;
throw std : : runtime_error ( " Test failed " ) ;
}
}
static std : : string read_file ( const std : : string & path ) {
2025-02-18 18:03:23 +00:00
std : : cerr < < " # Reading: " < < path < < ' \n ' < < std : : flush ;
2025-01-30 19:13:58 +00:00
std : : ifstream fs ( path , std : : ios_base : : binary ) ;
if ( ! fs . is_open ( ) ) {
fs = std : : ifstream ( " ../ " + path , std : : ios_base : : binary ) ;
if ( ! fs . is_open ( ) ) {
throw std : : runtime_error ( " Failed to open file: " + path ) ;
}
}
fs . seekg ( 0 , std : : ios_base : : end ) ;
auto size = fs . tellg ( ) ;
fs . seekg ( 0 ) ;
std : : string out ;
out . resize ( static_cast < size_t > ( size ) ) ;
2025-02-18 18:03:23 +00:00
fs . read ( out . data ( ) , static_cast < std : : streamsize > ( size ) ) ;
2025-01-30 19:13:58 +00:00
return out ;
}
2025-02-18 18:03:23 +00:00
static common_chat_templates_ptr read_templates ( const std : : string & path ) {
return common_chat_templates_ptr ( common_chat_templates_init ( /* model= */ nullptr , read_file ( path ) ) ) ;
}
2025-01-30 19:13:58 +00:00
static std : : unique_ptr < llama_grammar > build_grammar ( const std : : string & grammar_str ) {
return std : : unique_ptr < llama_grammar > (
llama_grammar_init_impl ( nullptr , grammar_str . c_str ( ) , " root " , false , nullptr , 0 , nullptr , 0 ) ) ;
}
// TODO: extract to common helper (copied from test-grammar-integration.cpp)
static bool match_string ( const std : : string & input , llama_grammar * grammar ) {
const auto cpts = unicode_cpts_from_utf8 ( input ) ;
auto & stacks_cur = llama_grammar_get_stacks ( grammar ) ;
for ( const auto & cpt : cpts ) {
llama_grammar_accept ( grammar , cpt ) ;
if ( stacks_cur . empty ( ) ) {
// no stacks means that the grammar failed to match at this point
return false ;
}
}
2025-02-18 18:03:23 +00:00
if ( std : : any_of ( stacks_cur . begin ( ) , stacks_cur . end ( ) , [ ] ( const auto & stack ) { return stack . empty ( ) ; } ) ) {
// An empty stack means that the grammar has been completed
return true ;
2025-01-30 19:13:58 +00:00
}
return false ;
}
2025-05-25 01:48:08 +01:00
static std : : string renormalize_json ( const std : : string & json_str ) {
try {
auto json_obj = json : : parse ( json_str ) ;
return json_obj . dump ( ) ;
} catch ( const std : : exception & e ) {
std : : cerr < < " Failed to parse JSON: " < < e . what ( ) < < ' \n ' ;
return json_str ;
}
}
2025-01-30 19:13:58 +00:00
static void assert_msg_equals ( const common_chat_msg & expected , const common_chat_msg & actual ) {
assert_equals ( expected . role , actual . role ) ;
assert_equals ( expected . content , actual . content ) ;
2025-02-18 18:03:23 +00:00
assert_equals ( expected . content_parts . size ( ) , actual . content_parts . size ( ) ) ;
for ( size_t i = 0 ; i < expected . content_parts . size ( ) ; i + + ) {
const auto & expected_part = expected . content_parts [ i ] ;
const auto & actual_part = actual . content_parts [ i ] ;
assert_equals ( expected_part . type , actual_part . type ) ;
assert_equals ( expected_part . text , actual_part . text ) ;
}
2025-02-13 10:05:16 +00:00
assert_equals ( expected . reasoning_content , actual . reasoning_content ) ;
2025-01-30 19:13:58 +00:00
assert_equals ( expected . tool_calls . size ( ) , actual . tool_calls . size ( ) ) ;
for ( size_t i = 0 ; i < expected . tool_calls . size ( ) ; i + + ) {
const auto & expected_tool_call = expected . tool_calls [ i ] ;
const auto & actual_tool_call = actual . tool_calls [ i ] ;
assert_equals ( expected_tool_call . name , actual_tool_call . name ) ;
2025-05-25 01:48:08 +01:00
assert_equals ( renormalize_json ( expected_tool_call . arguments ) , renormalize_json ( actual_tool_call . arguments ) ) ;
2025-01-30 19:13:58 +00:00
assert_equals ( expected_tool_call . id , actual_tool_call . id ) ;
}
}
2025-02-18 18:03:23 +00:00
common_chat_tool special_function_tool {
/* .name = */ " special_function " ,
/* .description = */ " I'm special " ,
/* .parameters = */ R " ({
" type " : " object " ,
" properties " : {
" arg1 " : {
" type " : " integer " ,
" description " : " The arg. "
}
} ,
" required " : [ " arg1 " ]
} ) " ,
} ;
common_chat_tool python_tool {
/* .name = */ " python " ,
/* .description = */ " an ipython interpreter " ,
/* .parameters = */ R " ({
" type " : " object " ,
" properties " : {
" code " : {
" type " : " string " ,
" description " : " Python code to execute. "
}
} ,
" required " : [ " code " ]
} ) " ,
} ;
common_chat_tool code_interpreter_tool {
/* .name = */ " code_interpreter " ,
/* .description = */ " an ipython interpreter " ,
/* .parameters = */ R " ({
" type " : " object " ,
" properties " : {
" code " : {
" type " : " string " ,
" description " : " Python code to execute. "
}
} ,
" required " : [ " code " ]
} ) " ,
} ;
std : : vector < common_chat_tool > tools { special_function_tool , python_tool } ;
std : : vector < common_chat_tool > llama_3_1_tools { special_function_tool , code_interpreter_tool } ;
2025-01-30 19:13:58 +00:00
struct delta_data {
std : : string delta ;
2025-02-02 09:25:38 +00:00
common_chat_params params ;
2025-01-30 19:13:58 +00:00
} ;
2025-02-18 18:03:23 +00:00
static delta_data init_delta ( const struct common_chat_templates * tmpls , const std : : vector < std : : string > & end_tokens ,
const common_chat_msg & user_message ,
const common_chat_msg & delta_message ,
const std : : vector < common_chat_tool > & tools ,
2025-05-25 01:48:08 +01:00
const common_chat_tool_choice & tool_choice ) {
2025-02-18 18:03:23 +00:00
common_chat_templates_inputs inputs ;
2025-01-30 19:13:58 +00:00
inputs . parallel_tool_calls = true ;
inputs . messages . push_back ( user_message ) ;
inputs . tools = tools ;
inputs . tool_choice = tool_choice ;
2025-02-18 18:03:23 +00:00
auto params_prefix = common_chat_templates_apply ( tmpls , inputs ) ;
2025-01-30 19:13:58 +00:00
inputs . messages . push_back ( delta_message ) ;
inputs . add_generation_prompt = false ;
2025-02-18 18:03:23 +00:00
auto params_full = common_chat_templates_apply ( tmpls , inputs ) ;
2025-01-30 19:13:58 +00:00
std : : string prefix = params_prefix . prompt ;
std : : string full = params_full . prompt ;
if ( full = = prefix ) {
throw std : : runtime_error ( " Full message is the same as the prefix " ) ;
}
2025-02-13 10:05:16 +00:00
size_t common_prefix_length = 0 ;
for ( size_t i = 0 ; i < prefix . size ( ) & & i < full . size ( ) ; + + i ) {
if ( prefix [ i ] ! = full [ i ] ) {
break ;
}
if ( prefix [ i ] = = ' < ' ) {
// DeepSeek R1's template (as of 20250209) adds a trailing <think> if add_generation_prompt,
// but it removes thinking tags for past messages.
// The prefix and full strings diverge at <think> vs. <| tool▁calls▁begin| >, we avoid consuming the leading <.
continue ;
}
common_prefix_length = i + 1 ;
}
auto delta = full . substr ( common_prefix_length ) ;
2025-01-30 19:13:58 +00:00
// Strip end tokens
for ( const auto & end_token : end_tokens ) {
// rfind to find the last occurrence
auto pos = delta . rfind ( end_token ) ;
if ( pos ! = std : : string : : npos ) {
delta = delta . substr ( 0 , pos ) ;
break ;
}
}
2025-02-02 09:25:38 +00:00
return { delta , params_full } ;
2025-01-30 19:13:58 +00:00
}
/*
Applies the template to 1 user message w / add_generation_prompt = true , then w / the test message w / add_generation_prompt = false ,
gets the diff , removes any end tokens and parses the result w / the grammar , checking that
the parsed message is the same as the test_message
*/
2025-02-18 18:03:23 +00:00
static void test_templates ( const struct common_chat_templates * tmpls , const std : : vector < std : : string > & end_tokens ,
const common_chat_msg & test_message ,
const std : : vector < common_chat_tool > & tools = { } ,
const std : : string & expected_delta = " " ,
2025-02-13 10:05:16 +00:00
bool expect_grammar_triggered = true ,
bool test_grammar_if_triggered = true ,
2025-05-25 01:48:08 +01:00
common_reasoning_format reasoning_format = COMMON_REASONING_FORMAT_NONE ) {
2025-02-18 18:03:23 +00:00
common_chat_msg user_message ;
user_message . role = " user " ;
user_message . content = " Hello, world! " ;
2025-01-30 19:13:58 +00:00
2025-02-18 18:03:23 +00:00
for ( const auto & tool_choice : std : : vector < common_chat_tool_choice > { COMMON_CHAT_TOOL_CHOICE_AUTO , COMMON_CHAT_TOOL_CHOICE_REQUIRED } ) {
2025-05-25 01:48:08 +01:00
auto data = init_delta ( tmpls , end_tokens , user_message , test_message , tools , tool_choice ) ;
2025-01-30 19:13:58 +00:00
if ( ! expected_delta . empty ( ) ) {
assert_equals ( expected_delta , data . delta ) ;
}
2025-02-02 09:25:38 +00:00
if ( expect_grammar_triggered ) {
2025-05-25 01:48:08 +01:00
common_chat_syntax syntax ;
syntax . format = data . params . format ;
syntax . reasoning_format = reasoning_format ;
const auto msg = common_chat_parse ( data . delta , /* is_partial= */ false , syntax ) ;
2025-02-18 18:03:23 +00:00
assert_msg_equals ( test_message , msg ) ;
2025-01-30 19:13:58 +00:00
}
2025-02-18 18:03:23 +00:00
if ( ! test_message . tool_calls . empty ( ) ) {
2025-02-02 09:25:38 +00:00
GGML_ASSERT ( ! data . params . grammar . empty ( ) ) ;
2025-01-30 19:13:58 +00:00
}
2025-02-02 09:25:38 +00:00
if ( ! data . params . grammar . empty ( ) ) {
auto grammar = build_grammar ( data . params . grammar ) ;
2025-01-30 19:13:58 +00:00
if ( ! grammar ) {
throw std : : runtime_error ( " Failed to build grammar " ) ;
}
2025-02-02 09:25:38 +00:00
auto earliest_trigger_pos = std : : string : : npos ;
auto constrained = data . delta ;
for ( const auto & trigger : data . params . grammar_triggers ) {
2025-03-05 13:05:13 +00:00
size_t pos = std : : string : : npos ;
std : : smatch match ;
switch ( trigger . type ) {
case COMMON_GRAMMAR_TRIGGER_TYPE_WORD :
{
const auto & word = trigger . value ;
pos = constrained . find ( word ) ;
break ;
}
case COMMON_GRAMMAR_TRIGGER_TYPE_PATTERN :
{
const auto & pattern = trigger . value ;
if ( std : : regex_search ( constrained , match , std : : regex ( pattern ) ) ) {
2025-05-25 01:48:08 +01:00
pos = match . position ( 1 ) ;
2025-03-05 13:05:13 +00:00
}
break ;
}
2025-05-25 01:48:08 +01:00
case COMMON_GRAMMAR_TRIGGER_TYPE_PATTERN_FULL :
2025-03-05 13:05:13 +00:00
{
const auto & pattern = trigger . value ;
2025-05-25 01:48:08 +01:00
if ( std : : regex_match ( constrained , match , std : : regex ( pattern ) ) ) {
auto mpos = std : : string : : npos ;
for ( size_t i = 1 ; i < match . size ( ) ; + + i ) {
if ( match [ i ] . length ( ) > 0 ) {
mpos = match . position ( i ) ;
break ;
}
}
if ( mpos = = std : : string : : npos ) {
mpos = match . position ( 0 ) ;
}
pos = mpos ;
2025-03-05 13:05:13 +00:00
}
break ;
}
default :
throw std : : runtime_error ( " Unknown trigger type " ) ;
2025-02-02 09:25:38 +00:00
}
2025-03-05 13:05:13 +00:00
if ( pos = = std : : string : : npos ) {
2025-02-02 09:25:38 +00:00
continue ;
}
if ( earliest_trigger_pos = = std : : string : : npos | | pos < earliest_trigger_pos ) {
earliest_trigger_pos = pos ;
2025-01-30 19:13:58 +00:00
}
}
2025-02-02 09:25:38 +00:00
auto grammar_triggered = false ;
if ( earliest_trigger_pos ! = std : : string : : npos ) {
constrained = constrained . substr ( earliest_trigger_pos ) ;
grammar_triggered = true ;
}
if ( data . params . grammar_lazy ) {
assert_equals ( expect_grammar_triggered , grammar_triggered ) ;
}
2025-02-13 10:05:16 +00:00
if ( grammar_triggered & & test_grammar_if_triggered & & ! match_string ( constrained , grammar . get ( ) ) ) {
2025-02-02 09:25:38 +00:00
throw std : : runtime_error ( " Failed to match delta against grammar: \n \n " + data . delta +
2025-03-05 13:05:13 +00:00
" \n \n Constrained: " + constrained +
" \n \n Grammar: " + data . params . grammar ) ;
2025-02-02 09:25:38 +00:00
}
2025-01-30 19:13:58 +00:00
}
}
}
2025-02-18 18:03:23 +00:00
const common_chat_msg message_user {
" user " ,
" Hey there! " ,
/* .content_parts = */ { } ,
/* .tool_calls = */ { } ,
/* .reasoning_content = */ " " ,
/* .tool_name = */ " " ,
/* .tool_call_id = */ " " ,
} ;
const common_chat_msg message_user_parts {
" user " ,
/* .content = */ " " ,
/* .content_parts = */ {
{ " text " , " Hey " } ,
{ " text " , " there " } ,
} ,
/* .tool_calls = */ { } ,
/* .reasoning_content = */ " " ,
/* .tool_name = */ " " ,
/* .tool_call_id = */ " " ,
} ;
2025-05-25 01:48:08 +01:00
static common_chat_msg simple_assist_msg ( const std : : string & content , const std : : string & reasoning_content = " " , const std : : string & tool_name = " " , const std : : string & arguments = " " , const std : : string & id = " " ) {
common_chat_msg msg ;
msg . role = " assistant " ;
msg . content = content ;
msg . reasoning_content = reasoning_content ;
if ( ! tool_name . empty ( ) ) {
msg . tool_calls . push_back ( { tool_name , arguments , id } ) ;
}
return msg ;
}
2025-05-26 08:03:57 -07:00
const common_chat_msg message_assist = simple_assist_msg ( " Hello, world! \n What's up? " ) ;
const common_chat_msg message_assist_empty = simple_assist_msg ( " " ) ;
const common_chat_msg message_assist_thoughts_unparsed_deepseek = simple_assist_msg ( " <think>I'm \n thinking</think>Hello, world! \n What's up? " ) ;
const common_chat_msg message_assist_thoughts_unparsed_md = simple_assist_msg ( " <think>I'm \n thinking</think>Hello, world! \n What's up? \n ```json \n {}``` " ) ;
const common_chat_msg message_assist_thoughts_unparsed_md_partial = simple_assist_msg ( " <think>I'm \n thinking</think>Hello, world! \n What's up? \n ```json \n {} " ) ;
2025-05-25 01:48:08 +01:00
const common_chat_msg message_assist_thoughts_unparsed_r7b = simple_assist_msg ( " <|START_THINKING|>I'm \n thinking<|END_THINKING|>Hello, world! \n What's up? " ) ;
2025-10-03 20:51:48 +02:00
const common_chat_msg message_assist_thoughts_unparsed_magistral = simple_assist_msg ( " [THINK]raisonnement[/THINK]Réponse " ) ;
2025-05-25 01:48:08 +01:00
const common_chat_msg message_assist_thoughts = simple_assist_msg ( " Hello, world! \n What's up? " , " I'm \n thinking " ) ;
const common_chat_msg message_assist_thoughts_unopened_unparsed = simple_assist_msg ( " I'm \n thinking</think>Hello, world! \n What's up? " ) ;
const common_chat_msg message_assist_thoughts_no_content = simple_assist_msg ( " " , " I'm \n thinking " ) ;
const common_chat_msg message_assist_call = simple_assist_msg ( " " , " " , " special_function " , " { \" arg1 \" : 1} " ) ;
const common_chat_msg message_assist_call_content = simple_assist_msg ( " Hello, world! \n What's up? " , " " , " special_function " , " { \" arg1 \" :1} " ) ;
const common_chat_msg message_assist_call_empty_args = simple_assist_msg ( " " , " " , " special_function " ) ;
const common_chat_msg message_assist_call_cutoff_args = simple_assist_msg ( " " , " " , " special_function " , " { \" arg " ) ;
const common_chat_msg message_assist_call_thoughts = simple_assist_msg ( " " , " I'm \n thinking " , " special_function " , " { \" arg1 \" :1} " ) ;
const common_chat_msg message_assist_call_thoughts_unparsed = simple_assist_msg ( " <think>I'm \n thinking</think> \n \n " , " " , " special_function " , " { \" arg1 \" : 1} " ) ;
2025-09-05 01:22:22 +02:00
const common_chat_msg message_assist_call_thoughts_content = simple_assist_msg ( " Hello, world! \n What's up? " , " I'm \n thinking " , " special_function " , " { \" arg1 \" : 1} " ) ;
2025-05-25 01:48:08 +01:00
const common_chat_msg message_assist_call_id = simple_assist_msg ( " " , " " , " special_function " , " { \" arg1 \" :1} " , /* .id = */ " 123456789 " ) ;
const common_chat_msg message_assist_call_idx = simple_assist_msg ( " " , " " , " special_function " , " { \" arg1 \" :1} " , /* .id = */ " 0 " ) ;
const common_chat_msg message_assist_thoughts_call_idx = simple_assist_msg ( " " , " I'm \n thinking " , " special_function " , " { \" arg1 \" : 1} " , /* id = */ " 0 " ) ;
const common_chat_msg message_assist_call_python = simple_assist_msg ( " " , " " , " python " , " { \" code \" : \" print('hey') \" } " ) ;
const common_chat_msg message_assist_call_python_lines = simple_assist_msg ( " " , " " , " python " , " { \" code \" : \" # This is a program: \\ nprint('hey') \" } " ) ;
const common_chat_msg message_assist_call_python_lines_unclosed = simple_assist_msg ( " " , " " , " python " , " { \" code \" : \" # This is a program: \\ nprint('hey') " ) ;
const common_chat_msg message_assist_call_code_interpreter = simple_assist_msg ( " " , " " , " code_interpreter " , " { \" code \" : \" print('hey') \" } " ) ;
2025-02-18 18:03:23 +00:00
static void test_msgs_oaicompat_json_conversion ( ) {
2025-05-25 01:48:08 +01:00
printf ( " [%s] \n " , __func__ ) ;
2025-02-18 18:03:23 +00:00
std : : vector < common_chat_msg > msgs {
message_user ,
message_user_parts ,
message_assist_call ,
message_assist_call_thoughts ,
message_assist_call_thoughts_unparsed ,
2025-09-05 01:22:22 +02:00
message_assist_call_thoughts_content ,
2025-02-18 18:03:23 +00:00
message_assist_call_id ,
message_assist_call_idx ,
message_assist_call_python ,
message_assist_call_code_interpreter ,
2025-01-30 19:13:58 +00:00
} ;
2025-02-18 18:03:23 +00:00
for ( const auto & msg : msgs ) {
auto oai_json = common_chat_msgs_to_json_oaicompat < json > ( { msg } ) ;
auto msgs2 = common_chat_msgs_parse_oaicompat ( oai_json ) ;
assert_equals ( ( size_t ) 1 , msgs2 . size ( ) ) ;
auto msg2 = msgs2 [ 0 ] ;
assert_msg_equals ( msg , msg2 ) ;
}
assert_equals (
std : : string (
" [ \n "
" { \n "
" \" role \" : \" user \" , \n "
" \" content \" : [ \n "
" { \n "
" \" type \" : \" text \" , \n "
" \" text \" : \" Hey \" \n "
" }, \n "
" { \n "
" \" type \" : \" text \" , \n "
" \" text \" : \" there \" \n "
" } \n "
" ] \n "
" } \n "
" ] "
) ,
common_chat_msgs_to_json_oaicompat < json > ( { message_user_parts } ) . dump ( 2 ) ) ;
assert_equals (
std : : string (
" [ \n "
" { \n "
" \" role \" : \" assistant \" , \n "
" \" content \" : null, \n "
" \" tool_calls \" : [ \n "
" { \n "
" \" type \" : \" function \" , \n "
" \" function \" : { \n "
" \" name \" : \" python \" , \n "
2025-05-25 01:48:08 +01:00
" \" arguments \" : \" { \\ \" code \\ \" : \\ \" print('hey') \\ \" } \" \n "
2025-02-18 18:03:23 +00:00
" } \n "
" } \n "
" ] \n "
" } \n "
" ] "
) ,
common_chat_msgs_to_json_oaicompat < json > ( { message_assist_call_python } ) . dump ( 2 ) ) ;
2025-03-10 09:45:07 +00:00
auto res = common_chat_msgs_parse_oaicompat ( json : : parse ( " [{ \" role \" : \" assistant \" , \" tool_calls \" : []}] " ) ) ;
assert_equals < size_t > ( 1 , res . size ( ) ) ;
assert_equals < std : : string > ( res [ 0 ] . role , " assistant " ) ;
assert_equals ( true , res [ 0 ] . content . empty ( ) ) ;
assert_equals ( true , res [ 0 ] . tool_calls . empty ( ) ) ;
try {
common_chat_msgs_parse_oaicompat ( json : : parse ( " [{ \" role \" : \" assistant \" }] " ) ) ;
throw std : : runtime_error ( " Expected exception " ) ;
} catch ( const std : : exception & e ) {
if ( std : : string ( e . what ( ) ) . find ( " 'content' " ) = = std : : string : : npos ) {
throw std : : runtime_error ( " Expected exception about missing 'content' " ) ;
}
}
2025-02-18 18:03:23 +00:00
}
static void test_tools_oaicompat_json_conversion ( ) {
2025-05-25 01:48:08 +01:00
printf ( " [%s] \n " , __func__ ) ;
2025-02-18 18:03:23 +00:00
std : : vector < common_chat_tool > tools {
special_function_tool ,
python_tool ,
code_interpreter_tool ,
2025-01-30 19:13:58 +00:00
} ;
2025-02-18 18:03:23 +00:00
for ( const auto & tool : tools ) {
auto oai_json = common_chat_tools_to_json_oaicompat < json > ( { tool } ) ;
auto tools2 = common_chat_tools_parse_oaicompat ( oai_json ) ;
assert_equals ( ( size_t ) 1 , tools2 . size ( ) ) ;
auto tool2 = tools2 [ 0 ] ;
assert_equals ( tool . name , tool2 . name ) ;
assert_equals ( tool . description , tool2 . description ) ;
assert_equals ( json : : parse ( tool . parameters ) . dump ( 2 ) , json : : parse ( tool2 . parameters ) . dump ( 2 ) ) ;
}
assert_equals (
std : : string (
" [ \n "
" { \n "
" \" type \" : \" function \" , \n "
" \" function \" : { \n "
" \" name \" : \" special_function \" , \n "
" \" description \" : \" I'm special \" , \n "
" \" parameters \" : { \n "
" \" type \" : \" object \" , \n "
" \" properties \" : { \n "
" \" arg1 \" : { \n "
" \" type \" : \" integer \" , \n "
" \" description \" : \" The arg. \" \n "
" } \n "
" }, \n "
" \" required \" : [ \n "
" \" arg1 \" \n "
" ] \n "
" } \n "
" } \n "
" } \n "
" ] "
) ,
common_chat_tools_to_json_oaicompat < json > ( { special_function_tool } ) . dump ( 2 ) ) ;
}
static void test_template_output_parsers ( ) {
2025-05-25 01:48:08 +01:00
printf ( " [%s] \n " , __func__ ) ;
2025-02-18 18:03:23 +00:00
common_chat_templates_inputs inputs_no_tools ;
inputs_no_tools . messages = { message_user } ;
2025-01-30 19:13:58 +00:00
2025-02-18 18:03:23 +00:00
common_chat_templates_inputs inputs_tools ;
inputs_tools . messages = { message_user } ;
inputs_tools . tools = { special_function_tool } ;
2025-02-13 10:05:16 +00:00
2025-02-18 18:03:23 +00:00
common_chat_templates_inputs inputs_tools_builtin ;
inputs_tools_builtin . messages = { message_user } ;
inputs_tools_builtin . tools = { python_tool } ;
2025-01-30 19:13:58 +00:00
2025-02-02 09:25:38 +00:00
{
// Not supported yet
2025-02-18 18:03:23 +00:00
auto tmpls = read_templates ( " models/templates/CohereForAI-c4ai-command-r-plus-tool_use.jinja " ) ;
2025-04-11 12:47:52 -07:00
assert_equals ( COMMON_CHAT_FORMAT_CONTENT_ONLY , common_chat_templates_apply ( tmpls . get ( ) , inputs_no_tools ) . format ) ;
2025-02-18 18:03:23 +00:00
assert_equals ( COMMON_CHAT_FORMAT_GENERIC , common_chat_templates_apply ( tmpls . get ( ) , inputs_tools ) . format ) ;
2025-02-02 09:25:38 +00:00
}
{
2025-02-18 18:03:23 +00:00
auto tmpls = read_templates ( " models/templates/CohereForAI-c4ai-command-r7b-12-2024-tool_use.jinja " ) ;
2025-02-02 09:25:38 +00:00
std : : vector < std : : string > end_tokens { " <|END_OF_TURN_TOKEN|> " } ;
2025-05-25 01:48:08 +01:00
for ( const auto & inputs : { inputs_no_tools , inputs_tools } ) {
auto params = common_chat_templates_apply ( tmpls . get ( ) , inputs ) ;
assert_equals ( COMMON_CHAT_FORMAT_COMMAND_R7B , params . format ) ;
assert_equals ( false , params . thinking_forced_open ) ;
}
2025-02-13 10:05:16 +00:00
2025-02-18 18:03:23 +00:00
assert_msg_equals ( message_assist ,
2025-02-13 10:05:16 +00:00
common_chat_parse (
" Hello, world! \n What's up? " ,
2025-05-25 01:48:08 +01:00
/* is_partial= */ false ,
{ COMMON_CHAT_FORMAT_COMMAND_R7B } ) ) ;
2025-02-18 18:03:23 +00:00
assert_msg_equals ( message_assist ,
2025-02-13 10:05:16 +00:00
common_chat_parse (
2025-05-25 01:48:08 +01:00
" <|START_RESPONSE|>Hello, world! \n What's up?<|END_RESPONSE|> " ,
/* is_partial= */ false ,
{ COMMON_CHAT_FORMAT_COMMAND_R7B } ) ) ;
assert_msg_equals ( message_assist_thoughts ,
2025-02-13 10:05:16 +00:00
common_chat_parse (
2025-05-25 01:48:08 +01:00
" <|START_THINKING|>I'm \n thinking<|END_THINKING|> "
2025-02-13 10:05:16 +00:00
" <|START_RESPONSE|>Hello, world! \n What's up?<|END_RESPONSE|> " ,
2025-05-25 01:48:08 +01:00
/* is_partial= */ false ,
{
/* .format = */ COMMON_CHAT_FORMAT_COMMAND_R7B ,
/* .reasoning_format = */ COMMON_REASONING_FORMAT_DEEPSEEK ,
} ) ) ;
assert_msg_equals ( message_assist_thoughts_unparsed_deepseek ,
2025-02-13 10:05:16 +00:00
common_chat_parse (
2025-05-25 01:48:08 +01:00
" <|START_THINKING|>I'm \n thinking<|END_THINKING|> "
2025-02-13 10:05:16 +00:00
" <|START_RESPONSE|>Hello, world! \n What's up?<|END_RESPONSE|> " ,
2025-05-25 01:48:08 +01:00
/* is_partial= */ false ,
{
/* .format = */ COMMON_CHAT_FORMAT_COMMAND_R7B ,
/* .reasoning_format = */ COMMON_REASONING_FORMAT_DEEPSEEK ,
/* .reasoning_in_content = */ true ,
/* .thinking_forced_open = */ false ,
} ) ) ;
2025-02-18 18:03:23 +00:00
assert_msg_equals ( message_assist_thoughts_unparsed_r7b ,
2025-02-13 10:05:16 +00:00
common_chat_parse (
2025-05-25 01:48:08 +01:00
" <|START_THINKING|>I'm \n thinking<|END_THINKING|> "
" <|START_RESPONSE|>Hello, world! \n What's up?<|END_RESPONSE|> " ,
/* is_partial= */ false ,
{ COMMON_CHAT_FORMAT_COMMAND_R7B } ) ) ;
2025-02-18 18:03:23 +00:00
assert_msg_equals ( message_assist_thoughts ,
2025-02-13 10:05:16 +00:00
common_chat_parse (
2025-05-25 01:48:08 +01:00
" <|START_THINKING|>I'm \n thinking<|END_THINKING|> "
2025-02-13 10:05:16 +00:00
" <|START_RESPONSE|>Hello, world! \n What's up?<|END_RESPONSE|> " ,
2025-05-25 01:48:08 +01:00
/* is_partial= */ false ,
{
/* .format = */ COMMON_CHAT_FORMAT_COMMAND_R7B ,
/* .reasoning_format = */ COMMON_REASONING_FORMAT_DEEPSEEK ,
} ) ) ;
assert_msg_equals ( message_assist_thoughts_call_idx ,
common_chat_parse (
" <|START_THINKING|>I'm \n thinking<|END_THINKING|> "
" <|START_ACTION|>[ \n "
" { \" tool_call_id \" : \" 0 \" , \" tool_name \" : \" special_function \" , \" parameters \" : { \" arg1 \" : 1}} \n "
" ]<|END_ACTION|> " ,
/* is_partial= */ false ,
{
/* .format = */ COMMON_CHAT_FORMAT_COMMAND_R7B ,
/* .reasoning_format = */ COMMON_REASONING_FORMAT_DEEPSEEK ,
} ) ) ;
assert_msg_equals ( message_assist_thoughts_no_content ,
common_chat_parse (
" <|START_THINKING|>I'm \n thinking<|END_THINKING|> "
" <|START_ACTION|>[ \n "
" { \" tool_call_id \" : \" 0 \" , \" tool_name \" : \" special " ,
/* is_partial= */ true ,
{
/* .format = */ COMMON_CHAT_FORMAT_COMMAND_R7B ,
/* .reasoning_format = */ COMMON_REASONING_FORMAT_DEEPSEEK ,
} ) ) ;
2025-02-13 10:05:16 +00:00
2025-02-18 18:03:23 +00:00
test_templates ( tmpls . get ( ) , end_tokens , message_assist_call_idx , tools ,
2025-02-13 10:05:16 +00:00
" <|START_THINKING|><|END_THINKING|> "
2025-02-02 09:25:38 +00:00
" <|START_ACTION|>[ \n "
" { \" tool_call_id \" : \" 0 \" , \" tool_name \" : \" special_function \" , \" parameters \" : { \" arg1 \" : 1}} \n "
2025-05-25 01:48:08 +01:00
" ]<|END_ACTION|> " ,
/* expect_grammar_triggered= */ true ,
/* test_grammar_if_triggered= */ true ,
COMMON_REASONING_FORMAT_DEEPSEEK ) ;
2025-02-18 18:03:23 +00:00
test_templates ( tmpls . get ( ) , end_tokens , message_assist , tools ,
2025-02-04 15:48:53 +00:00
" <|START_RESPONSE|>Hello, world! \n "
" What's up?<|END_RESPONSE|> " ,
2025-02-02 09:25:38 +00:00
/* expect_grammar_triggered= */ false ) ;
}
2025-01-30 19:13:58 +00:00
{
2025-02-18 18:03:23 +00:00
auto tmpls = read_templates ( " models/templates/google-gemma-2-2b-it.jinja " ) ;
2025-01-30 19:13:58 +00:00
std : : vector < std : : string > end_tokens { " <end_of_turn> " } ;
2025-02-18 18:03:23 +00:00
assert_equals ( COMMON_CHAT_FORMAT_CONTENT_ONLY , common_chat_templates_apply ( tmpls . get ( ) , inputs_no_tools ) . format ) ;
assert_equals ( COMMON_CHAT_FORMAT_GENERIC , common_chat_templates_apply ( tmpls . get ( ) , inputs_tools ) . format ) ;
2025-01-30 19:13:58 +00:00
assert_equals ( COMMON_CHAT_FORMAT_GENERIC ,
2025-02-18 18:03:23 +00:00
common_chat_templates_apply (
read_templates ( " models/templates/microsoft-Phi-3.5-mini-instruct.jinja " ) . get ( ) ,
2025-01-30 19:13:58 +00:00
inputs_tools )
. format ) ;
// Generic tool calls doesn't generate / parse content-only messages symmetrically.
2025-05-26 08:03:57 -07:00
assert_equals (
simple_assist_msg ( " { \" tool_call \" : { \" name \" : \" t " ) ,
common_chat_parse (
" { \" tool_call \" : { \" name \" : \" t " ,
/* is_partial= */ true ,
{
/* .format = */ COMMON_CHAT_FORMAT_GENERIC ,
/* .reasoning_format = */ COMMON_REASONING_FORMAT_DEEPSEEK ,
/* .reasoning_in_content = */ false ,
/* .thinking_forced_open = */ true ,
/* .parse_tool_calls = */ false ,
} ) ) ;
2025-05-25 01:48:08 +01:00
assert_equals (
message_assist_empty ,
common_chat_parse (
" { \" tool_call \" : { \" name \" : \" t " ,
/* is_partial= */ true ,
{ COMMON_CHAT_FORMAT_GENERIC } ) ) ;
assert_equals (
simple_assist_msg ( " " , " " , " puppeteer_screenshot " , " { \" name \" : \" servethehome_homepage \" , " ) ,
common_chat_parse (
R " ({ " tool_call " : { " name " : " puppeteer_screenshot " , " arguments " : { " name " : " servethehome_homepage " ,) " ,
/* is_partial= */ true ,
{ COMMON_CHAT_FORMAT_GENERIC } ) ) ;
assert_equals (
message_assist_call_empty_args ,
common_chat_parse (
" { \" tool_call \" : { \" name \" : \" special_function \" " ,
/* is_partial= */ true ,
{ COMMON_CHAT_FORMAT_GENERIC } ) ) ;
assert_equals (
message_assist_call_cutoff_args ,
common_chat_parse (
" { \" tool_call \" : { \" name \" : \" special_function \" , \" arguments \" : { \" arg " ,
/* is_partial= */ true ,
{ COMMON_CHAT_FORMAT_GENERIC } ) ) ;
2025-02-18 18:03:23 +00:00
assert_msg_equals ( message_assist ,
2025-05-25 01:48:08 +01:00
common_chat_parse (
" { \n "
" \" response \" : \" Hello, world! \\ nWhat's up? \" \n "
" } " ,
/* is_partial= */ false ,
{ COMMON_CHAT_FORMAT_GENERIC } ) ) ;
2025-02-18 18:03:23 +00:00
test_templates ( tmpls . get ( ) , end_tokens , message_assist_call_id , tools ,
2025-01-30 19:13:58 +00:00
" { \n "
" \" tool_calls \" : [ \n "
" { \n "
" \" name \" : \" special_function \" , \n "
" \" arguments \" : { \n "
" \" arg1 \" : 1 \n "
" }, \n "
" \" id \" : \" 123456789 \" \n "
" } \n "
" ] \n "
" } " ) ;
}
{
2025-02-18 18:03:23 +00:00
auto tmpls = read_templates ( " models/templates/mistralai-Mistral-Nemo-Instruct-2407.jinja " ) ;
2025-01-30 19:13:58 +00:00
std : : vector < std : : string > end_tokens { " </s> " } ;
2025-02-18 18:03:23 +00:00
assert_equals ( COMMON_CHAT_FORMAT_MISTRAL_NEMO , common_chat_templates_apply ( tmpls . get ( ) , inputs_tools ) . format ) ;
2025-01-30 19:13:58 +00:00
2025-02-18 18:03:23 +00:00
test_templates ( tmpls . get ( ) , end_tokens , message_assist , tools , " Hello, world! \n What's up? " , /* expect_grammar_triggered= */ false ) ;
test_templates (
tmpls . get ( ) , end_tokens , message_assist_call_id , tools ,
2025-02-02 09:25:38 +00:00
" [TOOL_CALLS][{ \" name \" : \" special_function \" , \" arguments \" : { \" arg1 \" : 1}, \" id \" : \" 123456789 \" }] " ) ;
2025-01-30 19:13:58 +00:00
}
2025-10-03 20:51:48 +02:00
{
assert_msg_equals (
simple_assist_msg ( " Réponse " , " raisonnement " ) ,
common_chat_parse (
message_assist_thoughts_unparsed_magistral . content ,
/* is_partial= */ false ,
{
/* .format = */ COMMON_CHAT_FORMAT_MAGISTRAL ,
/* .reasoning_format = */ COMMON_REASONING_FORMAT_AUTO ,
} ) ) ;
}
2025-05-25 01:48:08 +01:00
{
auto tmpls = read_templates ( " models/templates/Qwen-QwQ-32B.jinja " ) ;
std : : vector < std : : string > end_tokens { " <|im_end|> " } ;
2025-05-26 00:30:51 +01:00
assert_equals ( COMMON_CHAT_FORMAT_HERMES_2_PRO , common_chat_templates_apply ( tmpls . get ( ) , inputs_no_tools ) . format ) ;
2025-05-25 01:48:08 +01:00
assert_equals ( COMMON_CHAT_FORMAT_HERMES_2_PRO , common_chat_templates_apply ( tmpls . get ( ) , inputs_tools ) . format ) ;
}
2025-01-30 19:13:58 +00:00
{
2025-02-18 18:03:23 +00:00
auto tmpls = read_templates ( " models/templates/NousResearch-Hermes-2-Pro-Llama-3-8B-tool_use.jinja " ) ;
2025-01-30 19:13:58 +00:00
std : : vector < std : : string > end_tokens { " <|im_end|> " } ;
2025-05-26 00:30:51 +01:00
assert_equals ( COMMON_CHAT_FORMAT_HERMES_2_PRO , common_chat_templates_apply ( tmpls . get ( ) , inputs_no_tools ) . format ) ;
2025-02-18 18:03:23 +00:00
assert_equals ( COMMON_CHAT_FORMAT_HERMES_2_PRO , common_chat_templates_apply ( tmpls . get ( ) , inputs_tools ) . format ) ;
2025-01-30 19:13:58 +00:00
assert_equals (
COMMON_CHAT_FORMAT_HERMES_2_PRO ,
2025-02-18 18:03:23 +00:00
common_chat_templates_apply (
read_templates ( " models/templates/NousResearch-Hermes-3-Llama-3.1-8B-tool_use.jinja " ) . get ( ) ,
2025-01-30 19:13:58 +00:00
inputs_tools )
. format ) ;
assert_equals (
COMMON_CHAT_FORMAT_HERMES_2_PRO ,
2025-02-18 18:03:23 +00:00
common_chat_templates_apply (
read_templates ( " models/templates/Qwen-Qwen2.5-7B-Instruct.jinja " ) . get ( ) ,
2025-01-30 19:13:58 +00:00
inputs_tools )
. format ) ;
2025-03-05 13:05:13 +00:00
// Test parsing
2025-05-25 01:48:08 +01:00
assert_msg_equals (
simple_assist_msg ( " " , " " , " python " , " " ) ,
common_chat_parse (
" ```json \n "
" <function_call> { \" name \" : \" python \" " ,
/* is_partial= */ true ,
{ COMMON_CHAT_FORMAT_HERMES_2_PRO } ) ) ;
assert_msg_equals (
simple_assist_msg ( " Let's call something \n " ) ,
common_chat_parse (
" Let's call something \n "
" <tool_call>{ \" name \" " ,
/* is_partial= */ true ,
{
/* .format = */ COMMON_CHAT_FORMAT_HERMES_2_PRO ,
/* .reasoning_format = */ COMMON_REASONING_FORMAT_DEEPSEEK ,
} ) ) ;
assert_msg_equals (
2025-05-26 08:03:57 -07:00
simple_assist_msg ( " Let's call something \n " ) ,
2025-05-25 01:48:08 +01:00
common_chat_parse (
" Let's call something \n "
" <tool_call>{ \" name " ,
/* is_partial= */ true ,
{
/* .format = */ COMMON_CHAT_FORMAT_HERMES_2_PRO ,
/* .reasoning_format = */ COMMON_REASONING_FORMAT_DEEPSEEK ,
} ) ) ;
assert_msg_equals ( message_assist_call_thoughts ,
common_chat_parse (
// QwQ-32B's template adds a trailing <think> if add_generation_prompt
" I'm \n thinking</think> \n "
" <tool_call>{ \" name \" : \" special_function \" , \" arguments \" : { \" arg1 \" : 1}}</tool_call> " ,
/* is_partial= */ false ,
{
/* .format = */ COMMON_CHAT_FORMAT_HERMES_2_PRO ,
/* .reasoning_format = */ COMMON_REASONING_FORMAT_DEEPSEEK ,
/* .reasoning_in_content = */ false ,
/* .thinking_forced_open = */ true ,
} ) ) ;
assert_msg_equals (
message_assist_call ,
common_chat_parse (
" <tool_call> \n "
" { \" name \" : \" special_function \" , \" arguments \" : { \" arg1 \" : 1}} \n "
" </tool_call> " ,
/* is_partial= */ false ,
{ COMMON_CHAT_FORMAT_HERMES_2_PRO } ) ) ;
assert_msg_equals ( message_assist_call_content ,
common_chat_parse (
" Hello, world! \n What's up?<tool_call> \n "
" { \" name \" : \" special_function \" , \" arguments \" : { \" arg1 \" : 1}} \n "
" </tool_call> " ,
/* is_partial= */ false ,
{ COMMON_CHAT_FORMAT_HERMES_2_PRO } ) ) ;
assert_msg_equals (
message_assist_call ,
common_chat_parse (
" <function=special_function>{ \" arg1 \" : 1}</function> " ,
/* is_partial= */ false ,
{ COMMON_CHAT_FORMAT_HERMES_2_PRO } ) ) ;
assert_msg_equals (
message_assist_call ,
common_chat_parse (
" <function name= \" special_function \" > \n "
" { \" arg1 \" : 1} \n "
" </function> " ,
/* is_partial= */ false ,
{ COMMON_CHAT_FORMAT_HERMES_2_PRO } ) ) ;
assert_msg_equals (
message_assist_call ,
common_chat_parse (
" <tool> \n "
" { \" name \" : \" special_function \" , \" arguments \" : { \" arg1 \" : 1}} \n "
" </tool> " ,
/* is_partial= */ false ,
{ COMMON_CHAT_FORMAT_HERMES_2_PRO } ) ) ;
assert_msg_equals (
message_assist_call ,
common_chat_parse (
" <tools> \n "
" { \" name \" : \" special_function \" , \" arguments \" : { \" arg1 \" : 1}} \n "
" </tools> " ,
/* is_partial= */ false ,
{ COMMON_CHAT_FORMAT_HERMES_2_PRO } ) ) ;
assert_msg_equals (
message_assist_call ,
common_chat_parse (
" <response> \n "
" { \" name \" : \" special_function \" , \" arguments \" : { \" arg1 \" : 1}} \n "
" </response> " ,
/* is_partial= */ false ,
{ COMMON_CHAT_FORMAT_HERMES_2_PRO } ) ) ;
assert_msg_equals (
message_assist_call ,
common_chat_parse (
" ```xml \n "
" <response> \n "
" { \" name \" : \" special_function \" , \" arguments \" : { \" arg1 \" : 1}} \n "
" </response> \n "
" ``` " ,
/* is_partial= */ false ,
{ COMMON_CHAT_FORMAT_HERMES_2_PRO } ) ) ;
assert_msg_equals (
message_assist_call ,
common_chat_parse (
" ```xml \n "
" { \" name \" : \" special_function \" , \" arguments \" : { \" arg1 \" : 1}} \n "
" ``` " ,
/* is_partial= */ false ,
{ COMMON_CHAT_FORMAT_HERMES_2_PRO } ) ) ;
assert_msg_equals (
message_assist_call ,
common_chat_parse (
" ``` \n "
" { \" name \" : \" special_function \" , \" arguments \" : { \" arg1 \" : 1}} \n "
" ``` " ,
/* is_partial= */ false ,
{ COMMON_CHAT_FORMAT_HERMES_2_PRO } ) ) ;
assert_msg_equals (
message_assist_call ,
common_chat_parse (
" ``` \n "
" { \" name \" : \" special_function \" , \" arguments \" : { \" arg1 \" : 1}} \n "
" ``` " ,
/* is_partial= */ false ,
{ COMMON_CHAT_FORMAT_HERMES_2_PRO } ) ) ;
assert_msg_equals (
message_assist_call ,
common_chat_parse (
" ```json \n "
" { \" name \" : \" special_function \" , \" arguments \" : { \" arg1 \" : 1}} \n "
" ``` " ,
/* is_partial= */ false ,
{ COMMON_CHAT_FORMAT_HERMES_2_PRO } ) ) ;
assert_msg_equals (
message_assist_call ,
common_chat_parse (
" ```json \n "
" \n "
" <function_call> { \" name \" : \" special_function \" , \" arguments \" : { \" arg1 \" : 1}} \n "
" </function_call> \n "
" ``` " ,
/* is_partial= */ false ,
{ COMMON_CHAT_FORMAT_HERMES_2_PRO } ) ) ;
assert_msg_equals (
message_assist_call ,
common_chat_parse (
" <json> \n "
" { \" name \" : \" special_function \" , \" arguments \" : { \" arg1 \" : 1}} \n "
" </json> " ,
/* is_partial= */ false ,
{ COMMON_CHAT_FORMAT_HERMES_2_PRO } ) ) ;
assert_msg_equals (
message_assist_call ,
common_chat_parse (
" <xml> \n "
" { \n "
" \" name \" : \" special_function \" , \" arguments \" : { \" arg1 \" : 1} \n "
" } \n "
" </xml> " ,
/* is_partial= */ false ,
{ COMMON_CHAT_FORMAT_HERMES_2_PRO } ) ) ;
assert_msg_equals (
message_assist_call ,
common_chat_parse (
" <JSON> \n "
" { \" name \" : \" special_function \" , \" arguments \" : { \" arg1 \" : 1}} \n "
" </JSON> " ,
/* is_partial= */ false ,
{ COMMON_CHAT_FORMAT_HERMES_2_PRO } ) ) ;
assert_msg_equals (
message_assist_call ,
common_chat_parse (
" { \" name \" : \" special_function \" , \" arguments \" : { \" arg1 \" : 1}} " ,
/* is_partial= */ false ,
{ COMMON_CHAT_FORMAT_HERMES_2_PRO } ) ) ;
assert_msg_equals (
message_assist_call ,
common_chat_parse (
" { \n \" name \" : \" special_function \" , \" arguments \" : { \" arg1 \" : 1}} " ,
/* is_partial= */ false ,
{ COMMON_CHAT_FORMAT_HERMES_2_PRO } ) ) ;
2025-08-02 18:04:48 +08:00
// Test multiple tool calls
common_chat_msg message_assist_multiple_calls ;
message_assist_multiple_calls . role = " assistant " ;
message_assist_multiple_calls . content = " " ;
message_assist_multiple_calls . tool_calls . push_back ( { " special_function " , " { \" arg1 \" : 1} " , " " } ) ;
message_assist_multiple_calls . tool_calls . push_back ( { " python " , " { \" code \" : \" print('hello') \" } " , " " } ) ;
assert_msg_equals (
message_assist_multiple_calls ,
common_chat_parse (
" <tool_call> \n "
" { \" name \" : \" special_function \" , \" arguments \" : { \" arg1 \" : 1}} \n "
" </tool_call> \n "
" <tool_call> \n "
" { \" name \" : \" python \" , \" arguments \" : { \" code \" : \" print('hello') \" }} \n "
" </tool_call> " ,
/* is_partial= */ false ,
{ COMMON_CHAT_FORMAT_HERMES_2_PRO } ) ) ;
assert_msg_equals (
message_assist_multiple_calls ,
common_chat_parse (
" <function=special_function>{ \" arg1 \" : 1}</function> \n "
" <function=python>{ \" code \" : \" print('hello') \" }</function> " ,
/* is_partial= */ false ,
{ COMMON_CHAT_FORMAT_HERMES_2_PRO } ) ) ;
2025-05-25 01:48:08 +01:00
assert_msg_equals (
simple_assist_msg (
" This is not a tool call: " ,
" " ,
" special_function " ,
" { \" arg1 \" : 1} " ) ,
common_chat_parse (
" This is not a tool call: \n "
" { \" name \" : \" special_function \" , \" arguments \" : { \" arg1 \" : 1}} " ,
/* is_partial= */ false ,
{ COMMON_CHAT_FORMAT_HERMES_2_PRO } ) ) ;
assert_msg_equals ( message_assist ,
common_chat_parse (
" Hello, world! \n What's up? " ,
/* is_partial= */ false ,
{ COMMON_CHAT_FORMAT_HERMES_2_PRO } ) ) ;
assert_msg_equals ( message_assist_thoughts_unparsed_deepseek ,
common_chat_parse (
" <think>I'm \n thinking</think>Hello, world! \n What's up? " ,
/* is_partial= */ false ,
{ COMMON_CHAT_FORMAT_HERMES_2_PRO } ) ) ;
// assert_msg_equals(message_assist_thoughts_unparsed_deepseek,
// common_chat_parse(
// "I'm\nthinking</think>Hello, world!\nWhat's up?",
// COMMON_CHAT_FORMAT_HERMES_2_PRO));
2025-03-10 10:59:03 +00:00
assert_msg_equals ( message_assist_thoughts ,
2025-05-25 01:48:08 +01:00
common_chat_parse (
" <think>I'm \n thinking</think>Hello, world! \n What's up? " ,
/* is_partial= */ false ,
{
/* .format = */ COMMON_CHAT_FORMAT_HERMES_2_PRO ,
/* .reasoning_format = */ COMMON_REASONING_FORMAT_DEEPSEEK ,
2025-05-26 08:03:57 -07:00
} ) ) ;
assert_msg_equals ( message_assist_thoughts ,
common_chat_parse (
" <think>I'm \n thinking</think>Hello, world! \n What's up? " ,
/* is_partial= */ true ,
{
/* .format = */ COMMON_CHAT_FORMAT_HERMES_2_PRO ,
/* .reasoning_format = */ COMMON_REASONING_FORMAT_DEEPSEEK ,
} ) ) ;
assert_msg_equals ( message_assist_thoughts_unparsed_md ,
common_chat_parse (
" <think>I'm \n thinking</think>Hello, world! \n What's up? \n ```json \n {}``` " ,
/* is_partial= */ false ,
{
/* .format = */ COMMON_CHAT_FORMAT_HERMES_2_PRO ,
/* .reasoning_format = */ COMMON_REASONING_FORMAT_DEEPSEEK ,
/* .reasoning_in_content = */ true ,
/* .thinking_forced_open = */ false ,
/* .parse_tool_calls = */ false ,
} ) ) ;
assert_msg_equals ( message_assist_thoughts_unparsed_md_partial ,
common_chat_parse (
" <think>I'm \n thinking</think>Hello, world! \n What's up? \n ```json \n {}``` " ,
/* is_partial= */ true ,
{
/* .format = */ COMMON_CHAT_FORMAT_HERMES_2_PRO ,
/* .reasoning_format = */ COMMON_REASONING_FORMAT_DEEPSEEK ,
/* .reasoning_in_content = */ true ,
2025-05-25 01:48:08 +01:00
/* .thinking_forced_open = */ false ,
} ) ) ;
assert_msg_equals ( message_assist_thoughts_unopened_unparsed ,
common_chat_parse (
" I'm \n thinking</think>Hello, world! \n What's up? " ,
/* is_partial= */ false ,
{
/* .format = */ COMMON_CHAT_FORMAT_HERMES_2_PRO ,
/* .reasoning_format = */ COMMON_REASONING_FORMAT_DEEPSEEK ,
} ) ) ;
2025-03-10 10:59:03 +00:00
assert_msg_equals ( message_assist_thoughts ,
2025-05-25 01:48:08 +01:00
common_chat_parse (
" I'm \n thinking</think>Hello, world! \n What's up? " ,
/* is_partial= */ false ,
{
/* .format = */ COMMON_CHAT_FORMAT_HERMES_2_PRO ,
/* .reasoning_format = */ COMMON_REASONING_FORMAT_DEEPSEEK ,
/* .reasoning_in_content = */ false ,
/* .thinking_forced_open = */ true ,
} ) ) ;
2025-03-10 10:59:03 +00:00
2025-02-18 18:03:23 +00:00
test_templates ( tmpls . get ( ) , end_tokens , message_assist , tools , " Hello, world! \n What's up? " , /* expect_grammar_triggered= */ false ) ;
test_templates ( tmpls . get ( ) , end_tokens , message_assist_call , tools ,
2025-01-30 19:13:58 +00:00
" <tool_call> \n "
" { \" name \" : \" special_function \" , \" arguments \" : { \" arg1 \" : 1}} \n "
" </tool_call> " ) ;
2025-08-02 18:04:48 +08:00
// Test multiple tool calls with template
common_chat_msg message_assist_multiple_calls_template ;
message_assist_multiple_calls_template . role = " assistant " ;
message_assist_multiple_calls_template . content = " " ;
message_assist_multiple_calls_template . tool_calls . push_back ( { " special_function " , " { \" arg1 \" : 1} " , " " } ) ;
message_assist_multiple_calls_template . tool_calls . push_back ( { " python " , " { \" code \" : \" print('test') \" } " , " " } ) ;
test_templates ( tmpls . get ( ) , end_tokens , message_assist_multiple_calls_template , tools ,
" <tool_call> \n "
" { \" name \" : \" special_function \" , \" arguments \" : { \" arg1 \" : 1}} \n "
" </tool_call> \n "
" <tool_call> \n "
" { \" name \" : \" python \" , \" arguments \" : { \" code \" : \" print('test') \" }} \n "
" </tool_call> " ) ;
2025-05-25 01:48:08 +01:00
test_templates ( tmpls . get ( ) , end_tokens , message_assist_call_python_lines , tools ,
2025-01-30 19:13:58 +00:00
" <tool_call> \n "
2025-05-25 01:48:08 +01:00
" { \" name \" : \" python \" , \" arguments \" : { \" code \" : \" # This is a program: \\ nprint('hey') \" }} \n "
2025-01-30 19:13:58 +00:00
" </tool_call> " ) ;
2025-05-31 08:26:10 -07:00
assert_msg_equals (
simple_assist_msg ( " " , /* reasoning_content= */ " <tool_call>nah uhg</tool_call> " ) ,
common_chat_parse (
" <think><tool_call>nah uhg</tool_call> " ,
/* is_partial= */ false ,
{
/* .format = */ COMMON_CHAT_FORMAT_HERMES_2_PRO ,
/* .reasoning_format = */ COMMON_REASONING_FORMAT_DEEPSEEK ,
} ) ) ;
2025-01-30 19:13:58 +00:00
}
{
2025-02-18 18:03:23 +00:00
auto tmpls = read_templates ( " models/templates/meta-llama-Llama-3.1-8B-Instruct.jinja " ) ;
2025-01-30 19:13:58 +00:00
std : : vector < std : : string > end_tokens { " <|eom_id|> " , " <|eot_id|> " } ;
2025-04-11 12:47:52 -07:00
assert_equals ( COMMON_CHAT_FORMAT_CONTENT_ONLY , common_chat_templates_apply ( tmpls . get ( ) , inputs_no_tools ) . format ) ;
2025-02-18 18:03:23 +00:00
assert_equals ( COMMON_CHAT_FORMAT_LLAMA_3_X , common_chat_templates_apply ( tmpls . get ( ) , inputs_tools ) . format ) ;
2025-01-30 19:13:58 +00:00
assert_equals ( COMMON_CHAT_FORMAT_LLAMA_3_X_WITH_BUILTIN_TOOLS ,
2025-02-18 18:03:23 +00:00
common_chat_templates_apply ( tmpls . get ( ) , inputs_tools_builtin ) . format ) ;
2025-01-30 19:13:58 +00:00
assert_equals ( COMMON_CHAT_FORMAT_LLAMA_3_X_WITH_BUILTIN_TOOLS ,
2025-02-18 18:03:23 +00:00
common_chat_templates_apply (
read_templates ( " models/templates/meta-llama-Llama-3.3-70B-Instruct.jinja " ) . get ( ) ,
2025-01-30 19:13:58 +00:00
inputs_tools_builtin )
. format ) ;
2025-05-25 01:48:08 +01:00
assert_equals (
message_assist_call ,
common_chat_parse (
" { \" name \" : \" special_function \" , \" parameters \" : { \" arg1 \" : 1}} " ,
/* is_partial= */ false ,
{ COMMON_CHAT_FORMAT_LLAMA_3_X } ) ) ;
2025-02-18 18:03:23 +00:00
// test_templates(tmpls.get(), end_tokens, message_assist, tools, R"(?)", /* expect_grammar_triggered= */ false);
test_templates ( tmpls . get ( ) , end_tokens , message_assist_call_code_interpreter , llama_3_1_tools ,
2025-01-30 19:13:58 +00:00
" <|python_tag|>code_interpreter.call(code= \" print('hey') \" ) " ) ;
2025-02-18 18:03:23 +00:00
test_templates ( tmpls . get ( ) , end_tokens , message_assist_call_python , tools ,
2025-01-30 19:13:58 +00:00
" <|python_tag|>python.call(code= \" print('hey') \" ) " ) ;
2025-02-18 18:03:23 +00:00
test_templates ( tmpls . get ( ) , end_tokens , message_assist_call , tools ,
2025-01-30 19:13:58 +00:00
" { \" name \" : \" special_function \" , \" parameters \" : { \" arg1 \" : 1}} " ) ;
}
{
2025-02-18 18:03:23 +00:00
auto tmpls = read_templates ( " models/templates/meta-llama-Llama-3.2-3B-Instruct.jinja " ) ;
2025-01-30 19:13:58 +00:00
std : : vector < std : : string > end_tokens { " <|eom_id|> " , " <|eot_id|> " } ;
2025-02-18 18:03:23 +00:00
assert_equals ( COMMON_CHAT_FORMAT_LLAMA_3_X , common_chat_templates_apply ( tmpls . get ( ) , inputs_tools ) . format ) ;
2025-04-11 12:47:52 -07:00
assert_equals ( COMMON_CHAT_FORMAT_CONTENT_ONLY , common_chat_templates_apply ( tmpls . get ( ) , inputs_no_tools ) . format ) ;
2025-01-30 19:13:58 +00:00
2025-02-18 18:03:23 +00:00
test_templates ( tmpls . get ( ) , end_tokens , message_assist , tools , " Hello, world! \n What's up? " , /* expect_grammar_triggered= */ false ) ;
test_templates ( tmpls . get ( ) , end_tokens , message_assist_call , tools ,
2025-01-30 19:13:58 +00:00
" { \" name \" : \" special_function \" , \" parameters \" : { \" arg1 \" : 1}} " ) ;
}
{
2025-02-18 18:03:23 +00:00
auto tmpls = read_templates ( " models/templates/meetkai-functionary-medium-v3.1.jinja " ) ;
2025-01-30 19:13:58 +00:00
std : : vector < std : : string > end_tokens { " <|eom_id|> " , " <|eot_id|> " } ;
2025-04-11 12:47:52 -07:00
assert_equals ( COMMON_CHAT_FORMAT_CONTENT_ONLY ,
common_chat_templates_apply ( tmpls . get ( ) , inputs_no_tools ) . format ) ;
2025-01-30 19:13:58 +00:00
assert_equals ( COMMON_CHAT_FORMAT_FUNCTIONARY_V3_1_LLAMA_3_1 ,
2025-05-15 02:39:51 +01:00
common_chat_templates_apply ( tmpls . get ( ) , inputs_tools ) . format ) ;
assert_equals ( COMMON_CHAT_FORMAT_CONTENT_ONLY ,
common_chat_templates_apply ( tmpls . get ( ) , inputs_no_tools ) . format ) ;
2025-01-30 19:13:58 +00:00
2025-05-25 01:48:08 +01:00
for ( auto is_partial : { false , true } ) {
assert_equals (
message_assist_call ,
common_chat_parse (
" <function=special_function>{ \" arg1 \" : 1}</function> " ,
is_partial ,
{ COMMON_CHAT_FORMAT_FUNCTIONARY_V3_1_LLAMA_3_1 } ) ) ;
}
2025-05-26 08:03:57 -07:00
assert_equals (
message_assist_call ,
common_chat_parse (
" <function=special_function>{ \" arg1 \" : 1}< " ,
/* is_partial= */ true ,
{ COMMON_CHAT_FORMAT_FUNCTIONARY_V3_1_LLAMA_3_1 } ) ) ;
2025-02-18 18:03:23 +00:00
test_templates ( tmpls . get ( ) , end_tokens , message_assist , tools , " Hello, world! \n What's up? " , /* expect_grammar_triggered= */ false ) ;
test_templates ( tmpls . get ( ) , end_tokens , message_assist_call , tools ,
2025-01-30 19:13:58 +00:00
" <function=special_function>{ \" arg1 \" : 1}</function> " ) ;
}
{
2025-02-18 18:03:23 +00:00
auto tmpls = read_templates ( " models/templates/meetkai-functionary-medium-v3.2.jinja " ) ;
2025-01-30 19:13:58 +00:00
std : : vector < std : : string > end_tokens { " <|eom_id|> " , " <|eot_id|> " } ;
2025-02-18 18:03:23 +00:00
assert_equals ( COMMON_CHAT_FORMAT_FUNCTIONARY_V3_2 , common_chat_templates_apply ( tmpls . get ( ) , inputs_no_tools ) . format ) ;
assert_equals ( COMMON_CHAT_FORMAT_FUNCTIONARY_V3_2 , common_chat_templates_apply ( tmpls . get ( ) , inputs_tools ) . format ) ;
2025-01-30 19:13:58 +00:00
2025-05-25 01:48:08 +01:00
assert_msg_equals (
simple_assist_msg (
" Hello, world! \n nono \n What's up? " ,
" " ,
" special_function " ,
" { \" arg1 \" : 1} " ) ,
common_chat_parse (
" all \n "
" Hello, world! \n "
" nono \n "
" What's up?>>>special_function \n "
" { \" arg1 \" : 1} \n " ,
/* is_partial= */ false ,
{ COMMON_CHAT_FORMAT_FUNCTIONARY_V3_2 } ) ) ;
assert_msg_equals ( message_assist_call_python_lines ,
common_chat_parse (
" python \n "
" # This is a program: \n "
" print('hey') " ,
/* is_partial= */ false ,
{ COMMON_CHAT_FORMAT_FUNCTIONARY_V3_2 } ) ) ;
assert_msg_equals ( message_assist_call_python_lines_unclosed ,
common_chat_parse (
" python \n "
" # This is a program: \n "
" print('hey') " ,
/* is_partial= */ true ,
{ COMMON_CHAT_FORMAT_FUNCTIONARY_V3_2 } ) ) ;
assert_msg_equals ( message_assist_call ,
common_chat_parse (
" special_function \n "
" { \" arg1 \" : 1} \n " ,
/* is_partial= */ false ,
{ COMMON_CHAT_FORMAT_FUNCTIONARY_V3_2 } ) ) ;
assert_msg_equals ( message_assist ,
common_chat_parse (
" all \n "
" Hello, world! \n What's up? " ,
/* is_partial= */ false ,
{ COMMON_CHAT_FORMAT_FUNCTIONARY_V3_2 } ) ) ;
2025-02-18 18:03:23 +00:00
test_templates ( tmpls . get ( ) , end_tokens , message_assist , { } ,
2025-01-30 19:13:58 +00:00
" all \n "
2025-02-04 15:48:53 +00:00
" Hello, world! \n "
" What's up? " ,
2025-02-02 09:25:38 +00:00
/* expect_grammar_triggered= */ false ) ;
2025-02-18 18:03:23 +00:00
test_templates ( tmpls . get ( ) , end_tokens , message_assist_call , tools ,
2025-01-30 19:13:58 +00:00
" special_function \n "
" { \" arg1 \" : 1} " ) ;
}
{
2025-02-18 18:03:23 +00:00
auto tmpls = read_templates ( " models/templates/fireworks-ai-llama-3-firefunction-v2.jinja " ) ;
2025-01-30 19:13:58 +00:00
std : : vector < std : : string > end_tokens { " <|eot_id|> " } ;
2025-04-11 12:47:52 -07:00
assert_equals ( COMMON_CHAT_FORMAT_CONTENT_ONLY , common_chat_templates_apply ( tmpls . get ( ) , inputs_no_tools ) . format ) ;
2025-02-18 18:03:23 +00:00
assert_equals ( COMMON_CHAT_FORMAT_FIREFUNCTION_V2 , common_chat_templates_apply ( tmpls . get ( ) , inputs_tools ) . format ) ;
2025-01-30 19:13:58 +00:00
2025-02-18 18:03:23 +00:00
test_templates ( tmpls . get ( ) , end_tokens , message_assist , tools , " Hello, world! \n What's up? " , /* expect_grammar_triggered= */ false ) ;
test_templates ( tmpls . get ( ) , end_tokens , message_assist_call , tools ,
2025-01-30 19:13:58 +00:00
" functools[{ \" name \" : \" special_function \" , \" arguments \" : { \" arg1 \" : 1}}] " ) ;
}
{
2025-02-13 10:05:16 +00:00
// Original DeepSeek R1 template. Leaves <| tool▁calls▁begin| > and others unclosed. Our logic fixes the prompt.
2025-02-18 18:03:23 +00:00
auto tmpls = read_templates ( " models/templates/deepseek-ai-DeepSeek-R1-Distill-Llama-8B.jinja " ) ;
2025-01-30 19:13:58 +00:00
std : : vector < std : : string > end_tokens { " <| end▁of▁sentence| > " } ;
2025-05-25 01:48:08 +01:00
for ( const auto & inputs : { inputs_no_tools , inputs_tools } ) {
auto params = common_chat_templates_apply ( tmpls . get ( ) , inputs ) ;
assert_equals ( COMMON_CHAT_FORMAT_DEEPSEEK_R1 , params . format ) ;
assert_equals ( true , params . thinking_forced_open ) ;
}
2025-02-13 10:05:16 +00:00
2025-02-18 18:03:23 +00:00
test_templates ( tmpls . get ( ) , end_tokens , message_assist , tools , " Hello, world! \n What's up? " , /* expect_grammar_triggered= */ false ) ;
test_templates ( tmpls . get ( ) , end_tokens , message_assist_thoughts , tools , " Hello, world! \n What's up? " , /* expect_grammar_triggered= */ false ) ;
2025-05-25 01:48:08 +01:00
assert_msg_equals (
simple_assist_msg ( " Hello, world! \n What's up? " , " <think>I'm \n thinking " ) ,
common_chat_parse (
" <think>I'm \n thinking</think>Hello, world! \n What's up? " ,
/* is_partial= */ false ,
{
COMMON_CHAT_FORMAT_DEEPSEEK_R1 ,
/* .reasoning_format = */ COMMON_REASONING_FORMAT_DEEPSEEK ,
/* .reasoning_in_content = */ false ,
/* .thinking_forced_open = */ true ,
} ) ) ;
assert_msg_equals (
simple_assist_msg ( " " , " I need to remember the correct syntax. It starts with <| tool▁calls▁begin| > and ends with " ) ,
common_chat_parse (
" I need to remember the correct syntax. It starts with <| tool▁calls▁begin| > and ends with " ,
/* is_partial= */ true ,
{
COMMON_CHAT_FORMAT_DEEPSEEK_R1 ,
/* .reasoning_format = */ COMMON_REASONING_FORMAT_DEEPSEEK ,
/* .reasoning_in_content = */ false ,
/* .thinking_forced_open = */ true ,
} ) ) ;
assert_msg_equals ( message_assist_thoughts ,
common_chat_parse (
" <think>I'm \n thinking</think>Hello, world! \n What's up? " ,
/* is_partial= */ false ,
{
/* .format = */ COMMON_CHAT_FORMAT_DEEPSEEK_R1 ,
/* .reasoning_format = */ COMMON_REASONING_FORMAT_DEEPSEEK ,
} ) ) ;
assert_msg_equals ( message_assist_thoughts_unopened_unparsed ,
common_chat_parse (
" I'm \n thinking</think>Hello, world! \n What's up? " ,
/* is_partial= */ false ,
{
/* .format = */ COMMON_CHAT_FORMAT_DEEPSEEK_R1 ,
/* .reasoning_format = */ COMMON_REASONING_FORMAT_DEEPSEEK ,
} ) ) ;
2025-02-18 18:03:23 +00:00
assert_msg_equals ( message_assist_thoughts ,
2025-05-25 01:48:08 +01:00
common_chat_parse (
" I'm \n thinking</think>Hello, world! \n What's up? " ,
/* is_partial= */ false ,
{
/* .format = */ COMMON_CHAT_FORMAT_DEEPSEEK_R1 ,
/* .reasoning_format = */ COMMON_REASONING_FORMAT_DEEPSEEK ,
/* .reasoning_in_content = */ false ,
/* .thinking_forced_open = */ true ,
} ) ) ;
2025-02-18 18:03:23 +00:00
assert_msg_equals ( message_assist_thoughts ,
2025-02-13 10:05:16 +00:00
// Latest template update (ast of 20250209) adds a trailing <think>\n if add_generation_prompt is true.
2025-05-25 01:48:08 +01:00
common_chat_parse (
" I'm \n thinking</think>Hello, world! \n What's up? " ,
/* is_partial= */ false ,
{
/* .format = */ COMMON_CHAT_FORMAT_DEEPSEEK_R1 ,
/* .reasoning_format = */ COMMON_REASONING_FORMAT_DEEPSEEK ,
/* .reasoning_in_content = */ false ,
/* .thinking_forced_open = */ true ,
} ) ) ;
2025-02-18 18:03:23 +00:00
// test_templates(tmpls.get(), end_tokens, message_assist_call, tools,
2025-02-13 10:05:16 +00:00
// "<| tool▁calls▁begin| ><| tool▁call▁begin| >function<| tool▁sep| >special_function\n"
// "```json\n"
// "{\"arg1\": 1}\n"
// // Look what's not here: <| tool▁calls▁end| > (also missing the <| end▁of▁sentence| >, but that is removed lazily by the test's delta logic)
// "```<| tool▁call▁end| >",
// /* expect_grammar_triggered= */ true,
// /* test_grammar_if_triggered= */ false);
}
{
// Replacement DeepSeek R1 template. Makes the Distill Qwen 7B/32B models happy to call tools and all.
2025-02-18 18:03:23 +00:00
auto tmpls = read_templates ( " models/templates/llama-cpp-deepseek-r1.jinja " ) ;
2025-02-13 10:05:16 +00:00
std : : vector < std : : string > end_tokens { " <| end▁of▁sentence| > " } ;
2025-01-30 19:13:58 +00:00
2025-04-11 12:47:52 -07:00
assert_equals ( COMMON_CHAT_FORMAT_DEEPSEEK_R1 , common_chat_templates_apply ( tmpls . get ( ) , inputs_no_tools ) . format ) ;
2025-02-18 18:03:23 +00:00
assert_equals ( COMMON_CHAT_FORMAT_DEEPSEEK_R1 , common_chat_templates_apply ( tmpls . get ( ) , inputs_tools ) . format ) ;
2025-02-13 10:05:16 +00:00
2025-02-18 18:03:23 +00:00
test_templates ( tmpls . get ( ) , end_tokens , message_assist , tools , " Hello, world! \n What's up? " , /* expect_grammar_triggered= */ false ) ;
test_templates ( tmpls . get ( ) , end_tokens , message_assist_thoughts , tools , " Hello, world! \n What's up? " , /* expect_grammar_triggered= */ false ) ;
2025-05-25 01:48:08 +01:00
assert_msg_equals ( message_assist_thoughts_unparsed_deepseek ,
common_chat_parse (
" <think>I'm \n thinking</think>Hello, world! \n What's up? " ,
/* is_partial= */ false ,
{ COMMON_CHAT_FORMAT_DEEPSEEK_R1 } ) ) ;
2025-02-18 18:03:23 +00:00
assert_msg_equals ( message_assist_thoughts ,
2025-05-25 01:48:08 +01:00
common_chat_parse (
" <think>I'm \n thinking</think>Hello, world! \n What's up? " ,
/* is_partial= */ false ,
{
/* .format = */ COMMON_CHAT_FORMAT_DEEPSEEK_R1 ,
/* .reasoning_format = */ COMMON_REASONING_FORMAT_DEEPSEEK ,
} ) ) ;
assert_msg_equals ( message_assist_thoughts ,
common_chat_parse (
" I'm \n thinking</think>Hello, world! \n What's up? " ,
/* is_partial= */ false ,
{
/* .format = */ COMMON_CHAT_FORMAT_DEEPSEEK_R1 ,
/* .reasoning_format = */ COMMON_REASONING_FORMAT_DEEPSEEK ,
/* .reasoning_in_content = */ false ,
/* .thinking_forced_open = */ true ,
} ) ) ;
2025-02-13 10:05:16 +00:00
2025-02-18 18:03:23 +00:00
assert_msg_equals ( message_assist_call_thoughts_unparsed ,
2025-02-13 10:05:16 +00:00
common_chat_parse (
" <think>I'm \n thinking</think> \n \n "
" <| tool▁calls▁begin| ><| tool▁call▁begin| >function<| tool▁sep| >special_function \n "
" ```json \n "
" { \" arg1 \" : 1} \n "
" ```<| tool▁call▁end| ><| tool▁calls▁end| > " ,
2025-05-25 01:48:08 +01:00
/* is_partial= */ false ,
{ COMMON_CHAT_FORMAT_DEEPSEEK_R1 } ) ) ;
assert_msg_equals ( message_assist_call ,
common_chat_parse (
" <| tool▁calls| >function<| tool▁sep| >special_function \n "
" ```json \n "
" { \" arg1 \" : 1} \n "
" ```<| tool▁call▁end| ><| tool▁calls▁end| > " ,
/* is_partial= */ false ,
{ COMMON_CHAT_FORMAT_DEEPSEEK_R1 } ) ) ;
2025-02-18 18:03:23 +00:00
assert_msg_equals ( message_assist_call_thoughts ,
2025-02-13 10:05:16 +00:00
common_chat_parse (
" <think>I'm \n thinking</think> \n \n "
" <| tool▁calls▁begin| ><| tool▁call▁begin| >function<| tool▁sep| >special_function \n "
" ```json \n "
" { \" arg1 \" : 1} \n "
" ```<| tool▁call▁end| ><| tool▁calls▁end| > " ,
2025-05-25 01:48:08 +01:00
/* is_partial= */ false ,
{
/* .format = */ COMMON_CHAT_FORMAT_DEEPSEEK_R1 ,
/* .reasoning_format = */ COMMON_REASONING_FORMAT_DEEPSEEK ,
} ) ) ;
2025-02-18 18:03:23 +00:00
test_templates ( tmpls . get ( ) , end_tokens , message_assist_call , tools ,
2025-02-13 10:05:16 +00:00
" <| tool▁calls▁begin| ><| tool▁call▁begin| >function<| tool▁sep| >special_function \n "
" ```json \n "
" { \" arg1 \" : 1} \n "
" ```<| tool▁call▁end| ><| tool▁calls▁end| > " ) ;
2025-01-30 19:13:58 +00:00
}
2025-08-06 11:27:30 -07:00
{
auto tmpls = read_templates ( " models/templates/ibm-granite-granite-3.3-2B-Instruct.jinja " ) ;
std : : vector < std : : string > end_tokens { " <|end_of_text|> " } ;
assert_equals ( COMMON_CHAT_FORMAT_GRANITE , common_chat_templates_apply ( tmpls . get ( ) , inputs_no_tools ) . format ) ;
assert_equals ( COMMON_CHAT_FORMAT_GRANITE , common_chat_templates_apply ( tmpls . get ( ) , inputs_tools ) . format ) ;
// Test parsing regular content
assert_msg_equals ( message_assist ,
common_chat_parse (
" Hello, world! \n What's up? " ,
/* is_partial= */ false ,
{ COMMON_CHAT_FORMAT_GRANITE } ) ) ;
2025-09-20 00:57:30 +09:00
assert_msg_equals (
message_assist ,
common_chat_parse (
" Hello, world! \n What's up? " ,
/* is_partial= */ true ,
{ COMMON_CHAT_FORMAT_GRANITE } ) ) ;
2025-08-06 11:27:30 -07:00
// Test parsing content with thinking
assert_msg_equals ( message_assist_thoughts ,
common_chat_parse (
" <think>I'm \n thinking</think>Hello, world! \n What's up? " ,
/* is_partial= */ false ,
{
/* .format = */ COMMON_CHAT_FORMAT_GRANITE ,
2025-08-19 10:29:36 +02:00
/* .reasoning_format = */ COMMON_REASONING_FORMAT_DEEPSEEK ,
2025-08-06 11:27:30 -07:00
} ) ) ;
2025-09-20 00:57:30 +09:00
assert_msg_equals ( message_assist_thoughts_unparsed_deepseek ,
common_chat_parse (
" <think>I'm \n thinking</think>Hello, world! \n What's up? " ,
/* is_partial= */ false ,
{ COMMON_CHAT_FORMAT_GRANITE } ) ) ;
assert_msg_equals ( message_assist_thoughts ,
common_chat_parse (
" <think>I'm \n thinking</think><response>Hello, world! \n What's up? " ,
/* is_partial= */ true ,
{
/* .format = */ COMMON_CHAT_FORMAT_GRANITE ,
/* .reasoning_format = */ COMMON_REASONING_FORMAT_DEEPSEEK ,
} ) ) ;
assert_msg_equals ( message_assist_thoughts ,
common_chat_parse (
" <think>I'm \n thinking</think><response>Hello, world! \n What's up?</response> " ,
/* is_partial= */ false ,
{
/* .format = */ COMMON_CHAT_FORMAT_GRANITE ,
/* .reasoning_format = */ COMMON_REASONING_FORMAT_DEEPSEEK ,
} ) ) ;
assert_msg_equals ( simple_assist_msg ( " <think>I'm \n thinking</think><response>Hello, world! \n What's up?</response> " ) ,
common_chat_parse (
" <think>I'm \n thinking</think><response>Hello, world! \n What's up?</response> " ,
/* is_partial= */ false ,
{ COMMON_CHAT_FORMAT_GRANITE } ) ) ;
assert_msg_equals ( message_assist_empty ,
common_chat_parse (
" <think " ,
/* is_partial= */ true ,
{
/* .format = */ COMMON_CHAT_FORMAT_GRANITE ,
/* .reasoning_format = */ COMMON_REASONING_FORMAT_DEEPSEEK ,
} ) ) ;
assert_msg_equals ( message_assist_empty ,
common_chat_parse (
" <think " ,
/* is_partial= */ true ,
{ COMMON_CHAT_FORMAT_GRANITE } ) ) ;
assert_msg_equals ( message_assist_thoughts_no_content ,
common_chat_parse (
" <think>I'm \n thinking " ,
/* is_partial= */ true ,
{
/* .format = */ COMMON_CHAT_FORMAT_GRANITE ,
/* .reasoning_format = */ COMMON_REASONING_FORMAT_DEEPSEEK ,
} ) ) ;
assert_msg_equals (
message_assist_empty ,
common_chat_parse (
" <think>I'm \n thinking</think><response " ,
/* is_partial= */ true ,
{ COMMON_CHAT_FORMAT_GRANITE } ) ) ;
2025-08-06 11:27:30 -07:00
// Test parsing tool calls
assert_msg_equals ( message_assist_call ,
common_chat_parse (
" <|tool_call|>[{ \" name \" : \" special_function \" , \" arguments \" : { \" arg1 \" : 1}}] " ,
/* is_partial= */ false ,
{ COMMON_CHAT_FORMAT_GRANITE } ) ) ;
2025-09-20 00:57:30 +09:00
assert_msg_equals (
message_assist_call_empty_args ,
common_chat_parse (
" <|tool_call|>[{ \" name \" : \" special_function \" " ,
/* is_partial= */ true ,
{ COMMON_CHAT_FORMAT_GRANITE } ) ) ;
assert_msg_equals (
message_assist_call_cutoff_args ,
common_chat_parse (
" <|tool_call|>[{ \" name \" : \" special_function \" , \" arguments \" : { \" arg " ,
/* is_partial= */ true ,
{ COMMON_CHAT_FORMAT_GRANITE } ) ) ;
assert_msg_equals (
message_assist_call_cutoff_args ,
common_chat_parse (
" <|tool_call|>[{ \" name \" : \" special_function \" , \" arguments \" : { \" arg " ,
/* is_partial= */ true ,
{
/* .format = */ COMMON_CHAT_FORMAT_GRANITE ,
/* .reasoning_format = */ COMMON_REASONING_FORMAT_DEEPSEEK ,
} ) ) ;
// Test parsing tool calls with thinking
assert_msg_equals (
message_assist_call_thoughts ,
common_chat_parse (
" <think>I'm \n thinking</think><|tool_call|>[{ \" name \" : \" special_function \" , \" arguments \" : { \" arg1 \" : 1}, { " ,
/* is_partial= */ true ,
{
/* .format = */ COMMON_CHAT_FORMAT_GRANITE ,
/* .reasoning_format = */ COMMON_REASONING_FORMAT_DEEPSEEK ,
} ) ) ;
2025-08-06 11:27:30 -07:00
// Test template generation for regular content
test_templates ( tmpls . get ( ) , end_tokens , message_assist , tools ,
" Hello, world! \n What's up? " ,
/* expect_grammar_triggered= */ false ) ;
// Test template generation for tool calls
test_templates ( tmpls . get ( ) , end_tokens , message_assist_call_id , tools ,
" { \n "
" \" tool_calls \" : [ \n "
" { \n "
" \" name \" : \" special_function \" , \n "
" \" arguments \" : { \n "
" \" arg1 \" : 1 \n "
" }, \n "
" \" id \" : \" 123456789 \" \n "
" } \n "
" ] \n "
" } " ,
/* expect_grammar_triggered= */ false
) ;
}
2025-08-14 09:23:11 -05:00
{
auto tmpls = read_templates ( " models/templates/openai-gpt-oss-120b.jinja " ) ;
std : : vector < std : : string > end_tokens { " <|return|> " , " <|call|> " } ;
assert_equals ( COMMON_CHAT_FORMAT_GPT_OSS , common_chat_templates_apply ( tmpls . get ( ) , inputs_no_tools ) . format ) ;
assert_equals ( COMMON_CHAT_FORMAT_GPT_OSS , common_chat_templates_apply ( tmpls . get ( ) , inputs_tools ) . format ) ;
assert_msg_equals ( simple_assist_msg ( " " , " I'm \n think " ) ,
common_chat_parse (
" <|channel|>analysis<|message|>I'm \n think " ,
/* is_partial= */ true ,
{
/* .format = */ COMMON_CHAT_FORMAT_GPT_OSS ,
/* .reasoning_format = */ COMMON_REASONING_FORMAT_AUTO ,
} ) ) ;
assert_msg_equals ( simple_assist_msg ( " " , " I'm \n thinking " ) ,
common_chat_parse (
" <|channel|>analysis<|message|>I'm \n thinking<|end|> " ,
/* is_partial= */ true ,
{
/* .format = */ COMMON_CHAT_FORMAT_GPT_OSS ,
/* .reasoning_format = */ COMMON_REASONING_FORMAT_AUTO ,
} ) ) ;
assert_msg_equals ( simple_assist_msg ( " Hello, world! \n What's up? " , " I'm \n thinking " ) ,
common_chat_parse (
" <|channel|>analysis<|message|>I'm \n thinking<|end|> "
" <|start|>assistant<|channel|>final<|message|>Hello, world! \n What's up? " ,
/* is_partial= */ false ,
{
/* .format = */ COMMON_CHAT_FORMAT_GPT_OSS ,
/* .reasoning_format = */ COMMON_REASONING_FORMAT_AUTO ,
} ) ) ;
assert_msg_equals ( simple_assist_msg ( " " , " I'm \n thinking " , " special_function " , " { \" arg1 " ) ,
common_chat_parse (
" <|channel|>analysis<|message|>I'm \n thinking<|end|> "
" <|start|>assistant<|channel|>commentary to=functions.special_function <|constrain|>json<|message|>{ \" arg1 " ,
/* is_partial= */ true ,
{
/* .format = */ COMMON_CHAT_FORMAT_GPT_OSS ,
/* .reasoning_format = */ COMMON_REASONING_FORMAT_AUTO ,
} ) ) ;
assert_msg_equals ( simple_assist_msg ( " " , " I'm \n thinking " , " special_function " , " { \" arg1 " ) ,
common_chat_parse (
" <|channel|>analysis<|message|>I'm \n thinking<|end|> "
" <|start|>assistant<|channel|>commentary to=functions.special_function<|message|>{ \" arg1 " ,
/* is_partial= */ true ,
{
/* .format = */ COMMON_CHAT_FORMAT_GPT_OSS ,
/* .reasoning_format = */ COMMON_REASONING_FORMAT_AUTO ,
} ) ) ;
assert_msg_equals ( simple_assist_msg ( " " , " I'm \n thinking " , " special_function " , " { \" arg1 \" : 1} " ) ,
common_chat_parse (
" <|channel|>analysis<|message|>I'm \n thinking<|end|> "
" <|start|>assistant<|channel|>commentary to=functions.special_function <|constrain|>json<|message|>{ \" arg1 \" : 1} " ,
/* is_partial= */ false ,
{
/* .format = */ COMMON_CHAT_FORMAT_GPT_OSS ,
/* .reasoning_format = */ COMMON_REASONING_FORMAT_AUTO ,
} ) ) ;
assert_msg_equals ( simple_assist_msg ( " " , " I'm \n thinking " , " special_function " , " { \" arg1 \" : 1} " ) ,
common_chat_parse (
" <|channel|>analysis<|message|>I'm \n thinking<|end|> "
" <|start|>assistant<|channel|>analysis to=functions.special_function <|constrain|>json<|message|>{ \" arg1 \" : 1} " ,
/* is_partial= */ false ,
{
/* .format = */ COMMON_CHAT_FORMAT_GPT_OSS ,
/* .reasoning_format = */ COMMON_REASONING_FORMAT_AUTO ,
} ) ) ;
assert_msg_equals ( simple_assist_msg ( " Hello, world! \n What's up? " , " I'm \n thinking " ) ,
common_chat_parse (
" <|channel|>analysis<|message|>I'm \n thinking<|end|> "
" <|start|>assistant<|channel|>commentary<|message|>Hello, world! \n What's up? " ,
/* is_partial= */ true ,
{
/* .format = */ COMMON_CHAT_FORMAT_GPT_OSS ,
/* .reasoning_format = */ COMMON_REASONING_FORMAT_AUTO ,
} ) ) ;
assert_msg_equals ( simple_assist_msg ( " Hello, world! \n What's up? " , " I'm \n thinking " , " special_function " , " { \" arg1 \" : 1} " ) ,
common_chat_parse (
" <|channel|>analysis<|message|>I'm \n thinking<|end|> "
" <|start|>assistant<|channel|>commentary<|message|>Hello, world! \n What's up?<|end|> "
" <|start|>assistant<|channel|>commentary to=functions.special_function <|constrain|>json<|message|>{ \" arg1 \" : 1} " ,
/* is_partial= */ true ,
{
/* .format = */ COMMON_CHAT_FORMAT_GPT_OSS ,
/* .reasoning_format = */ COMMON_REASONING_FORMAT_AUTO ,
} ) ) ;
// Test parse_tool_calls == false
assert_msg_equals (
simple_assist_msg ( " Hello, world! \n What's up? " , " I'm \n thinking " ) ,
common_chat_parse (
" <|channel|>analysis<|message|>I'm \n thinking<|end|> "
" <|start|>assistant<|channel|>final<|message|>Hello, world! \n What's up? " ,
/* is_partial= */ true ,
{
/* .format = */ COMMON_CHAT_FORMAT_GPT_OSS ,
/* .reasoning_format = */ COMMON_REASONING_FORMAT_AUTO ,
/* .reasoning_in_content = */ false ,
/* .thinking_forced_open = */ false ,
/* .parse_tool_calls = */ false ,
} ) ) ;
assert_msg_equals (
simple_assist_msg ( " " , " I'm \n thinking " ) ,
common_chat_parse (
" <|channel|>analysis<|message|>I'm \n thinking<|end|> "
" <|start|>assistant<|channel|>commentary to=functions.special_function<|message|>{ \" arg1 " ,
/* is_partial= */ true ,
{
/* .format = */ COMMON_CHAT_FORMAT_GPT_OSS ,
/* .reasoning_format = */ COMMON_REASONING_FORMAT_AUTO ,
/* .reasoning_in_content = */ false ,
/* .thinking_forced_open = */ false ,
/* .parse_tool_calls = */ false ,
} ) ) ;
assert_msg_equals (
simple_assist_msg ( " " , " I'm \n thinking " ) ,
common_chat_parse (
" <|channel|>analysis<|message|>I'm \n thinking<|end|> "
" <|start|>assistant<|channel|>commentary to=functions.special_function <|constrain|>json<|message|>{ \" arg1 \" : 1} " ,
/* is_partial= */ false ,
{
/* .format = */ COMMON_CHAT_FORMAT_GPT_OSS ,
/* .reasoning_format = */ COMMON_REASONING_FORMAT_AUTO ,
/* .reasoning_in_content = */ false ,
/* .thinking_forced_open = */ false ,
/* .parse_tool_calls = */ false ,
} ) ) ;
// Test reasoning formats
assert_msg_equals (
simple_assist_msg (
" <|channel|>analysis<|message|>I'm \n thinking<|end|>Hello, world! \n What's up? " ) ,
common_chat_parse (
" <|channel|>analysis<|message|>I'm \n thinking<|end|> "
" <|start|>assistant<|channel|>final<|message|>Hello, world! \n What's up? " ,
/* is_partial= */ false ,
{
/* .format = */ COMMON_CHAT_FORMAT_GPT_OSS ,
/* .reasoning_format = */ COMMON_REASONING_FORMAT_NONE ,
} ) ) ;
assert_msg_equals (
simple_assist_msg (
" <|channel|>analysis<|message|>I'm \n thinking<|end|>Hello, world! \n What's up? " ) ,
common_chat_parse (
" <|channel|>analysis<|message|>I'm \n thinking<|end|> "
" <|start|>assistant<|channel|>final<|message|>Hello, world! \n What's up? " ,
/* is_partial= */ false ,
{
/* .format = */ COMMON_CHAT_FORMAT_GPT_OSS ,
/* .reasoning_format = */ COMMON_REASONING_FORMAT_AUTO ,
/* .reasoning_in_content = */ true ,
} ) ) ;
// Test tool calling in role header
assert_msg_equals ( simple_assist_msg ( " " , " " , " special_function " , " { \" arg1 \" : 1} " ) ,
common_chat_parse (
" to=functions.special_function<|channel|>commentary <|constrain|>json<|message|>{ \" arg1 \" : 1} " ,
/* is_partial= */ false ,
{
/* .format = */ COMMON_CHAT_FORMAT_GPT_OSS ,
/* .reasoning_format = */ COMMON_REASONING_FORMAT_AUTO ,
} ) ) ;
assert_msg_equals ( simple_assist_msg ( " " , " " , " special_function " , " { \" arg1 \" : 1} " ) ,
common_chat_parse (
" to=functions.special_function<|channel|>analysis <|constrain|>json<|message|>{ \" arg1 \" : 1} " ,
/* is_partial= */ false ,
{
/* .format = */ COMMON_CHAT_FORMAT_GPT_OSS ,
/* .reasoning_format = */ COMMON_REASONING_FORMAT_AUTO ,
} ) ) ;
assert_msg_equals ( simple_assist_msg ( " " , " I'm \n thinking " , " special_function " , " { \" arg1 \" : 1} " ) ,
common_chat_parse (
" <|channel|>analysis<|message|>I'm \n thinking<|end|> "
" <|start|>assistant to=functions.special_function<|channel|>analysis <|constrain|>json<|message|>{ \" arg1 \" : 1} " ,
/* is_partial= */ false ,
{
/* .format = */ COMMON_CHAT_FORMAT_GPT_OSS ,
/* .reasoning_format = */ COMMON_REASONING_FORMAT_AUTO ,
} ) ) ;
}
2025-08-29 14:53:41 +02:00
{
// Seed-OSS format tests
auto tmpls = read_templates ( " models/templates/ByteDance-Seed-OSS.jinja " ) ;
std : : vector < std : : string > end_tokens { " <seed:eos> " } ;
assert_equals ( COMMON_CHAT_FORMAT_SEED_OSS , common_chat_templates_apply ( tmpls . get ( ) , inputs_no_tools ) . format ) ;
assert_equals ( COMMON_CHAT_FORMAT_SEED_OSS , common_chat_templates_apply ( tmpls . get ( ) , inputs_tools ) . format ) ;
test_templates ( tmpls . get ( ) , end_tokens , message_assist , tools , " Hello, world! \n What's up? " , /* expect_grammar_triggered= */ false ) ;
// Test simple reasoning content
assert_msg_equals (
simple_assist_msg ( " Hello, world! " , " I'm thinking about the answer " ) ,
common_chat_parse (
" <seed:think>I'm thinking about the answer</seed:think>Hello, world! " ,
/* is_partial= */ false ,
{
/* .format = */ COMMON_CHAT_FORMAT_SEED_OSS ,
/* .reasoning_format = */ COMMON_REASONING_FORMAT_DEEPSEEK ,
} ) ) ;
// Test budget reflection tags
common_chat_msg msg_budget_reflect ;
msg_budget_reflect . role = " assistant " ;
msg_budget_reflect . content = " <seed:cot_budget_reflect>Token usage: 45/1000 \n I should continue thinking to find the best solution.</seed:cot_budget_reflect>I need to calculate this step by step. " ;
msg_budget_reflect . reasoning_content = " Token usage: 45/1000 \n I should continue thinking to find the best solution. " ;
assert_msg_equals (
msg_budget_reflect ,
common_chat_parse (
" <seed:think>Token usage: 45/1000 \n I should continue thinking to find the best solution.</seed:think> "
" <seed:cot_budget_reflect>Token usage: 45/1000 \n I should continue thinking to find the best solution.</seed:cot_budget_reflect> "
" I need to calculate this step by step. " ,
/* is_partial= */ false ,
{
/* .format = */ COMMON_CHAT_FORMAT_SEED_OSS ,
/* .reasoning_format = */ COMMON_REASONING_FORMAT_DEEPSEEK ,
} ) ) ;
// Test tool calls with Seed-OSS format
common_chat_msg msg_tool_call ;
msg_tool_call . role = " assistant " ;
msg_tool_call . tool_calls . push_back ( { " calculate_sum " , " { \" numbers \" : [1, 2, 3]} " , " " } ) ;
assert_msg_equals (
msg_tool_call ,
common_chat_parse (
" <seed:tool_call> \n "
" <function=calculate_sum> \n "
" <parameter=numbers>[1, 2, 3]</parameter> \n "
" </function> \n "
" </seed:tool_call> " ,
/* is_partial= */ false ,
{ COMMON_CHAT_FORMAT_SEED_OSS } ) ) ;
// Test reasoning + tool call combination
common_chat_msg msg_reasoning_tool ;
msg_reasoning_tool . role = " assistant " ;
msg_reasoning_tool . content = " " ;
msg_reasoning_tool . reasoning_content = " I need to calculate the sum of these numbers " ;
msg_reasoning_tool . tool_calls . push_back ( { " calculate_sum " , " { \" numbers \" : [1, 2, 3]} " , " " } ) ;
assert_msg_equals (
msg_reasoning_tool ,
common_chat_parse (
" <seed:think>I need to calculate the sum of these numbers</seed:think> "
" <seed:tool_call> \n "
" <function=calculate_sum> \n "
" <parameter=numbers>[1, 2, 3]</parameter> \n "
" </function> \n "
" </seed:tool_call> " ,
/* is_partial= */ false ,
{
/* .format = */ COMMON_CHAT_FORMAT_SEED_OSS ,
/* .reasoning_format = */ COMMON_REASONING_FORMAT_DEEPSEEK ,
} ) ) ;
// Test deltas: the number of tool calls in partial parses should never decrease
std : : string tool_msg = " <seed:tool_call> \n "
" <function=fun> \n "
" <parameter=smth>[1, 2, 3]</parameter> \n "
" </function> " ;
std : : size_t previousToolCalls = 0 ;
for ( std : : size_t i = std : : string ( " <seed:tool_call> " ) . length ( ) ; i < tool_msg . length ( ) - 1 ; i + + ) {
auto partial = tool_msg . substr ( 0 , i ) ;
auto partial_res = common_chat_parse ( partial , true , { COMMON_CHAT_FORMAT_SEED_OSS , COMMON_REASONING_FORMAT_DEEPSEEK } ) ;
if ( partial_res . tool_calls . size ( ) < previousToolCalls ) {
throw std : : runtime_error ( " Tool call size decreased on partial: " + partial + " from " + std : : to_string ( previousToolCalls ) + " to " + std : : to_string ( partial_res . tool_calls . size ( ) ) ) ;
}
previousToolCalls = partial_res . tool_calls . size ( ) ;
}
// Test multiple parameters in tool call
common_chat_msg msg_multi_param ;
msg_multi_param . role = " assistant " ;
msg_multi_param . tool_calls . push_back ( { " process_data " , " { \" input \" : \" test \" , \" format \" : \" json \" } " , " " } ) ;
assert_msg_equals (
msg_multi_param ,
common_chat_parse (
" <seed:tool_call> \n "
" <function=process_data> \n "
" <parameter=input>test</parameter> \n "
" <parameter=format>json</parameter> \n "
" </function> \n "
" </seed:tool_call> " ,
/* is_partial= */ false ,
{ COMMON_CHAT_FORMAT_SEED_OSS } ) ) ;
// Test partial parsing for incomplete tool call - don't actually add the call until parsing parameters is done
assert_msg_equals (
simple_assist_msg ( " " , " " ) ,
common_chat_parse (
" <seed:tool_call> \n "
" <function=calculate_sum> \n "
" <parameter=numbers>[1, \n " ,
/* is_partial= */ true ,
{ COMMON_CHAT_FORMAT_SEED_OSS } ) ) ;
// Test incomplete reasoning tag
assert_msg_equals (
simple_assist_msg ( " " , " I was thinking " ) ,
common_chat_parse (
" <seed:think>I was thinking " ,
/* is_partial= */ true ,
{
/* .format = */ COMMON_CHAT_FORMAT_SEED_OSS ,
/* .reasoning_format = */ COMMON_REASONING_FORMAT_DEEPSEEK ,
} ) ) ;
// Test content without reasoning
assert_msg_equals (
simple_assist_msg ( " This is a simple response without reasoning. " ) ,
common_chat_parse (
" This is a simple response without reasoning. " ,
/* is_partial= */ false ,
{ COMMON_CHAT_FORMAT_SEED_OSS } ) ) ;
}
2025-09-05 01:22:22 +02:00
{
auto tmpls = read_templates ( " models/templates/NVIDIA-Nemotron-Nano-v2.jinja " ) ;
std : : vector < std : : string > end_tokens { " <SPECIAL_12> " } ;
assert_equals ( COMMON_CHAT_FORMAT_NEMOTRON_V2 , common_chat_templates_apply ( tmpls . get ( ) , inputs_no_tools ) . format ) ;
assert_equals ( COMMON_CHAT_FORMAT_NEMOTRON_V2 , common_chat_templates_apply ( tmpls . get ( ) , inputs_tools ) . format ) ;
// Test parsing regular content
assert_msg_equals ( message_assist ,
common_chat_parse (
" Hello, world! \n What's up? " ,
/* is_partial= */ false ,
{ COMMON_CHAT_FORMAT_NEMOTRON_V2 } ) ) ;
// Test parsing content with thinking
assert_msg_equals ( message_assist_thoughts ,
common_chat_parse (
" <think>I'm \n thinking</think>Hello, world! \n What's up? " ,
/* is_partial= */ false ,
{
/* .format = */ COMMON_CHAT_FORMAT_NEMOTRON_V2 ,
/* .reasoning_format = */ COMMON_REASONING_FORMAT_DEEPSEEK ,
} ) ) ;
// Test parsing tool calls
assert_msg_equals ( message_assist_call ,
common_chat_parse (
" <TOOLCALL>[{ \" name \" : \" special_function \" , \" arguments \" : { \" arg1 \" : 1}}]</TOOLCALL> " ,
/* is_partial= */ false ,
{ COMMON_CHAT_FORMAT_NEMOTRON_V2 } ) ) ;
// Test parsing tool calls with thinking
assert_msg_equals ( message_assist_call_thoughts ,
common_chat_parse (
" <think>I'm \n thinking</think><TOOLCALL>[{ \" name \" : \" special_function \" , \" arguments \" : { \" arg1 \" : 1}}]</TOOLCALL> " ,
/* is_partial= */ false ,
{
/* .format = */ COMMON_CHAT_FORMAT_NEMOTRON_V2 ,
/* .reasoning_format = */ COMMON_REASONING_FORMAT_DEEPSEEK
} ) ) ;
// Test tool calls with extra content
assert_msg_equals ( message_assist_call_content ,
common_chat_parse (
" <TOOLCALL>[{ \" name \" : \" special_function \" , \" arguments \" : { \" arg1 \" : 1}}]</TOOLCALL>Hello, world! \n What's up? " ,
/* is_partial= */ false ,
{ COMMON_CHAT_FORMAT_NEMOTRON_V2 }
) ) ;
// Test tool calls with extra content AND thinking
assert_msg_equals ( message_assist_call_thoughts_content ,
common_chat_parse (
" <think>I'm \n thinking</think><TOOLCALL>[{ \" name \" : \" special_function \" , \" arguments \" : { \" arg1 \" : 1}}]</TOOLCALL>Hello, world! \n What's up? " ,
/* is_partial= */ false ,
{
/* .format = */ COMMON_CHAT_FORMAT_NEMOTRON_V2 ,
/* .reasoning_format = */ COMMON_REASONING_FORMAT_DEEPSEEK
} ) ) ;
// Test template generation for regular content
test_templates ( tmpls . get ( ) , end_tokens , message_assist , tools ,
" Hello, world! \n What's up? \n " ,
/* expect_grammar_triggered= */ false ) ;
// Test template generation for tool calls
test_templates ( tmpls . get ( ) , end_tokens , message_assist_call , tools ,
" <TOOLCALL>[{ \" name \" : \" special_function \" , \" arguments \" : { \" arg1 \" : 1}}]</TOOLCALL> " ,
/* expect_grammar_triggered= */ true
) ;
}
2025-09-08 10:59:48 -04:00
{
auto tmpls = read_templates ( " models/templates/deepseek-ai-DeepSeek-V3.1.jinja " ) ;
std : : vector < std : : string > end_tokens { " <| end▁of▁sentence| > " } ;
for ( const auto & inputs : { inputs_no_tools , inputs_tools } ) {
auto params = common_chat_templates_apply ( tmpls . get ( ) , inputs ) ;
assert_equals ( COMMON_CHAT_FORMAT_DEEPSEEK_V3_1 , params . format ) ;
assert_equals ( true , params . thinking_forced_open ) ;
}
test_templates ( tmpls . get ( ) , end_tokens , message_assist , tools , " </think>Hello, world! \n What's up? " , /* expect_grammar_triggered= */ false ) ;
test_templates ( tmpls . get ( ) , end_tokens , message_assist_thoughts , tools , " </think>Hello, world! \n What's up? " , /* expect_grammar_triggered= */ false ) ;
assert_msg_equals (
simple_assist_msg ( " Hello, world! \n What's up? " , " I'm \n thinking " ) ,
common_chat_parse (
" I'm \n thinking</think>Hello, world! \n What's up? " ,
/* is_partial= */ false ,
{
COMMON_CHAT_FORMAT_DEEPSEEK_V3_1 ,
/* .reasoning_format = */ COMMON_REASONING_FORMAT_DEEPSEEK ,
/* .reasoning_in_content = */ false ,
/* .thinking_forced_open = */ true ,
} ) ) ;
// variant: thinking forced open, reasoning_format none
assert_msg_equals (
simple_assist_msg ( " REASONING</think>ok " , " " ) ,
common_chat_parse (
" REASONING</think>ok " ,
/* is_partial= */ false ,
{
COMMON_CHAT_FORMAT_DEEPSEEK_V3_1 ,
/* .reasoning_format = */ COMMON_REASONING_FORMAT_NONE ,
/* .reasoning_in_content = */ false ,
/* .thinking_forced_open = */ true ,
/* .parse_tool_calls = */ true ,
} ) ) ;
// variant: happy path for when it works as the model card says it should
assert_msg_equals (
simple_assist_msg ( " " , " " , " get_time " , " { \" city \" : \" Tokyo \" } " ) ,
common_chat_parse (
" <| tool▁calls▁begin| ><| tool▁call▁begin| >get_time<| tool▁sep| >{ \" city \" : \" Tokyo \" }<| tool▁call▁end| ><| tool▁calls▁end| > " ,
/* is_partial= */ false ,
{
COMMON_CHAT_FORMAT_DEEPSEEK_V3_1 ,
/* .reasoning_format = */ COMMON_REASONING_FORMAT_DEEPSEEK ,
/* .reasoning_in_content = */ false ,
/* .thinking_forced_open = */ false ,
/* .parse_tool_calls = */ true ,
} ) ) ;
// variant: simple + thinking open
assert_msg_equals (
simple_assist_msg ( " " , " REASONING " , " get_time " , " { \" city \" : \" Tokyo \" } " ) ,
common_chat_parse (
" REASONING</think><| tool▁calls▁begin| ><| tool▁call▁begin| >get_time<| tool▁sep| >{ \" city \" : \" Tokyo \" }<| tool▁call▁end| ><| tool▁calls▁end| > " ,
/* is_partial= */ false ,
{
COMMON_CHAT_FORMAT_DEEPSEEK_V3_1 ,
/* .reasoning_format = */ COMMON_REASONING_FORMAT_DEEPSEEK ,
/* .reasoning_in_content = */ false ,
/* .thinking_forced_open = */ true ,
/* .parse_tool_calls = */ true ,
} ) ) ;
// variant: simple + multiple tool calls
common_chat_msg message_assist_multiple_calls ;
message_assist_multiple_calls . role = " assistant " ;
message_assist_multiple_calls . content = " CONTENT " ;
message_assist_multiple_calls . tool_calls . push_back ( { " get_time " , " { \" city \" : \" Paris \" } " , " " } ) ;
message_assist_multiple_calls . tool_calls . push_back ( { " get_weather " , " { \" city \" : \" Paris \" } " , " " } ) ;
assert_msg_equals (
message_assist_multiple_calls ,
common_chat_parse (
" CONTENT<| tool▁calls▁begin| ><| tool▁call▁begin| >get_time<| tool▁sep| >{ \" city \" : \" Paris \" }<| tool▁call▁end| ><| tool▁call▁begin| >get_weather<| tool▁sep| >{ \" city \" : \" Paris \" }<| tool▁call▁end| ><| tool▁calls▁end| > " ,
/* is_partial= */ false ,
{
COMMON_CHAT_FORMAT_DEEPSEEK_V3_1 ,
/* .reasoning_format = */ COMMON_REASONING_FORMAT_DEEPSEEK ,
/* .reasoning_in_content = */ false ,
/* .thinking_forced_open = */ false ,
/* .parse_tool_calls = */ true ,
} ) ) ;
// variant: thinking forced open + tool call in reasoning content
assert_msg_equals (
simple_assist_msg ( " " , " REASONING<| tool▁calls▁begin| ><| tool▁call▁begin| >get_time2<| tool▁sep| >{ \" city \" : \" Tokyo2 \" }<| tool▁call▁end| ><| tool▁calls▁end| >REASONING " , " get_time " , " { \" city \" : \" Tokyo \" } " ) ,
common_chat_parse (
" REASONING<| tool▁calls▁begin| ><| tool▁call▁begin| >get_time2<| tool▁sep| >{ \" city \" : \" Tokyo2 \" }<| tool▁call▁end| ><| tool▁calls▁end| >REASONING</think><| tool▁calls▁begin| ><| tool▁call▁begin| >get_time<| tool▁sep| >{ \" city \" : \" Tokyo \" }<| tool▁call▁end| ><| tool▁calls▁end| > " ,
/* is_partial= */ false ,
{
COMMON_CHAT_FORMAT_DEEPSEEK_V3_1 ,
/* .reasoning_format = */ COMMON_REASONING_FORMAT_DEEPSEEK ,
/* .reasoning_in_content = */ false ,
/* .thinking_forced_open = */ true ,
/* .parse_tool_calls = */ true ,
} ) ) ;
// variant: thinking forced open + tool call in reasoning content + no closing think + not partial
// This is a bit of a fine tuning issue on the model's part IMO. It really should not be attempting
// to make tool calls in reasoning content according to the model card, but it does sometimes, so
// add the reasoning content as regular content and parse the tool calls.
assert_msg_equals (
simple_assist_msg ( " REASONING " , " " , " get_time " , " { \" city \" : \" Tokyo \" } " ) ,
common_chat_parse (
" REASONING<| tool▁calls▁begin| ><| tool▁call▁begin| >get_time<| tool▁sep| >{ \" city \" : \" Tokyo \" }<| tool▁call▁end| ><| tool▁calls▁end| > " ,
/* is_partial= */ false ,
{
COMMON_CHAT_FORMAT_DEEPSEEK_V3_1 ,
/* .reasoning_format = */ COMMON_REASONING_FORMAT_DEEPSEEK ,
/* .reasoning_in_content = */ false ,
/* .thinking_forced_open = */ true ,
/* .parse_tool_calls = */ true ,
} ) ) ;
// variant: thinking forced open + tool call in reasoning content + no closing think + partial
assert_msg_equals (
simple_assist_msg ( " " , " REASONING<| tool▁calls▁begin| ><| tool▁call▁begin| >get_time<| tool▁sep| >{ \" city \" : \" Tokyo \" }<| tool▁call▁end| ><| tool▁calls▁end| > " , " " , " " ) ,
common_chat_parse (
" REASONING<| tool▁calls▁begin| ><| tool▁call▁begin| >get_time<| tool▁sep| >{ \" city \" : \" Tokyo \" }<| tool▁call▁end| ><| tool▁calls▁end| > " ,
/* is_partial= */ true ,
{
COMMON_CHAT_FORMAT_DEEPSEEK_V3_1 ,
/* .reasoning_format = */ COMMON_REASONING_FORMAT_DEEPSEEK ,
/* .reasoning_in_content = */ false ,
/* .thinking_forced_open = */ true ,
/* .parse_tool_calls = */ true ,
} ) ) ;
// variant: thinking not forced open + missing reasoning + no tool calls
assert_msg_equals (
simple_assist_msg ( " CONTENT " , " " ) ,
common_chat_parse (
" CONTENT " ,
/* is_partial= */ false ,
{
COMMON_CHAT_FORMAT_DEEPSEEK_V3_1 ,
/* .reasoning_format = */ COMMON_REASONING_FORMAT_DEEPSEEK ,
/* .reasoning_in_content = */ false ,
/* .thinking_forced_open = */ false ,
/* .parse_tool_calls = */ true ,
} ) ) ;
}
2025-10-02 19:43:22 +02:00
{
auto tmpls = read_templates ( " models/templates/Apertus-8B-Instruct.jinja " ) ;
std : : vector < std : : string > end_tokens { " <|assistant_end|> " } ;
assert_equals ( COMMON_CHAT_FORMAT_APERTUS , common_chat_templates_apply ( tmpls . get ( ) , inputs_no_tools ) . format ) ;
assert_equals ( COMMON_CHAT_FORMAT_APERTUS , common_chat_templates_apply ( tmpls . get ( ) , inputs_tools ) . format ) ;
// Test parsing regular content
assert_msg_equals ( message_assist ,
common_chat_parse (
" Hello, world! \n What's up? " ,
/* is_partial= */ false ,
{ COMMON_CHAT_FORMAT_APERTUS } ) ) ;
// Test parsing content with thinking
assert_msg_equals ( message_assist_thoughts ,
common_chat_parse (
" <|inner_prefix|>I'm \n thinking<|inner_suffix|>Hello, world! \n What's up? " ,
/* is_partial= */ false ,
{
/* .format = */ COMMON_CHAT_FORMAT_APERTUS ,
/* .reasoning_format = */ COMMON_REASONING_FORMAT_DEEPSEEK ,
} ) ) ;
// Test parsing tool calls
assert_msg_equals ( message_assist_call ,
common_chat_parse (
" <|tools_prefix|>[{ \" special_function \" : { \" arg1 \" : 1}}]<|tools_suffix|> " ,
/* is_partial= */ false ,
{ COMMON_CHAT_FORMAT_APERTUS } ) ) ;
// Test parsing tool calls with thinking
assert_msg_equals ( message_assist_call_thoughts ,
common_chat_parse (
" <|inner_prefix|>I'm \n thinking<|inner_suffix|><|tools_prefix|>[{ \" special_function \" : { \" arg1 \" : 1}}]<|tools_suffix|> " ,
/* is_partial= */ false ,
{
/* .format = */ COMMON_CHAT_FORMAT_APERTUS ,
/* .reasoning_format = */ COMMON_REASONING_FORMAT_DEEPSEEK
} ) ) ;
// Test tool calls with extra content
assert_msg_equals ( message_assist_call_content ,
common_chat_parse (
" <|tools_prefix|>[{ \" special_function \" : { \" arg1 \" : 1}}]<|tools_suffix|>Hello, world! \n What's up? " ,
/* is_partial= */ false ,
{ COMMON_CHAT_FORMAT_APERTUS }
) ) ;
// Test tool calls with extra content AND thinking
assert_msg_equals ( message_assist_call_thoughts_content ,
common_chat_parse (
" <|inner_prefix|>I'm \n thinking<|inner_suffix|><|tools_prefix|>[{ \" special_function \" : { \" arg1 \" : 1}}]<|tools_suffix|>Hello, world! \n What's up? " ,
/* is_partial= */ false ,
{
/* .format = */ COMMON_CHAT_FORMAT_APERTUS ,
/* .reasoning_format = */ COMMON_REASONING_FORMAT_DEEPSEEK
} ) ) ;
// Test template generation for regular content
test_templates ( tmpls . get ( ) , end_tokens , message_assist , tools ,
" Hello, world! \n What's up? " ,
/* expect_grammar_triggered= */ false ) ;
// Test template generation for tool calls
test_templates ( tmpls . get ( ) , end_tokens , message_assist_call , tools ,
" <|tools_prefix|>[{ \" special_function \" : { \" arg1 \" : 1}}]<|tools_suffix|> " ,
/* expect_grammar_triggered= */ true
) ;
assert_equals ( true , common_chat_templates_support_enable_thinking ( tmpls . get ( ) ) ) ;
}
2025-10-27 18:54:01 -04:00
{
// LFM2 format tests
auto tmpls = read_templates ( " models/templates/llama-cpp-lfm2.jinja " ) ;
std : : vector < std : : string > end_tokens { " <|im_end|> " } ;
auto inputs_tools_forced_json_schema = std : : invoke ( [ & ] ( ) - > common_chat_templates_inputs {
common_chat_templates_inputs inputs ;
inputs . messages = {
std : : invoke ( [ & ] ( ) - > common_chat_msg {
common_chat_msg msg ;
msg . role = " system " ;
msg . content = " force json schema. \n " ;
return msg ;
} ) ,
message_user ,
} ;
inputs . tools = { special_function_tool } ;
return inputs ;
} ) ;
{
auto params = common_chat_templates_apply ( tmpls . get ( ) , inputs_no_tools ) ;
assert_equals ( COMMON_CHAT_FORMAT_CONTENT_ONLY , params . format ) ;
assert_equals ( false , params . grammar_lazy ) ;
assert_equals ( std : : string ( R " (<|im_start|>user
Hey there ! < | im_end | >
< | im_start | > assistant
) " ), params.prompt);
}
{
auto params = common_chat_templates_apply ( tmpls . get ( ) , inputs_tools ) ;
assert_equals ( COMMON_CHAT_FORMAT_CONTENT_ONLY , params . format ) ;
assert_equals ( false , params . grammar_lazy ) ;
assert_equals ( std : : string ( R " (<|im_start|>system
List of tools : < | tool_list_start | > [ { " type " : " function " , " function " : { " name " : " special_function " , " description " : " I'm special " , " parameters " : { " type " : " object " , " properties " : { " arg1 " : { " type " : " integer " , " description " : " The arg. " } } , " required " : [ " arg1 " ] } } } ] < | tool_list_end | > < | im_end | >
< | im_start | > user
Hey there ! < | im_end | >
< | im_start | > assistant
) " ), params.prompt);
assert_equals ( true , params . grammar . empty ( ) ) ;
}
{
auto params = common_chat_templates_apply ( tmpls . get ( ) , inputs_tools_forced_json_schema ) ;
assert_equals ( COMMON_CHAT_FORMAT_LFM2_WITH_JSON_TOOLS , params . format ) ;
assert_equals ( true , params . grammar_lazy ) ;
assert_equals ( std : : string ( R " (<|im_start|>system
List of tools : < | tool_list_start | > [ { " type " : " function " , " function " : { " name " : " special_function " , " description " : " I'm special " , " parameters " : { " type " : " object " , " properties " : { " arg1 " : { " type " : " integer " , " description " : " The arg. " } } , " required " : [ " arg1 " ] } } } ] < | tool_list_end | > < | im_end | >
< | im_start | > user
Hey there ! < | im_end | >
< | im_start | > assistant
) " ), params.prompt);
assert_equals ( false , params . grammar . empty ( ) ) ;
}
// Test parsing regular content
assert_msg_equals ( message_assist ,
common_chat_parse (
" Hello, world! \n What's up? " ,
/* is_partial= */ false ,
{ COMMON_CHAT_FORMAT_LFM2_WITH_JSON_TOOLS } ) ) ;
// Test single tool call with JSON format
common_chat_msg msg_single_tool_call ;
msg_single_tool_call . role = " assistant " ;
msg_single_tool_call . tool_calls . push_back ( { " special_function " , " { \" arg1 \" :1} " , " " } ) ;
assert_msg_equals (
msg_single_tool_call ,
common_chat_parse (
" <|tool_call_start|>[{ \" name \" : \" special_function \" , \" arguments \" : { \" arg1 \" : 1}}]<|tool_call_end|> " ,
/* is_partial= */ false ,
{ COMMON_CHAT_FORMAT_LFM2_WITH_JSON_TOOLS } ) ) ;
// Test tool call with string argument
common_chat_msg msg_tool_call_string ;
msg_tool_call_string . role = " assistant " ;
msg_tool_call_string . tool_calls . push_back ( { " get_weather " , " { \" location \" : \" Paris \" } " , " " } ) ;
assert_msg_equals (
msg_tool_call_string ,
common_chat_parse (
" <|tool_call_start|>[{ \" name \" : \" get_weather \" , \" arguments \" : { \" location \" : \" Paris \" }}]<|tool_call_end|> " ,
/* is_partial= */ false ,
{ COMMON_CHAT_FORMAT_LFM2_WITH_JSON_TOOLS } ) ) ;
// Test tool call with multiple arguments
common_chat_msg msg_multi_args ;
msg_multi_args . role = " assistant " ;
msg_multi_args . tool_calls . push_back ( { " calculate " , " { \" x \" :10, \" y \" :20, \" operation \" : \" add \" } " , " " } ) ;
assert_msg_equals (
msg_multi_args ,
common_chat_parse (
" <|tool_call_start|>[{ \" name \" : \" calculate \" , \" arguments \" : { \" x \" : 10, \" y \" : 20, \" operation \" : \" add \" }}]<|tool_call_end|> " ,
/* is_partial= */ false ,
{ COMMON_CHAT_FORMAT_LFM2_WITH_JSON_TOOLS } ) ) ;
// Test multiple tool calls in single array
common_chat_msg msg_multiple_tools ;
msg_multiple_tools . role = " assistant " ;
msg_multiple_tools . tool_calls . push_back ( { " get_weather " , " { \" location \" : \" Paris \" } " , " " } ) ;
msg_multiple_tools . tool_calls . push_back ( { " get_time " , " { \" timezone \" : \" UTC \" } " , " " } ) ;
assert_msg_equals (
msg_multiple_tools ,
common_chat_parse (
" <|tool_call_start|>[{ \" name \" : \" get_weather \" , \" arguments \" : { \" location \" : \" Paris \" }}, { \" name \" : \" get_time \" , \" arguments \" : { \" timezone \" : \" UTC \" }}]<|tool_call_end|> " ,
/* is_partial= */ false ,
{ COMMON_CHAT_FORMAT_LFM2_WITH_JSON_TOOLS } ) ) ;
// Test tool call with content before
common_chat_msg msg_content_before_tool ;
msg_content_before_tool . role = " assistant " ;
msg_content_before_tool . content = " Let me check the weather for you. " ;
msg_content_before_tool . tool_calls . push_back ( { " get_weather " , " { \" location \" : \" Paris \" } " , " " } ) ;
assert_msg_equals (
msg_content_before_tool ,
common_chat_parse (
" Let me check the weather for you.<|tool_call_start|>[{ \" name \" : \" get_weather \" , \" arguments \" : { \" location \" : \" Paris \" }}]<|tool_call_end|> " ,
/* is_partial= */ false ,
{ COMMON_CHAT_FORMAT_LFM2_WITH_JSON_TOOLS } ) ) ;
// Test tool call with content after
common_chat_msg msg_content_after_tool ;
msg_content_after_tool . role = " assistant " ;
msg_content_after_tool . content = " Here's the result. " ;
msg_content_after_tool . tool_calls . push_back ( { " get_weather " , " { \" location \" : \" Paris \" } " , " " } ) ;
assert_msg_equals (
msg_content_after_tool ,
common_chat_parse (
" <|tool_call_start|>[{ \" name \" : \" get_weather \" , \" arguments \" : { \" location \" : \" Paris \" }}]<|tool_call_end|>Here's the result. " ,
/* is_partial= */ false ,
{ COMMON_CHAT_FORMAT_LFM2_WITH_JSON_TOOLS } ) ) ;
// Test tool call with newlines (common in LLM output)
common_chat_msg msg_tool_call_newlines ;
msg_tool_call_newlines . role = " assistant " ;
msg_tool_call_newlines . tool_calls . push_back ( { " get_current_time " , " { \" location \" : \" Paris \" } " , " " } ) ;
assert_msg_equals (
msg_tool_call_newlines ,
common_chat_parse (
" <|tool_call_start|>[{ \n \" name \" : \" get_current_time \" , \n \" arguments \" : { \n \" location \" : \" Paris \" \n } \n }]<|tool_call_end|> " ,
/* is_partial= */ false ,
{ COMMON_CHAT_FORMAT_LFM2_WITH_JSON_TOOLS } ) ) ;
// Note: LFM2 uses JSON format for tool calls: [{"name": "...", "arguments": {...}}]
// Unlike other formats, LFM2 template does not render tool calls in conversation history,
// so we don't use test_templates() for tool call generation. Instead, the parsing tests
// above verify edge cases and format variations for the tool call output format.
}
2025-10-02 19:43:22 +02:00
2025-01-30 19:13:58 +00:00
}
2025-05-25 01:48:08 +01:00
static void test_msg_diffs_compute ( ) {
printf ( " [%s] \n " , __func__ ) ;
{
common_chat_msg msg1 ;
common_chat_msg msg2 ;
msg2 . content = " Hello, world! " ;
common_chat_msg_diff diff ;
diff . content_delta = " Hello, world! " ;
assert_equals (
{ diff } ,
common_chat_msg_diff : : compute_diffs ( msg1 , msg2 ) ) ;
}
{
common_chat_msg msg1 ;
msg1 . content = " Hello, " ;
common_chat_msg msg2 ;
msg2 . content = " Hello, world! " ;
common_chat_msg_diff diff ;
diff . content_delta = " world! " ;
assert_equals (
{ diff } ,
common_chat_msg_diff : : compute_diffs ( msg1 , msg2 ) ) ;
}
{
common_chat_msg msg0 ;
common_chat_msg msg1 ;
msg1 . tool_calls = { { " special_function " , " { \" ar " , /* .id = */ " 123 " } } ;
common_chat_msg msg2 ;
msg2 . tool_calls = { { " special_function " , " { \" arg1 \" : 1} " , /* .id = */ " 123 " } } ;
common_chat_msg_diff diff01 ;
diff01 . tool_call_index = 0 ;
diff01 . tool_call_delta . name = " special_function " ;
diff01 . tool_call_delta . id = " 123 " ;
diff01 . tool_call_delta . arguments = " { \" ar " ;
assert_equals (
{ diff01 } ,
common_chat_msg_diff : : compute_diffs ( msg0 , msg1 ) ) ;
common_chat_msg_diff diff12 ;
diff12 . tool_call_index = 0 ;
2025-05-26 06:56:49 -07:00
// Note: neither id nor name change here.
2025-05-25 01:48:08 +01:00
diff12 . tool_call_delta . arguments = " g1 \" : 1} " ;
assert_equals (
{ diff12 } ,
common_chat_msg_diff : : compute_diffs ( msg1 , msg2 ) ) ;
}
{
common_chat_msg msg0 ;
common_chat_msg msg2 ;
msg2 . tool_calls = {
{ " f1 " , " { \" arg1 \" : 1} " , /* .id = */ " 123 " } ,
{ " f2 " , " { \" arg2 \" : 2} " , /* .id = */ " 222 " } ,
} ;
common_chat_msg_diff diff1 ;
diff1 . tool_call_index = 0 ;
diff1 . tool_call_delta . name = " f1 " ;
diff1 . tool_call_delta . id = " 123 " ;
diff1 . tool_call_delta . arguments = " { \" arg1 \" : 1} " ;
common_chat_msg_diff diff2 ;
diff2 . tool_call_index = 1 ;
diff2 . tool_call_delta . name = " f2 " ;
diff2 . tool_call_delta . id = " 222 " ;
diff2 . tool_call_delta . arguments = " { \" arg2 \" : 2} " ;
assert_equals (
{ diff1 , diff2 } ,
common_chat_msg_diff : : compute_diffs ( msg0 , msg2 ) ) ;
}
}
2025-01-30 19:13:58 +00:00
int main ( int argc , char * * argv ) {
2025-06-09 11:03:09 -07:00
common_log_set_verbosity_thold ( 999 ) ;
2025-03-05 13:05:13 +00:00
// try {
2025-01-30 19:13:58 +00:00
# ifndef _WIN32
2025-02-18 18:03:23 +00:00
if ( argc > 1 ) {
common_chat_templates_inputs inputs ;
common_chat_msg msg ;
msg . role = " user " ;
msg . content = " Hey " ;
inputs . messages = { msg } ;
inputs . tools = { special_function_tool } ;
std : : cout < < " | Template | Format | \n " ;
std : : cout < < " |----------|--------| \n " ;
for ( int i = 1 ; i < argc ; i + + ) {
try {
std : : string path = argv [ i ] ;
if ( path . rfind ( " .jinja " ) ! = path . size ( ) - 6 ) {
std : : cerr < < " Skipping non-jinja file: " < < path < < ' \n ' ;
continue ;
}
auto tmpls = read_templates ( path ) ;
auto parts = string_split ( path , " / " ) ;
auto name = parts [ parts . size ( ) - 1 ] ;
auto format = common_chat_format_name ( common_chat_templates_apply ( tmpls . get ( ) , inputs ) . format ) ;
std : : cout < < " | " < < name < < " | " < < format < < " | \n " ;
} catch ( const std : : exception & e ) {
std : : cerr < < " Failed to process " < < argv [ i ] < < " : " < < e . what ( ) < < ' \n ' ;
2025-02-13 10:05:16 +00:00
}
2025-01-30 19:13:58 +00:00
}
2025-02-18 18:03:23 +00:00
} else
2025-01-30 19:13:58 +00:00
# endif
2025-02-18 18:03:23 +00:00
{
2025-05-25 01:48:08 +01:00
test_msg_diffs_compute ( ) ;
2025-02-18 18:03:23 +00:00
test_msgs_oaicompat_json_conversion ( ) ;
test_tools_oaicompat_json_conversion ( ) ;
test_template_output_parsers ( ) ;
std : : cout < < " \n [chat] All tests passed! " < < ' \n ' ;
}
return 0 ;
2025-03-05 13:05:13 +00:00
// } catch (const std::exception & e) {
// std::cerr << "Error: " << e.what() << '\n';
// return 1;
// }
2025-01-30 19:13:58 +00:00
}