6
6
7
7
#include < optional>
8
8
9
+ static std::string format_time (const std::chrono::system_clock::time_point & now, const std::string & format) {
10
+ auto time = std::chrono::system_clock::to_time_t (now);
11
+ auto local_time = *std::localtime (&time );
12
+ std::ostringstream ss;
13
+ ss << std::put_time (&local_time, format.c_str ());
14
+ auto res = ss.str ();
15
+ return res;
16
+ }
17
+
9
18
typedef minja::chat_template common_chat_template;
10
19
11
20
struct common_chat_templates {
@@ -24,6 +33,7 @@ struct templates_params {
24
33
std::string grammar;
25
34
bool add_generation_prompt = true ;
26
35
bool extract_reasoning = true ;
36
+ std::chrono::system_clock::time_point now = std::chrono::system_clock::now();
27
37
};
28
38
29
39
common_chat_tool_choice common_chat_tool_choice_parse_oaicompat (const std::string & tool_choice) {
@@ -939,78 +949,83 @@ static void expect_tool_parameters(const std::string & name, const json & parame
939
949
}
940
950
}
941
951
942
- static common_chat_params common_chat_params_init_llama_3_1_tool_calls (const common_chat_template & tmpl, const struct templates_params & inputs, bool allow_python_tag_builtin_tools) {
952
+ static common_chat_params common_chat_params_init_llama_3_x (const common_chat_template & tmpl, const struct templates_params & inputs, bool allow_python_tag_builtin_tools) {
943
953
auto builtin_tools = json::array ();
944
954
common_chat_params data;
945
- data.grammar_lazy = inputs.tool_choice != COMMON_CHAT_TOOL_CHOICE_REQUIRED;
946
- data.grammar = build_grammar ([&](const common_grammar_builder & builder) {
947
- std::vector<std::string> tool_rules;
955
+ if (!inputs.tools .is_null ()) {
956
+ data.grammar_lazy = inputs.tool_choice != COMMON_CHAT_TOOL_CHOICE_REQUIRED;
957
+ data.grammar = build_grammar ([&](const common_grammar_builder & builder) {
958
+ std::vector<std::string> tool_rules;
948
959
949
- auto handle_builtin_tool = [&](const std::string & name, const json & parameters) {
950
- if (name == " wolfram_alpha" || name == " web_search" || name == " brave_search" ) {
951
- // https://github.com/meta-llama/llama-stack/blob/main/llama_stack/providers/remote/tool_runtime/wolfram_alpha/wolfram_alpha.py
952
- // https://github.com/meta-llama/llama-stack/blob/main/llama_stack/providers/remote/tool_runtime/brave_search/brave_search.py
953
- expect_tool_parameters (name, parameters, {" query" });
954
- } else if (name == " python" || name == " code_interpreter" ) {
955
- // https://github.com/meta-llama/llama-stack/blob/main/llama_stack/providers/inline/tool_runtime/code_interpreter/code_interpreter.py
956
- expect_tool_parameters (name, parameters, {" code" });
957
- } else {
958
- return false ;
959
- }
960
+ auto handle_builtin_tool = [&](const std::string & name, const json & parameters) {
961
+ if (name == " wolfram_alpha" || name == " web_search" || name == " brave_search" ) {
962
+ // https://github.com/meta-llama/llama-stack/blob/main/llama_stack/providers/remote/tool_runtime/wolfram_alpha/wolfram_alpha.py
963
+ // https://github.com/meta-llama/llama-stack/blob/main/llama_stack/providers/remote/tool_runtime/brave_search/brave_search.py
964
+ expect_tool_parameters (name, parameters, {" query" });
965
+ } else if (name == " python" || name == " code_interpreter" ) {
966
+ // https://github.com/meta-llama/llama-stack/blob/main/llama_stack/providers/inline/tool_runtime/code_interpreter/code_interpreter.py
967
+ expect_tool_parameters (name, parameters, {" code" });
968
+ } else {
969
+ return false ;
970
+ }
960
971
961
- std::vector<std::string> kvs;
962
- for (const auto & [key, value] : parameters.at (" properties" ).items ()) {
963
- kvs.push_back (" \" " + key + " =\" " + builder.add_schema (name + " -args-" + key, value)); // NOLINT
964
- }
972
+ std::vector<std::string> kvs;
973
+ for (const auto & [key, value] : parameters.at (" properties" ).items ()) {
974
+ kvs.push_back (" \" " + key + " =\" " + builder.add_schema (name + " -args-" + key, value)); // NOLINT
975
+ }
965
976
966
- tool_rules.push_back (
967
- builder.add_rule (
968
- name + " -call" ,
969
- " \" <|python_tag|>" + name + " .call(\" " + string_join (kvs, " \" , \" " ) + " \" )\" " ));
970
- builtin_tools.push_back (name);
977
+ tool_rules.push_back (
978
+ builder.add_rule (
979
+ name + " -call" ,
980
+ " \" <|python_tag|>" + name + " .call(\" " + string_join (kvs, " \" , \" " ) + " \" )\" " ));
981
+ builtin_tools.push_back (name);
971
982
972
- return true ;
973
- };
983
+ return true ;
984
+ };
974
985
975
- foreach_function (inputs.tools , [&](const json & tool) {
976
- const auto & function = tool.at (" function" );
977
- std::string name = function.at (" name" );
978
- auto parameters = function.at (" parameters" );
979
- builder.resolve_refs (parameters);
986
+ foreach_function (inputs.tools , [&](const json & tool) {
987
+ const auto & function = tool.at (" function" );
988
+ std::string name = function.at (" name" );
989
+ auto parameters = function.at (" parameters" );
990
+ builder.resolve_refs (parameters);
980
991
981
- // https://github.com/meta-llama/llama-stack/tree/main/llama_stack/providers/remote/tool_runtime
982
- if (allow_python_tag_builtin_tools) {
983
- handle_builtin_tool (name, parameters);
992
+ // https://github.com/meta-llama/llama-stack/tree/main/llama_stack/providers/remote/tool_runtime
993
+ if (allow_python_tag_builtin_tools) {
994
+ handle_builtin_tool (name, parameters);
995
+ }
996
+ tool_rules.push_back (
997
+ builder.add_rule (
998
+ name + " -call" ,
999
+ " \" {\" space "
1000
+ " ( \"\\\" type\\\"\" space \" :\" space \"\\\" function\\\"\" space \" ,\" space )? "
1001
+ " \"\\\" name\\\"\" space \" :\" space \"\\\" " + name + " \\\"\" space \" ,\" space "
1002
+ " \"\\\" parameters\\\"\" space \" :\" space " + builder.add_schema (name + " -args" , parameters) + " "
1003
+ " \" }\" space" ));
1004
+ });
1005
+ // Small models may hallucinate function names so we match anything (*at the start*) that looks like the JSON of a function call, regardless of the name.
1006
+ data.grammar_triggers .push_back ({
1007
+ COMMON_GRAMMAR_TRIGGER_TYPE_PATTERN_START,
1008
+ " \\ {\\ s*(?:\" type\"\\ s*:\\ s*\" function\"\\ s*,\\ s*)?\" name\"\\ s*:\\ s*\" " , // + name + "\"[\\s\\S]*",
1009
+ });
1010
+ if (!builtin_tools.empty ()) {
1011
+ data.grammar_triggers .push_back ({COMMON_GRAMMAR_TRIGGER_TYPE_WORD, " <|python_tag|>" });
1012
+ data.preserved_tokens .push_back (" <|python_tag|>" );
984
1013
}
985
- tool_rules.push_back (
986
- builder.add_rule (
987
- name + " -call" ,
988
- " \" {\" space "
989
- " ( \"\\\" type\\\"\" space \" :\" space \"\\\" function\\\"\" space \" ,\" space )? "
990
- " \"\\\" name\\\"\" space \" :\" space \"\\\" " + name + " \\\"\" space \" ,\" space "
991
- " \"\\\" parameters\\\"\" space \" :\" space " + builder.add_schema (name + " -args" , parameters) + " "
992
- " \" }\" space" ));
1014
+ // Allow a few empty lines on top of the usual constrained json schema space rule.
1015
+ builder.add_rule (" root" , string_join (tool_rules, " | " ));
1016
+ data.additional_stops .push_back (" <|eom_id|>" );
993
1017
});
994
- // Small models may hallucinate function names so we match anything (*at the start*) that looks like the JSON of a function call, regardless of the name.
995
- data.grammar_triggers .push_back ({
996
- COMMON_GRAMMAR_TRIGGER_TYPE_PATTERN_START,
997
- " \\ {\\ s*(?:\" type\"\\ s*:\\ s*\" function\"\\ s*,\\ s*)?\" name\"\\ s*:\\ s*\" " , // + name + "\"[\\s\\S]*",
998
- });
999
- if (!builtin_tools.empty ()) {
1000
- data.grammar_triggers .push_back ({COMMON_GRAMMAR_TRIGGER_TYPE_WORD, " <|python_tag|>" });
1001
- data.preserved_tokens .push_back (" <|python_tag|>" );
1002
- }
1003
- // Allow a few empty lines on top of the usual constrained json schema space rule.
1004
- builder.add_rule (" root" , string_join (tool_rules, " | " ));
1005
- });
1006
- data.additional_stops .push_back (" <|eom_id|>" );
1018
+ data.format = allow_python_tag_builtin_tools && !builtin_tools.empty ()
1019
+ ? COMMON_CHAT_FORMAT_LLAMA_3_X_WITH_BUILTIN_TOOLS
1020
+ : COMMON_CHAT_FORMAT_LLAMA_3_X;
1021
+ } else {
1022
+ data.format = COMMON_CHAT_FORMAT_CONTENT_ONLY;
1023
+ }
1007
1024
data.prompt = apply (tmpl, inputs.messages , inputs.tools .empty () ? json () : inputs.tools , inputs.add_generation_prompt , {
1025
+ {" date_string" , format_time (inputs.now , " %d %b %Y" )},
1008
1026
{" tools_in_user_message" , false },
1009
1027
{" builtin_tools" , builtin_tools.empty () ? json () : builtin_tools},
1010
1028
});
1011
- data.format = allow_python_tag_builtin_tools && !builtin_tools.empty ()
1012
- ? COMMON_CHAT_FORMAT_LLAMA_3_X_WITH_BUILTIN_TOOLS
1013
- : COMMON_CHAT_FORMAT_LLAMA_3_X;
1014
1029
return data;
1015
1030
}
1016
1031
static common_chat_msg common_chat_parse_llama_3_1 (const std::string & input, bool with_builtin_tools = false ) {
@@ -1150,7 +1165,7 @@ static common_chat_params common_chat_params_init_firefunction_v2(const common_c
1150
1165
LOG_DBG (" %s\n " , __func__);
1151
1166
common_chat_params data;
1152
1167
data.prompt = apply (tmpl, inputs.messages , /* tools= */ nullptr , inputs.add_generation_prompt , {
1153
- {" datetime" , " Jan 29 2025 13:00:00 GMT" },
1168
+ {" datetime" , format_time (inputs. now , " %b %d %Y %H:%M:%S GMT" ) },
1154
1169
{" functions" , json (inputs.tools .empty () ? " " : inputs.tools .dump (2 ))},
1155
1170
});
1156
1171
if (inputs.tools .is_array () && !inputs.tools .empty ()) {
@@ -1285,55 +1300,59 @@ static common_chat_msg common_chat_parse_functionary_v3_2(const std::string & in
1285
1300
static common_chat_params common_chat_params_init_functionary_v3_1_llama_3_1 (const common_chat_template & tmpl, const struct templates_params & inputs) {
1286
1301
// https://github.com/MeetKai/functionary/blob/main/tests/prompt_test_v3-llama3.1.txt
1287
1302
common_chat_params data;
1288
- json tools = inputs.tools .is_null () ? inputs.tools : json::array ();
1289
- std::string python_code_argument_name;
1290
- auto has_raw_python = false ;
1291
1303
1292
- data.grammar_lazy = inputs.tool_choice != COMMON_CHAT_TOOL_CHOICE_REQUIRED;
1293
- data.grammar = build_grammar ([&](const common_grammar_builder & builder) {
1294
- std::vector<std::string> tool_rules;
1295
- foreach_function (inputs.tools , [&](const json & tool) {
1296
- const auto & function = tool.at (" function" );
1297
- const auto & parameters = function.at (" parameters" );
1298
- std::string name = function.at (" name" );
1299
- if (name == " python" || name == " ipython" ) {
1300
- if (!parameters.contains (" type" )) {
1301
- throw std::runtime_error (" Missing type in python tool" );
1302
- }
1303
- has_raw_python = true ;
1304
- const auto & type = parameters.at (" type" );
1305
- if (type == " object" ) {
1306
- auto properties = parameters.at (" properties" );
1307
- for (auto it = properties.begin (); it != properties.end (); ++it) {
1308
- if (it.value ().at (" type" ) == " string" ) {
1309
- if (!python_code_argument_name.empty ()) {
1310
- throw std::runtime_error (" Multiple string arguments found in python tool" );
1304
+ if (!inputs.tools .is_null ()) {
1305
+ std::string python_code_argument_name;
1306
+ auto has_raw_python = false ;
1307
+
1308
+ data.grammar_lazy = inputs.tool_choice != COMMON_CHAT_TOOL_CHOICE_REQUIRED;
1309
+ data.grammar = build_grammar ([&](const common_grammar_builder & builder) {
1310
+ std::vector<std::string> tool_rules;
1311
+ foreach_function (inputs.tools , [&](const json & tool) {
1312
+ const auto & function = tool.at (" function" );
1313
+ const auto & parameters = function.at (" parameters" );
1314
+ std::string name = function.at (" name" );
1315
+ if (name == " python" || name == " ipython" ) {
1316
+ if (!parameters.contains (" type" )) {
1317
+ throw std::runtime_error (" Missing type in python tool" );
1318
+ }
1319
+ has_raw_python = true ;
1320
+ const auto & type = parameters.at (" type" );
1321
+ if (type == " object" ) {
1322
+ auto properties = parameters.at (" properties" );
1323
+ for (auto it = properties.begin (); it != properties.end (); ++it) {
1324
+ if (it.value ().at (" type" ) == " string" ) {
1325
+ if (!python_code_argument_name.empty ()) {
1326
+ throw std::runtime_error (" Multiple string arguments found in python tool" );
1327
+ }
1328
+ python_code_argument_name = it.key ();
1311
1329
}
1312
- python_code_argument_name = it.key ();
1313
1330
}
1331
+ if (python_code_argument_name.empty ()) {
1332
+ throw std::runtime_error (" No string argument found in python tool" );
1333
+ }
1334
+ } else if (type != " string" ) {
1335
+ throw std::runtime_error (" Invalid type in python tool: " + type.dump ());
1314
1336
}
1315
- if (python_code_argument_name.empty ()) {
1316
- throw std::runtime_error (" No string argument found in python tool" );
1317
- }
1318
- } else if (type != " string" ) {
1319
- throw std::runtime_error (" Invalid type in python tool: " + type.dump ());
1320
1337
}
1338
+ tool_rules.push_back (builder.add_rule (name + " -call" , " \" <function=" + name + " >\" " + builder.add_schema (name + " -args" , parameters) + " \" </function>\" space" ));
1339
+ });
1340
+ if (has_raw_python) {
1341
+ tool_rules.push_back (builder.add_rule (" python-call" , " \" <|python_tag|>\" .*" ));
1342
+ data.grammar_triggers .push_back ({COMMON_GRAMMAR_TRIGGER_TYPE_WORD, " <|python_tag|>" });
1343
+ data.preserved_tokens .push_back (" <|python_tag|>" );
1321
1344
}
1322
- tool_rules.push_back (builder.add_rule (name + " -call" , " \" <function=" + name + " >\" " + builder.add_schema (name + " -args" , parameters) + " \" </function>\" space" ));
1345
+ auto tool_call = builder.add_rule (" tool_call" , string_join (tool_rules, " | " )) + " space" ;
1346
+ builder.add_rule (" root" , inputs.parallel_tool_calls ? " (" + tool_call + " )+" : tool_call);
1347
+ data.grammar_triggers .push_back ({COMMON_GRAMMAR_TRIGGER_TYPE_WORD, " <function=" });
1323
1348
});
1324
- if (has_raw_python) {
1325
- tool_rules.push_back (builder.add_rule (" python-call" , " \" <|python_tag|>\" .*" ));
1326
- data.grammar_triggers .push_back ({COMMON_GRAMMAR_TRIGGER_TYPE_WORD, " <|python_tag|>" });
1327
- data.preserved_tokens .push_back (" <|python_tag|>" );
1328
- }
1329
- auto tool_call = builder.add_rule (" tool_call" , string_join (tool_rules, " | " )) + " space" ;
1330
- builder.add_rule (" root" , inputs.parallel_tool_calls ? " (" + tool_call + " )+" : tool_call);
1331
- data.grammar_triggers .push_back ({COMMON_GRAMMAR_TRIGGER_TYPE_WORD, " <function=" });
1332
- });
1349
+ data.format = COMMON_CHAT_FORMAT_FUNCTIONARY_V3_1_LLAMA_3_1;
1350
+ } else {
1351
+ data.format = COMMON_CHAT_FORMAT_CONTENT_ONLY;
1352
+ }
1333
1353
1334
1354
data.prompt = apply (tmpl, inputs.messages , inputs.tools .empty () ? json () : inputs.tools , inputs.add_generation_prompt );
1335
1355
// TODO: if (has_raw_python)
1336
- data.format = COMMON_CHAT_FORMAT_FUNCTIONARY_V3_1_LLAMA_3_1;
1337
1356
return data;
1338
1357
}
1339
1358
static common_chat_msg common_chat_parse_functionary_v3_1_llama_3_1 (const std::string & input) {
@@ -1593,6 +1612,7 @@ static common_chat_params common_chat_templates_apply_jinja(
1593
1612
params.extract_reasoning = inputs.extract_reasoning ;
1594
1613
params.tool_choice = inputs.tool_choice ;
1595
1614
params.grammar = inputs.grammar ;
1615
+ params.now = inputs.now ;
1596
1616
if (!inputs.json_schema .empty ()) {
1597
1617
params.json_schema = json::parse (inputs.json_schema );
1598
1618
}
@@ -1644,21 +1664,21 @@ static common_chat_params common_chat_templates_apply_jinja(
1644
1664
return common_chat_params_init_firefunction_v2 (tmpl, params);
1645
1665
}
1646
1666
1647
- // Plain handler (no tools)
1648
- if (params.tools .is_null () || inputs.tool_choice == COMMON_CHAT_TOOL_CHOICE_NONE) {
1649
- return common_chat_params_init_without_tools (tmpl, params);
1650
- }
1651
-
1652
1667
// Functionary v3.1 (w/ tools)
1653
1668
if (src.find (" <|start_header_id|>" ) != std::string::npos
1654
1669
&& src.find (" <function=" ) != std::string::npos) {
1655
1670
return common_chat_params_init_functionary_v3_1_llama_3_1 (tmpl, params);
1656
1671
}
1657
1672
1658
- // Llama 3.1, 3.2, 3.3 (w/ tools)
1673
+ // Llama 3.1, 3.2, 3.3 (also requires date_string so using it even w/o tools)
1659
1674
if (src.find (" <|start_header_id|>ipython<|end_header_id|>" ) != std::string::npos) {
1660
1675
auto allow_python_tag_builtin_tools = src.find (" <|python_tag|>" ) != std::string::npos;
1661
- return common_chat_params_init_llama_3_1_tool_calls (tmpl, params, allow_python_tag_builtin_tools);
1676
+ return common_chat_params_init_llama_3_x (tmpl, params, allow_python_tag_builtin_tools);
1677
+ }
1678
+
1679
+ // Plain handler (no tools)
1680
+ if (params.tools .is_null () || inputs.tool_choice == COMMON_CHAT_TOOL_CHOICE_NONE) {
1681
+ return common_chat_params_init_without_tools (tmpl, params);
1662
1682
}
1663
1683
1664
1684
// Mistral Nemo (w/ tools)
0 commit comments