@@ -1741,10 +1741,12 @@ static void common_chat_parse_gpt_oss(common_chat_msg_parser & builder) {
17411741static common_chat_params common_chat_params_init_firefunction_v2 (const common_chat_template & tmpl, const struct templates_params & inputs) {
17421742 LOG_DBG (" %s\n " , __func__);
17431743 common_chat_params data;
1744- data.prompt = apply (tmpl, inputs, /* messages_override =*/ std::nullopt , /* tools_override= */ json (), json {
1744+ const std::optional<json> tools_override = json ();
1745+ const std::optional<json> additional_context = json {
17451746 {" datetime" , format_time (inputs.now , " %b %d %Y %H:%M:%S GMT" )},
17461747 {" functions" , json (inputs.tools .empty () ? " " : inputs.tools .dump (2 ))},
1747- });
1748+ };
1749+ data.prompt = apply (tmpl, inputs, /* messages_override =*/ std::nullopt , tools_override, additional_context);
17481750 if (inputs.tools .is_array () && !inputs.tools .empty ()) {
17491751 data.grammar_lazy = inputs.tool_choice != COMMON_CHAT_TOOL_CHOICE_REQUIRED;
17501752 data.grammar = build_grammar ([&](const common_grammar_builder & builder) {
@@ -2230,15 +2232,28 @@ static common_chat_params common_chat_params_init_granite(const common_chat_temp
22302232
22312233static void common_chat_parse_granite (common_chat_msg_parser & builder) {
22322234 // Parse thinking tags
2235+ static const common_regex start_think_regex (regex_escape (" <think>" ));
2236+ static const common_regex end_think_regex (regex_escape (" </think>" ));
2237+ // Granite models output partial tokens such as "<" and "<think".
2238+ // By leveraging try_consume_regex()/try_find_regex() throwing
2239+ // common_chat_msg_partial_exception for these partial tokens,
2240+ // processing is interrupted and the tokens are not passed to add_content().
2241+ if (auto res = builder.try_consume_regex (start_think_regex)) {
2242+ // Restore position for try_parse_reasoning()
2243+ builder.move_to (res->groups [0 ].begin );
2244+ builder.try_find_regex (end_think_regex, std::string::npos, false );
2245+ // Restore position for try_parse_reasoning()
2246+ builder.move_to (res->groups [0 ].begin );
2247+ }
22332248 builder.try_parse_reasoning (" <think>" , " </think>" );
22342249
2235- // Parse response tags using regex
2236- static const common_regex response_regex ( " <response>([ \\ s \\ S]*?)</response> " );
2237- if ( auto res = builder. try_find_regex (response_regex)) {
2238- // Extract the content between the tags (capture group 1)
2239- auto content = builder. str (res-> groups [ 1 ]);
2240- builder.add_content (content);
2241- builder.move_to (res-> groups [ 0 ]. end );
2250+ // Parse response tags
2251+ static const common_regex start_response_regex ( regex_escape ( " <response>" ) );
2252+ static const common_regex end_response_regex ( regex_escape ( " </response> " ));
2253+ // Granite models output partial tokens such as "<" and "<response".
2254+ // Same hack as reasoning parsing.
2255+ if ( builder.try_consume_regex (start_response_regex)) {
2256+ builder.try_find_regex (end_response_regex );
22422257 }
22432258
22442259 if (!builder.syntax ().parse_tool_calls ) {
@@ -2252,13 +2267,10 @@ static void common_chat_parse_granite(common_chat_msg_parser & builder) {
22522267 builder.move_to (res->groups [0 ].end );
22532268
22542269 // Expect JSON array of tool calls
2255- auto tool_calls_data = builder.consume_json ();
2256- if (tool_calls_data.json .is_array ()) {
2257- if (!builder.add_tool_calls (tool_calls_data.json )) {
2258- builder.add_content (" <|tool_call|>" + tool_calls_data.json .dump ());
2270+ if (auto tool_call = builder.try_consume_json_with_dumped_args ({{{" arguments" }}})) {
2271+ if (!builder.add_tool_calls (tool_call->value ) || tool_call->is_partial ) {
2272+ throw common_chat_msg_partial_exception (" incomplete tool call" );
22592273 }
2260- } else {
2261- builder.add_content (" <|tool_call|>" + tool_calls_data.json .dump ());
22622274 }
22632275 } else {
22642276 builder.add_content (builder.consume_rest ());
0 commit comments