@@ -1331,62 +1331,235 @@ static void common_chat_parse_gpt_oss(common_chat_msg_parser & builder) {
13311331
13321332static common_chat_params common_chat_params_init_glm_4_5 (const common_chat_template & tmpl, const struct templates_params & inputs) {
13331333 common_chat_params data;
1334- data.prompt = apply (tmpl, inputs);
1334+
1335+ // Bypass minja's tool processing entirely
1336+ minja::chat_template_inputs tmpl_inputs;
1337+ tmpl_inputs.messages = inputs.messages ;
1338+ tmpl_inputs.tools = inputs.tools .empty () ? json () : inputs.tools ;
1339+ tmpl_inputs.add_generation_prompt = inputs.add_generation_prompt ;
1340+ tmpl_inputs.extra_context = inputs.extra_context ;
1341+ tmpl_inputs.now = std::chrono::system_clock::now ();
1342+
1343+ // Force XML mode through context
1344+ tmpl_inputs.extra_context [" xml_tool_format" ] = true ;
1345+ tmpl_inputs.extra_context [" native_tool_support" ] = true ;
1346+
1347+ minja::chat_template_options opts;
1348+ opts.apply_polyfills = false ; // Hard disable all polyfills
1349+ opts.use_bos_token = inputs.add_bos ;
1350+ opts.use_eos_token = inputs.add_eos ;
1351+
1352+ // Single apply call
1353+ auto result = tmpl.apply (tmpl_inputs, opts);
1354+
1355+ // Manual BOS/EOS handling (since you disabled automatic handling)
1356+ if (inputs.add_bos && string_starts_with (result, tmpl.bos_token ())) {
1357+ result = result.substr (tmpl.bos_token ().size ());
1358+ }
1359+ if (inputs.add_eos && string_ends_with (result, tmpl.eos_token ())) {
1360+ result = result.substr (0 , result.size () - tmpl.eos_token ().size ());
1361+ }
1362+
1363+ data.prompt = result;
13351364 data.format = COMMON_CHAT_FORMAT_GLM_4_5;
1365+ data.preserved_tokens = {
1366+ " <|system|>" , " <|assistant|>" , " <|observation|>" ,
1367+ " <tool_call>" , " </tool_call>" , " <arg_key>" , " </arg_key>" ,
1368+ " <arg_value>" , " </arg_value>" , " <think>" , " </think>" ,
1369+ " <tool_response>" , " </tool_response>" ,
1370+ };
1371+
1372+ // Store tools schema for type-aware parsing
1373+ data.tools_schema = inputs.tools ;
1374+
13361375 return data;
13371376}
13381377
1339- static void common_chat_parse_glm_4_5 (common_chat_msg_parser & builder) {
1340- builder.consume_spaces ();
1341- builder.try_parse_reasoning (" <think>" , " </think>" );
1342- if (!builder.syntax ().parse_tool_calls ) {
1343- builder.add_content (builder.consume_rest ());
1344- return ;
1345- }
1378+ static void debug_print_raw_input (const std::string& input) {
1379+ LOG_INF (" === GLM-4.5 RAW INPUT ===\n " );
1380+ for (size_t i = 0 ; i < input.size (); ++i) {
1381+ char ch = input[i];
1382+ if (ch == ' \n ' ) LOG_INF (" \\ n" );
1383+ else if (ch == ' \t ' ) LOG_INF (" \\ t" );
1384+ else if (ch == ' \r ' ) LOG_INF (" \\ r" );
1385+ else if (std::isspace (ch)) LOG_INF (" ·" ); // visible space
1386+ else LOG_INF (" %c" , ch);
1387+ }
1388+ LOG_INF (" \n === END RAW INPUT ===\n " );
1389+ }
13461390
1347- // GLM 4.5 uses format: <tool_call>function_name\n<arg_key>key</arg_key>\n<arg_value>value</arg_value>\n</tool_call>
1348- static const common_regex tool_call_start (" <tool_call>([^\n <]+)" );
1349- static const common_regex arg_key_regex (" <arg_key>([^<]+)</arg_key>" );
1350- static const common_regex arg_value_regex (" <arg_value>([\\ s\\ S]*?)</arg_value>" );
1351- static const common_regex tool_call_end (" </tool_call>" );
1391+ static void debug_print_parse_position (const std::string& input, size_t pos, const char * context) {
1392+ size_t start = (pos < 50 ) ? 0 : pos - 50 ;
1393+ size_t end = std::min (input.size (), pos + 50 );
1394+ LOG_INF (" === %s at position %zu ===\n " , context, pos);
1395+ for (size_t i = start; i < end; ++i) {
1396+ if (i == pos) LOG_INF (" >>>" );
1397+ char ch = input[i];
1398+ if (ch == ' \n ' ) LOG_INF (" \\ n" );
1399+ else if (ch == ' \t ' ) LOG_INF (" \\ t" );
1400+ else LOG_INF (" %c" , ch);
1401+ if (i == pos) LOG_INF (" <<<" );
1402+ }
1403+ LOG_INF (" \n === END POSITION ===\n " );
1404+ }
1405+
1406+ static void common_chat_parse_glm_4_5 (common_chat_msg_parser & builder) {
1407+ debug_print_raw_input (builder.input ());
1408+
1409+ // Helper function to get expected type from tool schema
1410+ auto get_expected_type = [&](const std::string& tool_name, const std::string& param_name) -> std::string {
1411+ // Access tools schema from builder syntax
1412+ const auto & tools_schema = builder.syntax ().tools_schema ;
1413+ if (tools_schema.is_array ()) {
1414+ for (const auto & tool : tools_schema) {
1415+ if (tool.contains (" function" ) && tool[" function" ][" name" ] == tool_name) {
1416+ auto params = tool[" function" ][" parameters" ];
1417+ if (params.contains (" properties" ) && params[" properties" ].contains (param_name)) {
1418+ return params[" properties" ][param_name].value (" type" , " string" );
1419+ }
1420+ }
1421+ }
1422+ }
1423+ return " string" ; // Default fallback
1424+ };
13521425
1353- while (auto res = builder.try_find_regex (tool_call_start)) {
1354- // Move to the start of the tool call and consume it
1355- builder.move_to (res->groups [0 ].begin );
1356- builder.consume_regex (tool_call_start);
1426+ auto handle_tool_call_end = [&] (common_chat_msg_parser & builder, auto end_pos) {
1427+ builder.move_to (end_pos);
1428+ builder.consume_literal (" </tool_call>" );
13571429
1358- std::string function_name = builder.str (res->groups [1 ]);
1359- json arguments = json::object ();
1430+ size_t obs_pos = builder.input ().find (" <|observation|>" , builder.pos ());
1431+ if (obs_pos != std::string::npos) {
1432+ if (obs_pos > builder.pos ()) {
1433+ std::string content = builder.input ().substr (builder.pos (), obs_pos - builder.pos ());
1434+ builder.add_content (content);
1435+ }
1436+
1437+ builder.move_to (obs_pos);
1438+ builder.consume_literal (" <|observation|>" );
1439+ } else {
1440+ std::string remaining = builder.consume_rest ();
1441+ if (!remaining.empty ()) builder.add_content (remaining);
1442+ }
1443+ };
1444+
1445+ builder.consume_spaces ();
1446+
1447+ builder.try_parse_reasoning (" <think>" , " </think>" );
1448+
1449+ size_t curr_pos = builder.pos ();
1450+ while (builder.input ().find (" <tool_call>" , builder.pos ()) != std::string::npos) {
1451+ size_t tool_call_start = builder.input ().find (" <tool_call>" , builder.pos ());
1452+ if (tool_call_start > builder.pos ()) {
1453+ std::string content = builder.input ().substr (builder.pos (), tool_call_start - builder.pos ());
1454+ builder.add_content (content);
1455+ }
13601456
1457+ size_t tool_call_end = builder.input ().find (" </tool_call>" , tool_call_start);
1458+ if (tool_call_end == std::string::npos) return ;
1459+
1460+ builder.move_to (tool_call_start);
1461+ builder.consume_literal (" <tool_call>" );
13611462 builder.consume_spaces ();
1362-
1363- // Parse all arg_key/arg_value pairs
1364- while (auto key_res = builder.try_consume_regex (arg_key_regex)) {
1365- std::string key = builder.str (key_res->groups [1 ]);
1366- builder.consume_spaces ();
1463+
1464+ size_t arg_key_start = builder.input ().find (" <arg_key>" , tool_call_start);
1465+ if (arg_key_start == std::string::npos) {
1466+ std::string function_content = builder.input ().substr (builder.pos (), tool_call_end - builder.pos ());
1467+ std::string function_name = string_strip (function_content);
1468+
1469+ if (!builder.add_tool_call (function_name, " " , " {}" )) {
1470+ LOG_INF (" %s: failed to add tool call\n " , __func__);
1471+ }
1472+
1473+ handle_tool_call_end (builder, tool_call_end);
1474+
1475+ } else {
1476+ std::string function_content = builder.input ().substr (builder.pos (), arg_key_start - builder.pos ());
1477+ std::string function_name = string_strip (function_content);
1478+
1479+ json args_json = json::object ();
1480+ builder.move_to (arg_key_start);
13671481
1368- if (auto value_res = builder.try_consume_regex (arg_value_regex)) {
1369- std::string value = builder.str (value_res->groups [1 ]);
1370- arguments[key] = value;
1482+ while (builder.pos () < tool_call_end) {
1483+ if (!builder.try_consume_literal (" <arg_key>" )) {
1484+ builder.consume_spaces ();
1485+ if (!builder.try_consume_literal (" <arg_key>" )) {
1486+ break ;
1487+ }
1488+ }
1489+
1490+ auto key_close = builder.try_find_literal (" </arg_key>" );
1491+ if (!key_close || key_close->groups [0 ].end > tool_call_end) {
1492+ throw common_chat_msg_partial_exception (" incomplete tool call" );
1493+ return ;
1494+ }
1495+
1496+ std::string key = string_strip (key_close->prelude );
1497+
13711498 builder.consume_spaces ();
1499+
1500+ if (!builder.try_consume_literal (" <arg_value>" )) {
1501+ throw common_chat_msg_partial_exception (" incomplete tool call" );
1502+ return ;
1503+ }
1504+
1505+ auto value_close = builder.try_find_literal (" </arg_value>" );
1506+ if (!value_close || value_close->groups [0 ].end > tool_call_end) {
1507+ throw common_chat_msg_partial_exception (" incomplete tool call" );
1508+ return ;
1509+ }
1510+
1511+ std::string value = string_strip (value_close->prelude );
1512+
1513+ // Schema-aware type conversion
1514+ std::string expected_type = get_expected_type (function_name, key);
1515+ json parsed_value;
1516+
1517+ if (expected_type == " integer" || expected_type == " number" ) {
1518+ try {
1519+ parsed_value = std::stod (value); // or std::stoi for integers
1520+ } catch (...) {
1521+ parsed_value = value; // Fallback to string
1522+ }
1523+ } else if (expected_type == " boolean" ) {
1524+ parsed_value = (value == " true" );
1525+ } else if (expected_type == " array" || expected_type == " object" ) {
1526+ try {
1527+ parsed_value = json::parse (value);
1528+ } catch (...) {
1529+ parsed_value = value;
1530+ }
1531+ } else {
1532+ // Default to string
1533+ parsed_value = value;
1534+ }
1535+
1536+ args_json[key] = parsed_value;
1537+
1538+ builder.consume_spaces ();
1539+ }
1540+
1541+ if (!builder.add_tool_call (function_name, " " , args_json.dump ())) {
1542+ LOG_INF (" %s: failed to add tool call with arguments\n " , __func__);
13721543 } else {
1373- throw common_chat_msg_partial_exception ( " Expected <arg_value> after <arg_key> " );
1544+ LOG_INF ( " %s: successfully added tool call with arguments \n " , __func__ );
13741545 }
1546+
1547+ handle_tool_call_end (builder, tool_call_end);
13751548 }
1376-
1377- // Consume closing tag
1378- builder.consume_regex (tool_call_end);
1379- builder.consume_spaces ();
1380-
1381- // Add the parsed tool call
1382- if (!builder.add_tool_call (function_name, " " , arguments.dump ())) {
1383- throw common_chat_msg_partial_exception (" Failed to add GLM tool call" );
1549+
1550+ if (curr_pos == builder.pos ()) {
1551+ // No progress made, avoid infinite loop
1552+ LOG_INF (" %s: no progress in parsing, stopping to avoid infinite loop\n " , __func__);
1553+ break ;
13841554 }
13851555 }
13861556
1387- builder.add_content (builder.consume_rest ());
1557+ if (builder.pos () < builder.input ().size ()) {
1558+ builder.add_content (builder.consume_rest ());
1559+ }
13881560}
13891561
1562+
13901563static common_chat_params common_chat_params_init_firefunction_v2 (const common_chat_template & tmpl, const struct templates_params & inputs) {
13911564 LOG_DBG (" %s\n " , __func__);
13921565 common_chat_params data;
0 commit comments