@@ -623,6 +623,7 @@ const char * common_chat_format_name(common_chat_format format) {
623623 case COMMON_CHAT_FORMAT_COMMAND_R7B: return " Command R7B" ;
624624 case COMMON_CHAT_FORMAT_GRANITE: return " Granite" ;
625625 case COMMON_CHAT_FORMAT_GPT_OSS: return " GPT-OSS" ;
626+ case COMMON_CHAT_FORMAT_SEED_OSS: return " Seed-OSS" ;
626627 default :
627628 throw std::runtime_error (" Unknown chat format" );
628629 }
@@ -2259,6 +2260,94 @@ static void common_chat_parse_granite(common_chat_msg_parser & builder) {
22592260 }
22602261}
22612262
2263+ static void common_chat_parse_seed_oss (common_chat_msg_parser & builder) {
2264+ // Parse thinking tags first - this handles the main reasoning content
2265+ builder.try_parse_reasoning (" <seed:think>" , " </seed:think>" );
2266+
2267+ if (!builder.syntax ().parse_tool_calls ) {
2268+ builder.add_content (builder.consume_rest ());
2269+ return ;
2270+ }
2271+
2272+ // Parse tool calls - Seed-OSS uses <seed:tool_call> format
2273+ static const common_regex tool_call_begin_regex (" <seed:tool_call>" );
2274+ static const common_regex tool_call_end_regex (" </seed:tool_call>" );
2275+ static const common_regex function_regex (" <function=([^>]+)>" );
2276+ static const common_regex param_regex (" <parameter=([^>]+)>" );
2277+
2278+ while (auto tool_res = builder.try_find_regex (tool_call_begin_regex)) {
2279+ builder.consume_spaces (); // Consume whitespace after <seed:tool_call>
2280+
2281+ // Look for function call inside tool call, ignore any content before it
2282+ if (auto func_res = builder.try_find_regex (function_regex, std::string::npos, false )) {
2283+ auto function_name = builder.str (func_res->groups [1 ]);
2284+
2285+ // Parse Seed-OSS parameters <parameter=name>value</parameter>
2286+ json args = json::object ();
2287+ // Parse all parameters
2288+ while (auto param_res = builder.try_find_regex (param_regex, std::string::npos, false )) {
2289+ // again, ignore noise around parameters
2290+ auto param_name = builder.str (param_res->groups [1 ]);
2291+ builder.move_to (param_res->groups [0 ].end );
2292+ builder.consume_spaces (); // Consume whitespace after parameter
2293+ auto savedPos = builder.pos ();
2294+ if (auto param_parse = builder.try_find_literal (" </parameter>" )) {
2295+ auto param = param_parse->prelude ;
2296+ builder.move_to (savedPos);
2297+ try {
2298+ if (auto param_res = builder.try_consume_json ()) {
2299+ args[param_name] = param_res->json ;
2300+ } else {
2301+ args[param_name] = param;
2302+ }
2303+ } catch (json::exception &) {
2304+ args[param_name] = param;
2305+ }
2306+ } else {
2307+ throw common_chat_msg_partial_exception (" Incomplete tool parameter" );
2308+ }
2309+ }
2310+ // Look for closing function tag
2311+ auto end_func = builder.try_find_literal (" </function>" );
2312+ if (end_func) {
2313+ builder.move_to (end_func->groups [0 ].end );
2314+ builder.consume_spaces (); // Consume whitespace after </function>
2315+
2316+ // Add the tool call with parsed arguments, but only if we REALLY got the literal
2317+ auto eaten_fragment = builder.input ().substr (end_func->groups [0 ].begin , end_func->groups [0 ].end );
2318+ auto funlen = std::string (" </function>" ).length ();
2319+ if (eaten_fragment.length () >= funlen && eaten_fragment.substr (0 , funlen) == std::string (" </function>" )) {
2320+ if (!builder.add_tool_call (function_name, " " , args.dump ())) {
2321+ throw common_chat_msg_partial_exception (" Incomplete tool call" );
2322+ }
2323+ } else {
2324+ throw common_chat_msg_partial_exception (" Incomplete tool call" );
2325+ }
2326+ } else {
2327+ throw common_chat_msg_partial_exception (" Incomplete tool call" );
2328+ }
2329+ // Look for closing tool call tag
2330+ if (auto end_tool = builder.try_find_regex (tool_call_end_regex, std::string::npos, false )) {
2331+ builder.move_to (end_tool->groups [0 ].end );
2332+ builder.consume_spaces (); // Consume trailing whitespace after tool call
2333+ } else {
2334+ throw common_chat_msg_partial_exception (" Incomplete tool call" );
2335+ }
2336+ } else {
2337+ // No function found - don't consume content here, let it be handled at the end
2338+ break ;
2339+ }
2340+ }
2341+
2342+ // Consume any remaining whitespace after all tool call processing
2343+ builder.consume_spaces ();
2344+ auto remaining = builder.consume_rest ();
2345+ // If there's any non-whitespace content remaining, add it as content
2346+ if (!string_strip (remaining).empty ()) {
2347+ builder.add_content (remaining);
2348+ }
2349+ }
2350+
22622351static common_chat_params common_chat_params_init_without_tools (const common_chat_template & tmpl, const struct templates_params & inputs) {
22632352 common_chat_params data;
22642353 data.prompt = apply (tmpl, inputs);
@@ -2275,8 +2364,62 @@ static common_chat_params common_chat_params_init_without_tools(const common_cha
22752364 return data;
22762365}
22772366
2367+ static common_chat_params common_chat_params_init_seed_oss (
2368+ const common_chat_template & tmpl,
2369+ templates_params & params,
2370+ const common_chat_templates_inputs & inputs)
2371+ {
2372+ common_chat_params data;
2373+ data.prompt = apply (tmpl, params);
2374+ data.format = COMMON_CHAT_FORMAT_SEED_OSS;
2375+ if (string_ends_with (data.prompt , " <seed:think>" )) {
2376+ if (!inputs.enable_thinking ) {
2377+ data.prompt += " </seed:think>" ;
2378+ } else {
2379+ data.thinking_forced_open = true ;
2380+ }
2381+ }
2382+
2383+ if (params.tools .is_array () && !params.tools .empty ()) {
2384+ data.grammar_lazy = inputs.tool_choice != COMMON_CHAT_TOOL_CHOICE_REQUIRED;
2385+ data.grammar = build_grammar ([&](const common_grammar_builder & builder) {
2386+ std::vector<std::string> tool_rules;
2387+ foreach_function (params.tools , [&](const json & tool) {
2388+ const auto & function = tool.at (" function" );
2389+ std::string name = function.at (" name" );
2390+ auto parameters = function.at (" parameters" );
2391+ builder.resolve_refs (parameters);
2392+
2393+ // Create rule for Seed-OSS function call format
2394+ std::string param_rules;
2395+ if (parameters.contains (" properties" )) {
2396+ for (const auto & [key, value] : parameters.at (" properties" ).items ()) {
2397+ param_rules += " \" <parameter=" + key + " >\" " + builder.add_schema (name + " -arg-" + key, value) +
2398+ " \" </parameter>\" " ;
2399+ }
2400+ }
2401+
2402+ tool_rules.push_back (builder.add_rule (name + " -call" ,
2403+ " \" <seed:tool_call>\" space \" <function=" + name + " >\" space " +
2404+ param_rules +
2405+ " \" </function>\" space \" </seed:tool_call>\" " ));
2406+ });
2407+
2408+ data.grammar_triggers .push_back ({ COMMON_GRAMMAR_TRIGGER_TYPE_WORD, " <seed:tool_call>" });
2409+
2410+ data.preserved_tokens = {
2411+ " <seed:think>" , " </seed:think>" , " <seed:tool_call>" , " </seed:tool_call>" ,
2412+ " <function=" , " </function>" , " <parameter=" , " </parameter>" ,
2413+ };
2414+
2415+ builder.add_rule (" root" , string_join (tool_rules, " | " ));
2416+ });
2417+ }
2418+ return data;
2419+ }
2420+
22782421static common_chat_params common_chat_templates_apply_jinja (
2279- const struct common_chat_templates * tmpls,
2422+ const struct common_chat_templates * tmpls,
22802423 const struct common_chat_templates_inputs & inputs)
22812424{
22822425 templates_params params;
@@ -2351,6 +2494,11 @@ static common_chat_params common_chat_templates_apply_jinja(
23512494 return common_chat_params_init_gpt_oss (tmpl, params);
23522495 }
23532496
2497+ // Seed-OSS
2498+ if (src.find (" <seed:think>" ) != std::string::npos) {
2499+ return common_chat_params_init_seed_oss (tmpl, params, inputs);
2500+ }
2501+
23542502 // Use generic handler when mixing tools + JSON schema.
23552503 // TODO: support that mix in handlers below.
23562504 if ((params.tools .is_array () && params.json_schema .is_object ())) {
@@ -2512,6 +2660,9 @@ static void common_chat_parse(common_chat_msg_parser & builder) {
25122660 case COMMON_CHAT_FORMAT_GPT_OSS:
25132661 common_chat_parse_gpt_oss (builder);
25142662 break ;
2663+ case COMMON_CHAT_FORMAT_SEED_OSS:
2664+ common_chat_parse_seed_oss (builder);
2665+ break ;
25152666 default :
25162667 throw std::runtime_error (std::string (" Unsupported format: " ) + common_chat_format_name (builder.syntax ().format ));
25172668 }
0 commit comments