@@ -1331,62 +1331,235 @@ static void common_chat_parse_gpt_oss(common_chat_msg_parser & builder) {
1331
1331
1332
1332
static common_chat_params common_chat_params_init_glm_4_5 (const common_chat_template & tmpl, const struct templates_params & inputs) {
1333
1333
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;
1335
1364
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
+
1336
1375
return data;
1337
1376
}
1338
1377
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
+ }
1346
1390
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
+ };
1352
1425
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>" );
1357
1429
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
+ }
1360
1456
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>" );
1361
1462
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);
1367
1481
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
+
1371
1498
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__);
1372
1543
} 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__ );
1374
1545
}
1546
+
1547
+ handle_tool_call_end (builder, tool_call_end);
1375
1548
}
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 ;
1384
1554
}
1385
1555
}
1386
1556
1387
- builder.add_content (builder.consume_rest ());
1557
+ if (builder.pos () < builder.input ().size ()) {
1558
+ builder.add_content (builder.consume_rest ());
1559
+ }
1388
1560
}
1389
1561
1562
+
1390
1563
static common_chat_params common_chat_params_init_firefunction_v2 (const common_chat_template & tmpl, const struct templates_params & inputs) {
1391
1564
LOG_DBG (" %s\n " , __func__);
1392
1565
common_chat_params data;
0 commit comments