@@ -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 }
@@ -2059,6 +2060,130 @@ static void common_chat_parse_granite(common_chat_msg_parser & builder) {
20592060 }
20602061}
20612062
2063+ static void purge_healing_marker (json & json_obj, const std::string & healing_marker) {
2064+ if (json_obj.is_array ()) {
2065+ // Check if the last element is the healing marker and remove it
2066+ if (!json_obj.empty () && json_obj.back ().is_string () && json_obj.back () == healing_marker) {
2067+ json_obj.erase (json_obj.size () - 1 );
2068+ }
2069+ } else if (json_obj.is_object ()) {
2070+ // Remove healing marker if it's a key
2071+ if (json_obj.contains (healing_marker)) {
2072+ json_obj.erase (healing_marker);
2073+ }
2074+
2075+ // Remove healing marker if it's a value
2076+ for (auto it = json_obj.begin (); it != json_obj.end ();) {
2077+ if (it.value ().is_string () && it.value () == healing_marker) {
2078+ it = json_obj.erase (it);
2079+ } else {
2080+ ++it;
2081+ }
2082+ }
2083+ }
2084+ }
2085+
2086+ static void common_chat_parse_seed_oss (common_chat_msg_parser & builder) {
2087+ // Parse thinking tags first - this handles the main reasoning content
2088+ builder.try_parse_reasoning (" <seed:think>" , " </seed:think>" );
2089+
2090+ if (!builder.syntax ().parse_tool_calls ) {
2091+ builder.add_content (builder.consume_rest ());
2092+ return ;
2093+ }
2094+
2095+ // Parse tool calls - Seed-OSS uses <seed:tool_call> format
2096+ static const common_regex tool_call_begin_regex (" <seed:tool_call>" );
2097+ static const common_regex tool_call_end_regex (" </seed:tool_call>" );
2098+ static const common_regex function_regex (" <function=([^>]+)>" );
2099+ static const common_regex close_function_regex (" </function>" );
2100+
2101+ while (auto tool_res = builder.try_find_regex (tool_call_begin_regex)) {
2102+ builder.consume_spaces (); // Consume whitespace after <seed:tool_call>
2103+
2104+ // Look for function call inside tool call, ignore any content before it
2105+ if (auto func_res = builder.try_find_regex (function_regex, std::string::npos, false )) {
2106+ auto function_name = builder.str (func_res->groups [1 ]);
2107+
2108+ // Parse Seed-OSS parameters <parameter=name>value</parameter>
2109+ json args = json::object ();
2110+ static const common_regex param_regex (" <parameter=([^>]+)>" );
2111+ // Parse all parameters
2112+ while (auto param_res = builder.try_find_regex (param_regex, std::string::npos, false )) {
2113+ // again, ignore noise around parameters
2114+ auto param_name = builder.str (param_res->groups [1 ]);
2115+ builder.move_to (param_res->groups [0 ].end );
2116+ builder.consume_spaces (); // Consume whitespace after parameter
2117+ auto savedPos = builder.pos ();
2118+ if (auto param_parse = builder.try_find_literal (" </parameter>" )) {
2119+ auto param = param_parse->prelude ;
2120+ builder.move_to (savedPos);
2121+ try {
2122+ if (auto param_res = builder.try_consume_json ()) {
2123+ args[param_name] = param_res->json ;
2124+ } else {
2125+ args[param_name] = param;
2126+ }
2127+ } catch (json::exception &) {
2128+ args[param_name] = param;
2129+ }
2130+ } else {
2131+ try {
2132+ if (auto param_res = builder.try_consume_json ()) {
2133+ auto json_obj = param_res->json ;
2134+ purge_healing_marker (json_obj, builder.healing_marker ());
2135+ if (!json_obj.is_null ()) {
2136+ args[param_name] = json_obj;
2137+ }
2138+ } else {
2139+ auto rest = builder.consume_rest ();
2140+ args[param_name] = rest;
2141+ }
2142+ } catch (json::exception &) {
2143+ auto rest = builder.consume_rest ();
2144+ args[param_name] = rest;
2145+ }
2146+ builder.add_tool_call (function_name, " " , args.dump ());
2147+ throw common_chat_msg_partial_exception (" Incomplete tool parameter" );
2148+ }
2149+ }
2150+ // Look for closing function tag
2151+ auto end_func = builder.try_find_literal (" </function>" );
2152+ if (end_func) {
2153+ builder.move_to (end_func->groups [0 ].end );
2154+ builder.consume_spaces (); // Consume whitespace after </function>
2155+
2156+ // Add the tool call with parsed arguments
2157+ if (!builder.add_tool_call (function_name, " " , args.dump ())) {
2158+ throw common_chat_msg_partial_exception (" Incomplete tool call" );
2159+ }
2160+ } else {
2161+ builder.add_tool_call (function_name, " " , args.dump ()); // add partial tool parse
2162+ throw common_chat_msg_partial_exception (" Incomplete tool call" );
2163+ }
2164+
2165+ // Look for closing tool call tag
2166+ if (auto end_tool = builder.try_find_regex (tool_call_end_regex, std::string::npos, false )) {
2167+ builder.move_to (end_tool->groups [0 ].end );
2168+ builder.consume_spaces (); // Consume trailing whitespace after tool call
2169+ } else {
2170+ throw common_chat_msg_partial_exception (" Incomplete tool call" );
2171+ }
2172+ } else {
2173+ // No function found - don't consume content here, let it be handled at the end
2174+ break ;
2175+ }
2176+ }
2177+
2178+ // Consume any remaining whitespace after all tool call processing
2179+ builder.consume_spaces ();
2180+ auto remaining = builder.consume_rest ();
2181+ // If there's any non-whitespace content remaining, add it as content
2182+ if (!string_strip (remaining).empty ()) {
2183+ builder.add_content (remaining);
2184+ }
2185+ }
2186+
20622187static common_chat_params common_chat_params_init_without_tools (const common_chat_template & tmpl, const struct templates_params & inputs) {
20632188 common_chat_params data;
20642189 data.prompt = apply (tmpl, inputs);
@@ -2075,10 +2200,60 @@ static common_chat_params common_chat_params_init_without_tools(const common_cha
20752200 return data;
20762201}
20772202
2078- static common_chat_params common_chat_templates_apply_jinja (
2079- const struct common_chat_templates * tmpls,
2080- const struct common_chat_templates_inputs & inputs)
2081- {
2203+ static common_chat_params common_chat_params_init_seed_oss ( const common_chat_template & tmpl,
2204+ templates_params & params,
2205+ const common_chat_templates_inputs & inputs) {
2206+ common_chat_params data;
2207+ data.prompt = apply (tmpl, params);
2208+ data.format = COMMON_CHAT_FORMAT_SEED_OSS;
2209+ if (string_ends_with (data.prompt , " <seed:think>" )) {
2210+ if (!inputs.enable_thinking ) {
2211+ data.prompt += " </seed:think>" ;
2212+ } else {
2213+ data.thinking_forced_open = true ;
2214+ }
2215+ }
2216+
2217+ if (params.tools .is_array () && !params.tools .empty ()) {
2218+ data.grammar_lazy = inputs.tool_choice != COMMON_CHAT_TOOL_CHOICE_REQUIRED;
2219+ data.grammar = build_grammar ([&](const common_grammar_builder & builder) {
2220+ std::vector<std::string> tool_rules;
2221+ foreach_function (params.tools , [&](const json & tool) {
2222+ const auto & function = tool.at (" function" );
2223+ std::string name = function.at (" name" );
2224+ auto parameters = function.at (" parameters" );
2225+ builder.resolve_refs (parameters);
2226+
2227+ // Create rule for Seed-OSS function call format
2228+ std::string param_rules;
2229+ if (parameters.contains (" properties" )) {
2230+ for (const auto & [key, value] : parameters.at (" properties" ).items ()) {
2231+ param_rules += " <parameter=" + key + " >" + builder.add_schema (name + " -arg-" + key, value) +
2232+ " </parameter>" ;
2233+ }
2234+ }
2235+
2236+ tool_rules.push_back (builder.add_rule (name + " -call" ,
2237+ " \" <seed:tool_call>\" space \" <function=" + name + " >\" space " +
2238+ param_rules +
2239+ " \" </function>\" space \" </seed:tool_call>\" " ));
2240+ });
2241+
2242+ data.grammar_triggers .push_back ({ COMMON_GRAMMAR_TRIGGER_TYPE_WORD, " <seed:tool_call>" });
2243+
2244+ data.preserved_tokens = {
2245+ " <seed:think>" , " </seed:think>" , " <seed:tool_call>" , " </seed:tool_call>" ,
2246+ " <function=" , " </function>" , " <parameter=" , " </parameter>" ,
2247+ };
2248+
2249+ builder.add_rule (" root" , string_join (tool_rules, " | " ));
2250+ });
2251+ }
2252+ return data;
2253+ }
2254+
2255+ static common_chat_params common_chat_templates_apply_jinja (const struct common_chat_templates * tmpls,
2256+ const struct common_chat_templates_inputs & inputs) {
20822257 templates_params params;
20832258 params.tools = common_chat_tools_to_json_oaicompat<json>(inputs.tools );
20842259 const auto & tmpl = params.tools .is_array () && tmpls->template_tool_use
@@ -2145,6 +2320,11 @@ static common_chat_params common_chat_templates_apply_jinja(
21452320 return common_chat_params_init_gpt_oss (tmpl, params);
21462321 }
21472322
2323+ // Seed-OSS
2324+ if (src.find (" <seed:think>" ) != std::string::npos) {
2325+ return common_chat_params_init_seed_oss (tmpl, params, inputs);
2326+ }
2327+
21482328 // Use generic handler when mixing tools + JSON schema.
21492329 // TODO: support that mix in handlers below.
21502330 if ((params.tools .is_array () && params.json_schema .is_object ())) {
@@ -2303,6 +2483,9 @@ static void common_chat_parse(common_chat_msg_parser & builder) {
23032483 case COMMON_CHAT_FORMAT_GPT_OSS:
23042484 common_chat_parse_gpt_oss (builder);
23052485 break ;
2486+ case COMMON_CHAT_FORMAT_SEED_OSS:
2487+ common_chat_parse_seed_oss (builder);
2488+ break ;
23062489 default :
23072490 throw std::runtime_error (std::string (" Unsupported format: " ) + common_chat_format_name (builder.syntax ().format ));
23082491 }
0 commit comments