@@ -624,6 +624,7 @@ const char * common_chat_format_name(common_chat_format format) {
624624 case COMMON_CHAT_FORMAT_QWEN3_CODER_XML: return " Qwen3 Coder XML" ;
625625 case COMMON_CHAT_FORMAT_GRANITE: return " Granite" ;
626626 case COMMON_CHAT_FORMAT_GPT_OSS: return " GPT-OSS" ;
627+ case COMMON_CHAT_FORMAT_SEED_OSS: return " Seed-OSS" ;
627628 default :
628629 throw std::runtime_error (" Unknown chat format" );
629630 }
@@ -2225,11 +2226,6 @@ static common_chat_params common_chat_params_init_qwen3_coder_xml(const common_c
22252226}
22262227
22272228static void common_chat_parse_qwen3_coder_xml (common_chat_msg_parser & builder) {
2228- if (!builder.syntax ().parse_tool_calls ) {
2229- builder.add_content (builder.consume_rest ());
2230- return ;
2231- }
2232-
22332229 std::string content = builder.consume_rest ();
22342230
22352231 // Try to parse Qwen3-Coder XML format
@@ -2243,6 +2239,94 @@ static void common_chat_parse_qwen3_coder_xml(common_chat_msg_parser & builder)
22432239 builder.add_content (content);
22442240}
22452241
2242+ static void common_chat_parse_seed_oss (common_chat_msg_parser & builder) {
2243+ // Parse thinking tags first - this handles the main reasoning content
2244+ builder.try_parse_reasoning (" <seed:think>" , " </seed:think>" );
2245+
2246+ if (!builder.syntax ().parse_tool_calls ) {
2247+ builder.add_content (builder.consume_rest ());
2248+ return ;
2249+ }
2250+
2251+ // Parse tool calls - Seed-OSS uses <seed:tool_call> format
2252+ static const common_regex tool_call_begin_regex (" <seed:tool_call>" );
2253+ static const common_regex tool_call_end_regex (" </seed:tool_call>" );
2254+ static const common_regex function_regex (" <function=([^>]+)>" );
2255+ static const common_regex param_regex (" <parameter=([^>]+)>" );
2256+
2257+ while (auto tool_res = builder.try_find_regex (tool_call_begin_regex)) {
2258+ builder.consume_spaces (); // Consume whitespace after <seed:tool_call>
2259+
2260+ // Look for function call inside tool call, ignore any content before it
2261+ if (auto func_res = builder.try_find_regex (function_regex, std::string::npos, false )) {
2262+ auto function_name = builder.str (func_res->groups [1 ]);
2263+
2264+ // Parse Seed-OSS parameters <parameter=name>value</parameter>
2265+ json args = json::object ();
2266+ // Parse all parameters
2267+ while (auto param_res = builder.try_find_regex (param_regex, std::string::npos, false )) {
2268+ // again, ignore noise around parameters
2269+ auto param_name = builder.str (param_res->groups [1 ]);
2270+ builder.move_to (param_res->groups [0 ].end );
2271+ builder.consume_spaces (); // Consume whitespace after parameter
2272+ auto savedPos = builder.pos ();
2273+ if (auto param_parse = builder.try_find_literal (" </parameter>" )) {
2274+ auto param = param_parse->prelude ;
2275+ builder.move_to (savedPos);
2276+ try {
2277+ if (auto param_res = builder.try_consume_json ()) {
2278+ args[param_name] = param_res->json ;
2279+ } else {
2280+ args[param_name] = param;
2281+ }
2282+ } catch (json::exception &) {
2283+ args[param_name] = param;
2284+ }
2285+ } else {
2286+ throw common_chat_msg_partial_exception (" Incomplete tool parameter" );
2287+ }
2288+ }
2289+ // Look for closing function tag
2290+ auto end_func = builder.try_find_literal (" </function>" );
2291+ if (end_func) {
2292+ builder.move_to (end_func->groups [0 ].end );
2293+ builder.consume_spaces (); // Consume whitespace after </function>
2294+
2295+ // Add the tool call with parsed arguments, but only if we REALLY got the literal
2296+ auto eaten_fragment = builder.input ().substr (end_func->groups [0 ].begin , end_func->groups [0 ].end );
2297+ auto funlen = std::string (" </function>" ).length ();
2298+ if (eaten_fragment.length () >= funlen && eaten_fragment.substr (0 , funlen) == std::string (" </function>" )) {
2299+ if (!builder.add_tool_call (function_name, " " , args.dump ())) {
2300+ throw common_chat_msg_partial_exception (" Incomplete tool call" );
2301+ }
2302+ } else {
2303+ throw common_chat_msg_partial_exception (" Incomplete tool call" );
2304+ }
2305+ } else {
2306+ throw common_chat_msg_partial_exception (" Incomplete tool call" );
2307+ }
2308+ // Look for closing tool call tag
2309+ if (auto end_tool = builder.try_find_regex (tool_call_end_regex, std::string::npos, false )) {
2310+ builder.move_to (end_tool->groups [0 ].end );
2311+ builder.consume_spaces (); // Consume trailing whitespace after tool call
2312+ } else {
2313+ throw common_chat_msg_partial_exception (" Incomplete tool call" );
2314+ }
2315+ } else {
2316+ // No function found - don't consume content here, let it be handled at the end
2317+ break ;
2318+ }
2319+ }
2320+
2321+ // Consume any remaining whitespace after all tool call processing
2322+ builder.consume_spaces ();
2323+ auto remaining = builder.consume_rest ();
2324+ // If there's any non-whitespace content remaining, add it as content
2325+ if (!string_strip (remaining).empty ()) {
2326+ builder.add_content (remaining);
2327+ }
2328+ }
2329+
22462330static common_chat_params common_chat_params_init_without_tools (const common_chat_template & tmpl, const struct templates_params & inputs) {
22472331 common_chat_params data;
22482332 data.prompt = apply (tmpl, inputs);
@@ -2259,8 +2343,62 @@ static common_chat_params common_chat_params_init_without_tools(const common_cha
22592343 return data;
22602344}
22612345
2346+ static common_chat_params common_chat_params_init_seed_oss (
2347+ const common_chat_template & tmpl,
2348+ templates_params & params,
2349+ const common_chat_templates_inputs & inputs)
2350+ {
2351+ common_chat_params data;
2352+ data.prompt = apply (tmpl, params);
2353+ data.format = COMMON_CHAT_FORMAT_SEED_OSS;
2354+ if (string_ends_with (data.prompt , " <seed:think>" )) {
2355+ if (!inputs.enable_thinking ) {
2356+ data.prompt += " </seed:think>" ;
2357+ } else {
2358+ data.thinking_forced_open = true ;
2359+ }
2360+ }
2361+
2362+ if (params.tools .is_array () && !params.tools .empty ()) {
2363+ data.grammar_lazy = inputs.tool_choice != COMMON_CHAT_TOOL_CHOICE_REQUIRED;
2364+ data.grammar = build_grammar ([&](const common_grammar_builder & builder) {
2365+ std::vector<std::string> tool_rules;
2366+ foreach_function (params.tools , [&](const json & tool) {
2367+ const auto & function = tool.at (" function" );
2368+ std::string name = function.at (" name" );
2369+ auto parameters = function.at (" parameters" );
2370+ builder.resolve_refs (parameters);
2371+
2372+ // Create rule for Seed-OSS function call format
2373+ std::string param_rules;
2374+ if (parameters.contains (" properties" )) {
2375+ for (const auto & [key, value] : parameters.at (" properties" ).items ()) {
2376+ param_rules += " \" <parameter=" + key + " >\" " + builder.add_schema (name + " -arg-" + key, value) +
2377+ " \" </parameter>\" " ;
2378+ }
2379+ }
2380+
2381+ tool_rules.push_back (builder.add_rule (name + " -call" ,
2382+ " \" <seed:tool_call>\" space \" <function=" + name + " >\" space " +
2383+ param_rules +
2384+ " \" </function>\" space \" </seed:tool_call>\" " ));
2385+ });
2386+
2387+ data.grammar_triggers .push_back ({ COMMON_GRAMMAR_TRIGGER_TYPE_WORD, " <seed:tool_call>" });
2388+
2389+ data.preserved_tokens = {
2390+ " <seed:think>" , " </seed:think>" , " <seed:tool_call>" , " </seed:tool_call>" ,
2391+ " <function=" , " </function>" , " <parameter=" , " </parameter>" ,
2392+ };
2393+
2394+ builder.add_rule (" root" , string_join (tool_rules, " | " ));
2395+ });
2396+ }
2397+ return data;
2398+ }
2399+
22622400static common_chat_params common_chat_templates_apply_jinja (
2263- const struct common_chat_templates * tmpls,
2401+ const struct common_chat_templates * tmpls,
22642402 const struct common_chat_templates_inputs & inputs)
22652403{
22662404 templates_params params;
@@ -2338,6 +2476,11 @@ static common_chat_params common_chat_templates_apply_jinja(
23382476 return common_chat_params_init_gpt_oss (tmpl, params);
23392477 }
23402478
2479+ // Seed-OSS
2480+ if (src.find (" <seed:think>" ) != std::string::npos) {
2481+ return common_chat_params_init_seed_oss (tmpl, params, inputs);
2482+ }
2483+
23412484 // Use generic handler when mixing tools + JSON schema.
23422485 // TODO: support that mix in handlers below.
23432486 if ((params.tools .is_array () && params.json_schema .is_object ())) {
@@ -2500,6 +2643,9 @@ static void common_chat_parse(common_chat_msg_parser & builder) {
25002643 case COMMON_CHAT_FORMAT_GPT_OSS:
25012644 common_chat_parse_gpt_oss (builder);
25022645 break ;
2646+ case COMMON_CHAT_FORMAT_SEED_OSS:
2647+ common_chat_parse_seed_oss (builder);
2648+ break ;
25032649 default :
25042650 throw std::runtime_error (std::string (" Unsupported format: " ) + common_chat_format_name (builder.syntax ().format ));
25052651 }
0 commit comments