test-chat.cpp 174 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036303730383039304030413042304330443045304630473048304930503051305230533054305530563057305830593060306130623063306430653066306730683069307030713072307330743075307630773078307930803081308230833084308530863087308830893090309130923093309430953096309730983099310031013102310331043105310631073108310931103111311231133114311531163117311831193120312131223123312431253126312731283129313031313132313331343135313631373138313931403141314231433144314531463147314831493150315131523153315431553156315731583159316031613162316331643165316631673168316931703171317231733174317531763177317831793180318131823183318431853186318731883189319031913192319331943195319631973198319932003201320232033204320532063207320832093210321132123213321432153216321732183219322032213222322332243225322632273228322932303231323232333234323532363237323832393240324132423243324432453246324732483249325032513252325332543255325632573258325932603261326232633264326532663267326832693270327132723273327432753276327732783279328032813282328332843285328632873288328932903291329232933294329532963297329832993300330133023303330433053306330733083309331033113312331333143315331633173318331933203321332233233324332533263327332833293330333133323333333433353336333733383339334033413342334333443345334633473348334933503351335233533354335533563357335833593360336133623363336433653366336733683369337033713372337333743375337633773378337933803381338233833384338533863387338833893390339133923393339433953396339733983399340034013402340334043405340634073408340934103411341234133414341534163417341834193420342134223423342434253426342734283429343034313432343334343435343634373438343934403441344234433444344534463447344834493450345134523453345434553456345734583459346034613462346334643465346634673468346934703471347234733474347534763477347834793480348134823483348434853486348734883489349034913492349334943495349634973498349935003501350235033504350535063507350835093510351135123513351435153516351735183519352035213522352335243525352635273528352935303531353235333534353535363537353835393540354135423543354435453546354735483549355035513552355335543555355635573558355935603561356235633564356535663567356835693570
  1. // Tests chat handling, including grammar generation and parsing for tool calling, for various templates.
  2. //
  3. // Also acts as a CLI to generate a Markdown summary of the formats of Jinja templates,
  4. // e.g. given Minja (http://github.com/google/minja) checked out in parent dir:
  5. //
  6. // cmake -B build && cmake --build build --parallel && ./build/bin/test-chat ../minja/build/tests/*.jinja 2>/dev/null
  7. //
  8. #include "chat.h"
  9. #include "log.h"
  10. #include "../src/unicode.h"
  11. #include "../src/llama-grammar.h"
  12. #include <nlohmann/json.hpp>
  13. #include <fstream>
  14. #include <iostream>
  15. #include <functional>
  16. #include <string>
  17. using json = nlohmann::ordered_json;
  18. static std::ostream & operator<<(std::ostream & os, const common_chat_msg_diff & diff) {
  19. os << "{ content_delta: " << diff.content_delta << "; ";
  20. os << "reasoning_content_delta: " << diff.reasoning_content_delta << "; ";
  21. if (diff.tool_call_index != std::string::npos) {
  22. os << "tool_call_index: " << diff.tool_call_index << "; ";
  23. os << "tool_call_delta.name: " << diff.tool_call_delta.name << "; ";
  24. os << "tool_call_delta.id: " << diff.tool_call_delta.id << "; ";
  25. os << "tool_call_delta.arguments: " << diff.tool_call_delta.arguments << "; ";
  26. }
  27. os << "}";
  28. return os;
  29. }
  30. // operator<< for vector<common_chat_msg_diff>:
  31. static std::ostream & operator<<(std::ostream & os, const std::vector<common_chat_msg_diff> & diffs) {
  32. os << "[\n";
  33. for (const auto & diff : diffs) {
  34. os << " " << diff << ",\n";
  35. }
  36. os << "]";
  37. return os;
  38. }
  39. static std::ostream & operator<<(std::ostream & os, const common_chat_msg & msg) {
  40. os << "{ role: " << msg.role << "; ";
  41. os << "content: " << msg.content << "; ";
  42. os << "content_parts: [\n";
  43. for (const auto & part : msg.content_parts) {
  44. os << " { type: " << part.type << "; text: " << part.text << " },\n";
  45. }
  46. os << "]; ";
  47. os << "reasoning_content: " << msg.reasoning_content << "; ";
  48. os << "tool_calls: [\n";
  49. for (const auto & tool_call : msg.tool_calls) {
  50. os << " { name: " << tool_call.name << "; arguments: " << tool_call.arguments << "; id: " << tool_call.id << " },\n";
  51. }
  52. os << "]";
  53. os << "}";
  54. return os;
  55. }
  56. template <class T> static bool equals(const T & expected, const T & actual) {
  57. return expected == actual;
  58. }
  59. static common_chat_msg normalize(const common_chat_msg & msg) {
  60. common_chat_msg normalized = msg;
  61. for (auto & tool_call : normalized.tool_calls) {
  62. try {
  63. tool_call.arguments = json::parse(tool_call.arguments).dump();
  64. } catch (const std::exception &) {
  65. // Do nothing
  66. }
  67. }
  68. return normalized;
  69. }
  70. template <>
  71. bool equals(const common_chat_msg & expected, const common_chat_msg & actual) {
  72. return normalize(expected) == normalize(actual);
  73. }
  74. template <class T> static void assert_equals(const T & expected, const T & actual) {
  75. if (!equals(expected, actual)) {
  76. std::cerr << "Expected: " << expected << std::endl;
  77. std::cerr << "Actual: " << actual << std::endl;
  78. std::cerr << std::flush;
  79. throw std::runtime_error("Test failed");
  80. }
  81. }
  82. static std::string read_file(const std::string & path) {
  83. std::cerr << "# Reading: " << path << '\n' << std::flush;
  84. std::ifstream fs(path, std::ios_base::binary);
  85. if (!fs.is_open()) {
  86. fs = std::ifstream("../" + path, std::ios_base::binary);
  87. if (!fs.is_open()) {
  88. throw std::runtime_error("Failed to open file: " + path);
  89. }
  90. }
  91. fs.seekg(0, std::ios_base::end);
  92. auto size = fs.tellg();
  93. fs.seekg(0);
  94. std::string out;
  95. out.resize(static_cast<size_t>(size));
  96. fs.read(out.data(), static_cast<std::streamsize>(size));
  97. return out;
  98. }
  99. static common_chat_templates_ptr read_templates(const std::string & path) {
  100. return common_chat_templates_ptr(common_chat_templates_init(/* model= */ nullptr, read_file(path)));
  101. }
  102. static std::unique_ptr<llama_grammar> build_grammar(const std::string & grammar_str) {
  103. return std::unique_ptr<llama_grammar>(
  104. llama_grammar_init_impl(nullptr, grammar_str.c_str(), "root", false, nullptr, 0, nullptr, 0));
  105. }
  106. // TODO: extract to common helper (copied from test-grammar-integration.cpp)
  107. static bool match_string(const std::string & input, llama_grammar * grammar) {
  108. const auto cpts = unicode_cpts_from_utf8(input);
  109. auto & stacks_cur = llama_grammar_get_stacks(grammar);
  110. for (const auto & cpt : cpts) {
  111. llama_grammar_accept(grammar, cpt);
  112. if (stacks_cur.empty()) {
  113. // no stacks means that the grammar failed to match at this point
  114. return false;
  115. }
  116. }
  117. if (std::any_of(stacks_cur.begin(), stacks_cur.end(), [](const auto & stack) { return stack.empty(); })) {
  118. // An empty stack means that the grammar has been completed
  119. return true;
  120. }
  121. return false;
  122. }
  123. static std::string renormalize_json(const std::string & json_str) {
  124. try {
  125. auto json_obj = json::parse(json_str);
  126. return json_obj.dump();
  127. } catch (const std::exception & e) {
  128. std::cerr << "Failed to parse JSON: " << e.what() << '\n';
  129. return json_str;
  130. }
  131. }
  132. static void assert_msg_equals(const common_chat_msg & expected, const common_chat_msg & actual, bool ignore_whitespace_differences = false) {
  133. assert_equals(expected.role, actual.role);
  134. if (ignore_whitespace_differences) {
  135. assert_equals(string_strip(expected.content), string_strip(actual.content));
  136. } else {
  137. assert_equals(expected.content, actual.content);
  138. }
  139. assert_equals(expected.content_parts.size(), actual.content_parts.size());
  140. for (size_t i = 0; i < expected.content_parts.size(); i++) {
  141. const auto & expected_part = expected.content_parts[i];
  142. const auto & actual_part = actual.content_parts[i];
  143. assert_equals(expected_part.type, actual_part.type);
  144. if (ignore_whitespace_differences) {
  145. assert_equals(string_strip(expected_part.text), string_strip(actual_part.text));
  146. } else {
  147. assert_equals(expected_part.text, actual_part.text);
  148. }
  149. }
  150. if (ignore_whitespace_differences) {
  151. assert_equals(string_strip(expected.reasoning_content), string_strip(actual.reasoning_content));
  152. } else {
  153. assert_equals(expected.reasoning_content, actual.reasoning_content);
  154. }
  155. assert_equals(expected.tool_calls.size(), actual.tool_calls.size());
  156. for (size_t i = 0; i < expected.tool_calls.size(); i++) {
  157. const auto & expected_tool_call = expected.tool_calls[i];
  158. const auto & actual_tool_call = actual.tool_calls[i];
  159. assert_equals(expected_tool_call.name, actual_tool_call.name);
  160. assert_equals(renormalize_json(expected_tool_call.arguments), renormalize_json(actual_tool_call.arguments));
  161. assert_equals(expected_tool_call.id, actual_tool_call.id);
  162. }
  163. }
  164. common_chat_tool special_function_tool {
  165. /* .name = */ "special_function",
  166. /* .description = */ "I'm special",
  167. /* .parameters = */ R"({
  168. "type": "object",
  169. "properties": {
  170. "arg1": {
  171. "type": "integer",
  172. "description": "The arg."
  173. }
  174. },
  175. "required": ["arg1"]
  176. })",
  177. };
  178. common_chat_tool special_function_tool_with_optional_param {
  179. /* .name = */ "special_function_with_opt",
  180. /* .description = */ "I'm special but have optional stuff",
  181. /* .parameters = */ R"({
  182. "type": "object",
  183. "properties": {
  184. "arg1": {
  185. "type": "integer",
  186. "description": "The arg."
  187. },
  188. "arg2": {
  189. "type": "integer",
  190. "description": "The optional arg."
  191. }
  192. },
  193. "required": ["arg1"]
  194. })",
  195. };
  196. common_chat_tool python_tool {
  197. /* .name = */ "python",
  198. /* .description = */ "an ipython interpreter",
  199. /* .parameters = */ R"({
  200. "type": "object",
  201. "properties": {
  202. "code": {
  203. "type": "string",
  204. "description": "Python code to execute."
  205. }
  206. },
  207. "required": ["code"]
  208. })",
  209. };
  210. common_chat_tool code_interpreter_tool {
  211. /* .name = */ "code_interpreter",
  212. /* .description = */ "an ipython interpreter",
  213. /* .parameters = */ R"({
  214. "type": "object",
  215. "properties": {
  216. "code": {
  217. "type": "string",
  218. "description": "Python code to execute."
  219. }
  220. },
  221. "required": ["code"]
  222. })",
  223. };
  224. std::vector<common_chat_tool> tools { special_function_tool, special_function_tool_with_optional_param, python_tool };
  225. std::vector<common_chat_tool> llama_3_1_tools { special_function_tool, code_interpreter_tool };
  226. struct delta_data {
  227. std::string delta;
  228. common_chat_params params;
  229. };
  230. 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 = "") {
  231. common_chat_msg msg;
  232. msg.role = "assistant";
  233. msg.content = content;
  234. msg.reasoning_content = reasoning_content;
  235. if (!tool_name.empty()) {
  236. msg.tool_calls.push_back({ tool_name, arguments, id });
  237. }
  238. return msg;
  239. }
  240. static delta_data init_delta(const struct common_chat_templates * tmpls, const std::vector<std::string> & end_tokens,
  241. const common_chat_msg & user_message,
  242. const common_chat_msg & delta_message,
  243. const std::vector<common_chat_tool> & tools,
  244. const common_chat_tool_choice & tool_choice) {
  245. common_chat_templates_inputs inputs;
  246. inputs.parallel_tool_calls = true;
  247. inputs.messages.push_back(user_message);
  248. inputs.tools = tools;
  249. inputs.tool_choice = tool_choice;
  250. auto params_prefix = common_chat_templates_apply(tmpls, inputs);
  251. inputs.messages.push_back(delta_message);
  252. inputs.add_generation_prompt = false;
  253. auto params_full = common_chat_templates_apply(tmpls, inputs);
  254. std::string prefix = params_prefix.prompt;
  255. std::string full = params_full.prompt;
  256. if (full == prefix) {
  257. throw std::runtime_error("Full message is the same as the prefix");
  258. }
  259. size_t common_prefix_length = 0;
  260. for (size_t i = 0; i < prefix.size() && i < full.size(); ++i) {
  261. if (prefix[i] != full[i]) {
  262. break;
  263. }
  264. if (prefix[i] == '<') {
  265. // DeepSeek R1's template (as of 20250209) adds a trailing <think> if add_generation_prompt,
  266. // but it removes thinking tags for past messages.
  267. // The prefix and full strings diverge at <think> vs. <|tool▁calls▁begin|>, we avoid consuming the leading <.
  268. continue;
  269. }
  270. common_prefix_length = i + 1;
  271. }
  272. auto delta = full.substr(common_prefix_length);
  273. // Strip end tokens
  274. for (const auto & end_token : end_tokens) {
  275. // rfind to find the last occurrence
  276. auto pos = delta.rfind(end_token);
  277. if (pos != std::string::npos) {
  278. delta = delta.substr(0, pos);
  279. break;
  280. }
  281. }
  282. return { delta, params_full };
  283. }
  284. /*
  285. Applies the template to 1 user message w/ add_generation_prompt=true, then w/ the test message w/ add_generation_prompt=false,
  286. gets the diff, removes any end tokens and parses the result w/ the grammar, checking that
  287. the parsed message is the same as the test_message
  288. */
  289. static void test_templates(const struct common_chat_templates * tmpls, const std::vector<std::string> & end_tokens,
  290. const common_chat_msg & test_message,
  291. const std::vector<common_chat_tool> & tools = {},
  292. const std::string & expected_delta = "",
  293. bool expect_grammar_triggered = true,
  294. bool test_grammar_if_triggered = true,
  295. common_reasoning_format reasoning_format = COMMON_REASONING_FORMAT_NONE,
  296. bool ignore_whitespace_differences = false
  297. ) {
  298. common_chat_msg user_message;
  299. user_message.role = "user";
  300. user_message.content = "Hello, world!";
  301. for (const auto & tool_choice : std::vector<common_chat_tool_choice> {COMMON_CHAT_TOOL_CHOICE_AUTO, COMMON_CHAT_TOOL_CHOICE_REQUIRED}) {
  302. auto data = init_delta(tmpls, end_tokens, user_message, test_message, tools, tool_choice);
  303. if (!expected_delta.empty()) {
  304. if (ignore_whitespace_differences) {
  305. assert_equals(string_strip(expected_delta), string_strip(data.delta));
  306. } else {
  307. assert_equals(expected_delta, data.delta);
  308. }
  309. }
  310. if (expect_grammar_triggered) {
  311. common_chat_syntax syntax;
  312. syntax.format = data.params.format;
  313. syntax.reasoning_format = reasoning_format;
  314. const auto msg = common_chat_parse(data.delta, /* is_partial= */ false, syntax);
  315. assert_msg_equals(test_message, msg, ignore_whitespace_differences);
  316. }
  317. if (!test_message.tool_calls.empty()) {
  318. GGML_ASSERT(!data.params.grammar.empty());
  319. }
  320. if (!data.params.grammar.empty()) {
  321. auto grammar = build_grammar(data.params.grammar);
  322. if (!grammar) {
  323. throw std::runtime_error("Failed to build grammar");
  324. }
  325. auto earliest_trigger_pos = std::string::npos;
  326. auto constrained = data.delta;
  327. for (const auto & trigger : data.params.grammar_triggers) {
  328. size_t pos = std::string::npos;
  329. std::smatch match;
  330. switch (trigger.type) {
  331. case COMMON_GRAMMAR_TRIGGER_TYPE_WORD:
  332. {
  333. const auto & word = trigger.value;
  334. pos = constrained.find(word);
  335. break;
  336. }
  337. case COMMON_GRAMMAR_TRIGGER_TYPE_PATTERN:
  338. {
  339. const auto & pattern = trigger.value;
  340. if (std::regex_search(constrained, match, std::regex(pattern))) {
  341. pos = match.position(1);
  342. }
  343. break;
  344. }
  345. case COMMON_GRAMMAR_TRIGGER_TYPE_PATTERN_FULL:
  346. {
  347. const auto & pattern = trigger.value;
  348. if (std::regex_match(constrained, match, std::regex(pattern))) {
  349. auto mpos = std::string::npos;
  350. for (size_t i = 1; i < match.size(); ++i) {
  351. if (match[i].length() > 0) {
  352. mpos = match.position(i);
  353. break;
  354. }
  355. }
  356. if (mpos == std::string::npos) {
  357. mpos = match.position(0);
  358. }
  359. pos = mpos;
  360. }
  361. break;
  362. }
  363. default:
  364. throw std::runtime_error("Unknown trigger type");
  365. }
  366. if (pos == std::string::npos) {
  367. continue;
  368. }
  369. if (earliest_trigger_pos == std::string::npos || pos < earliest_trigger_pos) {
  370. earliest_trigger_pos = pos;
  371. }
  372. }
  373. auto grammar_triggered = false;
  374. if (earliest_trigger_pos != std::string::npos) {
  375. constrained = constrained.substr(earliest_trigger_pos);
  376. grammar_triggered = true;
  377. }
  378. if (data.params.grammar_lazy) {
  379. assert_equals(expect_grammar_triggered, grammar_triggered);
  380. }
  381. if (grammar_triggered && test_grammar_if_triggered && !match_string(constrained, grammar.get())) {
  382. throw std::runtime_error("Failed to match delta against grammar:\n\n" + data.delta +
  383. "\n\nConstrained: " + constrained +
  384. "\n\nGrammar: " + data.params.grammar);
  385. }
  386. }
  387. }
  388. }
  389. /**
  390. * Test if streaming=true is consistant with streaming=false for given partial parser
  391. * Also test if there is any problem with partial message
  392. */
  393. template <typename T>
  394. static void test_parser_with_streaming(const common_chat_msg & expected, const std::string & raw_message, T parse_msg) {
  395. constexpr auto utf8_truncate_safe_len = [](const std::string_view s) -> size_t {
  396. auto len = s.size();
  397. if (len == 0) return 0;
  398. auto i = len;
  399. for (size_t back = 0; back < 4 && i > 0; ++back) {
  400. --i;
  401. unsigned char c = s[i];
  402. if ((c & 0x80) == 0) {
  403. return len;
  404. } else if ((c & 0xC0) == 0xC0) {
  405. size_t expected_len = 0;
  406. if ((c & 0xE0) == 0xC0) expected_len = 2;
  407. else if ((c & 0xF0) == 0xE0) expected_len = 3;
  408. else if ((c & 0xF8) == 0xF0) expected_len = 4;
  409. else return i;
  410. if (len - i >= expected_len) {
  411. return len;
  412. } else {
  413. return i;
  414. }
  415. }
  416. }
  417. return len - std::min(len, size_t(3));
  418. };
  419. constexpr auto utf8_truncate_safe_view = [utf8_truncate_safe_len](const std::string_view s) {
  420. return s.substr(0, utf8_truncate_safe_len(s));
  421. };
  422. auto merged = simple_assist_msg("");
  423. auto last_msg = parse_msg("");
  424. for (size_t i = 1; i <= raw_message.size(); ++i) {
  425. auto curr_msg = parse_msg(std::string(utf8_truncate_safe_view(std::string_view(raw_message).substr(0, i))));
  426. if (curr_msg == simple_assist_msg("")) continue;
  427. LOG_INF("Streaming msg: %s\n", common_chat_msgs_to_json_oaicompat<json>({curr_msg}).dump().c_str());
  428. for (auto diff: common_chat_msg_diff::compute_diffs(last_msg, curr_msg)) {
  429. LOG_INF("Streaming diff: %s\n", common_chat_msg_diff_to_json_oaicompat<json>(diff).dump().c_str());
  430. if (!diff.reasoning_content_delta.empty()) {
  431. merged.reasoning_content += diff.reasoning_content_delta;
  432. }
  433. if (!diff.content_delta.empty()) {
  434. merged.content += diff.content_delta;
  435. }
  436. if (diff.tool_call_index != std::string::npos) {
  437. if (!diff.tool_call_delta.name.empty()) {
  438. merged.tool_calls.push_back({diff.tool_call_delta.name, "", ""});
  439. }
  440. if (!diff.tool_call_delta.arguments.empty()) {
  441. GGML_ASSERT(!merged.tool_calls.empty());
  442. merged.tool_calls.back().arguments += diff.tool_call_delta.arguments;
  443. }
  444. }
  445. LOG_INF("Streaming merged: %s\n", common_chat_msgs_to_json_oaicompat<json>({merged}).dump().c_str());
  446. }
  447. assert_msg_equals(curr_msg, merged, true);
  448. last_msg = curr_msg;
  449. }
  450. assert_msg_equals(expected, parse_msg(raw_message), true);
  451. assert_msg_equals(expected, merged, true);
  452. }
  453. const common_chat_msg message_user {
  454. "user",
  455. "Hey there!",
  456. /* .content_parts = */ {},
  457. /* .tool_calls = */ {},
  458. /* .reasoning_content = */ "",
  459. /* .tool_name = */ "",
  460. /* .tool_call_id = */ "",
  461. };
  462. const common_chat_msg message_user_parts {
  463. "user",
  464. /* .content = */ "",
  465. /* .content_parts = */ {
  466. { "text", "Hey" },
  467. { "text", "there" },
  468. },
  469. /* .tool_calls = */ {},
  470. /* .reasoning_content = */ "",
  471. /* .tool_name = */ "",
  472. /* .tool_call_id = */ "",
  473. };
  474. const common_chat_msg message_assist = simple_assist_msg("Hello, world!\nWhat's up?");
  475. const common_chat_msg message_assist_empty = simple_assist_msg("");
  476. const common_chat_msg message_assist_thoughts_unparsed_deepseek = simple_assist_msg("<think>I'm\nthinking</think>Hello, world!\nWhat's up?");
  477. const common_chat_msg message_assist_thoughts_unparsed_md = simple_assist_msg("<think>I'm\nthinking</think>Hello, world!\nWhat's up?\n```json\n{}```");
  478. const common_chat_msg message_assist_thoughts_unparsed_md_partial = simple_assist_msg("<think>I'm\nthinking</think>Hello, world!\nWhat's up?\n```json\n{}");
  479. const common_chat_msg message_assist_thoughts_unparsed_r7b = simple_assist_msg("<|START_THINKING|>I'm\nthinking<|END_THINKING|>Hello, world!\nWhat's up?");
  480. const common_chat_msg message_assist_thoughts_unparsed_magistral = simple_assist_msg("[THINK]raisonnement[/THINK]Réponse");
  481. const common_chat_msg message_assist_thoughts = simple_assist_msg("Hello, world!\nWhat's up?", "I'm\nthinking");
  482. const common_chat_msg message_assist_thoughts_unopened_unparsed = simple_assist_msg("I'm\nthinking</think>Hello, world!\nWhat's up?");
  483. const common_chat_msg message_assist_thoughts_no_content = simple_assist_msg("", "I'm\nthinking");
  484. const common_chat_msg message_assist_call = simple_assist_msg("", "", "special_function", "{\"arg1\": 1}");
  485. const common_chat_msg message_assist_call_noopt = simple_assist_msg("", "", "special_function_with_opt", "{\"arg1\": 1}");
  486. const common_chat_msg message_assist_call_withopt = simple_assist_msg("", "", "special_function_with_opt", "{\"arg1\": 1, \"arg2\": 2}");
  487. const common_chat_msg message_assist_call_content = simple_assist_msg("Hello, world!\nWhat's up?", "", "special_function", "{\"arg1\":1}");
  488. const common_chat_msg message_assist_call_empty_args = simple_assist_msg("", "", "special_function");
  489. const common_chat_msg message_assist_call_cutoff_args = simple_assist_msg("", "", "special_function", "{\"arg");
  490. const common_chat_msg message_assist_call_thoughts = simple_assist_msg("", "I'm\nthinking", "special_function", "{\"arg1\":1}");
  491. const common_chat_msg message_assist_call_thoughts_unparsed = simple_assist_msg("<think>I'm\nthinking</think>\n\n", "", "special_function", "{\"arg1\": 1}");
  492. const common_chat_msg message_assist_call_thoughts_content = simple_assist_msg("Hello, world!\nWhat's up?", "I'm\nthinking", "special_function", "{\"arg1\": 1}");
  493. const common_chat_msg message_assist_call_id = simple_assist_msg("", "", "special_function", "{\"arg1\":1}", /* .id = */ "123456789");
  494. const common_chat_msg message_assist_call_idx = simple_assist_msg("", "", "special_function", "{\"arg1\":1}", /* .id = */ "0");
  495. const common_chat_msg message_assist_thoughts_call_idx = simple_assist_msg("", "I'm\nthinking", "special_function", "{\"arg1\": 1}", /* id = */ "0");
  496. const common_chat_msg message_assist_call_python = simple_assist_msg("", "", "python", "{\"code\":\"print('hey')\"}");
  497. const common_chat_msg message_assist_call_python_lines = simple_assist_msg("", "", "python", "{\"code\":\"# This is a program:\\nprint('hey')\"}");
  498. const common_chat_msg message_assist_call_python_lines_unclosed = simple_assist_msg("", "", "python", "{\"code\":\"# This is a program:\\nprint('hey')");
  499. const common_chat_msg message_assist_call_code_interpreter = simple_assist_msg("", "", "code_interpreter", "{\"code\":\"print('hey')\"}");
  500. static void test_msgs_oaicompat_json_conversion() {
  501. printf("[%s]\n", __func__);
  502. std::vector<common_chat_msg> msgs{
  503. message_user,
  504. message_user_parts,
  505. message_assist_call,
  506. message_assist_call_thoughts,
  507. message_assist_call_thoughts_unparsed,
  508. message_assist_call_thoughts_content,
  509. message_assist_call_id,
  510. message_assist_call_idx,
  511. message_assist_call_python,
  512. message_assist_call_code_interpreter,
  513. };
  514. for (const auto & msg : msgs) {
  515. auto oai_json = common_chat_msgs_to_json_oaicompat<json>({msg});
  516. auto msgs2 = common_chat_msgs_parse_oaicompat(oai_json);
  517. assert_equals((size_t) 1, msgs2.size());
  518. auto msg2 = msgs2[0];
  519. assert_msg_equals(msg, msg2);
  520. }
  521. assert_equals(
  522. std::string(
  523. "[\n"
  524. " {\n"
  525. " \"role\": \"user\",\n"
  526. " \"content\": [\n"
  527. " {\n"
  528. " \"type\": \"text\",\n"
  529. " \"text\": \"Hey\"\n"
  530. " },\n"
  531. " {\n"
  532. " \"type\": \"text\",\n"
  533. " \"text\": \"there\"\n"
  534. " }\n"
  535. " ]\n"
  536. " }\n"
  537. "]"
  538. ),
  539. common_chat_msgs_to_json_oaicompat<json>({message_user_parts}).dump(2));
  540. assert_equals(
  541. std::string(
  542. "[\n"
  543. " {\n"
  544. " \"role\": \"assistant\",\n"
  545. " \"content\": null,\n"
  546. " \"tool_calls\": [\n"
  547. " {\n"
  548. " \"type\": \"function\",\n"
  549. " \"function\": {\n"
  550. " \"name\": \"python\",\n"
  551. " \"arguments\": \"{\\\"code\\\":\\\"print('hey')\\\"}\"\n"
  552. " }\n"
  553. " }\n"
  554. " ]\n"
  555. " }\n"
  556. "]"
  557. ),
  558. common_chat_msgs_to_json_oaicompat<json>({message_assist_call_python}).dump(2));
  559. auto res = common_chat_msgs_parse_oaicompat(json::parse("[{\"role\": \"assistant\", \"tool_calls\": []}]"));
  560. assert_equals<size_t>(1, res.size());
  561. assert_equals<std::string>(res[0].role, "assistant");
  562. assert_equals(true, res[0].content.empty());
  563. assert_equals(true, res[0].tool_calls.empty());
  564. try {
  565. common_chat_msgs_parse_oaicompat(json::parse("[{\"role\": \"assistant\"}]"));
  566. throw std::runtime_error("Expected exception");
  567. } catch (const std::exception & e) {
  568. if (std::string(e.what()).find("'content'") == std::string::npos) {
  569. throw std::runtime_error("Expected exception about missing 'content'");
  570. }
  571. }
  572. }
  573. static void test_tools_oaicompat_json_conversion() {
  574. printf("[%s]\n", __func__);
  575. std::vector<common_chat_tool> tools{
  576. special_function_tool,
  577. python_tool,
  578. code_interpreter_tool,
  579. };
  580. for (const auto & tool : tools) {
  581. auto oai_json = common_chat_tools_to_json_oaicompat<json>({tool});
  582. auto tools2 = common_chat_tools_parse_oaicompat(oai_json);
  583. assert_equals((size_t) 1, tools2.size());
  584. auto tool2 = tools2[0];
  585. assert_equals(tool.name, tool2.name);
  586. assert_equals(tool.description, tool2.description);
  587. assert_equals(json::parse(tool.parameters).dump(2), json::parse(tool2.parameters).dump(2));
  588. }
  589. assert_equals(
  590. std::string(
  591. "[\n"
  592. " {\n"
  593. " \"type\": \"function\",\n"
  594. " \"function\": {\n"
  595. " \"name\": \"special_function\",\n"
  596. " \"description\": \"I'm special\",\n"
  597. " \"parameters\": {\n"
  598. " \"type\": \"object\",\n"
  599. " \"properties\": {\n"
  600. " \"arg1\": {\n"
  601. " \"type\": \"integer\",\n"
  602. " \"description\": \"The arg.\"\n"
  603. " }\n"
  604. " },\n"
  605. " \"required\": [\n"
  606. " \"arg1\"\n"
  607. " ]\n"
  608. " }\n"
  609. " }\n"
  610. " }\n"
  611. "]"
  612. ),
  613. common_chat_tools_to_json_oaicompat<json>({special_function_tool}).dump(2));
  614. }
  615. static void test_template_output_parsers() {
  616. printf("[%s]\n", __func__);
  617. common_chat_templates_inputs inputs_no_tools;
  618. inputs_no_tools.messages = {message_user};
  619. common_chat_templates_inputs inputs_tools;
  620. inputs_tools.messages = {message_user};
  621. inputs_tools.tools = {special_function_tool};
  622. common_chat_templates_inputs inputs_tools_builtin;
  623. inputs_tools_builtin.messages = {message_user};
  624. inputs_tools_builtin.tools = {python_tool};
  625. {
  626. // Not supported yet
  627. auto tmpls = read_templates("models/templates/CohereForAI-c4ai-command-r-plus-tool_use.jinja");
  628. assert_equals(COMMON_CHAT_FORMAT_CONTENT_ONLY, common_chat_templates_apply(tmpls.get(), inputs_no_tools).format);
  629. assert_equals(COMMON_CHAT_FORMAT_GENERIC, common_chat_templates_apply(tmpls.get(), inputs_tools).format);
  630. }
  631. {
  632. auto tmpls = read_templates("models/templates/CohereForAI-c4ai-command-r7b-12-2024-tool_use.jinja");
  633. std::vector<std::string> end_tokens{ "<|END_OF_TURN_TOKEN|>" };
  634. for (const auto & inputs : { inputs_no_tools, inputs_tools }) {
  635. auto params = common_chat_templates_apply(tmpls.get(), inputs);
  636. assert_equals(COMMON_CHAT_FORMAT_COMMAND_R7B, params.format);
  637. assert_equals(false, params.thinking_forced_open);
  638. }
  639. assert_msg_equals(message_assist,
  640. common_chat_parse(
  641. "Hello, world!\nWhat's up?",
  642. /* is_partial= */ false,
  643. {COMMON_CHAT_FORMAT_COMMAND_R7B}));
  644. assert_msg_equals(message_assist,
  645. common_chat_parse(
  646. "<|START_RESPONSE|>Hello, world!\nWhat's up?<|END_RESPONSE|>",
  647. /* is_partial= */ false,
  648. {COMMON_CHAT_FORMAT_COMMAND_R7B}));
  649. assert_msg_equals(message_assist_thoughts,
  650. common_chat_parse(
  651. "<|START_THINKING|>I'm\nthinking<|END_THINKING|>"
  652. "<|START_RESPONSE|>Hello, world!\nWhat's up?<|END_RESPONSE|>",
  653. /* is_partial= */ false,
  654. {
  655. /* .format = */ COMMON_CHAT_FORMAT_COMMAND_R7B,
  656. /* .reasoning_format = */ COMMON_REASONING_FORMAT_DEEPSEEK,
  657. }));
  658. assert_msg_equals(message_assist_thoughts_unparsed_deepseek,
  659. common_chat_parse(
  660. "<|START_THINKING|>I'm\nthinking<|END_THINKING|>"
  661. "<|START_RESPONSE|>Hello, world!\nWhat's up?<|END_RESPONSE|>",
  662. /* is_partial= */ false,
  663. {
  664. /* .format = */ COMMON_CHAT_FORMAT_COMMAND_R7B,
  665. /* .reasoning_format = */ COMMON_REASONING_FORMAT_DEEPSEEK,
  666. /* .reasoning_in_content = */ true,
  667. /* .thinking_forced_open = */ false,
  668. }));
  669. assert_msg_equals(message_assist_thoughts_unparsed_r7b,
  670. common_chat_parse(
  671. "<|START_THINKING|>I'm\nthinking<|END_THINKING|>"
  672. "<|START_RESPONSE|>Hello, world!\nWhat's up?<|END_RESPONSE|>",
  673. /* is_partial= */ false,
  674. {COMMON_CHAT_FORMAT_COMMAND_R7B}));
  675. assert_msg_equals(message_assist_thoughts,
  676. common_chat_parse(
  677. "<|START_THINKING|>I'm\nthinking<|END_THINKING|>"
  678. "<|START_RESPONSE|>Hello, world!\nWhat's up?<|END_RESPONSE|>",
  679. /* is_partial= */ false,
  680. {
  681. /* .format = */ COMMON_CHAT_FORMAT_COMMAND_R7B,
  682. /* .reasoning_format = */ COMMON_REASONING_FORMAT_DEEPSEEK,
  683. }));
  684. assert_msg_equals(message_assist_thoughts_call_idx,
  685. common_chat_parse(
  686. "<|START_THINKING|>I'm\nthinking<|END_THINKING|>"
  687. "<|START_ACTION|>[\n"
  688. " {\"tool_call_id\": \"0\", \"tool_name\": \"special_function\", \"parameters\": {\"arg1\": 1}}\n"
  689. "]<|END_ACTION|>",
  690. /* is_partial= */ false,
  691. {
  692. /* .format = */ COMMON_CHAT_FORMAT_COMMAND_R7B,
  693. /* .reasoning_format = */ COMMON_REASONING_FORMAT_DEEPSEEK,
  694. }));
  695. assert_msg_equals(message_assist_thoughts_no_content,
  696. common_chat_parse(
  697. "<|START_THINKING|>I'm\nthinking<|END_THINKING|>"
  698. "<|START_ACTION|>[\n"
  699. " {\"tool_call_id\": \"0\", \"tool_name\": \"special",
  700. /* is_partial= */ true,
  701. {
  702. /* .format = */ COMMON_CHAT_FORMAT_COMMAND_R7B,
  703. /* .reasoning_format = */ COMMON_REASONING_FORMAT_DEEPSEEK,
  704. }));
  705. test_templates(tmpls.get(), end_tokens, message_assist_call_idx, tools,
  706. "<|START_THINKING|><|END_THINKING|>"
  707. "<|START_ACTION|>[\n"
  708. " {\"tool_call_id\": \"0\", \"tool_name\": \"special_function\", \"parameters\": {\"arg1\": 1}}\n"
  709. "]<|END_ACTION|>",
  710. /* expect_grammar_triggered= */ true,
  711. /* test_grammar_if_triggered= */ true,
  712. COMMON_REASONING_FORMAT_DEEPSEEK);
  713. test_templates(tmpls.get(), end_tokens, message_assist, tools,
  714. "<|START_RESPONSE|>Hello, world!\n"
  715. "What's up?<|END_RESPONSE|>",
  716. /* expect_grammar_triggered= */ false);
  717. }
  718. {
  719. auto tmpls = read_templates("models/templates/google-gemma-2-2b-it.jinja");
  720. std::vector<std::string> end_tokens{ "<end_of_turn>" };
  721. assert_equals(COMMON_CHAT_FORMAT_CONTENT_ONLY, common_chat_templates_apply(tmpls.get(), inputs_no_tools).format);
  722. assert_equals(COMMON_CHAT_FORMAT_GENERIC, common_chat_templates_apply(tmpls.get(), inputs_tools).format);
  723. assert_equals(COMMON_CHAT_FORMAT_GENERIC,
  724. common_chat_templates_apply(
  725. read_templates("models/templates/microsoft-Phi-3.5-mini-instruct.jinja").get(),
  726. inputs_tools)
  727. .format);
  728. // Generic tool calls doesn't generate / parse content-only messages symmetrically.
  729. assert_equals(
  730. simple_assist_msg("{ \"tool_call\" : { \"name\" : \"t"),
  731. common_chat_parse(
  732. "{ \"tool_call\" : { \"name\" : \"t",
  733. /* is_partial= */ true,
  734. {
  735. /* .format = */ COMMON_CHAT_FORMAT_GENERIC,
  736. /* .reasoning_format = */ COMMON_REASONING_FORMAT_DEEPSEEK,
  737. /* .reasoning_in_content = */ false,
  738. /* .thinking_forced_open = */ true,
  739. /* .parse_tool_calls = */ false,
  740. }));
  741. assert_equals(
  742. message_assist_empty,
  743. common_chat_parse(
  744. "{ \"tool_call\" : { \"name\" : \"t",
  745. /* is_partial= */ true,
  746. {COMMON_CHAT_FORMAT_GENERIC}));
  747. assert_equals(
  748. simple_assist_msg("", "", "puppeteer_screenshot", "{\"name\":\"servethehome_homepage\","),
  749. common_chat_parse(
  750. R"({"tool_call": {"name": "puppeteer_screenshot", "arguments": {"name": "servethehome_homepage",)",
  751. /* is_partial= */ true,
  752. {COMMON_CHAT_FORMAT_GENERIC}));
  753. assert_equals(
  754. message_assist_call_empty_args,
  755. common_chat_parse(
  756. "{ \"tool_call\" : { \"name\" : \"special_function\"",
  757. /* is_partial= */ true,
  758. {COMMON_CHAT_FORMAT_GENERIC}));
  759. assert_equals(
  760. message_assist_call_cutoff_args,
  761. common_chat_parse(
  762. "{ \"tool_call\" : { \"name\" : \"special_function\", \"arguments\" : { \"arg",
  763. /* is_partial= */ true,
  764. {COMMON_CHAT_FORMAT_GENERIC}));
  765. assert_msg_equals(message_assist,
  766. common_chat_parse(
  767. "{\n"
  768. " \"response\": \"Hello, world!\\nWhat's up?\"\n"
  769. "}",
  770. /* is_partial= */ false,
  771. {COMMON_CHAT_FORMAT_GENERIC}));
  772. test_templates(tmpls.get(), end_tokens, message_assist_call_id, tools,
  773. "{\n"
  774. " \"tool_calls\": [\n"
  775. " {\n"
  776. " \"name\": \"special_function\",\n"
  777. " \"arguments\": {\n"
  778. " \"arg1\": 1\n"
  779. " },\n"
  780. " \"id\": \"123456789\"\n"
  781. " }\n"
  782. " ]\n"
  783. "}");
  784. }
  785. {
  786. auto tmpls = read_templates("models/templates/mistralai-Mistral-Nemo-Instruct-2407.jinja");
  787. std::vector<std::string> end_tokens{ "</s>" };
  788. assert_equals(COMMON_CHAT_FORMAT_MISTRAL_NEMO, common_chat_templates_apply(tmpls.get(), inputs_tools).format);
  789. test_templates(tmpls.get(), end_tokens, message_assist, tools, "Hello, world!\nWhat's up?", /* expect_grammar_triggered= */ false);
  790. test_templates(
  791. tmpls.get(), end_tokens, message_assist_call_id, tools,
  792. "[TOOL_CALLS][{\"name\": \"special_function\", \"arguments\": {\"arg1\": 1}, \"id\": \"123456789\"}]");
  793. }
  794. {
  795. assert_msg_equals(
  796. simple_assist_msg("Réponse", "raisonnement"),
  797. common_chat_parse(
  798. message_assist_thoughts_unparsed_magistral.content,
  799. /* is_partial= */ false,
  800. {
  801. /* .format = */ COMMON_CHAT_FORMAT_MAGISTRAL,
  802. /* .reasoning_format = */ COMMON_REASONING_FORMAT_AUTO,
  803. }));
  804. }
  805. {
  806. auto tmpls = read_templates("models/templates/Qwen-QwQ-32B.jinja");
  807. std::vector<std::string> end_tokens{ "<|im_end|>" };
  808. assert_equals(COMMON_CHAT_FORMAT_HERMES_2_PRO, common_chat_templates_apply(tmpls.get(), inputs_no_tools).format);
  809. assert_equals(COMMON_CHAT_FORMAT_HERMES_2_PRO, common_chat_templates_apply(tmpls.get(), inputs_tools).format);
  810. }
  811. {
  812. auto tmpls = read_templates("models/templates/NousResearch-Hermes-2-Pro-Llama-3-8B-tool_use.jinja");
  813. std::vector<std::string> end_tokens{ "<|im_end|>" };
  814. assert_equals(COMMON_CHAT_FORMAT_HERMES_2_PRO, common_chat_templates_apply(tmpls.get(), inputs_no_tools).format);
  815. assert_equals(COMMON_CHAT_FORMAT_HERMES_2_PRO, common_chat_templates_apply(tmpls.get(), inputs_tools).format);
  816. assert_equals(
  817. COMMON_CHAT_FORMAT_HERMES_2_PRO,
  818. common_chat_templates_apply(
  819. read_templates("models/templates/NousResearch-Hermes-3-Llama-3.1-8B-tool_use.jinja").get(),
  820. inputs_tools)
  821. .format);
  822. assert_equals(
  823. COMMON_CHAT_FORMAT_HERMES_2_PRO,
  824. common_chat_templates_apply(
  825. read_templates("models/templates/Qwen-Qwen2.5-7B-Instruct.jinja").get(),
  826. inputs_tools)
  827. .format);
  828. // Test parsing
  829. assert_msg_equals(
  830. simple_assist_msg("", "", "python", ""),
  831. common_chat_parse(
  832. "```json\n"
  833. "<function_call> { \"name\" : \"python\"",
  834. /* is_partial= */ true,
  835. {COMMON_CHAT_FORMAT_HERMES_2_PRO}));
  836. assert_msg_equals(
  837. simple_assist_msg("Let's call something\n"),
  838. common_chat_parse(
  839. "Let's call something\n"
  840. "<tool_call>{\"name\"",
  841. /* is_partial= */ true,
  842. {
  843. /* .format = */ COMMON_CHAT_FORMAT_HERMES_2_PRO,
  844. /* .reasoning_format = */ COMMON_REASONING_FORMAT_DEEPSEEK,
  845. }));
  846. assert_msg_equals(
  847. simple_assist_msg("Let's call something\n"),
  848. common_chat_parse(
  849. "Let's call something\n"
  850. "<tool_call>{\"name",
  851. /* is_partial= */ true,
  852. {
  853. /* .format = */ COMMON_CHAT_FORMAT_HERMES_2_PRO,
  854. /* .reasoning_format = */ COMMON_REASONING_FORMAT_DEEPSEEK,
  855. }));
  856. assert_msg_equals(message_assist_call_thoughts,
  857. common_chat_parse(
  858. // QwQ-32B's template adds a trailing <think> if add_generation_prompt
  859. "I'm\nthinking</think>\n"
  860. "<tool_call>{\"name\": \"special_function\", \"arguments\": {\"arg1\": 1}}</tool_call>",
  861. /* is_partial= */ false,
  862. {
  863. /* .format = */ COMMON_CHAT_FORMAT_HERMES_2_PRO,
  864. /* .reasoning_format = */ COMMON_REASONING_FORMAT_DEEPSEEK,
  865. /* .reasoning_in_content = */ false,
  866. /* .thinking_forced_open = */ true,
  867. }));
  868. assert_msg_equals(
  869. message_assist_call,
  870. common_chat_parse(
  871. "<tool_call>\n"
  872. "{\"name\": \"special_function\", \"arguments\": {\"arg1\": 1}}\n"
  873. "</tool_call>",
  874. /* is_partial= */ false,
  875. {COMMON_CHAT_FORMAT_HERMES_2_PRO}));
  876. assert_msg_equals(message_assist_call_content,
  877. common_chat_parse(
  878. "Hello, world!\nWhat's up?<tool_call>\n"
  879. "{\"name\": \"special_function\", \"arguments\": {\"arg1\": 1}}\n"
  880. "</tool_call>",
  881. /* is_partial= */ false,
  882. {COMMON_CHAT_FORMAT_HERMES_2_PRO}));
  883. assert_msg_equals(
  884. message_assist_call,
  885. common_chat_parse(
  886. "<function=special_function>{\"arg1\": 1}</function>",
  887. /* is_partial= */ false,
  888. {COMMON_CHAT_FORMAT_HERMES_2_PRO}));
  889. assert_msg_equals(
  890. message_assist_call,
  891. common_chat_parse(
  892. "<function name=\"special_function\">\n"
  893. "{\"arg1\": 1}\n"
  894. "</function>",
  895. /* is_partial= */ false,
  896. {COMMON_CHAT_FORMAT_HERMES_2_PRO}));
  897. assert_msg_equals(
  898. message_assist_call,
  899. common_chat_parse(
  900. "<tool>\n"
  901. " {\"name\": \"special_function\", \"arguments\": {\"arg1\": 1}}\n"
  902. "</tool>",
  903. /* is_partial= */ false,
  904. {COMMON_CHAT_FORMAT_HERMES_2_PRO}));
  905. assert_msg_equals(
  906. message_assist_call,
  907. common_chat_parse(
  908. "<tools>\n"
  909. " {\"name\": \"special_function\", \"arguments\": {\"arg1\": 1}}\n"
  910. "</tools>",
  911. /* is_partial= */ false,
  912. {COMMON_CHAT_FORMAT_HERMES_2_PRO}));
  913. assert_msg_equals(
  914. message_assist_call,
  915. common_chat_parse(
  916. "<response>\n"
  917. " {\"name\": \"special_function\", \"arguments\": {\"arg1\": 1}}\n"
  918. "</response>",
  919. /* is_partial= */ false,
  920. {COMMON_CHAT_FORMAT_HERMES_2_PRO}));
  921. assert_msg_equals(
  922. message_assist_call,
  923. common_chat_parse(
  924. "```xml\n"
  925. "<response>\n"
  926. " {\"name\": \"special_function\", \"arguments\": {\"arg1\": 1}}\n"
  927. "</response>\n"
  928. "```",
  929. /* is_partial= */ false,
  930. {COMMON_CHAT_FORMAT_HERMES_2_PRO}));
  931. assert_msg_equals(
  932. message_assist_call,
  933. common_chat_parse(
  934. "```xml\n"
  935. " {\"name\": \"special_function\", \"arguments\": {\"arg1\": 1}}\n"
  936. "```",
  937. /* is_partial= */ false,
  938. {COMMON_CHAT_FORMAT_HERMES_2_PRO}));
  939. assert_msg_equals(
  940. message_assist_call,
  941. common_chat_parse(
  942. "```\n"
  943. " {\"name\": \"special_function\", \"arguments\": {\"arg1\": 1}}\n"
  944. "```",
  945. /* is_partial= */ false,
  946. {COMMON_CHAT_FORMAT_HERMES_2_PRO}));
  947. assert_msg_equals(
  948. message_assist_call,
  949. common_chat_parse(
  950. "```\n"
  951. "{\"name\": \"special_function\", \"arguments\": {\"arg1\": 1}}\n"
  952. "```",
  953. /* is_partial= */ false,
  954. {COMMON_CHAT_FORMAT_HERMES_2_PRO}));
  955. assert_msg_equals(
  956. message_assist_call,
  957. common_chat_parse(
  958. "```json\n"
  959. " {\"name\": \"special_function\", \"arguments\": {\"arg1\": 1}}\n"
  960. "```",
  961. /* is_partial= */ false,
  962. {COMMON_CHAT_FORMAT_HERMES_2_PRO}));
  963. assert_msg_equals(
  964. message_assist_call,
  965. common_chat_parse(
  966. "```json\n"
  967. "\n"
  968. " <function_call> {\"name\": \"special_function\", \"arguments\": {\"arg1\": 1}} \n"
  969. " </function_call> \n"
  970. "``` ",
  971. /* is_partial= */ false,
  972. {COMMON_CHAT_FORMAT_HERMES_2_PRO}));
  973. assert_msg_equals(
  974. message_assist_call,
  975. common_chat_parse(
  976. "<json>\n"
  977. " {\"name\": \"special_function\", \"arguments\": {\"arg1\": 1}}\n"
  978. "</json>",
  979. /* is_partial= */ false,
  980. {COMMON_CHAT_FORMAT_HERMES_2_PRO}));
  981. assert_msg_equals(
  982. message_assist_call,
  983. common_chat_parse(
  984. "<xml>\n"
  985. " {\n"
  986. " \"name\": \"special_function\", \"arguments\": {\"arg1\": 1}\n"
  987. " }\n"
  988. "</xml>",
  989. /* is_partial= */ false,
  990. {COMMON_CHAT_FORMAT_HERMES_2_PRO}));
  991. assert_msg_equals(
  992. message_assist_call,
  993. common_chat_parse(
  994. "<JSON>\n"
  995. " {\"name\": \"special_function\", \"arguments\": {\"arg1\": 1}}\n"
  996. "</JSON>",
  997. /* is_partial= */ false,
  998. {COMMON_CHAT_FORMAT_HERMES_2_PRO}));
  999. assert_msg_equals(
  1000. message_assist_call,
  1001. common_chat_parse(
  1002. "{\"name\": \"special_function\", \"arguments\": {\"arg1\": 1}}",
  1003. /* is_partial= */ false,
  1004. {COMMON_CHAT_FORMAT_HERMES_2_PRO}));
  1005. assert_msg_equals(
  1006. message_assist_call,
  1007. common_chat_parse(
  1008. "{\n \"name\": \"special_function\", \"arguments\": {\"arg1\": 1}}",
  1009. /* is_partial= */ false,
  1010. {COMMON_CHAT_FORMAT_HERMES_2_PRO}));
  1011. // Test multiple tool calls
  1012. common_chat_msg message_assist_multiple_calls;
  1013. message_assist_multiple_calls.role = "assistant";
  1014. message_assist_multiple_calls.content = "";
  1015. message_assist_multiple_calls.tool_calls.push_back({"special_function", "{\"arg1\": 1}", ""});
  1016. message_assist_multiple_calls.tool_calls.push_back({"python", "{\"code\":\"print('hello')\"}", ""});
  1017. assert_msg_equals(
  1018. message_assist_multiple_calls,
  1019. common_chat_parse(
  1020. "<tool_call>\n"
  1021. "{\"name\": \"special_function\", \"arguments\": {\"arg1\": 1}}\n"
  1022. "</tool_call>\n"
  1023. "<tool_call>\n"
  1024. "{\"name\": \"python\", \"arguments\": {\"code\":\"print('hello')\"}}\n"
  1025. "</tool_call>",
  1026. /* is_partial= */ false,
  1027. {COMMON_CHAT_FORMAT_HERMES_2_PRO}));
  1028. assert_msg_equals(
  1029. message_assist_multiple_calls,
  1030. common_chat_parse(
  1031. "<function=special_function>{\"arg1\": 1}</function>\n"
  1032. "<function=python>{\"code\":\"print('hello')\"}</function>",
  1033. /* is_partial= */ false,
  1034. {COMMON_CHAT_FORMAT_HERMES_2_PRO}));
  1035. assert_msg_equals(
  1036. simple_assist_msg(
  1037. "This is not a tool call:",
  1038. "",
  1039. "special_function",
  1040. "{\"arg1\": 1}"),
  1041. common_chat_parse(
  1042. "This is not a tool call:\n"
  1043. "{\"name\": \"special_function\", \"arguments\": {\"arg1\": 1}}",
  1044. /* is_partial= */ false,
  1045. {COMMON_CHAT_FORMAT_HERMES_2_PRO}));
  1046. assert_msg_equals(message_assist,
  1047. common_chat_parse(
  1048. "Hello, world!\nWhat's up?",
  1049. /* is_partial= */ false,
  1050. {COMMON_CHAT_FORMAT_HERMES_2_PRO}));
  1051. assert_msg_equals(message_assist_thoughts_unparsed_deepseek,
  1052. common_chat_parse(
  1053. "<think>I'm\nthinking</think>Hello, world!\nWhat's up?",
  1054. /* is_partial= */ false,
  1055. {COMMON_CHAT_FORMAT_HERMES_2_PRO}));
  1056. // assert_msg_equals(message_assist_thoughts_unparsed_deepseek,
  1057. // common_chat_parse(
  1058. // "I'm\nthinking</think>Hello, world!\nWhat's up?",
  1059. // COMMON_CHAT_FORMAT_HERMES_2_PRO));
  1060. assert_msg_equals(message_assist_thoughts,
  1061. common_chat_parse(
  1062. "<think>I'm\nthinking</think>Hello, world!\nWhat's up?",
  1063. /* is_partial= */ false,
  1064. {
  1065. /* .format = */ COMMON_CHAT_FORMAT_HERMES_2_PRO,
  1066. /* .reasoning_format = */ COMMON_REASONING_FORMAT_DEEPSEEK,
  1067. }));
  1068. assert_msg_equals(message_assist_thoughts,
  1069. common_chat_parse(
  1070. "<think>I'm\nthinking</think>Hello, world!\nWhat's up?",
  1071. /* is_partial= */ true,
  1072. {
  1073. /* .format = */ COMMON_CHAT_FORMAT_HERMES_2_PRO,
  1074. /* .reasoning_format = */ COMMON_REASONING_FORMAT_DEEPSEEK,
  1075. }));
  1076. assert_msg_equals(message_assist_thoughts_unparsed_md,
  1077. common_chat_parse(
  1078. "<think>I'm\nthinking</think>Hello, world!\nWhat's up?\n```json\n{}```",
  1079. /* is_partial= */ false,
  1080. {
  1081. /* .format = */ COMMON_CHAT_FORMAT_HERMES_2_PRO,
  1082. /* .reasoning_format = */ COMMON_REASONING_FORMAT_DEEPSEEK,
  1083. /* .reasoning_in_content = */ true,
  1084. /* .thinking_forced_open = */ false,
  1085. /* .parse_tool_calls = */ false,
  1086. }));
  1087. assert_msg_equals(message_assist_thoughts_unparsed_md_partial,
  1088. common_chat_parse(
  1089. "<think>I'm\nthinking</think>Hello, world!\nWhat's up?\n```json\n{}```",
  1090. /* is_partial= */ true,
  1091. {
  1092. /* .format = */ COMMON_CHAT_FORMAT_HERMES_2_PRO,
  1093. /* .reasoning_format = */ COMMON_REASONING_FORMAT_DEEPSEEK,
  1094. /* .reasoning_in_content = */ true,
  1095. /* .thinking_forced_open = */ false,
  1096. }));
  1097. assert_msg_equals(message_assist_thoughts_unopened_unparsed,
  1098. common_chat_parse(
  1099. "I'm\nthinking</think>Hello, world!\nWhat's up?",
  1100. /* is_partial= */ false,
  1101. {
  1102. /* .format = */ COMMON_CHAT_FORMAT_HERMES_2_PRO,
  1103. /* .reasoning_format = */ COMMON_REASONING_FORMAT_DEEPSEEK,
  1104. }));
  1105. assert_msg_equals(message_assist_thoughts,
  1106. common_chat_parse(
  1107. "I'm\nthinking</think>Hello, world!\nWhat's up?",
  1108. /* is_partial= */ false,
  1109. {
  1110. /* .format = */ COMMON_CHAT_FORMAT_HERMES_2_PRO,
  1111. /* .reasoning_format = */ COMMON_REASONING_FORMAT_DEEPSEEK,
  1112. /* .reasoning_in_content = */ false,
  1113. /* .thinking_forced_open = */ true,
  1114. }));
  1115. test_templates(tmpls.get(), end_tokens, message_assist, tools, "Hello, world!\nWhat's up?", /* expect_grammar_triggered= */ false);
  1116. test_templates(tmpls.get(), end_tokens, message_assist_call, tools,
  1117. "<tool_call>\n"
  1118. "{\"name\": \"special_function\", \"arguments\": {\"arg1\": 1}}\n"
  1119. "</tool_call>");
  1120. // Test multiple tool calls with template
  1121. common_chat_msg message_assist_multiple_calls_template;
  1122. message_assist_multiple_calls_template.role = "assistant";
  1123. message_assist_multiple_calls_template.content = "";
  1124. message_assist_multiple_calls_template.tool_calls.push_back({"special_function", "{\"arg1\": 1}", ""});
  1125. message_assist_multiple_calls_template.tool_calls.push_back({"python", "{\"code\":\"print('test')\"}", ""});
  1126. test_templates(tmpls.get(), end_tokens, message_assist_multiple_calls_template, tools,
  1127. "<tool_call>\n"
  1128. "{\"name\": \"special_function\", \"arguments\": {\"arg1\": 1}}\n"
  1129. "</tool_call>\n"
  1130. "<tool_call>\n"
  1131. "{\"name\": \"python\", \"arguments\": {\"code\":\"print('test')\"}}\n"
  1132. "</tool_call>");
  1133. test_templates(tmpls.get(), end_tokens, message_assist_call_python_lines, tools,
  1134. "<tool_call>\n"
  1135. "{\"name\": \"python\", \"arguments\": {\"code\":\"# This is a program:\\nprint('hey')\"}}\n"
  1136. "</tool_call>");
  1137. assert_msg_equals(
  1138. simple_assist_msg("", /* reasoning_content= */ "<tool_call>nah uhg</tool_call>"),
  1139. common_chat_parse(
  1140. "<think><tool_call>nah uhg</tool_call>",
  1141. /* is_partial= */ false,
  1142. {
  1143. /* .format = */ COMMON_CHAT_FORMAT_HERMES_2_PRO,
  1144. /* .reasoning_format = */ COMMON_REASONING_FORMAT_DEEPSEEK,
  1145. }));
  1146. }
  1147. {
  1148. auto tmpls = read_templates("models/templates/meta-llama-Llama-3.1-8B-Instruct.jinja");
  1149. std::vector<std::string> end_tokens{ "<|eom_id|>", "<|eot_id|>" };
  1150. assert_equals(COMMON_CHAT_FORMAT_CONTENT_ONLY, common_chat_templates_apply(tmpls.get(), inputs_no_tools).format);
  1151. assert_equals(COMMON_CHAT_FORMAT_LLAMA_3_X, common_chat_templates_apply(tmpls.get(), inputs_tools).format);
  1152. assert_equals(COMMON_CHAT_FORMAT_LLAMA_3_X_WITH_BUILTIN_TOOLS,
  1153. common_chat_templates_apply(tmpls.get(), inputs_tools_builtin).format);
  1154. assert_equals(COMMON_CHAT_FORMAT_LLAMA_3_X_WITH_BUILTIN_TOOLS,
  1155. common_chat_templates_apply(
  1156. read_templates("models/templates/meta-llama-Llama-3.3-70B-Instruct.jinja").get(),
  1157. inputs_tools_builtin)
  1158. .format);
  1159. assert_equals(
  1160. message_assist_call,
  1161. common_chat_parse(
  1162. "{\"name\": \"special_function\", \"parameters\": {\"arg1\": 1}}",
  1163. /* is_partial= */ false,
  1164. {COMMON_CHAT_FORMAT_LLAMA_3_X}));
  1165. // test_templates(tmpls.get(), end_tokens, message_assist, tools, R"(?)", /* expect_grammar_triggered= */ false);
  1166. test_templates(tmpls.get(), end_tokens, message_assist_call_code_interpreter, llama_3_1_tools,
  1167. "<|python_tag|>code_interpreter.call(code=\"print('hey')\")");
  1168. test_templates(tmpls.get(), end_tokens, message_assist_call_python, tools,
  1169. "<|python_tag|>python.call(code=\"print('hey')\")");
  1170. test_templates(tmpls.get(), end_tokens, message_assist_call, tools,
  1171. "{\"name\": \"special_function\", \"parameters\": {\"arg1\": 1}}");
  1172. }
  1173. {
  1174. auto tmpls = read_templates("models/templates/meta-llama-Llama-3.2-3B-Instruct.jinja");
  1175. std::vector<std::string> end_tokens{ "<|eom_id|>", "<|eot_id|>" };
  1176. assert_equals(COMMON_CHAT_FORMAT_LLAMA_3_X, common_chat_templates_apply(tmpls.get(), inputs_tools).format);
  1177. assert_equals(COMMON_CHAT_FORMAT_CONTENT_ONLY, common_chat_templates_apply(tmpls.get(), inputs_no_tools).format);
  1178. test_templates(tmpls.get(), end_tokens, message_assist, tools, "Hello, world!\nWhat's up?", /* expect_grammar_triggered= */ false);
  1179. test_templates(tmpls.get(), end_tokens, message_assist_call, tools,
  1180. "{\"name\": \"special_function\", \"parameters\": {\"arg1\": 1}}");
  1181. }
  1182. {
  1183. auto tmpls = read_templates("models/templates/meetkai-functionary-medium-v3.1.jinja");
  1184. std::vector<std::string> end_tokens{ "<|eom_id|>", "<|eot_id|>" };
  1185. assert_equals(COMMON_CHAT_FORMAT_CONTENT_ONLY,
  1186. common_chat_templates_apply(tmpls.get(), inputs_no_tools).format);
  1187. assert_equals(COMMON_CHAT_FORMAT_FUNCTIONARY_V3_1_LLAMA_3_1,
  1188. common_chat_templates_apply(tmpls.get(), inputs_tools).format);
  1189. assert_equals(COMMON_CHAT_FORMAT_CONTENT_ONLY,
  1190. common_chat_templates_apply(tmpls.get(), inputs_no_tools).format);
  1191. for (auto is_partial : { false, true }) {
  1192. assert_equals(
  1193. message_assist_call,
  1194. common_chat_parse(
  1195. "<function=special_function>{\"arg1\": 1}</function>",
  1196. is_partial,
  1197. {COMMON_CHAT_FORMAT_FUNCTIONARY_V3_1_LLAMA_3_1}));
  1198. }
  1199. assert_equals(
  1200. message_assist_call,
  1201. common_chat_parse(
  1202. "<function=special_function>{\"arg1\": 1}<",
  1203. /* is_partial= */ true,
  1204. {COMMON_CHAT_FORMAT_FUNCTIONARY_V3_1_LLAMA_3_1}));
  1205. test_templates(tmpls.get(), end_tokens, message_assist, tools, "Hello, world!\nWhat's up?", /* expect_grammar_triggered= */ false);
  1206. test_templates(tmpls.get(), end_tokens, message_assist_call, tools,
  1207. "<function=special_function>{\"arg1\": 1}</function>");
  1208. }
  1209. {
  1210. auto tmpls = read_templates("models/templates/meetkai-functionary-medium-v3.2.jinja");
  1211. std::vector<std::string> end_tokens{ "<|eom_id|>", "<|eot_id|>" };
  1212. assert_equals(COMMON_CHAT_FORMAT_FUNCTIONARY_V3_2, common_chat_templates_apply(tmpls.get(), inputs_no_tools).format);
  1213. assert_equals(COMMON_CHAT_FORMAT_FUNCTIONARY_V3_2, common_chat_templates_apply(tmpls.get(), inputs_tools).format);
  1214. assert_msg_equals(
  1215. simple_assist_msg(
  1216. "Hello, world!\nnono\nWhat's up?",
  1217. "",
  1218. "special_function",
  1219. "{\"arg1\": 1}"),
  1220. common_chat_parse(
  1221. "all\n"
  1222. "Hello, world!\n"
  1223. "nono\n"
  1224. "What's up?>>>special_function\n"
  1225. "{\"arg1\": 1}\n",
  1226. /* is_partial= */ false,
  1227. {COMMON_CHAT_FORMAT_FUNCTIONARY_V3_2}));
  1228. assert_msg_equals(message_assist_call_python_lines,
  1229. common_chat_parse(
  1230. "python\n"
  1231. "# This is a program:\n"
  1232. "print('hey')",
  1233. /* is_partial= */ false,
  1234. {COMMON_CHAT_FORMAT_FUNCTIONARY_V3_2}));
  1235. assert_msg_equals(message_assist_call_python_lines_unclosed,
  1236. common_chat_parse(
  1237. "python\n"
  1238. "# This is a program:\n"
  1239. "print('hey')",
  1240. /* is_partial= */ true,
  1241. {COMMON_CHAT_FORMAT_FUNCTIONARY_V3_2}));
  1242. assert_msg_equals(message_assist_call,
  1243. common_chat_parse(
  1244. "special_function\n"
  1245. "{\"arg1\": 1} \n ",
  1246. /* is_partial= */ false,
  1247. {COMMON_CHAT_FORMAT_FUNCTIONARY_V3_2}));
  1248. assert_msg_equals(message_assist,
  1249. common_chat_parse(
  1250. "all\n"
  1251. "Hello, world!\nWhat's up?",
  1252. /* is_partial= */ false,
  1253. {COMMON_CHAT_FORMAT_FUNCTIONARY_V3_2}));
  1254. test_templates(tmpls.get(), end_tokens, message_assist, {},
  1255. "all\n"
  1256. "Hello, world!\n"
  1257. "What's up?",
  1258. /* expect_grammar_triggered= */ false);
  1259. test_templates(tmpls.get(), end_tokens, message_assist_call, tools,
  1260. "special_function\n"
  1261. "{\"arg1\": 1}");
  1262. }
  1263. {
  1264. auto tmpls = read_templates("models/templates/fireworks-ai-llama-3-firefunction-v2.jinja");
  1265. std::vector<std::string> end_tokens{ "<|eot_id|>" };
  1266. assert_equals(COMMON_CHAT_FORMAT_CONTENT_ONLY, common_chat_templates_apply(tmpls.get(), inputs_no_tools).format);
  1267. assert_equals(COMMON_CHAT_FORMAT_FIREFUNCTION_V2, common_chat_templates_apply(tmpls.get(), inputs_tools).format);
  1268. test_templates(tmpls.get(), end_tokens, message_assist, tools, "Hello, world!\nWhat's up?", /* expect_grammar_triggered= */ false);
  1269. test_templates(tmpls.get(), end_tokens, message_assist_call, tools,
  1270. " functools[{\"name\": \"special_function\", \"arguments\": {\"arg1\": 1}}]");
  1271. }
  1272. {
  1273. // Original DeepSeek R1 template. Leaves <|tool▁calls▁begin|> and others unclosed. Our logic fixes the prompt.
  1274. auto tmpls = read_templates("models/templates/deepseek-ai-DeepSeek-R1-Distill-Llama-8B.jinja");
  1275. std::vector<std::string> end_tokens{ "<|end▁of▁sentence|>" };
  1276. for (const auto & inputs : { inputs_no_tools, inputs_tools }) {
  1277. auto params = common_chat_templates_apply(tmpls.get(), inputs);
  1278. assert_equals(COMMON_CHAT_FORMAT_DEEPSEEK_R1, params.format);
  1279. assert_equals(true, params.thinking_forced_open);
  1280. }
  1281. test_templates(tmpls.get(), end_tokens, message_assist, tools, "Hello, world!\nWhat's up?", /* expect_grammar_triggered= */ false);
  1282. test_templates(tmpls.get(), end_tokens, message_assist_thoughts, tools, "Hello, world!\nWhat's up?", /* expect_grammar_triggered= */ false);
  1283. assert_msg_equals(
  1284. simple_assist_msg("Hello, world!\nWhat's up?", "<think>I'm\nthinking"),
  1285. common_chat_parse(
  1286. "<think>I'm\nthinking</think>Hello, world!\nWhat's up?",
  1287. /* is_partial= */ false,
  1288. {
  1289. COMMON_CHAT_FORMAT_DEEPSEEK_R1,
  1290. /* .reasoning_format = */ COMMON_REASONING_FORMAT_DEEPSEEK,
  1291. /* .reasoning_in_content = */ false,
  1292. /* .thinking_forced_open = */ true,
  1293. }));
  1294. assert_msg_equals(
  1295. simple_assist_msg("", "I need to remember the correct syntax. It starts with <|tool▁calls▁begin|> and ends with"),
  1296. common_chat_parse(
  1297. "I need to remember the correct syntax. It starts with <|tool▁calls▁begin|> and ends with",
  1298. /* is_partial= */ true,
  1299. {
  1300. COMMON_CHAT_FORMAT_DEEPSEEK_R1,
  1301. /* .reasoning_format = */ COMMON_REASONING_FORMAT_DEEPSEEK,
  1302. /* .reasoning_in_content = */ false,
  1303. /* .thinking_forced_open = */ true,
  1304. }));
  1305. assert_msg_equals(message_assist_thoughts,
  1306. common_chat_parse(
  1307. "<think>I'm\nthinking</think>Hello, world!\nWhat's up?",
  1308. /* is_partial= */ false,
  1309. {
  1310. /* .format = */ COMMON_CHAT_FORMAT_DEEPSEEK_R1,
  1311. /* .reasoning_format = */ COMMON_REASONING_FORMAT_DEEPSEEK,
  1312. }));
  1313. assert_msg_equals(message_assist_thoughts_unopened_unparsed,
  1314. common_chat_parse(
  1315. "I'm\nthinking</think>Hello, world!\nWhat's up?",
  1316. /* is_partial= */ false,
  1317. {
  1318. /* .format = */ COMMON_CHAT_FORMAT_DEEPSEEK_R1,
  1319. /* .reasoning_format = */ COMMON_REASONING_FORMAT_DEEPSEEK,
  1320. }));
  1321. assert_msg_equals(message_assist_thoughts,
  1322. common_chat_parse(
  1323. "I'm\nthinking</think>Hello, world!\nWhat's up?",
  1324. /* is_partial= */ false,
  1325. {
  1326. /* .format = */ COMMON_CHAT_FORMAT_DEEPSEEK_R1,
  1327. /* .reasoning_format = */ COMMON_REASONING_FORMAT_DEEPSEEK,
  1328. /* .reasoning_in_content = */ false,
  1329. /* .thinking_forced_open = */ true,
  1330. }));
  1331. assert_msg_equals(message_assist_thoughts,
  1332. // Latest template update (ast of 20250209) adds a trailing <think>\n if add_generation_prompt is true.
  1333. common_chat_parse(
  1334. "I'm\nthinking</think>Hello, world!\nWhat's up?",
  1335. /* is_partial= */ false,
  1336. {
  1337. /* .format = */ COMMON_CHAT_FORMAT_DEEPSEEK_R1,
  1338. /* .reasoning_format = */ COMMON_REASONING_FORMAT_DEEPSEEK,
  1339. /* .reasoning_in_content = */ false,
  1340. /* .thinking_forced_open = */ true,
  1341. }));
  1342. // test_templates(tmpls.get(), end_tokens, message_assist_call, tools,
  1343. // "<|tool▁calls▁begin|><|tool▁call▁begin|>function<|tool▁sep|>special_function\n"
  1344. // "```json\n"
  1345. // "{\"arg1\": 1}\n"
  1346. // // 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)
  1347. // "```<|tool▁call▁end|>",
  1348. // /* expect_grammar_triggered= */ true,
  1349. // /* test_grammar_if_triggered= */ false);
  1350. }
  1351. {
  1352. // Replacement DeepSeek R1 template. Makes the Distill Qwen 7B/32B models happy to call tools and all.
  1353. auto tmpls = read_templates("models/templates/llama-cpp-deepseek-r1.jinja");
  1354. std::vector<std::string> end_tokens{ "<|end▁of▁sentence|>" };
  1355. assert_equals(COMMON_CHAT_FORMAT_DEEPSEEK_R1, common_chat_templates_apply(tmpls.get(), inputs_no_tools).format);
  1356. assert_equals(COMMON_CHAT_FORMAT_DEEPSEEK_R1, common_chat_templates_apply(tmpls.get(), inputs_tools).format);
  1357. test_templates(tmpls.get(), end_tokens, message_assist, tools, "Hello, world!\nWhat's up?", /* expect_grammar_triggered= */ false);
  1358. test_templates(tmpls.get(), end_tokens, message_assist_thoughts, tools, "Hello, world!\nWhat's up?", /* expect_grammar_triggered= */ false);
  1359. assert_msg_equals(message_assist_thoughts_unparsed_deepseek,
  1360. common_chat_parse(
  1361. "<think>I'm\nthinking</think>Hello, world!\nWhat's up?",
  1362. /* is_partial= */ false,
  1363. {COMMON_CHAT_FORMAT_DEEPSEEK_R1}));
  1364. assert_msg_equals(message_assist_thoughts,
  1365. common_chat_parse(
  1366. "<think>I'm\nthinking</think>Hello, world!\nWhat's up?",
  1367. /* is_partial= */ false,
  1368. {
  1369. /* .format = */ COMMON_CHAT_FORMAT_DEEPSEEK_R1,
  1370. /* .reasoning_format = */ COMMON_REASONING_FORMAT_DEEPSEEK,
  1371. }));
  1372. assert_msg_equals(message_assist_thoughts,
  1373. common_chat_parse(
  1374. "I'm\nthinking</think>Hello, world!\nWhat's up?",
  1375. /* is_partial= */ false,
  1376. {
  1377. /* .format = */ COMMON_CHAT_FORMAT_DEEPSEEK_R1,
  1378. /* .reasoning_format = */ COMMON_REASONING_FORMAT_DEEPSEEK,
  1379. /* .reasoning_in_content = */ false,
  1380. /* .thinking_forced_open = */ true,
  1381. }));
  1382. assert_msg_equals(message_assist_call_thoughts_unparsed,
  1383. common_chat_parse(
  1384. "<think>I'm\nthinking</think>\n\n"
  1385. "<|tool▁calls▁begin|><|tool▁call▁begin|>function<|tool▁sep|>special_function\n"
  1386. "```json\n"
  1387. "{\"arg1\": 1}\n"
  1388. "```<|tool▁call▁end|><|tool▁calls▁end|>",
  1389. /* is_partial= */ false,
  1390. {COMMON_CHAT_FORMAT_DEEPSEEK_R1}));
  1391. assert_msg_equals(message_assist_call,
  1392. common_chat_parse(
  1393. "<|tool▁calls|>function<|tool▁sep|>special_function\n"
  1394. "```json\n"
  1395. "{\"arg1\": 1}\n"
  1396. "```<|tool▁call▁end|><|tool▁calls▁end|>",
  1397. /* is_partial= */ false,
  1398. {COMMON_CHAT_FORMAT_DEEPSEEK_R1}));
  1399. assert_msg_equals(message_assist_call_thoughts,
  1400. common_chat_parse(
  1401. "<think>I'm\nthinking</think>\n\n"
  1402. "<|tool▁calls▁begin|><|tool▁call▁begin|>function<|tool▁sep|>special_function\n"
  1403. "```json\n"
  1404. "{\"arg1\": 1}\n"
  1405. "```<|tool▁call▁end|><|tool▁calls▁end|>",
  1406. /* is_partial= */ false,
  1407. {
  1408. /* .format = */ COMMON_CHAT_FORMAT_DEEPSEEK_R1,
  1409. /* .reasoning_format = */ COMMON_REASONING_FORMAT_DEEPSEEK,
  1410. }));
  1411. test_templates(tmpls.get(), end_tokens, message_assist_call, tools,
  1412. "<|tool▁calls▁begin|><|tool▁call▁begin|>function<|tool▁sep|>special_function\n"
  1413. "```json\n"
  1414. "{\"arg1\": 1}\n"
  1415. "```<|tool▁call▁end|><|tool▁calls▁end|>");
  1416. }
  1417. {
  1418. auto tmpls = read_templates("models/templates/ibm-granite-granite-3.3-2B-Instruct.jinja");
  1419. std::vector<std::string> end_tokens{ "<|end_of_text|>" };
  1420. assert_equals(COMMON_CHAT_FORMAT_GRANITE, common_chat_templates_apply(tmpls.get(), inputs_no_tools).format);
  1421. assert_equals(COMMON_CHAT_FORMAT_GRANITE, common_chat_templates_apply(tmpls.get(), inputs_tools).format);
  1422. // Test parsing regular content
  1423. assert_msg_equals(message_assist,
  1424. common_chat_parse(
  1425. "Hello, world!\nWhat's up?",
  1426. /* is_partial= */ false,
  1427. {COMMON_CHAT_FORMAT_GRANITE}));
  1428. assert_msg_equals(
  1429. message_assist,
  1430. common_chat_parse(
  1431. "Hello, world!\nWhat's up?",
  1432. /* is_partial= */ true,
  1433. {COMMON_CHAT_FORMAT_GRANITE}));
  1434. // Test parsing content with thinking
  1435. assert_msg_equals(message_assist_thoughts,
  1436. common_chat_parse(
  1437. "<think>I'm\nthinking</think>Hello, world!\nWhat's up?",
  1438. /* is_partial= */ false,
  1439. {
  1440. /* .format = */ COMMON_CHAT_FORMAT_GRANITE,
  1441. /* .reasoning_format = */ COMMON_REASONING_FORMAT_DEEPSEEK,
  1442. }));
  1443. assert_msg_equals(message_assist_thoughts_unparsed_deepseek,
  1444. common_chat_parse(
  1445. "<think>I'm\nthinking</think>Hello, world!\nWhat's up?",
  1446. /* is_partial= */ false,
  1447. {COMMON_CHAT_FORMAT_GRANITE}));
  1448. assert_msg_equals(message_assist_thoughts,
  1449. common_chat_parse(
  1450. "<think>I'm\nthinking</think><response>Hello, world!\nWhat's up?",
  1451. /* is_partial= */ true,
  1452. {
  1453. /* .format = */ COMMON_CHAT_FORMAT_GRANITE,
  1454. /* .reasoning_format = */ COMMON_REASONING_FORMAT_DEEPSEEK,
  1455. }));
  1456. assert_msg_equals(message_assist_thoughts,
  1457. common_chat_parse(
  1458. "<think>I'm\nthinking</think><response>Hello, world!\nWhat's up?</response>",
  1459. /* is_partial= */ false,
  1460. {
  1461. /* .format = */ COMMON_CHAT_FORMAT_GRANITE,
  1462. /* .reasoning_format = */ COMMON_REASONING_FORMAT_DEEPSEEK,
  1463. }));
  1464. assert_msg_equals(simple_assist_msg("<think>I'm\nthinking</think><response>Hello, world!\nWhat's up?</response>"),
  1465. common_chat_parse(
  1466. "<think>I'm\nthinking</think><response>Hello, world!\nWhat's up?</response>",
  1467. /* is_partial= */ false,
  1468. {COMMON_CHAT_FORMAT_GRANITE}));
  1469. assert_msg_equals(message_assist_empty,
  1470. common_chat_parse(
  1471. "<think",
  1472. /* is_partial= */ true,
  1473. {
  1474. /* .format = */ COMMON_CHAT_FORMAT_GRANITE,
  1475. /* .reasoning_format = */ COMMON_REASONING_FORMAT_DEEPSEEK,
  1476. }));
  1477. assert_msg_equals(message_assist_empty,
  1478. common_chat_parse(
  1479. "<think",
  1480. /* is_partial= */ true,
  1481. {COMMON_CHAT_FORMAT_GRANITE}));
  1482. assert_msg_equals(message_assist_thoughts_no_content,
  1483. common_chat_parse(
  1484. "<think>I'm\nthinking",
  1485. /* is_partial= */ true,
  1486. {
  1487. /* .format = */ COMMON_CHAT_FORMAT_GRANITE,
  1488. /* .reasoning_format = */ COMMON_REASONING_FORMAT_DEEPSEEK,
  1489. }));
  1490. assert_msg_equals(
  1491. message_assist_empty,
  1492. common_chat_parse(
  1493. "<think>I'm\nthinking</think><response",
  1494. /* is_partial= */ true,
  1495. {COMMON_CHAT_FORMAT_GRANITE}));
  1496. // Test parsing tool calls
  1497. assert_msg_equals(message_assist_call,
  1498. common_chat_parse(
  1499. "<|tool_call|>[{\"name\": \"special_function\", \"arguments\": {\"arg1\": 1}}]",
  1500. /* is_partial= */ false,
  1501. {COMMON_CHAT_FORMAT_GRANITE}));
  1502. assert_msg_equals(
  1503. message_assist_call_empty_args,
  1504. common_chat_parse(
  1505. "<|tool_call|>[{\"name\": \"special_function\"",
  1506. /* is_partial= */ true,
  1507. {COMMON_CHAT_FORMAT_GRANITE}));
  1508. assert_msg_equals(
  1509. message_assist_call_cutoff_args,
  1510. common_chat_parse(
  1511. "<|tool_call|>[{\"name\": \"special_function\", \"arguments\": {\"arg",
  1512. /* is_partial= */ true,
  1513. {COMMON_CHAT_FORMAT_GRANITE}));
  1514. assert_msg_equals(
  1515. message_assist_call_cutoff_args,
  1516. common_chat_parse(
  1517. "<|tool_call|>[{\"name\": \"special_function\", \"arguments\": {\"arg",
  1518. /* is_partial= */ true,
  1519. {
  1520. /* .format = */ COMMON_CHAT_FORMAT_GRANITE,
  1521. /* .reasoning_format = */ COMMON_REASONING_FORMAT_DEEPSEEK,
  1522. }));
  1523. // Test parsing tool calls with thinking
  1524. assert_msg_equals(
  1525. message_assist_call_thoughts,
  1526. common_chat_parse(
  1527. "<think>I'm\nthinking</think><|tool_call|>[{\"name\": \"special_function\", \"arguments\": {\"arg1\": 1}, {",
  1528. /* is_partial= */ true,
  1529. {
  1530. /* .format = */ COMMON_CHAT_FORMAT_GRANITE,
  1531. /* .reasoning_format = */ COMMON_REASONING_FORMAT_DEEPSEEK,
  1532. }));
  1533. // Test template generation for regular content
  1534. test_templates(tmpls.get(), end_tokens, message_assist, tools,
  1535. "Hello, world!\nWhat's up?",
  1536. /* expect_grammar_triggered= */ false);
  1537. // Test template generation for tool calls
  1538. test_templates(tmpls.get(), end_tokens, message_assist_call_id, tools,
  1539. "{\n"
  1540. " \"tool_calls\": [\n"
  1541. " {\n"
  1542. " \"name\": \"special_function\",\n"
  1543. " \"arguments\": {\n"
  1544. " \"arg1\": 1\n"
  1545. " },\n"
  1546. " \"id\": \"123456789\"\n"
  1547. " }\n"
  1548. " ]\n"
  1549. "}",
  1550. /* expect_grammar_triggered= */ false
  1551. );
  1552. }
  1553. {
  1554. auto tmpls = read_templates("models/templates/openai-gpt-oss-120b.jinja");
  1555. std::vector<std::string> end_tokens{ "<|return|>", "<|call|>" };
  1556. assert_equals(COMMON_CHAT_FORMAT_GPT_OSS, common_chat_templates_apply(tmpls.get(), inputs_no_tools).format);
  1557. assert_equals(COMMON_CHAT_FORMAT_GPT_OSS, common_chat_templates_apply(tmpls.get(), inputs_tools).format);
  1558. assert_msg_equals(simple_assist_msg("", "I'm\nthink"),
  1559. common_chat_parse(
  1560. "<|channel|>analysis<|message|>I'm\nthink",
  1561. /* is_partial= */ true,
  1562. {
  1563. /* .format = */ COMMON_CHAT_FORMAT_GPT_OSS,
  1564. /* .reasoning_format = */ COMMON_REASONING_FORMAT_AUTO,
  1565. }));
  1566. assert_msg_equals(simple_assist_msg("", "I'm\nthinking"),
  1567. common_chat_parse(
  1568. "<|channel|>analysis<|message|>I'm\nthinking<|end|>",
  1569. /* is_partial= */ true,
  1570. {
  1571. /* .format = */ COMMON_CHAT_FORMAT_GPT_OSS,
  1572. /* .reasoning_format = */ COMMON_REASONING_FORMAT_AUTO,
  1573. }));
  1574. assert_msg_equals(simple_assist_msg("Hello, world!\nWhat's up?", "I'm\nthinking"),
  1575. common_chat_parse(
  1576. "<|channel|>analysis<|message|>I'm\nthinking<|end|>"
  1577. "<|start|>assistant<|channel|>final<|message|>Hello, world!\nWhat's up?",
  1578. /* is_partial= */ false,
  1579. {
  1580. /* .format = */ COMMON_CHAT_FORMAT_GPT_OSS,
  1581. /* .reasoning_format = */ COMMON_REASONING_FORMAT_AUTO,
  1582. }));
  1583. assert_msg_equals(simple_assist_msg("", "I'm\nthinking", "special_function", "{\"arg1"),
  1584. common_chat_parse(
  1585. "<|channel|>analysis<|message|>I'm\nthinking<|end|>"
  1586. "<|start|>assistant<|channel|>commentary to=functions.special_function <|constrain|>json<|message|>{\"arg1",
  1587. /* is_partial= */ true,
  1588. {
  1589. /* .format = */ COMMON_CHAT_FORMAT_GPT_OSS,
  1590. /* .reasoning_format = */ COMMON_REASONING_FORMAT_AUTO,
  1591. }));
  1592. assert_msg_equals(simple_assist_msg("", "I'm\nthinking", "special_function", "{\"arg1"),
  1593. common_chat_parse(
  1594. "<|channel|>analysis<|message|>I'm\nthinking<|end|>"
  1595. "<|start|>assistant<|channel|>commentary to=functions.special_function<|message|>{\"arg1",
  1596. /* is_partial= */ true,
  1597. {
  1598. /* .format = */ COMMON_CHAT_FORMAT_GPT_OSS,
  1599. /* .reasoning_format = */ COMMON_REASONING_FORMAT_AUTO,
  1600. }));
  1601. assert_msg_equals(simple_assist_msg("", "I'm\nthinking", "special_function", "{\"arg1\": 1}"),
  1602. common_chat_parse(
  1603. "<|channel|>analysis<|message|>I'm\nthinking<|end|>"
  1604. "<|start|>assistant<|channel|>commentary to=functions.special_function <|constrain|>json<|message|>{\"arg1\": 1}",
  1605. /* is_partial= */ false,
  1606. {
  1607. /* .format = */ COMMON_CHAT_FORMAT_GPT_OSS,
  1608. /* .reasoning_format = */ COMMON_REASONING_FORMAT_AUTO,
  1609. }));
  1610. assert_msg_equals(simple_assist_msg("", "I'm\nthinking", "special_function", "{\"arg1\": 1}"),
  1611. common_chat_parse(
  1612. "<|channel|>analysis<|message|>I'm\nthinking<|end|>"
  1613. "<|start|>assistant<|channel|>analysis to=functions.special_function <|constrain|>json<|message|>{\"arg1\": 1}",
  1614. /* is_partial= */ false,
  1615. {
  1616. /* .format = */ COMMON_CHAT_FORMAT_GPT_OSS,
  1617. /* .reasoning_format = */ COMMON_REASONING_FORMAT_AUTO,
  1618. }));
  1619. assert_msg_equals(simple_assist_msg("Hello, world!\nWhat's up?", "I'm\nthinking"),
  1620. common_chat_parse(
  1621. "<|channel|>analysis<|message|>I'm\nthinking<|end|>"
  1622. "<|start|>assistant<|channel|>commentary<|message|>Hello, world!\nWhat's up?",
  1623. /* is_partial= */ true,
  1624. {
  1625. /* .format = */ COMMON_CHAT_FORMAT_GPT_OSS,
  1626. /* .reasoning_format = */ COMMON_REASONING_FORMAT_AUTO,
  1627. }));
  1628. assert_msg_equals(simple_assist_msg("Hello, world!\nWhat's up?", "I'm\nthinking", "special_function", "{\"arg1\": 1}"),
  1629. common_chat_parse(
  1630. "<|channel|>analysis<|message|>I'm\nthinking<|end|>"
  1631. "<|start|>assistant<|channel|>commentary<|message|>Hello, world!\nWhat's up?<|end|>"
  1632. "<|start|>assistant<|channel|>commentary to=functions.special_function <|constrain|>json<|message|>{\"arg1\": 1}",
  1633. /* is_partial= */ true,
  1634. {
  1635. /* .format = */ COMMON_CHAT_FORMAT_GPT_OSS,
  1636. /* .reasoning_format = */ COMMON_REASONING_FORMAT_AUTO,
  1637. }));
  1638. // Test parse_tool_calls == false
  1639. assert_msg_equals(
  1640. simple_assist_msg("Hello, world!\nWhat's up?", "I'm\nthinking"),
  1641. common_chat_parse(
  1642. "<|channel|>analysis<|message|>I'm\nthinking<|end|>"
  1643. "<|start|>assistant<|channel|>final<|message|>Hello, world!\nWhat's up?",
  1644. /* is_partial= */ true,
  1645. {
  1646. /* .format = */ COMMON_CHAT_FORMAT_GPT_OSS,
  1647. /* .reasoning_format = */ COMMON_REASONING_FORMAT_AUTO,
  1648. /* .reasoning_in_content = */ false,
  1649. /* .thinking_forced_open = */ false,
  1650. /* .parse_tool_calls = */ false,
  1651. }));
  1652. assert_msg_equals(
  1653. simple_assist_msg("", "I'm\nthinking"),
  1654. common_chat_parse(
  1655. "<|channel|>analysis<|message|>I'm\nthinking<|end|>"
  1656. "<|start|>assistant<|channel|>commentary to=functions.special_function<|message|>{\"arg1",
  1657. /* is_partial= */ true,
  1658. {
  1659. /* .format = */ COMMON_CHAT_FORMAT_GPT_OSS,
  1660. /* .reasoning_format = */ COMMON_REASONING_FORMAT_AUTO,
  1661. /* .reasoning_in_content = */ false,
  1662. /* .thinking_forced_open = */ false,
  1663. /* .parse_tool_calls = */ false,
  1664. }));
  1665. assert_msg_equals(
  1666. simple_assist_msg("", "I'm\nthinking"),
  1667. common_chat_parse(
  1668. "<|channel|>analysis<|message|>I'm\nthinking<|end|>"
  1669. "<|start|>assistant<|channel|>commentary to=functions.special_function <|constrain|>json<|message|>{\"arg1\": 1}",
  1670. /* is_partial= */ false,
  1671. {
  1672. /* .format = */ COMMON_CHAT_FORMAT_GPT_OSS,
  1673. /* .reasoning_format = */ COMMON_REASONING_FORMAT_AUTO,
  1674. /* .reasoning_in_content = */ false,
  1675. /* .thinking_forced_open = */ false,
  1676. /* .parse_tool_calls = */ false,
  1677. }));
  1678. // Test reasoning formats
  1679. assert_msg_equals(
  1680. simple_assist_msg(
  1681. "<|channel|>analysis<|message|>I'm\nthinking<|end|>Hello, world!\nWhat's up?"),
  1682. common_chat_parse(
  1683. "<|channel|>analysis<|message|>I'm\nthinking<|end|>"
  1684. "<|start|>assistant<|channel|>final<|message|>Hello, world!\nWhat's up?",
  1685. /* is_partial= */ false,
  1686. {
  1687. /* .format = */ COMMON_CHAT_FORMAT_GPT_OSS,
  1688. /* .reasoning_format = */ COMMON_REASONING_FORMAT_NONE,
  1689. }));
  1690. assert_msg_equals(
  1691. simple_assist_msg(
  1692. "<|channel|>analysis<|message|>I'm\nthinking<|end|>Hello, world!\nWhat's up?"),
  1693. common_chat_parse(
  1694. "<|channel|>analysis<|message|>I'm\nthinking<|end|>"
  1695. "<|start|>assistant<|channel|>final<|message|>Hello, world!\nWhat's up?",
  1696. /* is_partial= */ false,
  1697. {
  1698. /* .format = */ COMMON_CHAT_FORMAT_GPT_OSS,
  1699. /* .reasoning_format = */ COMMON_REASONING_FORMAT_AUTO,
  1700. /* .reasoning_in_content = */ true,
  1701. }));
  1702. // Test tool calling in role header
  1703. assert_msg_equals(simple_assist_msg("", "", "special_function", "{\"arg1\": 1}"),
  1704. common_chat_parse(
  1705. " to=functions.special_function<|channel|>commentary <|constrain|>json<|message|>{\"arg1\": 1}",
  1706. /* is_partial= */ false,
  1707. {
  1708. /* .format = */ COMMON_CHAT_FORMAT_GPT_OSS,
  1709. /* .reasoning_format = */ COMMON_REASONING_FORMAT_AUTO,
  1710. }));
  1711. assert_msg_equals(simple_assist_msg("", "", "special_function", "{\"arg1\": 1}"),
  1712. common_chat_parse(
  1713. " to=functions.special_function<|channel|>analysis <|constrain|>json<|message|>{\"arg1\": 1}",
  1714. /* is_partial= */ false,
  1715. {
  1716. /* .format = */ COMMON_CHAT_FORMAT_GPT_OSS,
  1717. /* .reasoning_format = */ COMMON_REASONING_FORMAT_AUTO,
  1718. }));
  1719. assert_msg_equals(simple_assist_msg("", "I'm\nthinking", "special_function", "{\"arg1\": 1}"),
  1720. common_chat_parse(
  1721. "<|channel|>analysis<|message|>I'm\nthinking<|end|>"
  1722. "<|start|>assistant to=functions.special_function<|channel|>analysis <|constrain|>json<|message|>{\"arg1\": 1}",
  1723. /* is_partial= */ false,
  1724. {
  1725. /* .format = */ COMMON_CHAT_FORMAT_GPT_OSS,
  1726. /* .reasoning_format = */ COMMON_REASONING_FORMAT_AUTO,
  1727. }));
  1728. }
  1729. {
  1730. // Seed-OSS format tests
  1731. auto tmpls = read_templates("models/templates/ByteDance-Seed-OSS.jinja");
  1732. std::vector<std::string> end_tokens{ "<seed:eos>" };
  1733. assert_equals(COMMON_CHAT_FORMAT_SEED_OSS, common_chat_templates_apply(tmpls.get(), inputs_no_tools).format);
  1734. assert_equals(COMMON_CHAT_FORMAT_SEED_OSS, common_chat_templates_apply(tmpls.get(), inputs_tools).format);
  1735. test_templates(tmpls.get(), end_tokens, message_assist, tools, "Hello, world!\nWhat's up?", /* expect_grammar_triggered= */ false);
  1736. // Test simple reasoning content
  1737. assert_msg_equals(
  1738. simple_assist_msg("Hello, world!", "I'm thinking about the answer"),
  1739. common_chat_parse(
  1740. "<seed:think>I'm thinking about the answer</seed:think>Hello, world!",
  1741. /* is_partial= */ false,
  1742. {
  1743. /* .format = */ COMMON_CHAT_FORMAT_SEED_OSS,
  1744. /* .reasoning_format = */ COMMON_REASONING_FORMAT_DEEPSEEK,
  1745. }));
  1746. // Test budget reflection tags
  1747. common_chat_msg msg_budget_reflect;
  1748. msg_budget_reflect.role = "assistant";
  1749. msg_budget_reflect.content = "<seed:cot_budget_reflect>Token usage: 45/1000\nI should continue thinking to find the best solution.</seed:cot_budget_reflect>I need to calculate this step by step.";
  1750. msg_budget_reflect.reasoning_content = "Token usage: 45/1000\nI should continue thinking to find the best solution.";
  1751. assert_msg_equals(
  1752. msg_budget_reflect,
  1753. common_chat_parse(
  1754. "<seed:think>Token usage: 45/1000\nI should continue thinking to find the best solution.</seed:think>"
  1755. "<seed:cot_budget_reflect>Token usage: 45/1000\nI should continue thinking to find the best solution.</seed:cot_budget_reflect>"
  1756. "I need to calculate this step by step.",
  1757. /* is_partial= */ false,
  1758. {
  1759. /* .format = */ COMMON_CHAT_FORMAT_SEED_OSS,
  1760. /* .reasoning_format = */ COMMON_REASONING_FORMAT_DEEPSEEK,
  1761. }));
  1762. // Test tool calls with Seed-OSS format
  1763. common_chat_msg msg_tool_call;
  1764. msg_tool_call.role = "assistant";
  1765. msg_tool_call.tool_calls.push_back({"calculate_sum", "{\"numbers\": [1, 2, 3]}", ""});
  1766. assert_msg_equals(
  1767. msg_tool_call,
  1768. common_chat_parse(
  1769. "<seed:tool_call>\n"
  1770. "<function=calculate_sum>\n"
  1771. "<parameter=numbers>[1, 2, 3]</parameter>\n"
  1772. "</function>\n"
  1773. "</seed:tool_call>",
  1774. /* is_partial= */ false,
  1775. {COMMON_CHAT_FORMAT_SEED_OSS}));
  1776. // Test reasoning + tool call combination
  1777. common_chat_msg msg_reasoning_tool;
  1778. msg_reasoning_tool.role = "assistant";
  1779. msg_reasoning_tool.content = "";
  1780. msg_reasoning_tool.reasoning_content = "I need to calculate the sum of these numbers";
  1781. msg_reasoning_tool.tool_calls.push_back({"calculate_sum", "{\"numbers\": [1, 2, 3]}", ""});
  1782. assert_msg_equals(
  1783. msg_reasoning_tool,
  1784. common_chat_parse(
  1785. "<seed:think>I need to calculate the sum of these numbers</seed:think>"
  1786. "<seed:tool_call>\n"
  1787. "<function=calculate_sum>\n"
  1788. "<parameter=numbers>[1, 2, 3]</parameter>\n"
  1789. "</function>\n"
  1790. "</seed:tool_call>",
  1791. /* is_partial= */ false,
  1792. {
  1793. /* .format = */ COMMON_CHAT_FORMAT_SEED_OSS,
  1794. /* .reasoning_format = */ COMMON_REASONING_FORMAT_DEEPSEEK,
  1795. }));
  1796. // Test deltas: the number of tool calls in partial parses should never decrease
  1797. std::string tool_msg = "<seed:tool_call>\n"
  1798. "<function=fun>\n"
  1799. "<parameter=smth>[1, 2, 3]</parameter>\n"
  1800. "</function>";
  1801. std::size_t previousToolCalls = 0;
  1802. for (std::size_t i = std::string("<seed:tool_call>").length(); i < tool_msg.length() - 1; i++) {
  1803. auto partial = tool_msg.substr(0, i);
  1804. auto partial_res = common_chat_parse(partial, true, { COMMON_CHAT_FORMAT_SEED_OSS, COMMON_REASONING_FORMAT_DEEPSEEK });
  1805. if (partial_res.tool_calls.size() < previousToolCalls) {
  1806. 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()));
  1807. }
  1808. previousToolCalls = partial_res.tool_calls.size();
  1809. }
  1810. // Test multiple parameters in tool call
  1811. common_chat_msg msg_multi_param;
  1812. msg_multi_param.role = "assistant";
  1813. msg_multi_param.tool_calls.push_back({"process_data", "{\"input\": \"test\", \"format\": \"json\"}", ""});
  1814. assert_msg_equals(
  1815. msg_multi_param,
  1816. common_chat_parse(
  1817. "<seed:tool_call>\n"
  1818. "<function=process_data>\n"
  1819. "<parameter=input>test</parameter>\n"
  1820. "<parameter=format>json</parameter>\n"
  1821. "</function>\n"
  1822. "</seed:tool_call>",
  1823. /* is_partial= */ false,
  1824. {COMMON_CHAT_FORMAT_SEED_OSS}));
  1825. // Test partial parsing for incomplete tool call - don't actually add the call until parsing parameters is done
  1826. assert_msg_equals(
  1827. simple_assist_msg("", "", "calculate_sum", "{\"numbers\":"),
  1828. common_chat_parse(
  1829. "<seed:tool_call>\n"
  1830. "<function=calculate_sum>\n"
  1831. "<parameter=numbers>[1,\n",
  1832. /* is_partial= */ true,
  1833. {COMMON_CHAT_FORMAT_SEED_OSS}));
  1834. // Test incomplete reasoning tag
  1835. assert_msg_equals(
  1836. simple_assist_msg("", "I was thinking"),
  1837. common_chat_parse(
  1838. "<seed:think>I was thinking",
  1839. /* is_partial= */ true,
  1840. {
  1841. /* .format = */ COMMON_CHAT_FORMAT_SEED_OSS,
  1842. /* .reasoning_format = */ COMMON_REASONING_FORMAT_DEEPSEEK,
  1843. }));
  1844. // Test content without reasoning
  1845. assert_msg_equals(
  1846. simple_assist_msg("This is a simple response without reasoning."),
  1847. common_chat_parse(
  1848. "This is a simple response without reasoning.",
  1849. /* is_partial= */ false,
  1850. {COMMON_CHAT_FORMAT_SEED_OSS}));
  1851. }
  1852. {
  1853. auto tmpls = read_templates("models/templates/NVIDIA-Nemotron-Nano-v2.jinja");
  1854. std::vector<std::string> end_tokens{ "<SPECIAL_12>" };
  1855. assert_equals(COMMON_CHAT_FORMAT_NEMOTRON_V2, common_chat_templates_apply(tmpls.get(), inputs_no_tools).format);
  1856. assert_equals(COMMON_CHAT_FORMAT_NEMOTRON_V2, common_chat_templates_apply(tmpls.get(), inputs_tools).format);
  1857. // Test parsing regular content
  1858. assert_msg_equals(message_assist,
  1859. common_chat_parse(
  1860. "Hello, world!\nWhat's up?",
  1861. /* is_partial= */ false,
  1862. {COMMON_CHAT_FORMAT_NEMOTRON_V2}));
  1863. // Test parsing content with thinking
  1864. assert_msg_equals(message_assist_thoughts,
  1865. common_chat_parse(
  1866. "<think>I'm\nthinking</think>Hello, world!\nWhat's up?",
  1867. /* is_partial= */ false,
  1868. {
  1869. /* .format = */ COMMON_CHAT_FORMAT_NEMOTRON_V2,
  1870. /* .reasoning_format = */ COMMON_REASONING_FORMAT_DEEPSEEK,
  1871. }));
  1872. // Test parsing tool calls
  1873. assert_msg_equals(message_assist_call,
  1874. common_chat_parse(
  1875. "<TOOLCALL>[{\"name\": \"special_function\", \"arguments\": {\"arg1\": 1}}]</TOOLCALL>",
  1876. /* is_partial= */ false,
  1877. {COMMON_CHAT_FORMAT_NEMOTRON_V2}));
  1878. // Test parsing tool calls with thinking
  1879. assert_msg_equals(message_assist_call_thoughts,
  1880. common_chat_parse(
  1881. "<think>I'm\nthinking</think><TOOLCALL>[{\"name\": \"special_function\", \"arguments\": {\"arg1\": 1}}]</TOOLCALL>",
  1882. /* is_partial= */ false,
  1883. {
  1884. /* .format = */ COMMON_CHAT_FORMAT_NEMOTRON_V2,
  1885. /* .reasoning_format = */ COMMON_REASONING_FORMAT_DEEPSEEK
  1886. }));
  1887. // Test tool calls with extra content
  1888. assert_msg_equals(message_assist_call_content,
  1889. common_chat_parse(
  1890. "<TOOLCALL>[{\"name\": \"special_function\", \"arguments\": {\"arg1\": 1}}]</TOOLCALL>Hello, world!\nWhat's up?",
  1891. /* is_partial= */ false,
  1892. {COMMON_CHAT_FORMAT_NEMOTRON_V2}
  1893. ));
  1894. // Test tool calls with extra content AND thinking
  1895. assert_msg_equals(message_assist_call_thoughts_content,
  1896. common_chat_parse(
  1897. "<think>I'm\nthinking</think><TOOLCALL>[{\"name\": \"special_function\", \"arguments\": {\"arg1\": 1}}]</TOOLCALL>Hello, world!\nWhat's up?",
  1898. /* is_partial= */ false,
  1899. {
  1900. /* .format = */ COMMON_CHAT_FORMAT_NEMOTRON_V2,
  1901. /* .reasoning_format = */ COMMON_REASONING_FORMAT_DEEPSEEK
  1902. }));
  1903. // Test template generation for regular content
  1904. test_templates(tmpls.get(), end_tokens, message_assist, tools,
  1905. "Hello, world!\nWhat's up?\n",
  1906. /* expect_grammar_triggered= */ false);
  1907. // Test template generation for tool calls
  1908. test_templates(tmpls.get(), end_tokens, message_assist_call, tools,
  1909. "<TOOLCALL>[{\"name\": \"special_function\", \"arguments\": {\"arg1\": 1}}]</TOOLCALL>",
  1910. /* expect_grammar_triggered= */ true
  1911. );
  1912. }
  1913. {
  1914. auto tmpls = read_templates("models/templates/deepseek-ai-DeepSeek-V3.1.jinja");
  1915. std::vector<std::string> end_tokens{ "<|end▁of▁sentence|>" };
  1916. for (const auto & inputs : { inputs_no_tools, inputs_tools }) {
  1917. auto params = common_chat_templates_apply(tmpls.get(), inputs);
  1918. assert_equals(COMMON_CHAT_FORMAT_DEEPSEEK_V3_1, params.format);
  1919. assert_equals(true, params.thinking_forced_open);
  1920. }
  1921. test_templates(tmpls.get(), end_tokens, message_assist, tools, "</think>Hello, world!\nWhat's up?", /* expect_grammar_triggered= */ false);
  1922. test_templates(tmpls.get(), end_tokens, message_assist_thoughts, tools, "</think>Hello, world!\nWhat's up?", /* expect_grammar_triggered= */ false);
  1923. assert_msg_equals(
  1924. simple_assist_msg("Hello, world!\nWhat's up?", "I'm\nthinking"),
  1925. common_chat_parse(
  1926. "I'm\nthinking</think>Hello, world!\nWhat's up?",
  1927. /* is_partial= */ false,
  1928. {
  1929. COMMON_CHAT_FORMAT_DEEPSEEK_V3_1,
  1930. /* .reasoning_format = */ COMMON_REASONING_FORMAT_DEEPSEEK,
  1931. /* .reasoning_in_content = */ false,
  1932. /* .thinking_forced_open = */ true,
  1933. }));
  1934. // variant: thinking forced open, reasoning_format none
  1935. assert_msg_equals(
  1936. simple_assist_msg("REASONING</think>ok", ""),
  1937. common_chat_parse(
  1938. "REASONING</think>ok",
  1939. /* is_partial= */ false,
  1940. {
  1941. COMMON_CHAT_FORMAT_DEEPSEEK_V3_1,
  1942. /* .reasoning_format = */ COMMON_REASONING_FORMAT_NONE,
  1943. /* .reasoning_in_content = */ false,
  1944. /* .thinking_forced_open = */ true,
  1945. /* .parse_tool_calls = */ true,
  1946. }));
  1947. // variant: happy path for when it works as the model card says it should
  1948. assert_msg_equals(
  1949. simple_assist_msg("", "", "get_time", "{\"city\":\"Tokyo\"}"),
  1950. common_chat_parse(
  1951. "<|tool▁calls▁begin|><|tool▁call▁begin|>get_time<|tool▁sep|>{\"city\": \"Tokyo\"}<|tool▁call▁end|><|tool▁calls▁end|>",
  1952. /* is_partial= */ false,
  1953. {
  1954. COMMON_CHAT_FORMAT_DEEPSEEK_V3_1,
  1955. /* .reasoning_format = */ COMMON_REASONING_FORMAT_DEEPSEEK,
  1956. /* .reasoning_in_content = */ false,
  1957. /* .thinking_forced_open = */ false,
  1958. /* .parse_tool_calls = */ true,
  1959. }));
  1960. // variant: simple + thinking open
  1961. assert_msg_equals(
  1962. simple_assist_msg("", "REASONING", "get_time", "{\"city\":\"Tokyo\"}"),
  1963. common_chat_parse(
  1964. "REASONING</think><|tool▁calls▁begin|><|tool▁call▁begin|>get_time<|tool▁sep|>{\"city\": \"Tokyo\"}<|tool▁call▁end|><|tool▁calls▁end|>",
  1965. /* is_partial= */ false,
  1966. {
  1967. COMMON_CHAT_FORMAT_DEEPSEEK_V3_1,
  1968. /* .reasoning_format = */ COMMON_REASONING_FORMAT_DEEPSEEK,
  1969. /* .reasoning_in_content = */ false,
  1970. /* .thinking_forced_open = */ true,
  1971. /* .parse_tool_calls = */ true,
  1972. }));
  1973. // variant: simple + multiple tool calls
  1974. common_chat_msg message_assist_multiple_calls;
  1975. message_assist_multiple_calls.role = "assistant";
  1976. message_assist_multiple_calls.content = "CONTENT";
  1977. message_assist_multiple_calls.tool_calls.push_back({"get_time", "{\"city\":\"Paris\"}", ""});
  1978. message_assist_multiple_calls.tool_calls.push_back({"get_weather", "{\"city\":\"Paris\"}", ""});
  1979. assert_msg_equals(
  1980. message_assist_multiple_calls,
  1981. common_chat_parse(
  1982. "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|>",
  1983. /* is_partial= */ false,
  1984. {
  1985. COMMON_CHAT_FORMAT_DEEPSEEK_V3_1,
  1986. /* .reasoning_format = */ COMMON_REASONING_FORMAT_DEEPSEEK,
  1987. /* .reasoning_in_content = */ false,
  1988. /* .thinking_forced_open = */ false,
  1989. /* .parse_tool_calls = */ true,
  1990. }));
  1991. // variant: thinking forced open + tool call in reasoning content
  1992. assert_msg_equals(
  1993. 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\"}"),
  1994. common_chat_parse(
  1995. "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|>",
  1996. /* is_partial= */ false,
  1997. {
  1998. COMMON_CHAT_FORMAT_DEEPSEEK_V3_1,
  1999. /* .reasoning_format = */ COMMON_REASONING_FORMAT_DEEPSEEK,
  2000. /* .reasoning_in_content = */ false,
  2001. /* .thinking_forced_open = */ true,
  2002. /* .parse_tool_calls = */ true,
  2003. }));
  2004. // variant: thinking forced open + tool call in reasoning content + no closing think + not partial
  2005. // This is a bit of a fine tuning issue on the model's part IMO. It really should not be attempting
  2006. // to make tool calls in reasoning content according to the model card, but it does sometimes, so
  2007. // add the reasoning content as regular content and parse the tool calls.
  2008. assert_msg_equals(
  2009. simple_assist_msg("REASONING", "", "get_time", "{\"city\":\"Tokyo\"}"),
  2010. common_chat_parse(
  2011. "REASONING<|tool▁calls▁begin|><|tool▁call▁begin|>get_time<|tool▁sep|>{\"city\": \"Tokyo\"}<|tool▁call▁end|><|tool▁calls▁end|>",
  2012. /* is_partial= */ false,
  2013. {
  2014. COMMON_CHAT_FORMAT_DEEPSEEK_V3_1,
  2015. /* .reasoning_format = */ COMMON_REASONING_FORMAT_DEEPSEEK,
  2016. /* .reasoning_in_content = */ false,
  2017. /* .thinking_forced_open = */ true,
  2018. /* .parse_tool_calls = */ true,
  2019. }));
  2020. // variant: thinking forced open + tool call in reasoning content + no closing think + partial
  2021. assert_msg_equals(
  2022. simple_assist_msg("", "REASONING<|tool▁calls▁begin|><|tool▁call▁begin|>get_time<|tool▁sep|>{\"city\": \"Tokyo\"}<|tool▁call▁end|><|tool▁calls▁end|>", "", ""),
  2023. common_chat_parse(
  2024. "REASONING<|tool▁calls▁begin|><|tool▁call▁begin|>get_time<|tool▁sep|>{\"city\": \"Tokyo\"}<|tool▁call▁end|><|tool▁calls▁end|>",
  2025. /* is_partial= */ true,
  2026. {
  2027. COMMON_CHAT_FORMAT_DEEPSEEK_V3_1,
  2028. /* .reasoning_format = */ COMMON_REASONING_FORMAT_DEEPSEEK,
  2029. /* .reasoning_in_content = */ false,
  2030. /* .thinking_forced_open = */ true,
  2031. /* .parse_tool_calls = */ true,
  2032. }));
  2033. // variant: thinking not forced open + missing reasoning + no tool calls
  2034. assert_msg_equals(
  2035. simple_assist_msg("CONTENT", ""),
  2036. common_chat_parse(
  2037. "CONTENT",
  2038. /* is_partial= */ false,
  2039. {
  2040. COMMON_CHAT_FORMAT_DEEPSEEK_V3_1,
  2041. /* .reasoning_format = */ COMMON_REASONING_FORMAT_DEEPSEEK,
  2042. /* .reasoning_in_content = */ false,
  2043. /* .thinking_forced_open = */ false,
  2044. /* .parse_tool_calls = */ true,
  2045. }));
  2046. }
  2047. {
  2048. auto tmpls = read_templates("models/templates/Apertus-8B-Instruct.jinja");
  2049. std::vector<std::string> end_tokens{ "<|assistant_end|>" };
  2050. assert_equals(COMMON_CHAT_FORMAT_APERTUS, common_chat_templates_apply(tmpls.get(), inputs_no_tools).format);
  2051. assert_equals(COMMON_CHAT_FORMAT_APERTUS, common_chat_templates_apply(tmpls.get(), inputs_tools).format);
  2052. // Test parsing regular content
  2053. assert_msg_equals(message_assist,
  2054. common_chat_parse(
  2055. "Hello, world!\nWhat's up?",
  2056. /* is_partial= */ false,
  2057. {COMMON_CHAT_FORMAT_APERTUS}));
  2058. // Test parsing content with thinking
  2059. assert_msg_equals(message_assist_thoughts,
  2060. common_chat_parse(
  2061. "<|inner_prefix|>I'm\nthinking<|inner_suffix|>Hello, world!\nWhat's up?",
  2062. /* is_partial= */ false,
  2063. {
  2064. /* .format = */ COMMON_CHAT_FORMAT_APERTUS,
  2065. /* .reasoning_format = */ COMMON_REASONING_FORMAT_DEEPSEEK,
  2066. }));
  2067. // Test parsing tool calls
  2068. assert_msg_equals(message_assist_call,
  2069. common_chat_parse(
  2070. "<|tools_prefix|>[{\"special_function\": {\"arg1\": 1}}]<|tools_suffix|>",
  2071. /* is_partial= */ false,
  2072. {COMMON_CHAT_FORMAT_APERTUS}));
  2073. // Test parsing tool calls with thinking
  2074. assert_msg_equals(message_assist_call_thoughts,
  2075. common_chat_parse(
  2076. "<|inner_prefix|>I'm\nthinking<|inner_suffix|><|tools_prefix|>[{\"special_function\": {\"arg1\": 1}}]<|tools_suffix|>",
  2077. /* is_partial= */ false,
  2078. {
  2079. /* .format = */ COMMON_CHAT_FORMAT_APERTUS,
  2080. /* .reasoning_format = */ COMMON_REASONING_FORMAT_DEEPSEEK
  2081. }));
  2082. // Test tool calls with extra content
  2083. assert_msg_equals(message_assist_call_content,
  2084. common_chat_parse(
  2085. "<|tools_prefix|>[{\"special_function\": {\"arg1\": 1}}]<|tools_suffix|>Hello, world!\nWhat's up?",
  2086. /* is_partial= */ false,
  2087. {COMMON_CHAT_FORMAT_APERTUS}
  2088. ));
  2089. // Test tool calls with extra content AND thinking
  2090. assert_msg_equals(message_assist_call_thoughts_content,
  2091. common_chat_parse(
  2092. "<|inner_prefix|>I'm\nthinking<|inner_suffix|><|tools_prefix|>[{\"special_function\": {\"arg1\": 1}}]<|tools_suffix|>Hello, world!\nWhat's up?",
  2093. /* is_partial= */ false,
  2094. {
  2095. /* .format = */ COMMON_CHAT_FORMAT_APERTUS,
  2096. /* .reasoning_format = */ COMMON_REASONING_FORMAT_DEEPSEEK
  2097. }));
  2098. // Test template generation for regular content
  2099. test_templates(tmpls.get(), end_tokens, message_assist, tools,
  2100. "Hello, world!\nWhat's up?",
  2101. /* expect_grammar_triggered= */ false);
  2102. // Test template generation for tool calls
  2103. test_templates(tmpls.get(), end_tokens, message_assist_call, tools,
  2104. "<|tools_prefix|>[{\"special_function\": {\"arg1\": 1}}]<|tools_suffix|>",
  2105. /* expect_grammar_triggered= */ true
  2106. );
  2107. assert_equals(true, common_chat_templates_support_enable_thinking(tmpls.get()));
  2108. }
  2109. {
  2110. // LFM2 format tests
  2111. auto tmpls = read_templates("models/templates/llama-cpp-lfm2.jinja");
  2112. std::vector<std::string> end_tokens{ "<|im_end|>" };
  2113. auto inputs_tools_forced_json_schema = std::invoke([&]() -> common_chat_templates_inputs {
  2114. common_chat_templates_inputs inputs;
  2115. inputs.messages = {
  2116. std::invoke([&]() -> common_chat_msg {
  2117. common_chat_msg msg;
  2118. msg.role = "system";
  2119. msg.content = "force json schema.\n";
  2120. return msg;
  2121. }),
  2122. message_user,
  2123. };
  2124. inputs.tools = {special_function_tool};
  2125. return inputs;
  2126. });
  2127. {
  2128. auto params = common_chat_templates_apply(tmpls.get(), inputs_no_tools);
  2129. assert_equals(COMMON_CHAT_FORMAT_CONTENT_ONLY, params.format);
  2130. assert_equals(false, params.grammar_lazy);
  2131. assert_equals(std::string(R"(<|im_start|>user
  2132. Hey there!<|im_end|>
  2133. <|im_start|>assistant
  2134. )"), params.prompt);
  2135. }
  2136. {
  2137. auto params = common_chat_templates_apply(tmpls.get(), inputs_tools);
  2138. assert_equals(COMMON_CHAT_FORMAT_CONTENT_ONLY, params.format);
  2139. assert_equals(false, params.grammar_lazy);
  2140. assert_equals(std::string(R"(<|im_start|>system
  2141. 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|>
  2142. <|im_start|>user
  2143. Hey there!<|im_end|>
  2144. <|im_start|>assistant
  2145. )"), params.prompt);
  2146. assert_equals(true, params.grammar.empty());
  2147. }
  2148. {
  2149. auto params = common_chat_templates_apply(tmpls.get(), inputs_tools_forced_json_schema);
  2150. assert_equals(COMMON_CHAT_FORMAT_LFM2_WITH_JSON_TOOLS, params.format);
  2151. assert_equals(true, params.grammar_lazy);
  2152. assert_equals(std::string(R"(<|im_start|>system
  2153. 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|>
  2154. <|im_start|>user
  2155. Hey there!<|im_end|>
  2156. <|im_start|>assistant
  2157. )"), params.prompt);
  2158. assert_equals(false, params.grammar.empty());
  2159. }
  2160. // Test parsing regular content
  2161. assert_msg_equals(message_assist,
  2162. common_chat_parse(
  2163. "Hello, world!\nWhat's up?",
  2164. /* is_partial= */ false,
  2165. {COMMON_CHAT_FORMAT_LFM2_WITH_JSON_TOOLS}));
  2166. // Test single tool call with JSON format
  2167. common_chat_msg msg_single_tool_call;
  2168. msg_single_tool_call.role = "assistant";
  2169. msg_single_tool_call.tool_calls.push_back({"special_function", "{\"arg1\":1}", ""});
  2170. assert_msg_equals(
  2171. msg_single_tool_call,
  2172. common_chat_parse(
  2173. "<|tool_call_start|>[{\"name\": \"special_function\", \"arguments\": {\"arg1\": 1}}]<|tool_call_end|>",
  2174. /* is_partial= */ false,
  2175. {COMMON_CHAT_FORMAT_LFM2_WITH_JSON_TOOLS}));
  2176. // Test tool call with string argument
  2177. common_chat_msg msg_tool_call_string;
  2178. msg_tool_call_string.role = "assistant";
  2179. msg_tool_call_string.tool_calls.push_back({"get_weather", "{\"location\":\"Paris\"}", ""});
  2180. assert_msg_equals(
  2181. msg_tool_call_string,
  2182. common_chat_parse(
  2183. "<|tool_call_start|>[{\"name\": \"get_weather\", \"arguments\": {\"location\": \"Paris\"}}]<|tool_call_end|>",
  2184. /* is_partial= */ false,
  2185. {COMMON_CHAT_FORMAT_LFM2_WITH_JSON_TOOLS}));
  2186. // Test tool call with multiple arguments
  2187. common_chat_msg msg_multi_args;
  2188. msg_multi_args.role = "assistant";
  2189. msg_multi_args.tool_calls.push_back({"calculate", "{\"x\":10,\"y\":20,\"operation\":\"add\"}", ""});
  2190. assert_msg_equals(
  2191. msg_multi_args,
  2192. common_chat_parse(
  2193. "<|tool_call_start|>[{\"name\": \"calculate\", \"arguments\": {\"x\": 10, \"y\": 20, \"operation\": \"add\"}}]<|tool_call_end|>",
  2194. /* is_partial= */ false,
  2195. {COMMON_CHAT_FORMAT_LFM2_WITH_JSON_TOOLS}));
  2196. // Test multiple tool calls in single array
  2197. common_chat_msg msg_multiple_tools;
  2198. msg_multiple_tools.role = "assistant";
  2199. msg_multiple_tools.tool_calls.push_back({"get_weather", "{\"location\":\"Paris\"}", ""});
  2200. msg_multiple_tools.tool_calls.push_back({"get_time", "{\"timezone\":\"UTC\"}", ""});
  2201. assert_msg_equals(
  2202. msg_multiple_tools,
  2203. common_chat_parse(
  2204. "<|tool_call_start|>[{\"name\": \"get_weather\", \"arguments\": {\"location\": \"Paris\"}}, {\"name\": \"get_time\", \"arguments\": {\"timezone\": \"UTC\"}}]<|tool_call_end|>",
  2205. /* is_partial= */ false,
  2206. {COMMON_CHAT_FORMAT_LFM2_WITH_JSON_TOOLS}));
  2207. // Test tool call with content before
  2208. common_chat_msg msg_content_before_tool;
  2209. msg_content_before_tool.role = "assistant";
  2210. msg_content_before_tool.content = "Let me check the weather for you.";
  2211. msg_content_before_tool.tool_calls.push_back({"get_weather", "{\"location\":\"Paris\"}", ""});
  2212. assert_msg_equals(
  2213. msg_content_before_tool,
  2214. common_chat_parse(
  2215. "Let me check the weather for you.<|tool_call_start|>[{\"name\": \"get_weather\", \"arguments\": {\"location\": \"Paris\"}}]<|tool_call_end|>",
  2216. /* is_partial= */ false,
  2217. {COMMON_CHAT_FORMAT_LFM2_WITH_JSON_TOOLS}));
  2218. // Test tool call with content after
  2219. common_chat_msg msg_content_after_tool;
  2220. msg_content_after_tool.role = "assistant";
  2221. msg_content_after_tool.content = "Here's the result.";
  2222. msg_content_after_tool.tool_calls.push_back({"get_weather", "{\"location\":\"Paris\"}", ""});
  2223. assert_msg_equals(
  2224. msg_content_after_tool,
  2225. common_chat_parse(
  2226. "<|tool_call_start|>[{\"name\": \"get_weather\", \"arguments\": {\"location\": \"Paris\"}}]<|tool_call_end|>Here's the result.",
  2227. /* is_partial= */ false,
  2228. {COMMON_CHAT_FORMAT_LFM2_WITH_JSON_TOOLS}));
  2229. // Test tool call with newlines (common in LLM output)
  2230. common_chat_msg msg_tool_call_newlines;
  2231. msg_tool_call_newlines.role = "assistant";
  2232. msg_tool_call_newlines.tool_calls.push_back({"get_current_time", "{\"location\":\"Paris\"}", ""});
  2233. assert_msg_equals(
  2234. msg_tool_call_newlines,
  2235. common_chat_parse(
  2236. "<|tool_call_start|>[{\n \"name\": \"get_current_time\",\n \"arguments\": {\n \"location\": \"Paris\"\n }\n}]<|tool_call_end|>",
  2237. /* is_partial= */ false,
  2238. {COMMON_CHAT_FORMAT_LFM2_WITH_JSON_TOOLS}));
  2239. // Note: LFM2 uses JSON format for tool calls: [{"name": "...", "arguments": {...}}]
  2240. // Unlike other formats, LFM2 template does not render tool calls in conversation history,
  2241. // so we don't use test_templates() for tool call generation. Instead, the parsing tests
  2242. // above verify edge cases and format variations for the tool call output format.
  2243. }
  2244. {
  2245. auto tmpls = read_templates("models/templates/MiniMax-M2.jinja");
  2246. std::vector<std::string> end_tokens{ "[e~[" };
  2247. assert_equals(COMMON_CHAT_FORMAT_MINIMAX_M2, common_chat_templates_apply(tmpls.get(), inputs_no_tools).format);
  2248. assert_equals(COMMON_CHAT_FORMAT_MINIMAX_M2, common_chat_templates_apply(tmpls.get(), inputs_tools).format);
  2249. // Test parsing regular content
  2250. assert_msg_equals(message_assist,
  2251. common_chat_parse(
  2252. "Hello, world!\nWhat's up?",
  2253. /* is_partial= */ false,
  2254. {COMMON_CHAT_FORMAT_MINIMAX_M2}));
  2255. // Test parsing content with thinking
  2256. assert_msg_equals(message_assist_thoughts,
  2257. common_chat_parse(
  2258. "<think>I'm\nthinking</think>Hello, world!\nWhat's up?",
  2259. /* is_partial= */ false,
  2260. {
  2261. /* .format = */ COMMON_CHAT_FORMAT_MINIMAX_M2,
  2262. /* .reasoning_format = */ COMMON_REASONING_FORMAT_DEEPSEEK,
  2263. }));
  2264. // Test parsing tool calls
  2265. assert_msg_equals(message_assist_call,
  2266. common_chat_parse(
  2267. "<minimax:tool_call><invoke name=\"special_function\"><parameter name=\"arg1\">1</parameter></invoke></minimax:tool_call>",
  2268. /* is_partial= */ false,
  2269. {COMMON_CHAT_FORMAT_MINIMAX_M2}));
  2270. // Test parsing tool calls with thinking
  2271. assert_msg_equals(message_assist_call_thoughts,
  2272. common_chat_parse(
  2273. "<think>I'm\nthinking</think><minimax:tool_call><invoke name=\"special_function\"><parameter name=\"arg1\">1</parameter></invoke></minimax:tool_call>",
  2274. /* is_partial= */ false,
  2275. {
  2276. /* .format = */ COMMON_CHAT_FORMAT_MINIMAX_M2,
  2277. /* .reasoning_format = */ COMMON_REASONING_FORMAT_DEEPSEEK
  2278. }));
  2279. // Test tool calls with extra content
  2280. assert_msg_equals(message_assist_call_content,
  2281. common_chat_parse(
  2282. "<minimax:tool_call><invoke name=\"special_function\"><parameter name=\"arg1\">1</parameter></invoke></minimax:tool_call>Hello, world!\nWhat's up?",
  2283. /* is_partial= */ false,
  2284. {COMMON_CHAT_FORMAT_MINIMAX_M2}
  2285. ));
  2286. // Test tool calls with extra content AND thinking
  2287. assert_msg_equals(message_assist_call_thoughts_content,
  2288. common_chat_parse(
  2289. "<think>I'm\nthinking</think><minimax:tool_call><invoke name=\"special_function\"><parameter name=\"arg1\">1</parameter></invoke></minimax:tool_call>Hello, world!\nWhat's up?",
  2290. /* is_partial= */ false,
  2291. {
  2292. /* .format = */ COMMON_CHAT_FORMAT_MINIMAX_M2,
  2293. /* .reasoning_format = */ COMMON_REASONING_FORMAT_DEEPSEEK
  2294. }));
  2295. // Test streaming
  2296. test_parser_with_streaming(message_assist_call_thoughts_content,
  2297. "<think>I'm\nthinking\n</think>Hello, world!\nWhat's up?\n<minimax:tool_call><invoke name=\"special_function\"><parameter name=\"arg1\">1</parameter></invoke></minimax:tool_call>",
  2298. [&](const std::string &msg) { return common_chat_parse(msg, /* is_partial= */ true, {
  2299. /* .format = */ COMMON_CHAT_FORMAT_MINIMAX_M2,
  2300. /* .reasoning_format = */ COMMON_REASONING_FORMAT_DEEPSEEK
  2301. }); });
  2302. test_parser_with_streaming(message_assist_call_thoughts_unparsed,
  2303. "<think>I'm\nthinking</think>\n\n<minimax:tool_call><invoke name=\"special_function\"><parameter name=\"arg1\">1</parameter></invoke></minimax:tool_call>",
  2304. [&](const std::string &msg) { return common_chat_parse(msg, /* is_partial= */ true, {
  2305. /* .format = */ COMMON_CHAT_FORMAT_MINIMAX_M2,
  2306. /* .reasoning_format = */ COMMON_REASONING_FORMAT_NONE
  2307. }); });
  2308. test_parser_with_streaming(message_assist_call_thoughts_content,
  2309. "<think>I'm\nthinking\n</think>\n\nHello, world!\nWhat's up?\n\n<minimax:tool_call>\n<invoke name=\"special_function\">\n<parameter name=\"arg1\">1</parameter>\n</invoke>\n</minimax:tool_call>\n",
  2310. [&](const std::string &msg) { return common_chat_parse(msg, /* is_partial= */ true, {
  2311. /* .format = */ COMMON_CHAT_FORMAT_MINIMAX_M2,
  2312. /* .reasoning_format = */ COMMON_REASONING_FORMAT_DEEPSEEK
  2313. }); });
  2314. test_parser_with_streaming(message_assist_call_withopt,
  2315. "<minimax:tool_call>\n<invoke name=\"special_function_with_opt\">\n<parameter name=\"arg1\">1</parameter>\n<parameter name=\"arg2\">2</parameter>\n</invoke>\n</minimax:tool_call>",
  2316. [&](const std::string &msg) { return common_chat_parse(msg, /* is_partial= */ true, {
  2317. /* .format = */ COMMON_CHAT_FORMAT_MINIMAX_M2,
  2318. /* .reasoning_format = */ COMMON_REASONING_FORMAT_NONE
  2319. }); });
  2320. // Test template generation for regular content
  2321. test_templates(tmpls.get(), end_tokens, message_assist, tools,
  2322. "Hello, world!\nWhat's up?",
  2323. /* expect_grammar_triggered= */ false);
  2324. // Test template generation for tool calls
  2325. test_templates(tmpls.get(), end_tokens, message_assist_call, tools,
  2326. "<minimax:tool_call>\n<invoke name=\"special_function\">\n<parameter name=\"arg1\">1</parameter>\n</invoke>\n</minimax:tool_call>",
  2327. /* expect_grammar_triggered= */ true,
  2328. /* test_grammar_if_triggered= */ true,
  2329. /* common_reasoning_format= */ COMMON_REASONING_FORMAT_NONE,
  2330. /* ignore_whitespace_differences= */ true
  2331. );
  2332. // Test template generation for tools with optional parameters
  2333. test_templates(tmpls.get(), end_tokens, message_assist_call_noopt, tools,
  2334. "<minimax:tool_call>\n<invoke name=\"special_function_with_opt\">\n<parameter name=\"arg1\">1</parameter>\n</invoke>\n</minimax:tool_call>",
  2335. /* expect_grammar_triggered= */ true,
  2336. /* test_grammar_if_triggered= */ true,
  2337. /* common_reasoning_format= */ COMMON_REASONING_FORMAT_NONE,
  2338. /* ignore_whitespace_differences= */ true
  2339. );
  2340. test_templates(tmpls.get(), end_tokens, message_assist_call_withopt, tools,
  2341. "<minimax:tool_call>\n<invoke name=\"special_function_with_opt\">\n<parameter name=\"arg1\">1</parameter>\n<parameter name=\"arg2\">2</parameter>\n</invoke>\n</minimax:tool_call>",
  2342. /* expect_grammar_triggered= */ true,
  2343. /* test_grammar_if_triggered= */ true,
  2344. /* common_reasoning_format= */ COMMON_REASONING_FORMAT_NONE,
  2345. /* ignore_whitespace_differences= */ true
  2346. );
  2347. }
  2348. {
  2349. auto tmpls = read_templates("models/templates/GLM-4.6.jinja");
  2350. std::vector<std::string> end_tokens{ "<|assistant|>", "<|observation|>" };
  2351. assert_equals(COMMON_CHAT_FORMAT_GLM_4_5, common_chat_templates_apply(tmpls.get(), inputs_no_tools).format);
  2352. assert_equals(COMMON_CHAT_FORMAT_GLM_4_5, common_chat_templates_apply(tmpls.get(), inputs_tools).format);
  2353. // Test parsing regular content
  2354. assert_msg_equals(message_assist,
  2355. common_chat_parse(
  2356. "Hello, world!\nWhat's up?",
  2357. /* is_partial= */ false,
  2358. {COMMON_CHAT_FORMAT_GLM_4_5}));
  2359. // Test parsing content with thinking
  2360. assert_msg_equals(message_assist_thoughts,
  2361. common_chat_parse(
  2362. "\n<think>I'm\nthinking</think>\nHello, world!\nWhat's up?",
  2363. /* is_partial= */ false,
  2364. {
  2365. /* .format = */ COMMON_CHAT_FORMAT_GLM_4_5,
  2366. /* .reasoning_format = */ COMMON_REASONING_FORMAT_DEEPSEEK,
  2367. }), true);
  2368. // Test parsing tool calls
  2369. assert_msg_equals(message_assist_call,
  2370. common_chat_parse(
  2371. "\n<tool_call>special_function\n<arg_key>arg1</arg_key>\n<arg_value>1</arg_value>\n</tool_call>",
  2372. /* is_partial= */ false,
  2373. {COMMON_CHAT_FORMAT_GLM_4_5}), true);
  2374. // Test parsing tool calls with thinking
  2375. assert_msg_equals(message_assist_call_thoughts,
  2376. common_chat_parse(
  2377. "\n<think>I'm\nthinking</think>\n<tool_call>special_function\n<arg_key>arg1</arg_key>\n<arg_value>1</arg_value>\n</tool_call>",
  2378. /* is_partial= */ false,
  2379. {
  2380. /* .format = */ COMMON_CHAT_FORMAT_GLM_4_5,
  2381. /* .reasoning_format = */ COMMON_REASONING_FORMAT_DEEPSEEK
  2382. }), true);
  2383. // Test tool calls with extra content
  2384. assert_msg_equals(message_assist_call_content,
  2385. common_chat_parse(
  2386. "\n<tool_call>special_function\n<arg_key>arg1</arg_key>\n<arg_value>1</arg_value>\n</tool_call>Hello, world!\nWhat's up?",
  2387. /* is_partial= */ false,
  2388. {COMMON_CHAT_FORMAT_GLM_4_5}
  2389. ), true);
  2390. // Test tool calls with extra content AND thinking
  2391. assert_msg_equals(message_assist_call_thoughts_content,
  2392. common_chat_parse(
  2393. "\n<think>I'm\nthinking</think>Hello, world!\nWhat's up?\n<tool_call>special_function\n<arg_key>arg1</arg_key>\n<arg_value>1</arg_value>\n</tool_call>",
  2394. /* is_partial= */ false,
  2395. {
  2396. /* .format = */ COMMON_CHAT_FORMAT_GLM_4_5,
  2397. /* .reasoning_format = */ COMMON_REASONING_FORMAT_DEEPSEEK
  2398. }), true);
  2399. // Test streaming
  2400. test_parser_with_streaming(message_assist_call_thoughts_content,
  2401. "\n<think>I'm\nthinking</think>Hello, world!\nWhat's up?\n<tool_call>special_function\n<arg_key>arg1</arg_key>\n<arg_value>1</arg_value>\n</tool_call>",
  2402. [&](const std::string &msg) { return common_chat_parse(msg, /* is_partial= */ true, {
  2403. /* .format = */ COMMON_CHAT_FORMAT_GLM_4_5,
  2404. /* .reasoning_format = */ COMMON_REASONING_FORMAT_DEEPSEEK
  2405. }); });
  2406. test_parser_with_streaming(message_assist_call_thoughts_unparsed,
  2407. "\n<think>I'm\nthinking</think>\n\n<tool_call>special_function\n<arg_key>arg1</arg_key>\n<arg_value>1</arg_value>\n</tool_call>",
  2408. [&](const std::string &msg) { return common_chat_parse(msg, /* is_partial= */ true, {
  2409. /* .format = */ COMMON_CHAT_FORMAT_GLM_4_5,
  2410. /* .reasoning_format = */ COMMON_REASONING_FORMAT_NONE
  2411. }); });
  2412. test_parser_with_streaming(message_assist_call_withopt,
  2413. "\n<think></think>\n<tool_call>special_function_with_opt\n<arg_key>arg1</arg_key>\n<arg_value>1</arg_value>\n<arg_key>arg2</arg_key>\n<arg_value>2</arg_value>\n</tool_call>\n",
  2414. [&](const std::string &msg) { return common_chat_parse(msg, /* is_partial= */ true, {
  2415. /* .format = */ COMMON_CHAT_FORMAT_GLM_4_5,
  2416. /* .reasoning_format = */ COMMON_REASONING_FORMAT_DEEPSEEK
  2417. }); });
  2418. test_parser_with_streaming(
  2419. simple_assist_msg("", "", "complex_function", "{\"name\":\"John Doe\",\"age\":30,\"active\":true,\"score\":95.5}"),
  2420. "<tool_call>complex_function\n"
  2421. "<arg_key>name</arg_key>\n"
  2422. "<arg_value>John Doe</arg_value>\n"
  2423. "<arg_key>age</arg_key>\n"
  2424. "<arg_value>30</arg_value>\n"
  2425. "<arg_key>active</arg_key>\n"
  2426. "<arg_value>true</arg_value>\n"
  2427. "<arg_key>score</arg_key>\n"
  2428. "<arg_value>95.5</arg_value>\n"
  2429. "</tool_call>",
  2430. [&](const std::string &msg) { return common_chat_parse(msg, /* is_partial= */ true, {COMMON_CHAT_FORMAT_GLM_4_5}); });
  2431. test_parser_with_streaming(
  2432. simple_assist_msg("", "", "web_search", "{\"query\":\"\\\"From Zero\\\" Linkin Park album tracklist complete songs\",\"limit\":3,\"type\":\"text\"}"),
  2433. "<tool_call>web_search\n"
  2434. "<arg_key>query</arg_key>\n"
  2435. "<arg_value>\"From Zero\" Linkin Park album tracklist complete songs</arg_value>\n"
  2436. "<arg_key>limit</arg_key>\n"
  2437. "<arg_value>3</arg_value>\n"
  2438. "<arg_key>type</arg_key>\n"
  2439. "<arg_value>text</arg_value>\n"
  2440. "</tool_call>",
  2441. [&](const std::string &msg) { return common_chat_parse(msg, /* is_partial= */ true, {COMMON_CHAT_FORMAT_GLM_4_5}); });
  2442. // Test interleaved thinking
  2443. test_parser_with_streaming(simple_assist_msg("Hello, world!\n\nWhat's up?", "I'm\nthinkingThinking2", "special_function", "{\"arg1\": 1}"),
  2444. "\n<think>I'm\nthinking</think>Hello, world!\n<think>Thinking2</think>What's up?\n<tool_call>special_function\n<arg_key>arg1</arg_key>\n<arg_value>1</arg_value>\n</tool_call>",
  2445. [&](const std::string &msg) { return common_chat_parse(msg, /* is_partial= */ true, {
  2446. /* .format = */ COMMON_CHAT_FORMAT_GLM_4_5,
  2447. /* .reasoning_format = */ COMMON_REASONING_FORMAT_DEEPSEEK
  2448. }); });
  2449. test_parser_with_streaming(simple_assist_msg("\n<think>I'm\nthinking</think>Hello, world!\n<think>Thinking2</think>What's up?", "", "special_function", "{\"arg1\": 1}"),
  2450. "\n<think>I'm\nthinking</think>Hello, world!\n<think>Thinking2</think>What's up?\n<tool_call>special_function\n<arg_key>arg1</arg_key>\n<arg_value>1</arg_value>\n</tool_call>",
  2451. [&](const std::string &msg) { return common_chat_parse(msg, /* is_partial= */ true, {
  2452. /* .format = */ COMMON_CHAT_FORMAT_GLM_4_5,
  2453. /* .reasoning_format = */ COMMON_REASONING_FORMAT_NONE
  2454. }); });
  2455. // Test template generation for regular content
  2456. test_templates(tmpls.get(), end_tokens, message_assist, tools,
  2457. "\n<think></think>\nHello, world!\nWhat's up?",
  2458. /* expect_grammar_triggered= */ false);
  2459. // Test template generation for tool calls
  2460. test_templates(tmpls.get(), end_tokens, message_assist_call, tools,
  2461. "\n<think></think>\n<tool_call>special_function\n<arg_key>arg1</arg_key>\n<arg_value>1</arg_value>\n</tool_call>\n",
  2462. /* expect_grammar_triggered= */ true,
  2463. /* test_grammar_if_triggered= */ false,
  2464. /* common_reasoning_format= */ COMMON_REASONING_FORMAT_DEEPSEEK,
  2465. /* ignore_whitespace_differences= */ true
  2466. );
  2467. // Test template generation for tools with optional parameters
  2468. test_templates(tmpls.get(), end_tokens, message_assist_call_noopt, tools,
  2469. "\n<think></think>\n<tool_call>special_function_with_opt\n<arg_key>arg1</arg_key>\n<arg_value>1</arg_value>\n</tool_call>\n",
  2470. /* expect_grammar_triggered= */ true,
  2471. /* test_grammar_if_triggered= */ false,
  2472. /* common_reasoning_format= */ COMMON_REASONING_FORMAT_DEEPSEEK,
  2473. /* ignore_whitespace_differences= */ true
  2474. );
  2475. test_templates(tmpls.get(), end_tokens, message_assist_call_withopt, tools,
  2476. "\n<think></think>\n<tool_call>special_function_with_opt\n<arg_key>arg1</arg_key>\n<arg_value>1</arg_value>\n<arg_key>arg2</arg_key>\n<arg_value>2</arg_value>\n</tool_call>\n",
  2477. /* expect_grammar_triggered= */ true,
  2478. /* test_grammar_if_triggered= */ false,
  2479. /* common_reasoning_format= */ COMMON_REASONING_FORMAT_DEEPSEEK,
  2480. /* ignore_whitespace_differences= */ true
  2481. );
  2482. }
  2483. {
  2484. auto tmpls = read_templates("models/templates/Kimi-K2-Thinking.jinja");
  2485. std::vector<std::string> end_tokens{ "<|im_end|>" };
  2486. assert_equals(COMMON_CHAT_FORMAT_KIMI_K2, common_chat_templates_apply(tmpls.get(), inputs_no_tools).format);
  2487. assert_equals(COMMON_CHAT_FORMAT_KIMI_K2, common_chat_templates_apply(tmpls.get(), inputs_tools).format);
  2488. // Test parsing regular content
  2489. assert_msg_equals(message_assist,
  2490. common_chat_parse(
  2491. "Hello, world!\nWhat's up?",
  2492. /* is_partial= */ false,
  2493. {COMMON_CHAT_FORMAT_KIMI_K2}));
  2494. // Test parsing content with thinking
  2495. assert_msg_equals(message_assist_thoughts,
  2496. common_chat_parse(
  2497. "<think>I'm\nthinking</think>Hello, world!\nWhat's up?",
  2498. /* is_partial= */ false,
  2499. {
  2500. /* .format = */ COMMON_CHAT_FORMAT_KIMI_K2,
  2501. /* .reasoning_format = */ COMMON_REASONING_FORMAT_DEEPSEEK,
  2502. }));
  2503. // Test parsing tool calls
  2504. assert_msg_equals(message_assist_call,
  2505. common_chat_parse(
  2506. "<|tool_calls_section_begin|><|tool_call_begin|>functions.special_function:0<|tool_call_argument_begin|>{\"arg1\": 1}<|tool_call_end|><|tool_calls_section_end|>",
  2507. /* is_partial= */ false,
  2508. {COMMON_CHAT_FORMAT_KIMI_K2}));
  2509. // Test parsing tool calls with thinking
  2510. assert_msg_equals(message_assist_call_thoughts,
  2511. common_chat_parse(
  2512. "<think>I'm\nthinking</think><|tool_calls_section_begin|><|tool_call_begin|>functions.special_function:0<|tool_call_argument_begin|>{\"arg1\": 1}<|tool_call_end|><|tool_calls_section_end|>",
  2513. /* is_partial= */ false,
  2514. {
  2515. /* .format = */ COMMON_CHAT_FORMAT_KIMI_K2,
  2516. /* .reasoning_format = */ COMMON_REASONING_FORMAT_DEEPSEEK
  2517. }));
  2518. // Test tool calls with extra content
  2519. assert_msg_equals(message_assist_call_content,
  2520. common_chat_parse(
  2521. "<|tool_calls_section_begin|><|tool_call_begin|>functions.special_function:0<|tool_call_argument_begin|>{\"arg1\": 1}<|tool_call_end|><|tool_calls_section_end|>Hello, world!\nWhat's up?",
  2522. /* is_partial= */ false,
  2523. {COMMON_CHAT_FORMAT_KIMI_K2}
  2524. ));
  2525. // Test tool calls with extra content AND thinking
  2526. assert_msg_equals(message_assist_call_thoughts_content,
  2527. common_chat_parse(
  2528. "<think>I'm\nthinking</think><|tool_calls_section_begin|><|tool_call_begin|>functions.special_function:0<|tool_call_argument_begin|>{\"arg1\": 1}<|tool_call_end|><|tool_calls_section_end|>Hello, world!\nWhat's up?",
  2529. /* is_partial= */ false,
  2530. {
  2531. /* .format = */ COMMON_CHAT_FORMAT_KIMI_K2,
  2532. /* .reasoning_format = */ COMMON_REASONING_FORMAT_DEEPSEEK
  2533. }));
  2534. // Test streaming
  2535. test_parser_with_streaming(message_assist_call_thoughts_content,
  2536. "<think>I'm\nthinking\n</think>Hello, world!\nWhat's up?\n<|tool_calls_section_begin|><|tool_call_begin|>functions.special_function:0<|tool_call_argument_begin|>{\"arg1\": 1}<|tool_call_end|><|tool_calls_section_end|>",
  2537. [&](const std::string &msg) { return common_chat_parse(msg, /* is_partial= */ true, {
  2538. /* .format = */ COMMON_CHAT_FORMAT_KIMI_K2,
  2539. /* .reasoning_format = */ COMMON_REASONING_FORMAT_DEEPSEEK
  2540. }); });
  2541. test_parser_with_streaming(message_assist_call_thoughts_unparsed,
  2542. "<think>I'm\nthinking</think>\n\n<|tool_calls_section_begin|><|tool_call_begin|>functions.special_function:0<|tool_call_argument_begin|>{\"arg1\": 1}<|tool_call_end|><|tool_calls_section_end|>",
  2543. [&](const std::string &msg) { return common_chat_parse(msg, /* is_partial= */ true, {
  2544. /* .format = */ COMMON_CHAT_FORMAT_KIMI_K2,
  2545. /* .reasoning_format = */ COMMON_REASONING_FORMAT_NONE
  2546. }); });
  2547. test_parser_with_streaming(message_assist_call_thoughts_content,
  2548. "<think>I'm\nthinking\n</think>\n\nHello, world!\nWhat's up?\n\n<|tool_calls_section_begin|><|tool_call_begin|>functions.special_function:0<|tool_call_argument_begin|>{\"arg1\": 1}<|tool_call_end|><|tool_calls_section_end|>\n",
  2549. [&](const std::string &msg) { return common_chat_parse(msg, /* is_partial= */ true, {
  2550. /* .format = */ COMMON_CHAT_FORMAT_KIMI_K2,
  2551. /* .reasoning_format = */ COMMON_REASONING_FORMAT_DEEPSEEK
  2552. }); });
  2553. test_parser_with_streaming(message_assist_call_withopt,
  2554. "<|tool_calls_section_begin|><|tool_call_begin|>functions.special_function_with_opt:0<|tool_call_argument_begin|>{\"arg1\": 1, \"arg2\": 2}<|tool_call_end|><|tool_calls_section_end|>",
  2555. [&](const std::string &msg) { return common_chat_parse(msg, /* is_partial= */ true, {
  2556. /* .format = */ COMMON_CHAT_FORMAT_KIMI_K2,
  2557. /* .reasoning_format = */ COMMON_REASONING_FORMAT_NONE
  2558. }); });
  2559. test_parser_with_streaming(simple_assist_msg("Hello, world!\nWhat's up?", "I'm\nthinking", "special_function", "{\"arg1\": \"123456\"}"),
  2560. "<think>I'm\nthinking</think>Hello, world!\nWhat's up?\n<|tool_calls_section_begin|><|tool_call_begin|>functions.special_function:0<|tool_call_argument_begin|>{\"arg1\": \"123456\"}<|tool_call_end|><|tool_calls_section_end|>",
  2561. [&](const std::string &msg) { return common_chat_parse(msg, /* is_partial= */ true, {
  2562. /* .format = */ COMMON_CHAT_FORMAT_KIMI_K2,
  2563. /* .reasoning_format = */ COMMON_REASONING_FORMAT_DEEPSEEK
  2564. }); });
  2565. test_parser_with_streaming(simple_assist_msg("Hello, world!\nWhat's up?", "I'm\nthinking", "special_function", "{\"arg1\": [1, 2, \"345\", 6]}"),
  2566. "<think>I'm\nthinking</think>Hello, world!\nWhat's up?\n<|tool_calls_section_begin|><|tool_call_begin|>functions.special_function:0<|tool_call_argument_begin|>{\"arg1\": [1, 2, \"345\", 6]}<|tool_call_end|><|tool_calls_section_end|>",
  2567. [&](const std::string &msg) { return common_chat_parse(msg, /* is_partial= */ true, {
  2568. /* .format = */ COMMON_CHAT_FORMAT_KIMI_K2,
  2569. /* .reasoning_format = */ COMMON_REASONING_FORMAT_DEEPSEEK
  2570. }); });
  2571. test_parser_with_streaming(simple_assist_msg("Hello, world!\nWhat's up?", "I'm\nthinking", "special_function", "{\"arg1\": {\"12\": 34, \"5\": [67, 8], \"9\": \"10\"}}"),
  2572. "<think>I'm\nthinking</think>Hello, world!\nWhat's up?\n<|tool_calls_section_begin|><|tool_call_begin|>functions.special_function:0<|tool_call_argument_begin|>{\"arg1\": {\"12\": 34, \"5\": [67, 8], \"9\": \"10\"}}<|tool_call_end|><|tool_calls_section_end|>",
  2573. [&](const std::string &msg) { return common_chat_parse(msg, /* is_partial= */ true, {
  2574. /* .format = */ COMMON_CHAT_FORMAT_KIMI_K2,
  2575. /* .reasoning_format = */ COMMON_REASONING_FORMAT_DEEPSEEK
  2576. }); });
  2577. test_parser_with_streaming(
  2578. simple_assist_msg("", "", "complex_function", "{\"name\":\"John Doe\",\"age\":30,\"active\":true,\"score\":95.5}"),
  2579. "<|tool_calls_section_begin|><|tool_call_begin|>functions.complex_function:0<|tool_call_argument_begin|>"
  2580. "{\"name\": \"John Doe\", \"age\": 30, \"active\": true, \"score\": 95.5}"
  2581. "<|tool_call_end|><|tool_calls_section_end|>",
  2582. [&](const std::string &msg) { return common_chat_parse(msg, /* is_partial= */ true, {COMMON_CHAT_FORMAT_KIMI_K2}); });
  2583. test_parser_with_streaming(
  2584. simple_assist_msg("", "", "web_search", "{\"query\":\"\\\"From Zero\\\" Linkin Park album tracklist complete songs\",\"limit\":3,\"type\":\"text\"}"),
  2585. "<|tool_calls_section_begin|><|tool_call_begin|>functions.web_search:0<|tool_call_argument_begin|>"
  2586. "{\"query\":\"\\\"From Zero\\\" Linkin Park album tracklist complete songs\",\"limit\":3,\"type\":\"text\"}"
  2587. "<|tool_call_end|><|tool_calls_section_end|>",
  2588. [&](const std::string &msg) { return common_chat_parse(msg, /* is_partial= */ true, {COMMON_CHAT_FORMAT_KIMI_K2}); });
  2589. test_parser_with_streaming(
  2590. simple_assist_msg("", "", "read_file", "{\"args\": [{\"path\": \"src/providers/ThemeProvider.tsx\"}, {\"path\": \"src/components/Header.tsx\"}, {\"path\": \"src/components/ThemeToggle.tsx\"}, {\"path\": \"src/app/globals.css\"}, {\"path\": \"src/app/layout.tsx\"}]}"),
  2591. "<|tool_calls_section_begin|><|tool_call_begin|>functions.read_file:0<|tool_call_argument_begin|>"
  2592. "{\"args\": [{\"path\": \"src/providers/ThemeProvider.tsx\"}, {\"path\": \"src/components/Header.tsx\"}, {\"path\": \"src/components/ThemeToggle.tsx\"}, {\"path\": \"src/app/globals.css\"}, {\"path\": \"src/app/layout.tsx\"}]}"
  2593. "<|tool_call_end|><|tool_calls_section_end|>",
  2594. [&](const std::string &msg) { return common_chat_parse(msg, /* is_partial= */ true, {COMMON_CHAT_FORMAT_KIMI_K2}); });
  2595. test_parser_with_streaming(
  2596. simple_assist_msg(
  2597. "Let me start by examining the relevant files to understand the current implementation.", "",
  2598. "read_file",
  2599. "{\"files\": [{\"path\": \"src/app/Partners.tsx\", \"line_ranges\": [\"1-100\"]}]}"),
  2600. "Let me start by examining the relevant files to understand the current implementation."
  2601. "<|tool_calls_section_begin|><|tool_call_begin|>functions.read_file:0<|tool_call_argument_begin|>"
  2602. "{\"files\":[{\"path\":\"src/app/Partners.tsx\",\"line_ranges\":[\"1-100\"]}]}"
  2603. "<|tool_call_end|><|tool_calls_section_end|>",
  2604. [&](const std::string &msg) { return common_chat_parse(msg, /* is_partial= */ true, {COMMON_CHAT_FORMAT_KIMI_K2}); });
  2605. auto multi_tool_msg = simple_assist_msg("Let me call multiple tools.", "I'm thinking.");
  2606. multi_tool_msg.tool_calls.push_back({ "read_file", "{\"files\": [{\"path\": \"src/app/Partners.tsx\", \"line_ranges\": [\"1-100\"]}]}", "" });
  2607. multi_tool_msg.tool_calls.push_back({ "web_search", "{\"query\":\"\\\"From Zero\\\" Linkin Park album tracklist complete songs\",\"limit\":3,\"type\":\"text\"}", "" });
  2608. multi_tool_msg.tool_calls.push_back({ "complex_function", "{\"name\": \"John Doe\", \"age\": 30, \"active\": true, \"score\": 95.5}", "" });
  2609. multi_tool_msg.tool_calls.push_back({ "emoji_function", "{\"message\":\"Hello! 👋 🌟 🚀 Testing emojis: 😀😃😄😁 and symbols: ∑∏∆∇\"}", "" });
  2610. test_parser_with_streaming(multi_tool_msg,
  2611. "<think>I'm thinking.</think>Let me call multiple tools."
  2612. "<|tool_calls_section_begin|>"
  2613. "<|tool_call_begin|>functions.read_file:0<|tool_call_argument_begin|>"
  2614. "{\"files\":[{\"path\":\"src/app/Partners.tsx\",\"line_ranges\":[\"1-100\"]}]}"
  2615. "<|tool_call_end|>"
  2616. "<|tool_call_begin|>functions.web_search:1<|tool_call_argument_begin|>"
  2617. "{\"query\":\"\\\"From Zero\\\" Linkin Park album tracklist complete songs\",\"limit\":3,\"type\":\"text\"}"
  2618. "<|tool_call_end|>"
  2619. "<|tool_call_begin|>functions.complex_function:2<|tool_call_argument_begin|>"
  2620. "{\"name\": \"John Doe\", \"age\": 30, \"active\": true, \"score\": 95.5}"
  2621. "<|tool_call_end|>"
  2622. "<|tool_call_begin|>functions.emoji_function:3<|tool_call_argument_begin|>"
  2623. "{\"message\":\"Hello! 👋 🌟 🚀 Testing emojis: 😀😃😄😁 and symbols: ∑∏∆∇\"}"
  2624. "<|tool_call_end|>"
  2625. "<|tool_calls_section_end|>",
  2626. [&](const std::string &msg) { return common_chat_parse(msg, /* is_partial= */ true, {
  2627. COMMON_CHAT_FORMAT_KIMI_K2,
  2628. COMMON_REASONING_FORMAT_DEEPSEEK
  2629. }); });
  2630. test_parser_with_streaming(
  2631. simple_assist_msg("", "I'm thinking", "complex_function_in_think", "{\"name\":\"John Doe\",\"age\":30,\"active\":true,\"score\":95.5}"),
  2632. "<think>I'm thinking<|tool_calls_section_begin|><|tool_call_begin|>functions.complex_function_in_think:0<|tool_call_argument_begin|>"
  2633. "{\"name\": \"John Doe\", \"age\": 30, \"active\": true, \"score\": 95.5}"
  2634. "<|tool_call_end|><|tool_calls_section_end|>",
  2635. [&](const std::string &msg) { return common_chat_parse(msg, /* is_partial= */ true, {
  2636. COMMON_CHAT_FORMAT_KIMI_K2,
  2637. COMMON_REASONING_FORMAT_DEEPSEEK
  2638. }); });
  2639. test_parser_with_streaming(
  2640. simple_assist_msg("Hello", "I'm thinkingI'm still thinking", "complex_function_in_think", "{\"name\":\"John Doe\",\"age\":30,\"active\":true,\"score\":95.5}"),
  2641. "<think>I'm thinking<|tool_calls_section_begin|><|tool_call_begin|>functions.complex_function_in_think:0<|tool_call_argument_begin|>"
  2642. "{\"name\": \"John Doe\", \"age\": 30, \"active\": true, \"score\": 95.5}"
  2643. "<|tool_call_end|><|tool_calls_section_end|>I'm still thinking</think>Hello",
  2644. [&](const std::string &msg) { return common_chat_parse(msg, /* is_partial= */ true, {
  2645. COMMON_CHAT_FORMAT_KIMI_K2,
  2646. COMMON_REASONING_FORMAT_DEEPSEEK
  2647. }); });
  2648. // Test template rendering
  2649. common_chat_templates_inputs conversation_with_tools = inputs_tools;
  2650. conversation_with_tools.messages.push_back(simple_assist_msg("Let's do it", "Think first", "complex_function", "{\"name\":\"John Doe\",\"age\":30,\"active\":true,\"score\":95.5}"));
  2651. conversation_with_tools.messages.push_back({
  2652. "tool",
  2653. "Tool response 1",
  2654. /* .content_parts = */ {},
  2655. /* .tool_calls = */ {},
  2656. /* .reasoning_content = */ "",
  2657. /* .tool_name = */ "complex_function",
  2658. /* .tool_call_id = */ "",
  2659. });
  2660. conversation_with_tools.messages.push_back(simple_assist_msg("Continue", "Think next", "web_search", "{\"query\":\"\\\"From Zero\\\" Linkin Park album tracklist complete songs\",\"limit\":3,\"type\":\"text\"}"));
  2661. conversation_with_tools.messages.push_back({
  2662. "tool",
  2663. "Tool response 2",
  2664. /* .content_parts = */ {},
  2665. /* .tool_calls = */ {},
  2666. /* .reasoning_content = */ "",
  2667. /* .tool_name = */ "web_search",
  2668. /* .tool_call_id = */ "",
  2669. });
  2670. conversation_with_tools.messages.push_back(simple_assist_msg("CC", "Think last", "read_file", "{\"args\": [{\"path\": \"src/providers/ThemeProvider.tsx\"}, {\"path\": \"src/components/Header.tsx\"}, {\"path\": \"src/components/ThemeToggle.tsx\"}, {\"path\": \"src/app/globals.css\"}, {\"path\": \"src/app/layout.tsx\"}]}"));
  2671. conversation_with_tools.messages.push_back({
  2672. "tool",
  2673. "Tool response 3",
  2674. /* .content_parts = */ {},
  2675. /* .tool_calls = */ {},
  2676. /* .reasoning_content = */ "",
  2677. /* .tool_name = */ "read_file",
  2678. /* .tool_call_id = */ "",
  2679. });
  2680. assert_equals(common_chat_templates_apply(tmpls.get(), conversation_with_tools).prompt, std::string("<|im_system|>tool_declare<|im_middle|>[{\"type\": \"function\", \"function\": {\"name\": \"special_function\", \"description\": \"I'm special\", \"parameters\": {\"type\": \"object\", \"properties\": {\"arg1\": {\"type\": \"integer\", \"description\": \"The arg.\"}}, \"required\": [\"arg1\"]}}}]<|im_end|><|im_system|>system<|im_middle|>You are Kimi, an AI assistant created by Moonshot AI.<|im_end|><|im_user|>user<|im_middle|>Hey there!<|im_end|><|im_assistant|>assistant<|im_middle|><think>Think first</think>Let's do it<|tool_calls_section_begin|><|tool_call_begin|>functions.complex_function:0<|tool_call_argument_begin|>{\"name\":\"John Doe\",\"age\":30,\"active\":true,\"score\":95.5}<|tool_call_end|><|tool_calls_section_end|><|im_end|><|im_system|>complex_function<|im_middle|>## Return of functions.complex_function:0\nTool response 1<|im_end|><|im_assistant|>assistant<|im_middle|><think>Think next</think>Continue<|tool_calls_section_begin|><|tool_call_begin|>functions.web_search:1<|tool_call_argument_begin|>{\"query\":\"\\\"From Zero\\\" Linkin Park album tracklist complete songs\",\"limit\":3,\"type\":\"text\"}<|tool_call_end|><|tool_calls_section_end|><|im_end|><|im_system|>web_search<|im_middle|>## Return of functions.web_search:1\nTool response 2<|im_end|><|im_assistant|>assistant<|im_middle|><think>Think last</think>CC<|tool_calls_section_begin|><|tool_call_begin|>functions.read_file:2<|tool_call_argument_begin|>{\"args\": [{\"path\": \"src/providers/ThemeProvider.tsx\"}, {\"path\": \"src/components/Header.tsx\"}, {\"path\": \"src/components/ThemeToggle.tsx\"}, {\"path\": \"src/app/globals.css\"}, {\"path\": \"src/app/layout.tsx\"}]}<|tool_call_end|><|tool_calls_section_end|><|im_end|><|im_system|>read_file<|im_middle|>## Return of functions.read_file:2\nTool response 3<|im_end|><|im_assistant|>assistant<|im_middle|>"));
  2681. // Test template generation for regular content
  2682. test_templates(tmpls.get(), end_tokens, message_assist, tools,
  2683. "<think></think>Hello, world!\nWhat's up?",
  2684. /* expect_grammar_triggered= */ false);
  2685. // Test template generation for tool calls
  2686. test_templates(tmpls.get(), end_tokens, message_assist_call, tools,
  2687. "<think></think><|tool_calls_section_begin|><|tool_call_begin|>functions.special_function:0<|tool_call_argument_begin|>{\"arg1\": 1}<|tool_call_end|><|tool_calls_section_end|>",
  2688. /* expect_grammar_triggered= */ true,
  2689. /* test_grammar_if_triggered= */ true,
  2690. /* common_reasoning_format= */ COMMON_REASONING_FORMAT_DEEPSEEK,
  2691. /* ignore_whitespace_differences= */ true
  2692. );
  2693. // Test template generation for tools with optional parameters
  2694. test_templates(tmpls.get(), end_tokens, message_assist_call_noopt, tools,
  2695. "<think></think><|tool_calls_section_begin|><|tool_call_begin|>functions.special_function_with_opt:0<|tool_call_argument_begin|>{\"arg1\": 1}<|tool_call_end|><|tool_calls_section_end|>",
  2696. /* expect_grammar_triggered= */ true,
  2697. /* test_grammar_if_triggered= */ true,
  2698. /* common_reasoning_format= */ COMMON_REASONING_FORMAT_DEEPSEEK,
  2699. /* ignore_whitespace_differences= */ true
  2700. );
  2701. test_templates(tmpls.get(), end_tokens, message_assist_call_withopt, tools,
  2702. "<think></think><|tool_calls_section_begin|><|tool_call_begin|>functions.special_function_with_opt:0<|tool_call_argument_begin|>{\"arg1\": 1, \"arg2\": 2}<|tool_call_end|><|tool_calls_section_end|>",
  2703. /* expect_grammar_triggered= */ true,
  2704. /* test_grammar_if_triggered= */ true,
  2705. /* common_reasoning_format= */ COMMON_REASONING_FORMAT_DEEPSEEK,
  2706. /* ignore_whitespace_differences= */ true
  2707. );
  2708. }
  2709. // Test Qwen3-Coder XML format
  2710. {
  2711. // Basic XML tool call parsing
  2712. assert_msg_equals(
  2713. message_assist_call,
  2714. common_chat_parse(
  2715. "<tool_call>\n"
  2716. " <function=special_function>\n"
  2717. " <parameter=arg1>\n"
  2718. " 1\n"
  2719. " </parameter>\n"
  2720. " </function>\n"
  2721. "</tool_call>",
  2722. /* is_partial= */ false,
  2723. {COMMON_CHAT_FORMAT_QWEN3_CODER_XML}));
  2724. // Multiple parameters with different types
  2725. common_chat_msg expected_multi_param;
  2726. expected_multi_param.role = "assistant";
  2727. expected_multi_param.tool_calls = {
  2728. { "complex_function", "{\"name\":\"John Doe\",\"age\":30,\"active\":true,\"score\":95.5}", "" }
  2729. };
  2730. test_parser_with_streaming(expected_multi_param,
  2731. "<tool_call>\n"
  2732. " <function=complex_function>\n"
  2733. " <parameter=name>\n"
  2734. " John Doe\n"
  2735. " </parameter>\n"
  2736. " <parameter=age>\n"
  2737. " 30\n"
  2738. " </parameter>\n"
  2739. " <parameter=active>\n"
  2740. " true\n"
  2741. " </parameter>\n"
  2742. " <parameter=score>\n"
  2743. " 95.5\n"
  2744. " </parameter>\n"
  2745. " </function>\n"
  2746. "</tool_call>",
  2747. [&](const std::string &msg) { return common_chat_parse(msg, /* is_partial= */ true, {COMMON_CHAT_FORMAT_QWEN3_CODER_XML}); });
  2748. // Special characters and Unicode
  2749. common_chat_msg expected_special_chars;
  2750. expected_special_chars.role = "assistant";
  2751. expected_special_chars.tool_calls = {
  2752. { "unicode_function", "{\"message\":\"Hello 世界! 🌍 Special chars: @#$%^&*()\"}", "" }
  2753. };
  2754. test_parser_with_streaming(expected_special_chars,
  2755. "<tool_call>\n"
  2756. " <function=unicode_function>\n"
  2757. " <parameter=message>\n"
  2758. " Hello 世界! 🌍 Special chars: @#$%^&*()\n"
  2759. " </parameter>\n"
  2760. " </function>\n"
  2761. "</tool_call>",
  2762. [&](const std::string &msg) { return common_chat_parse(msg, /* is_partial= */ true, {COMMON_CHAT_FORMAT_QWEN3_CODER_XML}); });
  2763. // Multiline content with newlines and indentation
  2764. common_chat_msg expected_multiline;
  2765. expected_multiline.role = "assistant";
  2766. expected_multiline.tool_calls = {
  2767. { "code_function", "{\"code\":\"def hello():\\n print(\\\"Hello, World!\\\")\\n return True\"}", "" }
  2768. };
  2769. test_parser_with_streaming(expected_multiline,
  2770. "<tool_call>\n"
  2771. " <function=code_function>\n"
  2772. " <parameter=code>\n"
  2773. "def hello():\n"
  2774. " print(\"Hello, World!\")\n"
  2775. " return True\n"
  2776. " </parameter>\n"
  2777. " </function>\n"
  2778. "</tool_call>",
  2779. [&](const std::string &msg) { return common_chat_parse(msg, /* is_partial= */ true, {COMMON_CHAT_FORMAT_QWEN3_CODER_XML}); });
  2780. // JSON object as parameter value
  2781. common_chat_msg expected_json_param;
  2782. expected_json_param.role = "assistant";
  2783. expected_json_param.tool_calls = {
  2784. { "json_function", "{\"config\":{\"host\":\"localhost\",\"port\":8080,\"ssl\":false}}", "" }
  2785. };
  2786. test_parser_with_streaming(
  2787. expected_json_param,
  2788. "<tool_call>\n"
  2789. " <function=json_function>\n"
  2790. " <parameter=config>\n"
  2791. " {\"host\": \"localhost\", \"port\": 8080, \"ssl\": false}\n"
  2792. " </parameter>\n"
  2793. " </function>\n"
  2794. "</tool_call>",
  2795. [&](const std::string &msg) { return common_chat_parse(msg, /* is_partial= */ true, {COMMON_CHAT_FORMAT_QWEN3_CODER_XML}); });
  2796. // Array as parameter value
  2797. common_chat_msg expected_array_param;
  2798. expected_array_param.role = "assistant";
  2799. expected_array_param.tool_calls = {
  2800. { "array_function", "{\"items\":[\"apple\",\"banana\",\"cherry\"]}", "" }
  2801. };
  2802. test_parser_with_streaming(
  2803. expected_array_param,
  2804. "<tool_call>\n"
  2805. " <function=array_function>\n"
  2806. " <parameter=items>\n"
  2807. " [\"apple\", \"banana\", \"cherry\"]\n"
  2808. " </parameter>\n"
  2809. " </function>\n"
  2810. "</tool_call>",
  2811. [&](const std::string &msg) { return common_chat_parse(msg, /* is_partial= */ true, {COMMON_CHAT_FORMAT_QWEN3_CODER_XML}); });
  2812. // Empty parameter
  2813. common_chat_msg expected_empty_param;
  2814. expected_empty_param.role = "assistant";
  2815. expected_empty_param.tool_calls = {
  2816. { "empty_function", "{\"empty_param\":\"\"}", "" }
  2817. };
  2818. test_parser_with_streaming(
  2819. expected_empty_param,
  2820. "<tool_call>\n"
  2821. " <function=empty_function>\n"
  2822. " <parameter=empty_param>\n"
  2823. " </parameter>\n"
  2824. " </function>\n"
  2825. "</tool_call>",
  2826. [&](const std::string &msg) { return common_chat_parse(msg, /* is_partial= */ true, {COMMON_CHAT_FORMAT_QWEN3_CODER_XML}); });
  2827. // Boolean values (true/false)
  2828. common_chat_msg expected_boolean;
  2829. expected_boolean.role = "assistant";
  2830. expected_boolean.tool_calls = {
  2831. { "boolean_function", "{\"enabled\":true,\"debug\":false}", "" }
  2832. };
  2833. test_parser_with_streaming(
  2834. expected_boolean,
  2835. "<tool_call>\n"
  2836. " <function=boolean_function>\n"
  2837. " <parameter=enabled>\n"
  2838. " true\n"
  2839. " </parameter>\n"
  2840. " <parameter=debug>\n"
  2841. " false\n"
  2842. " </parameter>\n"
  2843. " </function>\n"
  2844. "</tool_call>",
  2845. [&](const std::string &msg) { return common_chat_parse(msg, /* is_partial= */ true, {COMMON_CHAT_FORMAT_QWEN3_CODER_XML}); });
  2846. // Null value
  2847. common_chat_msg expected_null;
  2848. expected_null.role = "assistant";
  2849. expected_null.tool_calls = {
  2850. { "null_function", "{\"optional_param\":null}", "" }
  2851. };
  2852. test_parser_with_streaming(
  2853. expected_null,
  2854. "<tool_call>\n"
  2855. " <function=null_function>\n"
  2856. " <parameter=optional_param>\n"
  2857. " null\n"
  2858. " </parameter>\n"
  2859. " </function>\n"
  2860. "</tool_call>",
  2861. [&](const std::string &msg) { return common_chat_parse(msg, /* is_partial= */ true, {COMMON_CHAT_FORMAT_QWEN3_CODER_XML}); });
  2862. // Negative numbers and scientific notation
  2863. common_chat_msg expected_numbers;
  2864. expected_numbers.role = "assistant";
  2865. expected_numbers.tool_calls = {
  2866. { "math_function", "{\"negative\":-42,\"decimal\":-3.14,\"scientific\":1.23e-4}", "" }
  2867. };
  2868. test_parser_with_streaming(
  2869. expected_numbers,
  2870. "<tool_call>\n"
  2871. " <function=math_function>\n"
  2872. " <parameter=negative>\n"
  2873. " -42\n"
  2874. " </parameter>\n"
  2875. " <parameter=decimal>\n"
  2876. " -3.14\n"
  2877. " </parameter>\n"
  2878. " <parameter=scientific>\n"
  2879. " 1.23e-4\n"
  2880. " </parameter>\n"
  2881. " </function>\n"
  2882. "</tool_call>",
  2883. [&](const std::string &msg) { return common_chat_parse(msg, /* is_partial= */ true, {COMMON_CHAT_FORMAT_QWEN3_CODER_XML}); });
  2884. // XML-like content in parameters (should be escaped)
  2885. common_chat_msg expected_xml_content;
  2886. expected_xml_content.role = "assistant";
  2887. expected_xml_content.tool_calls = {
  2888. { "xml_function", "{\"xml_content\":\"<root><item>value</item></root>\"}", "" }
  2889. };
  2890. test_parser_with_streaming(
  2891. expected_xml_content,
  2892. "<tool_call>\n"
  2893. " <function=xml_function>\n"
  2894. " <parameter=xml_content>\n"
  2895. " <root><item>value</item></root>\n"
  2896. " </parameter>\n"
  2897. " </function>\n"
  2898. "</tool_call>",
  2899. [&](const std::string &msg) { return common_chat_parse(msg, /* is_partial= */ true, {COMMON_CHAT_FORMAT_QWEN3_CODER_XML}); });
  2900. // Quotes and escape characters
  2901. common_chat_msg expected_quotes;
  2902. expected_quotes.role = "assistant";
  2903. expected_quotes.tool_calls = {
  2904. { "quote_function", "{\"message\":\"She said \\\"Hello!\\\" and left.\"}", "" }
  2905. };
  2906. test_parser_with_streaming(
  2907. expected_quotes,
  2908. "<tool_call>\n"
  2909. " <function=quote_function>\n"
  2910. " <parameter=message>\n"
  2911. " She said \"Hello!\" and left.\n"
  2912. " </parameter>\n"
  2913. " </function>\n"
  2914. "</tool_call>",
  2915. [&](const std::string &msg) { return common_chat_parse(msg, /* is_partial= */ true, {COMMON_CHAT_FORMAT_QWEN3_CODER_XML}); });
  2916. // Long parameter value (simplified)
  2917. std::string long_text = "This is a long text parameter that should test the parser's ability to handle larger amounts of text data.";
  2918. common_chat_msg expected_long_text;
  2919. expected_long_text.role = "assistant";
  2920. expected_long_text.tool_calls = {
  2921. { "long_function", "{\"long_text\":\"" + long_text + "\"}", "" }
  2922. };
  2923. test_parser_with_streaming(
  2924. expected_long_text,
  2925. "<tool_call>\n"
  2926. " <function=long_function>\n"
  2927. " <parameter=long_text>\n"
  2928. " " + long_text + "\n"
  2929. " </parameter>\n"
  2930. " </function>\n"
  2931. "</tool_call>",
  2932. [&](const std::string &msg) { return common_chat_parse(msg, /* is_partial= */ true, {COMMON_CHAT_FORMAT_QWEN3_CODER_XML}); });
  2933. // Mixed content with text before and after tool call
  2934. common_chat_msg expected_mixed_content;
  2935. expected_mixed_content.role = "assistant";
  2936. expected_mixed_content.content = "I'll help you search for products. ";
  2937. expected_mixed_content.tool_calls = {
  2938. { "search_function", "{\"query\":\"laptops\"}", "" }
  2939. };
  2940. test_parser_with_streaming(
  2941. expected_mixed_content,
  2942. "I'll help you search for products. <tool_call>\n"
  2943. " <function=search_function>\n"
  2944. " <parameter=query>\n"
  2945. " laptops\n"
  2946. " </parameter>\n"
  2947. " </function>\n"
  2948. "</tool_call>",
  2949. [&](const std::string &msg) { return common_chat_parse(msg, /* is_partial= */ true, {COMMON_CHAT_FORMAT_QWEN3_CODER_XML}); });
  2950. // Compact format (no extra whitespace)
  2951. common_chat_msg expected_compact;
  2952. expected_compact.role = "assistant";
  2953. expected_compact.tool_calls = {
  2954. { "compact_function", "{\"param\":\"value\"}", "" }
  2955. };
  2956. test_parser_with_streaming(
  2957. expected_compact,
  2958. "<tool_call><function=compact_function><parameter=param>value</parameter></function></tool_call>",
  2959. [&](const std::string &msg) { return common_chat_parse(msg, /* is_partial= */ true, {COMMON_CHAT_FORMAT_QWEN3_CODER_XML}); });
  2960. // Function name with underscores and numbers
  2961. common_chat_msg expected_complex_name;
  2962. expected_complex_name.role = "assistant";
  2963. expected_complex_name.tool_calls = {
  2964. { "get_user_data_v2", "{\"user_id\":12345}", "" }
  2965. };
  2966. test_parser_with_streaming(
  2967. expected_complex_name,
  2968. "<tool_call>\n"
  2969. " <function=get_user_data_v2>\n"
  2970. " <parameter=user_id>\n"
  2971. " 12345\n"
  2972. " </parameter>\n"
  2973. " </function>\n"
  2974. "</tool_call>",
  2975. [&](const std::string &msg) { return common_chat_parse(msg, /* is_partial= */ true, {COMMON_CHAT_FORMAT_QWEN3_CODER_XML}); });
  2976. // Parameter names with underscores and numbers
  2977. common_chat_msg expected_complex_params;
  2978. expected_complex_params.role = "assistant";
  2979. expected_complex_params.tool_calls = {
  2980. { "test_function", "{\"param_1\":\"value1\",\"param_2_name\":\"value2\",\"param3\":123}", "" }
  2981. };
  2982. test_parser_with_streaming(
  2983. expected_complex_params,
  2984. "<tool_call>\n"
  2985. " <function=test_function>\n"
  2986. " <parameter=param_1>\n"
  2987. " value1\n"
  2988. " </parameter>\n"
  2989. " <parameter=param_2_name>\n"
  2990. " value2\n"
  2991. " </parameter>\n"
  2992. " <parameter=param3>\n"
  2993. " 123\n"
  2994. " </parameter>\n"
  2995. " </function>\n"
  2996. "</tool_call>",
  2997. [&](const std::string &msg) { return common_chat_parse(msg, /* is_partial= */ true, {COMMON_CHAT_FORMAT_QWEN3_CODER_XML}); });
  2998. // Very deeply nested XML content in parameter
  2999. common_chat_msg expected_deep_xml;
  3000. expected_deep_xml.role = "assistant";
  3001. expected_deep_xml.tool_calls = {
  3002. { "xml_parser", "{\"xml\":\"<root><level1><level2><level3>deep content</level3></level2></level1></root>\"}", "" }
  3003. };
  3004. test_parser_with_streaming(
  3005. expected_deep_xml,
  3006. "<tool_call>\n"
  3007. " <function=xml_parser>\n"
  3008. " <parameter=xml>\n"
  3009. " <root><level1><level2><level3>deep content</level3></level2></level1></root>\n"
  3010. " </parameter>\n"
  3011. " </function>\n"
  3012. "</tool_call>",
  3013. [&](const std::string &msg) { return common_chat_parse(msg, /* is_partial= */ true, {COMMON_CHAT_FORMAT_QWEN3_CODER_XML}); });
  3014. // Parameter with only whitespace
  3015. common_chat_msg expected_whitespace_param;
  3016. expected_whitespace_param.role = "assistant";
  3017. expected_whitespace_param.tool_calls = {
  3018. { "whitespace_function", "{\"spaces\":\"\"}", "" }
  3019. };
  3020. test_parser_with_streaming(
  3021. expected_whitespace_param,
  3022. "<tool_call>\n"
  3023. " <function=whitespace_function>\n"
  3024. " <parameter=spaces>\n"
  3025. " \n"
  3026. " </parameter>\n"
  3027. " </function>\n"
  3028. "</tool_call>",
  3029. [&](const std::string &msg) { return common_chat_parse(msg, /* is_partial= */ true, {COMMON_CHAT_FORMAT_QWEN3_CODER_XML}); });
  3030. // Parameter with tabs and mixed whitespace
  3031. common_chat_msg expected_mixed_whitespace;
  3032. expected_mixed_whitespace.role = "assistant";
  3033. expected_mixed_whitespace.tool_calls = {
  3034. { "tab_function", "{\"content\":\"line1\\n\\tindented line\\n spaces\"}", "" }
  3035. };
  3036. test_parser_with_streaming(
  3037. expected_mixed_whitespace,
  3038. "<tool_call>\n"
  3039. " <function=tab_function>\n"
  3040. " <parameter=content>\n"
  3041. "line1\n"
  3042. "\tindented line\n"
  3043. " spaces\n"
  3044. " </parameter>\n"
  3045. " </function>\n"
  3046. "</tool_call>",
  3047. [&](const std::string &msg) { return common_chat_parse(msg, /* is_partial= */ true, {COMMON_CHAT_FORMAT_QWEN3_CODER_XML}); });
  3048. // Control characters and special Unicode
  3049. common_chat_msg expected_control_chars;
  3050. expected_control_chars.role = "assistant";
  3051. expected_control_chars.tool_calls = {
  3052. { "control_function", "{\"text\":\"Line1\\nLine2\\tTabbed\\rCarriage return\"}", "" }
  3053. };
  3054. test_parser_with_streaming(
  3055. expected_control_chars,
  3056. "<tool_call>\n"
  3057. " <function=control_function>\n"
  3058. " <parameter=text>\n"
  3059. "Line1\nLine2\tTabbed\rCarriage return\n"
  3060. " </parameter>\n"
  3061. " </function>\n"
  3062. "</tool_call>",
  3063. [&](const std::string &msg) { return common_chat_parse(msg, /* is_partial= */ true, {COMMON_CHAT_FORMAT_QWEN3_CODER_XML}); });
  3064. // Emoji and extended Unicode characters
  3065. common_chat_msg expected_emoji;
  3066. expected_emoji.role = "assistant";
  3067. expected_emoji.tool_calls = {
  3068. { "emoji_function", "{\"message\":\"Hello! 👋 🌟 🚀 Testing emojis: 😀😃😄😁 and symbols: ∑∏∆∇\"}", "" }
  3069. };
  3070. test_parser_with_streaming(
  3071. expected_emoji,
  3072. "<tool_call>\n"
  3073. " <function=emoji_function>\n"
  3074. " <parameter=message>\n"
  3075. " Hello! 👋 🌟 🚀 Testing emojis: 😀😃😄😁 and symbols: ∑∏∆∇\n"
  3076. " </parameter>\n"
  3077. " </function>\n"
  3078. "</tool_call>",
  3079. [&](const std::string &msg) { return common_chat_parse(msg, /* is_partial= */ true, {COMMON_CHAT_FORMAT_QWEN3_CODER_XML}); });
  3080. // Mathematical expressions and formulas
  3081. common_chat_msg expected_math;
  3082. expected_math.role = "assistant";
  3083. expected_math.tool_calls = {
  3084. { "math_function", "{\"formula\":\"E = mc² and ∫f(x)dx = F(x) + C\"}", "" }
  3085. };
  3086. test_parser_with_streaming(
  3087. expected_math,
  3088. "<tool_call>\n"
  3089. " <function=math_function>\n"
  3090. " <parameter=formula>\n"
  3091. " E = mc² and ∫f(x)dx = F(x) + C\n"
  3092. " </parameter>\n"
  3093. " </function>\n"
  3094. "</tool_call>",
  3095. [&](const std::string &msg) { return common_chat_parse(msg, /* is_partial= */ true, {COMMON_CHAT_FORMAT_QWEN3_CODER_XML}); });
  3096. // SQL injection-like content (should be safely escaped)
  3097. common_chat_msg expected_sql;
  3098. expected_sql.role = "assistant";
  3099. expected_sql.tool_calls = {
  3100. { "sql_function", "{\"query\":\"SELECT * FROM users WHERE id = 1; DROP TABLE users; --\"}", "" }
  3101. };
  3102. test_parser_with_streaming(
  3103. expected_sql,
  3104. "<tool_call>\n"
  3105. " <function=sql_function>\n"
  3106. " <parameter=query>\n"
  3107. " SELECT * FROM users WHERE id = 1; DROP TABLE users; --\n"
  3108. " </parameter>\n"
  3109. " </function>\n"
  3110. "</tool_call>",
  3111. [&](const std::string &msg) { return common_chat_parse(msg, /* is_partial= */ true, {COMMON_CHAT_FORMAT_QWEN3_CODER_XML}); });
  3112. // HTML/XML injection content
  3113. common_chat_msg expected_html;
  3114. expected_html.role = "assistant";
  3115. expected_html.tool_calls = {
  3116. { "html_function", "{\"content\":\"<script>alert('xss')</script><img src=x onerror=alert(1)>\"}", "" }
  3117. };
  3118. test_parser_with_streaming(
  3119. expected_html,
  3120. "<tool_call>\n"
  3121. " <function=html_function>\n"
  3122. " <parameter=content>\n"
  3123. " <script>alert('xss')</script><img src=x onerror=alert(1)>\n"
  3124. " </parameter>\n"
  3125. " </function>\n"
  3126. "</tool_call>",
  3127. [&](const std::string &msg) { return common_chat_parse(msg, /* is_partial= */ true, {COMMON_CHAT_FORMAT_QWEN3_CODER_XML}); });
  3128. // Binary-like content (base64)
  3129. common_chat_msg expected_binary;
  3130. expected_binary.role = "assistant";
  3131. expected_binary.tool_calls = {
  3132. { "binary_function", "{\"data\":\"SGVsbG8gV29ybGQhIFRoaXMgaXMgYmFzZTY0IGVuY29kZWQgdGV4dC4=\"}", "" }
  3133. };
  3134. test_parser_with_streaming(
  3135. expected_binary,
  3136. "<tool_call>\n"
  3137. " <function=binary_function>\n"
  3138. " <parameter=data>\n"
  3139. " SGVsbG8gV29ybGQhIFRoaXMgaXMgYmFzZTY0IGVuY29kZWQgdGV4dC4=\n"
  3140. " </parameter>\n"
  3141. " </function>\n"
  3142. "</tool_call>",
  3143. [&](const std::string &msg) { return common_chat_parse(msg, /* is_partial= */ true, {COMMON_CHAT_FORMAT_QWEN3_CODER_XML}); });
  3144. // Very large numbers (should be parsed as scientific notation)
  3145. common_chat_msg expected_large_numbers;
  3146. expected_large_numbers.role = "assistant";
  3147. expected_large_numbers.tool_calls = {
  3148. { "number_function", "{\"big_int\":1e+60}", "" } // Large number becomes scientific notation
  3149. };
  3150. test_parser_with_streaming(
  3151. expected_large_numbers,
  3152. "<tool_call>\n"
  3153. " <function=number_function>\n"
  3154. " <parameter=big_int>\n"
  3155. " 999999999999999999999999999999999999999999999999999999999999\n"
  3156. " </parameter>\n"
  3157. " </function>\n"
  3158. "</tool_call>",
  3159. [&](const std::string &msg) { return common_chat_parse(msg, /* is_partial= */ true, {COMMON_CHAT_FORMAT_QWEN3_CODER_XML}); });
  3160. }
  3161. {
  3162. // Qwen3-Coder template
  3163. auto tmpls = read_templates("models/templates/Qwen3-Coder.jinja");
  3164. common_chat_templates_inputs inputs;
  3165. inputs.messages = { message_user };
  3166. common_chat_tool qwen_union_tool {
  3167. /* .name = */ "qwen_union",
  3168. /* .description = */ "Test tool for union/anyOf handling",
  3169. /* .parameters = */ R"({
  3170. "type": "object",
  3171. "properties": {
  3172. "priority": { "type": ["number", "null"] },
  3173. "maybe_text": { "anyOf": [ { "type": "string" } ] },
  3174. "config": { "anyOf": [ { "type": "object" }, { "type": "null" } ] }
  3175. },
  3176. "required": []
  3177. })",
  3178. };
  3179. inputs.tools = { qwen_union_tool };
  3180. auto params = common_chat_templates_apply(tmpls.get(), inputs);
  3181. assert_equals(COMMON_CHAT_FORMAT_QWEN3_CODER_XML, params.format);
  3182. assert_equals(false, params.grammar.empty());
  3183. // Grammar should compile successfully
  3184. auto grammar = build_grammar(params.grammar);
  3185. GGML_ASSERT(grammar && "Failed to build Qwen3-Coder grammar with union types");
  3186. }
  3187. }
  3188. static void test_msg_diffs_compute() {
  3189. printf("[%s]\n", __func__);
  3190. {
  3191. common_chat_msg msg1;
  3192. common_chat_msg msg2;
  3193. msg2.content = "Hello, world!";
  3194. common_chat_msg_diff diff;
  3195. diff.content_delta = "Hello, world!";
  3196. assert_equals(
  3197. {diff},
  3198. common_chat_msg_diff::compute_diffs(msg1, msg2));
  3199. }
  3200. {
  3201. common_chat_msg msg1;
  3202. msg1.content = "Hello,";
  3203. common_chat_msg msg2;
  3204. msg2.content = "Hello, world!";
  3205. common_chat_msg_diff diff;
  3206. diff.content_delta = " world!";
  3207. assert_equals(
  3208. {diff},
  3209. common_chat_msg_diff::compute_diffs(msg1, msg2));
  3210. }
  3211. {
  3212. common_chat_msg msg0;
  3213. common_chat_msg msg1;
  3214. msg1.tool_calls = { { "special_function", "{\"ar", /* .id = */ "123" } };
  3215. common_chat_msg msg2;
  3216. msg2.tool_calls = { { "special_function", "{\"arg1\": 1}", /* .id = */ "123" } };
  3217. common_chat_msg_diff diff01;
  3218. diff01.tool_call_index = 0;
  3219. diff01.tool_call_delta.name = "special_function";
  3220. diff01.tool_call_delta.id = "123";
  3221. diff01.tool_call_delta.arguments = "{\"ar";
  3222. assert_equals(
  3223. {diff01},
  3224. common_chat_msg_diff::compute_diffs(msg0, msg1));
  3225. common_chat_msg_diff diff12;
  3226. diff12.tool_call_index = 0;
  3227. // Note: neither id nor name change here.
  3228. diff12.tool_call_delta.arguments = "g1\": 1}";
  3229. assert_equals(
  3230. {diff12},
  3231. common_chat_msg_diff::compute_diffs(msg1, msg2));
  3232. }
  3233. {
  3234. common_chat_msg msg0;
  3235. common_chat_msg msg2;
  3236. msg2.tool_calls = {
  3237. { "f1", "{\"arg1\": 1}", /* .id = */ "123" },
  3238. { "f2", "{\"arg2\": 2}", /* .id = */ "222" },
  3239. };
  3240. common_chat_msg_diff diff1;
  3241. diff1.tool_call_index = 0;
  3242. diff1.tool_call_delta.name = "f1";
  3243. diff1.tool_call_delta.id = "123";
  3244. diff1.tool_call_delta.arguments = "{\"arg1\": 1}";
  3245. common_chat_msg_diff diff2;
  3246. diff2.tool_call_index = 1;
  3247. diff2.tool_call_delta.name = "f2";
  3248. diff2.tool_call_delta.id = "222";
  3249. diff2.tool_call_delta.arguments = "{\"arg2\": 2}";
  3250. assert_equals(
  3251. {diff1, diff2},
  3252. common_chat_msg_diff::compute_diffs(msg0, msg2));
  3253. }
  3254. }
  3255. int main(int argc, char ** argv) {
  3256. common_log_set_verbosity_thold(999);
  3257. // try {
  3258. #ifndef _WIN32
  3259. if (argc > 1) {
  3260. common_chat_templates_inputs inputs;
  3261. common_chat_msg msg;
  3262. msg.role = "user";
  3263. msg.content = "Hey";
  3264. inputs.messages = {msg};
  3265. inputs.tools = { special_function_tool };
  3266. std::cout << "| Template | Format |\n";
  3267. std::cout << "|----------|--------|\n";
  3268. for (int i = 1; i < argc; i++) {
  3269. try {
  3270. std::string path = argv[i];
  3271. if (path.rfind(".jinja") != path.size() - 6) {
  3272. std::cerr << "Skipping non-jinja file: " << path << '\n';
  3273. continue;
  3274. }
  3275. auto tmpls = read_templates(path);
  3276. auto parts = string_split(path, "/");
  3277. auto name = parts[parts.size() - 1];
  3278. auto format = common_chat_format_name(common_chat_templates_apply(tmpls.get(), inputs).format);
  3279. std::cout << "| " << name << " | " << format << " |\n";
  3280. } catch (const std::exception & e) {
  3281. std::cerr << "Failed to process " << argv[i] << ": " << e.what() << '\n';
  3282. }
  3283. }
  3284. } else
  3285. #endif
  3286. {
  3287. test_msg_diffs_compute();
  3288. test_msgs_oaicompat_json_conversion();
  3289. test_tools_oaicompat_json_conversion();
  3290. test_template_output_parsers();
  3291. std::cout << "\n[chat] All tests passed!" << '\n';
  3292. }
  3293. return 0;
  3294. // } catch (const std::exception & e) {
  3295. // std::cerr << "Error: " << e.what() << '\n';
  3296. // return 1;
  3297. // }
  3298. }