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