@@ -1342,24 +1342,60 @@ static common_chat_params common_chat_params_init_gpt_oss(const common_chat_temp
1342
1342
if (inputs.tools .is_array () && !inputs.tools .empty ()) {
1343
1343
data.grammar_lazy = inputs.tool_choice != COMMON_CHAT_TOOL_CHOICE_REQUIRED;
1344
1344
data.grammar = build_grammar ([&](const common_grammar_builder & builder) {
1345
- std::vector<std::string> tool_rules;
1345
+ // tool calls can appear in commentary or analysis channels
1346
+ auto channel = builder.add_rule (" channel" , " \" <|channel|>\" ( \" commentary\" | \" analysis\" )" );
1347
+
1348
+ std::vector<std::string> tool_rules_recipient_in_role;
1349
+ std::vector<std::string> tool_rules_recipient_in_channel;
1346
1350
foreach_function (inputs.tools , [&](const json & tool) {
1347
1351
const auto & function = tool.at (" function" );
1348
1352
std::string name = function.at (" name" );
1349
1353
auto parameters = function.at (" parameters" );
1350
1354
builder.resolve_refs (parameters);
1351
1355
1352
- tool_rules.push_back (builder.add_rule (name + " -call" ,
1353
- " \" " + name + " \" " + " space \" <|constrain|>\" ? \" json\" space \" <|message|>\" " + builder.add_schema (name + " -args" , parameters)
1354
- ));
1356
+ tool_rules_recipient_in_role.push_back (
1357
+ builder.add_rule (name + " -call" ,
1358
+ " \" " + name + " \" " + channel + " \" <|constrain|>json\" ? \" <|message|>\" " +
1359
+ builder.add_schema (name + " -args" , parameters)
1360
+ )
1361
+ );
1362
+
1363
+ tool_rules_recipient_in_channel.push_back (
1364
+ builder.add_rule (name + " -call" ,
1365
+ " \" " + name + " \" " + " \" <|constrain|>json\" ? \" <|message|>\" " +
1366
+ builder.add_schema (name + " -args" , parameters)
1367
+ )
1368
+ );
1355
1369
});
1356
1370
1357
- auto tool_call = builder.add_rule (" tool_call" , string_join (tool_rules, " | " ));
1358
- builder.add_rule (" root" , " \" <|channel|>commentary to=functions.\" " + tool_call);
1371
+ auto recipient_in_role = builder.add_rule (" recipient_in_role" ,
1372
+ " \" <|start|>assistant\" ? \" to=functions.\" " +
1373
+ string_join (tool_rules_recipient_in_role, " | " )
1374
+ );
1359
1375
1376
+ auto recipient_in_channel = builder.add_rule (" recipient_in_channel" ,
1377
+ channel + " \" to=functions.\" " +
1378
+ string_join (tool_rules_recipient_in_channel, " | " )
1379
+ );
1380
+
1381
+ builder.add_rule (" root" , recipient_in_role + " | " + recipient_in_channel);
1382
+
1383
+ // Trigger on tool calls that appear in the commentary channel
1360
1384
data.grammar_triggers .push_back ({
1361
1385
COMMON_GRAMMAR_TRIGGER_TYPE_PATTERN,
1362
- " <\\ |channel\\ |>commentary to"
1386
+ " <\\ |channel\\ |>(commentary|analysis) to"
1387
+ });
1388
+
1389
+ // Trigger tool calls that appear in the role section, either at the
1390
+ // start or in the middle.
1391
+ data.grammar_triggers .push_back ({
1392
+ COMMON_GRAMMAR_TRIGGER_TYPE_PATTERN_FULL,
1393
+ " ^ to"
1394
+ });
1395
+
1396
+ data.grammar_triggers .push_back ({
1397
+ COMMON_GRAMMAR_TRIGGER_TYPE_PATTERN,
1398
+ " <\\ |start\\ |>assistant to"
1363
1399
});
1364
1400
1365
1401
data.preserved_tokens = {
@@ -1377,12 +1413,12 @@ static common_chat_params common_chat_params_init_gpt_oss(const common_chat_temp
1377
1413
static void common_chat_parse_gpt_oss (common_chat_msg_parser & builder) {
1378
1414
static const common_regex message_regex (" <\\ |message\\ |>" );
1379
1415
static const common_regex channel_regex (" <\\ |channel\\ |>(final|analysis|commentary)" );
1416
+ static const common_regex tool_call_channel_regex (" <\\ |channel\\ |>(commentary|analysis)" );
1380
1417
static const common_regex start_regex (" <\\ |start\\ |>assistant" );
1381
1418
static const common_regex end_regex (" <\\ |end\\ |>" );
1382
1419
static const common_regex to_regex (" to=" );
1383
- static const common_regex user_tool_call_regex (
1384
- " functions\\ .([a-zA-Z_][a-zA-Z0-9_]*)\\ s*(?:(?:<\\ |constrain\\ |>)?([a-zA-Z]+))?\\ s*<\\ |message\\ |>"
1385
- );
1420
+ static const common_regex function_regex (" functions\\ .([a-zA-Z_][a-zA-Z0-9_]*)" );
1421
+ static const common_regex user_tool_call_regex (" (?: <\\ |constrain\\ |>([a-zA-Z]+))?<\\ |message\\ |>" );
1386
1422
static const common_regex builtin_tool_call_regex (" (?:browser|python)[\\ s\\ S]*<\\ |message\\ |>" );
1387
1423
1388
1424
// Save the start of the message so we can roll back when we encounter a tool call and parse_tool_calls == false.
@@ -1410,40 +1446,51 @@ static void common_chat_parse_gpt_oss(common_chat_msg_parser & builder) {
1410
1446
return false ;
1411
1447
};
1412
1448
1413
- auto tool_call = [&]() {
1449
+ auto tool_call = [&](bool recipient_in_role ) {
1414
1450
if (!builder.syntax ().parse_tool_calls ) {
1415
1451
// Move back to the start and consume up to the next message
1416
1452
builder.move_to (message_start_pos);
1417
1453
builder.add_content (consume_until_next (message_start_pos + 1 ));
1418
1454
return ;
1419
1455
}
1420
1456
1421
- if (auto res = builder.try_consume_regex (user_tool_call_regex )) {
1457
+ if (auto res = builder.try_consume_regex (function_regex )) {
1422
1458
auto name = builder.str (res->groups [1 ]);
1423
- if (auto args = builder.try_consume_json_with_dumped_args ({{}})) {
1424
- if (!builder.add_tool_call (name, " " , args->value ) || args->is_partial ) {
1425
- throw common_chat_msg_partial_exception (" incomplete tool call" );
1459
+
1460
+ if (recipient_in_role) {
1461
+ if (!builder.try_consume_regex (tool_call_channel_regex)) {
1462
+ throw common_chat_msg_parse_exception (" expected <|channel|>(commentary|analysis), got: " + consume_until_next ());
1463
+ }
1464
+ }
1465
+
1466
+ if (builder.try_consume_regex (user_tool_call_regex)) {
1467
+ if (auto args = builder.try_consume_json_with_dumped_args ({{}})) {
1468
+ if (!builder.add_tool_call (name, " " , args->value ) || args->is_partial ) {
1469
+ throw common_chat_msg_partial_exception (" incomplete tool call" );
1470
+ }
1426
1471
}
1472
+ } else {
1473
+ throw common_chat_msg_parse_exception (" expected function args, got: " + consume_until_next ());
1427
1474
}
1428
1475
} else if (builder.try_consume_regex (builtin_tool_call_regex)) {
1429
1476
builder.consume_rest ();
1430
1477
LOG_ERR (" builtin tool calls not implemented\n " );
1431
1478
} else {
1432
- throw common_chat_msg_parse_exception (" expected function call , got: " + consume_until_next ());
1479
+ throw common_chat_msg_parse_exception (" expected function name , got: " + consume_until_next ());
1433
1480
}
1434
1481
};
1435
1482
1436
1483
auto commentary = [&]() {
1437
1484
if (builder.try_consume_regex (to_regex)) {
1438
- tool_call ();
1485
+ tool_call (false );
1439
1486
} else if (!try_consume_message ()) {
1440
1487
throw common_chat_msg_parse_exception (" expected: \" to=\" or <|message|>, got: " + consume_until_next ());
1441
1488
}
1442
1489
};
1443
1490
1444
1491
auto analysis = [&]() {
1445
1492
if (builder.try_consume_regex (to_regex)) {
1446
- tool_call (); // built-in tools can be called in the analysis channel
1493
+ tool_call (false ); // built-in tools can be called in the analysis channel
1447
1494
} else if (builder.try_consume_regex (message_regex)) {
1448
1495
// Defer reasoning parsing to builder
1449
1496
builder.move_to (channel_start_pos);
@@ -1458,26 +1505,34 @@ static void common_chat_parse_gpt_oss(common_chat_msg_parser & builder) {
1458
1505
}
1459
1506
};
1460
1507
1461
- auto channel = [&]() {
1508
+ auto channel = [&](const common_chat_msg_parser::find_regex_result & match) {
1509
+ auto type = builder.str (match.groups [1 ]);
1510
+ if (type == " analysis" ) {
1511
+ analysis ();
1512
+ } else if (type == " commentary" ) {
1513
+ commentary ();
1514
+ } else if (type == " final" ) {
1515
+ if (!try_consume_message ()) {
1516
+ throw common_chat_msg_parse_exception (" expected: <|message|>, got: " + consume_until_next ());
1517
+ }
1518
+ } else {
1519
+ throw common_chat_msg_parse_exception (" expected one of: [analysis, commentary, final], got: " + consume_until_next ());
1520
+ }
1521
+ };
1522
+
1523
+ auto message = [&]() {
1462
1524
if (auto res = builder.try_consume_regex (channel_regex)) {
1463
1525
channel_start_pos = res->groups [0 ].begin ;
1464
- auto type = builder.str (res->groups [1 ]);
1465
- if (type == " analysis" ) {
1466
- analysis ();
1467
- } else if (type == " commentary" ) {
1468
- commentary ();
1469
- } else if (type == " final" ) {
1470
- if (!try_consume_message ()) {
1471
- throw common_chat_msg_parse_exception (" expected: <|message|>, got: " + consume_until_next ());
1472
- }
1473
- }
1526
+ channel (*res);
1527
+ } else if (builder.try_consume_regex (to_regex)) {
1528
+ tool_call (true );
1474
1529
} else {
1475
- throw common_chat_msg_parse_exception (" expected: <|channel|>, got: " + consume_until_next ());
1530
+ throw common_chat_msg_parse_exception (" expected: <|channel|> or \" to \" , got: " + consume_until_next ());
1476
1531
}
1477
1532
};
1478
1533
1479
1534
try {
1480
- channel ();
1535
+ message ();
1481
1536
} catch (const common_chat_msg_parse_exception & e) {
1482
1537
LOG_DBG (" Parse error: %s\n " , e.what ());
1483
1538
}
@@ -1486,7 +1541,7 @@ static void common_chat_parse_gpt_oss(common_chat_msg_parser & builder) {
1486
1541
while (auto res = builder.try_consume_regex (start_regex)) {
1487
1542
message_start_pos = res->groups [0 ].begin ;
1488
1543
try {
1489
- channel ();
1544
+ message ();
1490
1545
} catch (const common_chat_msg_parse_exception & e) {
1491
1546
LOG_DBG (" Parse error: %s\n " , e.what ());
1492
1547
}
0 commit comments