@@ -933,17 +933,12 @@ static common_chat_params common_chat_params_init_gpt_oss(const common_chat_temp
933933
934934 // Copy reasoning to the "thinking" field as expected by the gpt-oss template
935935 auto adjusted_messages = json::array ();
936- for (const auto & msg : inputs.messages ) {
937- auto has_reasoning_content = msg.contains (" reasoning_content" ) && msg.at (" reasoning_content" ).is_string ();
938- auto has_tool_calls = msg.contains (" tool_calls" ) && msg.at (" tool_calls" ).is_array ();
939-
940- if (has_reasoning_content && has_tool_calls) {
941- auto adjusted_message = msg;
942- adjusted_message[" thinking" ] = msg.at (" reasoning_content" );
943- adjusted_messages.push_back (adjusted_message);
944- } else {
945- adjusted_messages.push_back (msg);
936+ for (auto msg : inputs.messages ) {
937+ if (msg.contains (" reasoning_content" ) && msg.at (" reasoning_content" ).is_string ()) {
938+ msg[" thinking" ] = msg.at (" reasoning_content" );
939+ msg.erase (" content" );
946940 }
941+ adjusted_messages.push_back (msg);
947942 }
948943
949944 auto prompt = common_chat_template_direct_apply (tmpl, inputs, /* messages_override= */ adjusted_messages);
@@ -969,45 +964,31 @@ static common_chat_params common_chat_params_init_gpt_oss(const common_chat_temp
969964 " <|channel|>" , " <|constrain|>" , " <|message|>" , " <|start|>" , " <|end|>" ,
970965 };
971966
972- auto has_tools = inputs.tools .is_array () && !inputs.tools .empty ();
973- auto extract_reasoning = inputs.reasoning_format != COMMON_REASONING_FORMAT_NONE ;
974- auto include_grammar = inputs.tool_choice != COMMON_CHAT_TOOL_CHOICE_NONE && has_tools ;
967+ auto has_tools = inputs.tools .is_array () && !inputs.tools .empty ();
968+ auto has_response_format = ! inputs.json_schema . is_null () && inputs. json_schema . is_object () ;
969+ auto include_grammar = has_response_format || (has_tools && inputs.tool_choice != COMMON_CHAT_TOOL_CHOICE_NONE) ;
975970
976971 auto parser = build_chat_peg_parser ([&](common_chat_peg_builder & p) {
977- const std::string END = " <|end|>" ;
978- const std::string START = " <|start|>" ;
979- const std::string MESSAGE = " <|message|>" ;
980- const std::string CHANNEL = " <|channel|>" ;
981- const std::string CONSTRAIN = " <|constrain|>" ;
982- const std::string START_ASSISTANT = START + " assistant" ;
983- const std::string CHANNEL_ANALYSIS = CHANNEL + " analysis" ;
984- const std::string CHANNEL_COMMENTARY = CHANNEL + " commentary" ;
985- const std::string CHANNEL_FINAL = CHANNEL + " final" ;
986-
987- auto the_end = END | p.end ();
988-
989- const std::string analysis_header = CHANNEL_ANALYSIS + MESSAGE;
990- auto segment_content = p.until (END);
991- auto analysis_segment = extract_reasoning ?
992- p.literal (analysis_header) + p.reasoning (segment_content) + p.until (END) + the_end :
993- p.content (analysis_header + p.until (END) + the_end);
994-
995- auto channel_header_content = p.until_one_of ({ " to=functions." , MESSAGE });
996- auto content_header = p.choice ({ p.literal (CHANNEL_COMMENTARY), p.literal (CHANNEL_FINAL) });
997- auto content_segment = p.rule (" content-segment" , content_header + channel_header_content + MESSAGE +
998- p.content (segment_content) + the_end);
999-
1000- if (!inputs.json_schema .is_null ()) {
1001- auto final_header = p.literal (CHANNEL_FINAL);
1002- auto constraint = p.optional (p.space () + p.literal (CONSTRAIN) + channel_header_content);
1003- return p.optional (analysis_segment) + final_header + constraint + MESSAGE +
1004- p.content (p.schema (p.json (), " response-format" , inputs.json_schema ));
972+ auto start = p.rule (" start" , p.literal (" <|start|>assistant" ));
973+ auto end = p.rule (" end" , p.literal (" <|end|>" ));
974+ auto content = p.rule (" message-content" , p.until (" <|end|>" ));
975+ auto channel = p.literal (" <|channel|>" ) + (p.literal (" commentary" ) | p.literal (" analysis" ));
976+ auto constrain_type = p.chars (" [A-Za-z0-9_-]" , 1 , -1 );
977+
978+ auto analysis = p.rule (" analysis" , p.literal (" <|channel|>analysis<|message|>" ) + p.reasoning (content) + end);
979+ auto preamble = p.rule (" preamble" , p.literal (" <|channel|>commentary<|message|>" ) + p.content (content) + end);
980+ auto final_msg = p.rule (" final" , p.literal (" <|channel|>final<|message|>" ) + p.content (content));
981+ auto any = p.rule (" any" , preamble | analysis);
982+
983+ if (has_response_format) {
984+ auto constraint = p.optional (p.space () + p.literal (" <|constrain|>" ) + constrain_type);
985+ auto response_format = p.rule (" response-format" ,
986+ p.literal (" <|channel|>final" ) + constraint + p.literal (" <|message|>" ) +
987+ p.content (p.schema (p.json (), " response-format-schema" , inputs.json_schema )));
988+
989+ return response_format | (analysis + p.zero_or_more (start + analysis) + start + response_format);
1005990 }
1006991
1007- auto segment = p.optional (START_ASSISTANT + p.space ()) + p.choice ({ content_segment, analysis_segment });
1008- auto contents = p.optional (segment + p.repeat (p.optional (p.space ()) + segment, 0 , -1 )) + p.end ();
1009-
1010- // Tool call parser
1011992 if (has_tools && inputs.tool_choice != COMMON_CHAT_TOOL_CHOICE_NONE) {
1012993 auto tool_choice = p.choice ();
1013994
@@ -1016,42 +997,37 @@ static common_chat_params common_chat_params_init_gpt_oss(const common_chat_temp
1016997 std::string name = function.at (" name" );
1017998 const auto & params = function.at (" parameters" );
1018999
1019- // Tool call can appear as:
1020- // 1. In role header: " to=functions.NAME<|channel|>..."
1021- // 2. In channel: "<|channel|>(analysis|commentary) to=functions.NAME..."
1022- auto func_name = p.literal (" to=functions." ) + p.tool_name (p.literal (name));
1023-
1024- auto channel = p.literal (CHANNEL_COMMENTARY) | p.literal (CHANNEL_ANALYSIS);
1025- auto constraint = p.space () + p.optional (p.literal (CONSTRAIN) + channel_header_content);
1000+ auto func_name = p.literal (" to=functions." ) + p.tool_name (p.literal (name));
1001+ auto constraint = p.optional (p.space () + p.literal (" <|constrain|>" ) + constrain_type);
10261002 auto args = p.tool_args (p.schema (p.json (), " tool-" + name + " -schema" , params));
10271003
1028- // Pattern 1: recipient in role header
1029- // " to=functions.NAME<|channel|>(analysis| commentary)[constraint]<|message|>ARGS"
1030- auto tool_in_role = p.tool (p.tool_open (func_name + channel) + constraint + MESSAGE + args);
1004+ // recipient in role header
1005+ // <|start|>assistant to=functions.NAME<|channel|>(commentary|analysis )[constraint]<|message|>ARGS
1006+ auto tool_in_role = p.tool (p.tool_open (func_name + channel + constraint + p. literal ( " <|message|> " )) + args);
10311007
1032- // Pattern 2: recipient in channel header
1033- // " <|channel|>(analysis| commentary) to=functions.NAME[constraint]<|message|>ARGS"
1034- auto tool_in_channel = p.tool (channel + p.tool_open (func_name + constraint + MESSAGE ) + args);
1008+ // recipient in channel header
1009+ // <|channel|>(commentary|analysis ) to=functions.NAME[constraint]<|message|>ARGS
1010+ auto tool_in_channel = p.tool (p.tool_open (channel + func_name + constraint + p. literal ( " <|message|> " ) ) + args);
10351011
1036- tool_choice |= tool_in_role | tool_in_channel;
1012+ tool_choice |= p. rule ( " tool- " + name, tool_in_role | tool_in_channel) ;
10371013 });
10381014
1039- auto min_calls = inputs.tool_choice == COMMON_CHAT_TOOL_CHOICE_REQUIRED ? 1 : 0 ;
1040- auto max_calls = inputs.parallel_tool_calls ? -1 : 1 ;
1015+ auto tool_call = p.trigger_rule (" tool-call" , tool_choice);
10411016
1042- auto role_start = p.optional (p.space () + p.literal (START_ASSISTANT));
1043- auto tool_call = p.rule (" tool-call" , p.repeat (role_start + tool_choice, min_calls, max_calls) + p.end ());
1017+ if (inputs.tool_choice == COMMON_CHAT_TOOL_CHOICE_REQUIRED) {
1018+ return tool_call | ( any + p.zero_or_more (start + any) + start + tool_call);
1019+ }
10441020
1045- return p. choice ({ p. trigger_rule ( " single-tool " , tool_call), p. trigger_rule ( " tools " , p. one_or_more (segment ) + tool_call) } );
1021+ return tool_call | final_msg | (any + p. zero_or_more (start + any ) + start + ( tool_call | final_msg) );
10461022 }
10471023
1048- return contents ;
1024+ return final_msg | (any + p. zero_or_more (start + any) + start + final_msg) ;
10491025 });
10501026
10511027 data.parser = parser.save ();
10521028
10531029 if (include_grammar) {
1054- data.grammar_lazy = has_tools && inputs.tool_choice == COMMON_CHAT_TOOL_CHOICE_AUTO ;
1030+ data.grammar_lazy = !(has_response_format || ( has_tools && inputs.tool_choice == COMMON_CHAT_TOOL_CHOICE_REQUIRED)) ;
10551031 data.grammar = build_grammar ([&](const common_grammar_builder & builder) {
10561032 foreach_function (inputs.tools , [&](const json & tool) {
10571033 const auto & function = tool.at (" function" );
@@ -1062,10 +1038,9 @@ static common_chat_params common_chat_params_init_gpt_oss(const common_chat_temp
10621038 });
10631039
10641040 data.grammar_triggers = {
1065- { COMMON_GRAMMAR_TRIGGER_TYPE_PATTERN, " ^(?:<\\ |start\\ |>assistant\\ s*)?(\\ s+to=functions)" },
1066- { COMMON_GRAMMAR_TRIGGER_TYPE_PATTERN, " (?:<\\ |end\\ |>)(?:<\\ |start\\ |>assistant\\ s*)?(\\ s+to=functions)" },
1067- { COMMON_GRAMMAR_TRIGGER_TYPE_PATTERN,
1068- " (?:<\\ |start\\ |>assistant\\ s*)?(<\\ |channel\\ |>(?:commentary|analysis)\\ s+to=functions)" }
1041+ { COMMON_GRAMMAR_TRIGGER_TYPE_PATTERN, " ^\\ s+to$" },
1042+ { COMMON_GRAMMAR_TRIGGER_TYPE_PATTERN, " <\\ |start\\ |>assistant(\\ s+to)" },
1043+ { COMMON_GRAMMAR_TRIGGER_TYPE_PATTERN, " <\\ |start\\ |>assistant(<\\ |channel\\ |>(?:commentary|analysis)\\ s+to)" }
10691044 };
10701045 }
10711046
0 commit comments