@@ -622,6 +622,7 @@ const char * common_chat_format_name(common_chat_format format) {
622
622
case COMMON_CHAT_FORMAT_COMMAND_R7B: return " Command R7B" ;
623
623
case COMMON_CHAT_FORMAT_GRANITE: return " Granite" ;
624
624
case COMMON_CHAT_FORMAT_GPT_OSS: return " GPT-OSS" ;
625
+ case COMMON_CHAT_FORMAT_SEED_OSS: return " Seed-OSS" ;
625
626
default :
626
627
throw std::runtime_error (" Unknown chat format" );
627
628
}
@@ -2059,6 +2060,94 @@ static void common_chat_parse_granite(common_chat_msg_parser & builder) {
2059
2060
}
2060
2061
}
2061
2062
2063
+ static void common_chat_parse_seed_oss (common_chat_msg_parser & builder) {
2064
+ // Parse thinking tags first - this handles the main reasoning content
2065
+ builder.try_parse_reasoning (" <seed:think>" , " </seed:think>" );
2066
+
2067
+ if (!builder.syntax ().parse_tool_calls ) {
2068
+ builder.add_content (builder.consume_rest ());
2069
+ return ;
2070
+ }
2071
+
2072
+ // Parse tool calls - Seed-OSS uses <seed:tool_call> format
2073
+ static const common_regex tool_call_begin_regex (" <seed:tool_call>" );
2074
+ static const common_regex tool_call_end_regex (" </seed:tool_call>" );
2075
+ static const common_regex function_regex (" <function=([^>]+)>" );
2076
+ static const common_regex param_regex (" <parameter=([^>]+)>" );
2077
+
2078
+ while (auto tool_res = builder.try_find_regex (tool_call_begin_regex)) {
2079
+ builder.consume_spaces (); // Consume whitespace after <seed:tool_call>
2080
+
2081
+ // Look for function call inside tool call, ignore any content before it
2082
+ if (auto func_res = builder.try_find_regex (function_regex, std::string::npos, false )) {
2083
+ auto function_name = builder.str (func_res->groups [1 ]);
2084
+
2085
+ // Parse Seed-OSS parameters <parameter=name>value</parameter>
2086
+ json args = json::object ();
2087
+ // Parse all parameters
2088
+ while (auto param_res = builder.try_find_regex (param_regex, std::string::npos, false )) {
2089
+ // again, ignore noise around parameters
2090
+ auto param_name = builder.str (param_res->groups [1 ]);
2091
+ builder.move_to (param_res->groups [0 ].end );
2092
+ builder.consume_spaces (); // Consume whitespace after parameter
2093
+ auto savedPos = builder.pos ();
2094
+ if (auto param_parse = builder.try_find_literal (" </parameter>" )) {
2095
+ auto param = param_parse->prelude ;
2096
+ builder.move_to (savedPos);
2097
+ try {
2098
+ if (auto param_res = builder.try_consume_json ()) {
2099
+ args[param_name] = param_res->json ;
2100
+ } else {
2101
+ args[param_name] = param;
2102
+ }
2103
+ } catch (json::exception &) {
2104
+ args[param_name] = param;
2105
+ }
2106
+ } else {
2107
+ throw common_chat_msg_partial_exception (" Incomplete tool parameter" );
2108
+ }
2109
+ }
2110
+ // Look for closing function tag
2111
+ auto end_func = builder.try_find_literal (" </function>" );
2112
+ if (end_func) {
2113
+ builder.move_to (end_func->groups [0 ].end );
2114
+ builder.consume_spaces (); // Consume whitespace after </function>
2115
+
2116
+ // Add the tool call with parsed arguments, but only if we REALLY got the literal
2117
+ auto eaten_fragment = builder.input ().substr (end_func->groups [0 ].begin , end_func->groups [0 ].end );
2118
+ auto funlen = std::string (" </function>" ).length ();
2119
+ if (eaten_fragment.length () >= funlen && eaten_fragment.substr (0 , funlen) == std::string (" </function>" )) {
2120
+ if (!builder.add_tool_call (function_name, " " , args.dump ())) {
2121
+ throw common_chat_msg_partial_exception (" Incomplete tool call" );
2122
+ }
2123
+ } else {
2124
+ throw common_chat_msg_partial_exception (" Incomplete tool call" );
2125
+ }
2126
+ } else {
2127
+ throw common_chat_msg_partial_exception (" Incomplete tool call" );
2128
+ }
2129
+ // Look for closing tool call tag
2130
+ if (auto end_tool = builder.try_find_regex (tool_call_end_regex, std::string::npos, false )) {
2131
+ builder.move_to (end_tool->groups [0 ].end );
2132
+ builder.consume_spaces (); // Consume trailing whitespace after tool call
2133
+ } else {
2134
+ throw common_chat_msg_partial_exception (" Incomplete tool call" );
2135
+ }
2136
+ } else {
2137
+ // No function found - don't consume content here, let it be handled at the end
2138
+ break ;
2139
+ }
2140
+ }
2141
+
2142
+ // Consume any remaining whitespace after all tool call processing
2143
+ builder.consume_spaces ();
2144
+ auto remaining = builder.consume_rest ();
2145
+ // If there's any non-whitespace content remaining, add it as content
2146
+ if (!string_strip (remaining).empty ()) {
2147
+ builder.add_content (remaining);
2148
+ }
2149
+ }
2150
+
2062
2151
static common_chat_params common_chat_params_init_without_tools (const common_chat_template & tmpl, const struct templates_params & inputs) {
2063
2152
common_chat_params data;
2064
2153
data.prompt = apply (tmpl, inputs);
@@ -2075,8 +2164,62 @@ static common_chat_params common_chat_params_init_without_tools(const common_cha
2075
2164
return data;
2076
2165
}
2077
2166
2167
+ static common_chat_params common_chat_params_init_seed_oss (
2168
+ const common_chat_template & tmpl,
2169
+ templates_params & params,
2170
+ const common_chat_templates_inputs & inputs)
2171
+ {
2172
+ common_chat_params data;
2173
+ data.prompt = apply (tmpl, params);
2174
+ data.format = COMMON_CHAT_FORMAT_SEED_OSS;
2175
+ if (string_ends_with (data.prompt , " <seed:think>" )) {
2176
+ if (!inputs.enable_thinking ) {
2177
+ data.prompt += " </seed:think>" ;
2178
+ } else {
2179
+ data.thinking_forced_open = true ;
2180
+ }
2181
+ }
2182
+
2183
+ if (params.tools .is_array () && !params.tools .empty ()) {
2184
+ data.grammar_lazy = inputs.tool_choice != COMMON_CHAT_TOOL_CHOICE_REQUIRED;
2185
+ data.grammar = build_grammar ([&](const common_grammar_builder & builder) {
2186
+ std::vector<std::string> tool_rules;
2187
+ foreach_function (params.tools , [&](const json & tool) {
2188
+ const auto & function = tool.at (" function" );
2189
+ std::string name = function.at (" name" );
2190
+ auto parameters = function.at (" parameters" );
2191
+ builder.resolve_refs (parameters);
2192
+
2193
+ // Create rule for Seed-OSS function call format
2194
+ std::string param_rules;
2195
+ if (parameters.contains (" properties" )) {
2196
+ for (const auto & [key, value] : parameters.at (" properties" ).items ()) {
2197
+ param_rules += " \" <parameter=" + key + " >\" " + builder.add_schema (name + " -arg-" + key, value) +
2198
+ " \" </parameter>\" " ;
2199
+ }
2200
+ }
2201
+
2202
+ tool_rules.push_back (builder.add_rule (name + " -call" ,
2203
+ " \" <seed:tool_call>\" space \" <function=" + name + " >\" space " +
2204
+ param_rules +
2205
+ " \" </function>\" space \" </seed:tool_call>\" " ));
2206
+ });
2207
+
2208
+ data.grammar_triggers .push_back ({ COMMON_GRAMMAR_TRIGGER_TYPE_WORD, " <seed:tool_call>" });
2209
+
2210
+ data.preserved_tokens = {
2211
+ " <seed:think>" , " </seed:think>" , " <seed:tool_call>" , " </seed:tool_call>" ,
2212
+ " <function=" , " </function>" , " <parameter=" , " </parameter>" ,
2213
+ };
2214
+
2215
+ builder.add_rule (" root" , string_join (tool_rules, " | " ));
2216
+ });
2217
+ }
2218
+ return data;
2219
+ }
2220
+
2078
2221
static common_chat_params common_chat_templates_apply_jinja (
2079
- const struct common_chat_templates * tmpls,
2222
+ const struct common_chat_templates * tmpls,
2080
2223
const struct common_chat_templates_inputs & inputs)
2081
2224
{
2082
2225
templates_params params;
@@ -2145,6 +2288,11 @@ static common_chat_params common_chat_templates_apply_jinja(
2145
2288
return common_chat_params_init_gpt_oss (tmpl, params);
2146
2289
}
2147
2290
2291
+ // Seed-OSS
2292
+ if (src.find (" <seed:think>" ) != std::string::npos) {
2293
+ return common_chat_params_init_seed_oss (tmpl, params, inputs);
2294
+ }
2295
+
2148
2296
// Use generic handler when mixing tools + JSON schema.
2149
2297
// TODO: support that mix in handlers below.
2150
2298
if ((params.tools .is_array () && params.json_schema .is_object ())) {
@@ -2303,6 +2451,9 @@ static void common_chat_parse(common_chat_msg_parser & builder) {
2303
2451
case COMMON_CHAT_FORMAT_GPT_OSS:
2304
2452
common_chat_parse_gpt_oss (builder);
2305
2453
break ;
2454
+ case COMMON_CHAT_FORMAT_SEED_OSS:
2455
+ common_chat_parse_seed_oss (builder);
2456
+ break ;
2306
2457
default :
2307
2458
throw std::runtime_error (std::string (" Unsupported format: " ) + common_chat_format_name (builder.syntax ().format ));
2308
2459
}
0 commit comments