@@ -622,6 +622,7 @@ const char * common_chat_format_name(common_chat_format format) {
622622        case  COMMON_CHAT_FORMAT_COMMAND_R7B: return  " Command R7B" 
623623        case  COMMON_CHAT_FORMAT_GRANITE: return  " Granite" 
624624        case  COMMON_CHAT_FORMAT_GPT_OSS: return  " GPT-OSS" 
625+         case  COMMON_CHAT_FORMAT_SEED_OSS: return  " Seed-OSS" 
625626        default :
626627            throw  std::runtime_error (" Unknown chat format" 
627628    }
@@ -2088,6 +2089,94 @@ static void common_chat_parse_granite(common_chat_msg_parser & builder) {
20882089    }
20892090}
20902091
2092+ static  void  common_chat_parse_seed_oss (common_chat_msg_parser & builder) {
2093+     //  Parse thinking tags first - this handles the main reasoning content
2094+     builder.try_parse_reasoning (" <seed:think>" " </seed:think>" 
2095+ 
2096+     if  (!builder.syntax ().parse_tool_calls ) {
2097+         builder.add_content (builder.consume_rest ());
2098+         return ;
2099+     }
2100+ 
2101+     //  Parse tool calls - Seed-OSS uses <seed:tool_call> format
2102+     static  const  common_regex tool_call_begin_regex (" <seed:tool_call>" 
2103+     static  const  common_regex tool_call_end_regex (" </seed:tool_call>" 
2104+     static  const  common_regex function_regex (" <function=([^>]+)>" 
2105+     static  const  common_regex param_regex (" <parameter=([^>]+)>" 
2106+ 
2107+     while  (auto  tool_res = builder.try_find_regex (tool_call_begin_regex)) {
2108+         builder.consume_spaces ();  //  Consume whitespace after <seed:tool_call>
2109+ 
2110+         //  Look for function call inside tool call, ignore any content before it
2111+         if  (auto  func_res = builder.try_find_regex (function_regex, std::string::npos, false )) {
2112+             auto  function_name = builder.str (func_res->groups [1 ]);
2113+ 
2114+             //  Parse Seed-OSS parameters <parameter=name>value</parameter>
2115+             json args = json::object ();
2116+             //  Parse all parameters
2117+             while  (auto  param_res = builder.try_find_regex (param_regex, std::string::npos, false )) {
2118+                 //  again, ignore noise around parameters
2119+                 auto  param_name = builder.str (param_res->groups [1 ]);
2120+                 builder.move_to (param_res->groups [0 ].end );
2121+                 builder.consume_spaces ();  //  Consume whitespace after parameter
2122+                 auto  savedPos = builder.pos ();
2123+                 if  (auto  param_parse = builder.try_find_literal (" </parameter>" 
2124+                     auto  param = param_parse->prelude ;
2125+                     builder.move_to (savedPos);
2126+                     try  {
2127+                         if  (auto  param_res = builder.try_consume_json ()) {
2128+                             args[param_name] = param_res->json ;
2129+                         } else  {
2130+                             args[param_name] = param;
2131+                         }
2132+                     } catch  (json::exception &) {
2133+                         args[param_name] = param;
2134+                     }
2135+                 } else  {
2136+                     throw  common_chat_msg_partial_exception (" Incomplete tool parameter" 
2137+                 }
2138+             }
2139+             //  Look for closing function tag
2140+             auto  end_func = builder.try_find_literal (" </function>" 
2141+             if  (end_func) {
2142+                 builder.move_to (end_func->groups [0 ].end );
2143+                 builder.consume_spaces ();  //  Consume whitespace after </function>
2144+ 
2145+                 //  Add the tool call with parsed arguments, but only if we REALLY got the literal
2146+                 auto  eaten_fragment = builder.input ().substr (end_func->groups [0 ].begin , end_func->groups [0 ].end );
2147+                 auto  funlen = std::string (" </function>" length ();
2148+                 if  (eaten_fragment.length () >= funlen && eaten_fragment.substr (0 , funlen) == std::string (" </function>" 
2149+                     if  (!builder.add_tool_call (function_name, " " dump ())) {
2150+                         throw  common_chat_msg_partial_exception (" Incomplete tool call" 
2151+                     }
2152+                 } else  {
2153+                     throw  common_chat_msg_partial_exception (" Incomplete tool call" 
2154+                 }
2155+             } else  {
2156+                 throw  common_chat_msg_partial_exception (" Incomplete tool call" 
2157+             }
2158+             //  Look for closing tool call tag
2159+             if  (auto  end_tool = builder.try_find_regex (tool_call_end_regex, std::string::npos, false )) {
2160+                 builder.move_to (end_tool->groups [0 ].end );
2161+                 builder.consume_spaces ();  //  Consume trailing whitespace after tool call
2162+             } else  {
2163+                 throw  common_chat_msg_partial_exception (" Incomplete tool call" 
2164+             }
2165+         } else  {
2166+             //  No function found - don't consume content here, let it be handled at the end
2167+             break ;
2168+         }
2169+     }
2170+ 
2171+     //  Consume any remaining whitespace after all tool call processing
2172+     builder.consume_spaces ();
2173+     auto  remaining = builder.consume_rest ();
2174+     //  If there's any non-whitespace content remaining, add it as content
2175+     if  (!string_strip (remaining).empty ()) {
2176+         builder.add_content (remaining);
2177+     }
2178+ }
2179+ 
20912180static  common_chat_params common_chat_params_init_without_tools (const  common_chat_template & tmpl, const  struct  templates_params  & inputs) {
20922181    common_chat_params data;
20932182    data.prompt  = apply (tmpl, inputs);
@@ -2104,8 +2193,62 @@ static common_chat_params common_chat_params_init_without_tools(const common_cha
21042193    return  data;
21052194}
21062195
2196+ static  common_chat_params common_chat_params_init_seed_oss (
2197+     const  common_chat_template         & tmpl,
2198+     templates_params                   & params,
2199+     const  common_chat_templates_inputs & inputs)
2200+ {
2201+     common_chat_params data;
2202+     data.prompt  = apply (tmpl, params);
2203+     data.format  = COMMON_CHAT_FORMAT_SEED_OSS;
2204+     if  (string_ends_with (data.prompt , " <seed:think>" 
2205+         if  (!inputs.enable_thinking ) {
2206+             data.prompt  += " </seed:think>" 
2207+         } else  {
2208+             data.thinking_forced_open  = true ;
2209+         }
2210+     }
2211+ 
2212+     if  (params.tools .is_array () && !params.tools .empty ()) {
2213+         data.grammar_lazy  = inputs.tool_choice  != COMMON_CHAT_TOOL_CHOICE_REQUIRED;
2214+         data.grammar       = build_grammar ([&](const  common_grammar_builder & builder) {
2215+             std::vector<std::string> tool_rules;
2216+             foreach_function (params.tools , [&](const  json & tool) {
2217+                 const  auto  & function   = tool.at (" function" 
2218+                 std::string  name       = function.at (" name" 
2219+                 auto          parameters = function.at (" parameters" 
2220+                 builder.resolve_refs (parameters);
2221+ 
2222+                 //  Create rule for Seed-OSS function call format
2223+                 std::string param_rules;
2224+                 if  (parameters.contains (" properties" 
2225+                     for  (const  auto  & [key, value] : parameters.at (" properties" items ()) {
2226+                         param_rules += " \" <parameter=" " >\" " add_schema (name + " -arg-" 
2227+                                        " \" </parameter>\" " 
2228+                     }
2229+                 }
2230+ 
2231+                 tool_rules.push_back (builder.add_rule (name + " -call" 
2232+                                                       " \" <seed:tool_call>\"  space \" <function=" " >\"  space " 
2233+                                                           param_rules +
2234+                                                           "  \" </function>\"  space \" </seed:tool_call>\" " 
2235+             });
2236+ 
2237+             data.grammar_triggers .push_back ({ COMMON_GRAMMAR_TRIGGER_TYPE_WORD, " <seed:tool_call>" 
2238+ 
2239+             data.preserved_tokens  = {
2240+                 " <seed:think>" " </seed:think>" " <seed:tool_call>" " </seed:tool_call>" 
2241+                 " <function=" " </function>" " <parameter=" " </parameter>" 
2242+             };
2243+ 
2244+             builder.add_rule (" root" string_join (tool_rules, "  | " 
2245+         });
2246+     }
2247+     return  data;
2248+ }
2249+ 
21072250static  common_chat_params common_chat_templates_apply_jinja (
2108-     const  struct  common_chat_templates  * tmpls,
2251+     const  struct  common_chat_templates          * tmpls,
21092252    const  struct  common_chat_templates_inputs  & inputs)
21102253{
21112254    templates_params params;
@@ -2174,6 +2317,11 @@ static common_chat_params common_chat_templates_apply_jinja(
21742317        return  common_chat_params_init_gpt_oss (tmpl, params);
21752318    }
21762319
2320+     //  Seed-OSS
2321+     if  (src.find (" <seed:think>" 
2322+         return  common_chat_params_init_seed_oss (tmpl, params, inputs);
2323+     }
2324+ 
21772325    //  Use generic handler when mixing tools + JSON schema.
21782326    //  TODO: support that mix in handlers below.
21792327    if  ((params.tools .is_array () && params.json_schema .is_object ())) {
@@ -2332,6 +2480,9 @@ static void common_chat_parse(common_chat_msg_parser & builder) {
23322480        case  COMMON_CHAT_FORMAT_GPT_OSS:
23332481            common_chat_parse_gpt_oss (builder);
23342482            break ;
2483+         case  COMMON_CHAT_FORMAT_SEED_OSS:
2484+             common_chat_parse_seed_oss (builder);
2485+             break ;
23352486        default :
23362487            throw  std::runtime_error (std::string (" Unsupported format: " common_chat_format_name (builder.syntax ().format ));
23372488    }
0 commit comments