Skip to content

Commit 2bf7f3f

Browse files
author
damian.winnicki
committed
chat: GPT-OSS: parse commentary tool calls, handle glued 'json' suffix; add unit tests; tidy comments
1 parent 7ad67ba commit 2bf7f3f

File tree

2 files changed

+107
-2
lines changed

2 files changed

+107
-2
lines changed

common/chat.cpp

Lines changed: 48 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1319,9 +1319,55 @@ static common_chat_params common_chat_params_init_gpt_oss(const common_chat_temp
13191319
return data;
13201320
}
13211321
static void common_chat_parse_gpt_oss(common_chat_msg_parser & builder) {
1322-
// TODO @ngxson : this won't work with --special enabled, we should fix that
1323-
builder.try_parse_reasoning("<|channel|>analysis<|message|>", "<|start|>assistant<|channel|>final<|message|>");
1322+
static const common_regex next_block(
1323+
"<\\|start\\|>assistant<\\|channel\\|>(?:commentary\\s+to=functions\\.([^\\s<\\|]+)(?:\\s+json)?|final)<\\|message\\|>");
1324+
13241325
if (!builder.syntax().parse_tool_calls) {
1326+
if (auto res = builder.try_find_regex(next_block)) {
1327+
while (true) {
1328+
std::string fname = builder.str(res->groups[1]);
1329+
const std::string header = builder.str(res->groups[0]);
1330+
if (fname.size() > 4 && header.find(" json<|message|>") == std::string::npos) {
1331+
if (fname.rfind("json") == fname.size() - 4) {
1332+
fname.resize(fname.size() - 4);
1333+
}
1334+
}
1335+
if (!fname.empty()) {
1336+
if (!builder.try_consume_json_with_dumped_args({{}})) {
1337+
break;
1338+
}
1339+
res = builder.try_find_regex(next_block);
1340+
if (!res) break;
1341+
continue;
1342+
}
1343+
builder.add_content(builder.consume_rest());
1344+
return;
1345+
}
1346+
}
1347+
builder.add_content(builder.consume_rest());
1348+
return;
1349+
}
1350+
1351+
while (true) {
1352+
auto res = builder.try_find_regex(next_block);
1353+
if (!res) {
1354+
builder.add_content(builder.consume_rest());
1355+
return;
1356+
}
1357+
std::string fname = builder.str(res->groups[1]);
1358+
const std::string header = builder.str(res->groups[0]);
1359+
if (fname.size() > 4 && header.find(" json<|message|>") == std::string::npos) {
1360+
if (fname.rfind("json") == fname.size() - 4) {
1361+
fname.resize(fname.size() - 4);
1362+
}
1363+
}
1364+
if (!fname.empty()) {
1365+
auto arguments = builder.consume_json_with_dumped_args({{}});
1366+
if (!builder.add_tool_call(fname, "", arguments.value) || arguments.is_partial) {
1367+
throw common_chat_msg_partial_exception("incomplete tool call");
1368+
}
1369+
continue;
1370+
}
13251371
builder.add_content(builder.consume_rest());
13261372
return;
13271373
}

tests/test-chat.cpp

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1441,6 +1441,64 @@ static void test_template_output_parsers() {
14411441
}
14421442
}
14431443

1444+
static void test_gpt_oss_parser() {
1445+
printf("[%s]\n", __func__);
1446+
1447+
// Standard commentary header with explicit ' json'
1448+
{
1449+
const std::string input =
1450+
"<|start|>assistant<|channel|>commentary to=functions.shell json<|message|>" \
1451+
"{\\\"command\\\":[\\\"bash\\\",\\\"-lc\\\",\\\"echo hi\\\"]}" \
1452+
"<|start|>assistant<|channel|>final<|message|>Done";
1453+
common_chat_msg expected = simple_assist_msg("Done", "", "shell",
1454+
"{\"command\":[\"bash\",\"-lc\",\"echo hi\"]}");
1455+
assert_msg_equals(
1456+
expected,
1457+
common_chat_parse(
1458+
input,
1459+
/* is_partial= */ false,
1460+
{COMMON_CHAT_FORMAT_GPT_OSS}));
1461+
}
1462+
1463+
// Commentary header where 'json' is glued to function name (e.g., 'shelljson')
1464+
{
1465+
const std::string input =
1466+
"<|start|>assistant<|channel|>commentary to=functions.shelljson<|message|>" \
1467+
"{\\\"command\\\":[\\\"bash\\\",\\\"-lc\\\",\\\"echo hi\\\"]}" \
1468+
"<|start|>assistant<|channel|>final<|message|>Done";
1469+
common_chat_msg expected = simple_assist_msg("Done", "", "shell",
1470+
"{\"command\":[\"bash\",\"-lc\",\"echo hi\"]}");
1471+
assert_msg_equals(
1472+
expected,
1473+
common_chat_parse(
1474+
input,
1475+
/* is_partial= */ false,
1476+
{COMMON_CHAT_FORMAT_GPT_OSS}));
1477+
}
1478+
1479+
// Multiple commentary tool calls then final
1480+
{
1481+
const std::string input =
1482+
"<|start|>assistant<|channel|>commentary to=functions.shell json<|message|>" \
1483+
"{\\\"command\\\":[\\\"bash\\\",\\\"-lc\\\",\\\"echo hi\\\"]}" \
1484+
"<|start|>assistant<|channel|>commentary to=functions.update_plan json<|message|>" \
1485+
"{\\\"plan\\\":[{\\\"step\\\":\\\"x\\\",\\\"status\\\":\\\"in_progress\\\"}]}" \
1486+
"<|start|>assistant<|channel|>final<|message|>ok";
1487+
common_chat_msg expected;
1488+
expected.role = "assistant";
1489+
expected.content = "ok";
1490+
expected.tool_calls.push_back({"shell", "{\"command\":[\"bash\",\"-lc\",\"echo hi\"]}", ""});
1491+
expected.tool_calls.push_back({"update_plan", "{\"plan\":[{\"step\":\"x\",\"status\":\"in_progress\"}]}", ""});
1492+
1493+
assert_msg_equals(
1494+
expected,
1495+
common_chat_parse(
1496+
input,
1497+
/* is_partial= */ false,
1498+
{COMMON_CHAT_FORMAT_GPT_OSS}));
1499+
}
1500+
}
1501+
14441502
static void test_msg_diffs_compute() {
14451503
printf("[%s]\n", __func__);
14461504
{
@@ -1564,6 +1622,7 @@ int main(int argc, char ** argv) {
15641622
test_msgs_oaicompat_json_conversion();
15651623
test_tools_oaicompat_json_conversion();
15661624
test_template_output_parsers();
1625+
test_gpt_oss_parser();
15671626
std::cout << "\n[chat] All tests passed!" << '\n';
15681627
}
15691628
return 0;

0 commit comments

Comments
 (0)