@@ -1274,11 +1274,12 @@ static common_chat_params common_chat_params_init_kimi_k2(const common_chat_temp
12741274 return data;
12751275}
12761276
1277- // LFM2 format:
1278- // - Reasoning: <think>{reasoning}</think> (optional, only if enable_thinking is true)
1279- // - Content: text after reasoning (optional)
1280- // - Tool calls: <|tool_call_start|>[function_name(arg1="value1", arg2="value2")]<|tool_call_end|>
1281- // Tool calls can appear multiple times (parallel tool calls)
1277+ // LFM2 format: uses <|tool_list_start|>[...]<|tool_list_end|> in system prompt
1278+ // and <|tool_call_start|>[name(arg="val")]<|tool_call_end|> for tool calls.
1279+ // - Reasoning: <think>{reasoning}</think> (optional)
1280+ // - Content: text before a tool call (optional)
1281+ // - Tool calls: Python-style, e.g. [function_name(arg1="value1", arg2="value2")]
1282+ // Tool calls can appear multiple times (parallel tool calls supported)
12821283static common_chat_params common_chat_params_init_lfm2 (const common_chat_template & tmpl,
12831284 const autoparser::generation_params & inputs) {
12841285 common_chat_params data;
@@ -1319,9 +1320,9 @@ static common_chat_params common_chat_params_init_lfm2(const common_chat_templat
13191320 if (!has_tools || inputs.tool_choice == COMMON_CHAT_TOOL_CHOICE_NONE ) {
13201321 return generation_prompt + reasoning + p.content (p.rest ()) + end;
13211322 }
1322-
13231323 auto tool_calls = p.rule (" tool-calls" ,
1324- p.trigger_rule (" tool-call" , p.literal (TOOL_CALL_START ) +
1324+ p.trigger_rule (" tool-call" ,
1325+ p.literal (TOOL_CALL_START ) +
13251326 p.python_style_tool_calls (inputs.tools , inputs.parallel_tool_calls ) +
13261327 p.literal (TOOL_CALL_END )
13271328 )
@@ -1349,6 +1350,80 @@ static common_chat_params common_chat_params_init_lfm2(const common_chat_templat
13491350 { COMMON_GRAMMAR_TRIGGER_TYPE_WORD , TOOL_CALL_START }
13501351 };
13511352 }
1353+ return data;
1354+ }
1355+
1356+ // LFM2.5 format: uses plain "List of tools: [...]" in system prompt, no wrapper tokens.
1357+ // Tool calls are bare [name(arg="val")], though model may optionally emit <|tool_call_start|>.
1358+ // - Reasoning: <think>{reasoning}</think> (optional)
1359+ // - Content: text before a tool call (optional)
1360+ // - Tool calls: Python-style, e.g. [function_name(arg1="value1", arg2="value2")]
1361+ // Tool calls can appear multiple times (parallel tool calls supported)
1362+ static common_chat_params common_chat_params_init_lfm2_5 (const common_chat_template & tmpl,
1363+ const autoparser::generation_params & inputs) {
1364+ common_chat_params data;
1365+
1366+ data.prompt = common_chat_template_direct_apply (tmpl, inputs);
1367+ data.format = COMMON_CHAT_FORMAT_PEG_NATIVE ;
1368+ data.supports_thinking = true ;
1369+ data.preserved_tokens = {
1370+ " <|tool_call_start|>" ,
1371+ " <|tool_call_end|>" ,
1372+ " <think>" ,
1373+ " </think>" ,
1374+ };
1375+
1376+ auto has_tools = inputs.tools .is_array () && !inputs.tools .empty ();
1377+ auto extract_reasoning = inputs.reasoning_format != COMMON_REASONING_FORMAT_NONE ;
1378+ auto include_grammar = has_tools && inputs.tool_choice != COMMON_CHAT_TOOL_CHOICE_NONE ;
1379+
1380+ const std::string THINK_START = " <think>" ;
1381+ const std::string THINK_END = " </think>" ;
1382+
1383+ data.thinking_start_tag = THINK_START ;
1384+ data.thinking_end_tag = THINK_END ;
1385+
1386+ auto parser = build_chat_peg_parser ([&](common_chat_peg_builder & p) {
1387+ auto generation_prompt = p.prefix (inputs.generation_prompt , THINK_START );
1388+ auto end = p.end ();
1389+
1390+ auto reasoning = p.eps ();
1391+ if (extract_reasoning && inputs.enable_thinking ) {
1392+ reasoning = p.optional (THINK_START + p.reasoning (p.until (THINK_END )) + THINK_END );
1393+ }
1394+
1395+ if (!has_tools || inputs.tool_choice == COMMON_CHAT_TOOL_CHOICE_NONE ) {
1396+ return generation_prompt + reasoning + p.content (p.rest ()) + end;
1397+ }
1398+
1399+ auto tool_calls = p.rule (" tool-calls" ,
1400+ p.trigger_rule (" tool-call" ,
1401+ p.python_style_tool_calls (inputs.tools , inputs.parallel_tool_calls )
1402+ )
1403+ );
1404+
1405+ auto content = p.content (p.until_one_of ({" <|tool_call_start|>" , " [" }));
1406+ auto maybe_start = p.optional (p.literal (" <|tool_call_start|>" ));
1407+ return generation_prompt + reasoning + content + maybe_start + tool_calls + end;
1408+ });
1409+
1410+ data.parser = parser.save ();
1411+
1412+ if (include_grammar) {
1413+ data.grammar_lazy = inputs.tool_choice == COMMON_CHAT_TOOL_CHOICE_AUTO ;
1414+ data.grammar = build_grammar ([&](const common_grammar_builder & builder) {
1415+ foreach_function (inputs.tools , [&](const json & tool) {
1416+ const auto & function = tool.at (" function" );
1417+ auto schema = function.at (" parameters" );
1418+ builder.resolve_refs (schema);
1419+ });
1420+ parser.build_grammar (builder, data.grammar_lazy );
1421+ });
1422+ foreach_function (inputs.tools , [&](const json & tool) {
1423+ const std::string name = tool.at (" function" ).at (" name" );
1424+ data.grammar_triggers .push_back ({ COMMON_GRAMMAR_TRIGGER_TYPE_WORD , " [" + name + " (" });
1425+ });
1426+ }
13521427
13531428 return data;
13541429}
@@ -1530,14 +1605,21 @@ static std::optional<common_chat_params> try_specialized_template(
15301605 return common_chat_params_init_kimi_k2 (tmpl, params);
15311606 }
15321607
1533- // LFM2 - uses <|tool_list_start|>/ <|tool_list_end|> markers and <|tool_call_start|>[name(args)]<|tool_call_end|> format
1534- // Detection: template has "<|tool_list_start|>" and "<|tool_list_end|>" markers
1608+ // LFM2 format detection: template uses <|tool_list_start|>[...] <|tool_list_end|> around the tool list
1609+ // and <|tool_call_start|>[...]<|tool_call_end|> around each tool call
15351610 if (src.find (" <|tool_list_start|>" ) != std::string::npos &&
15361611 src.find (" <|tool_list_end|>" ) != std::string::npos) {
15371612 LOG_DBG (" Using specialized template: LFM2\n " );
15381613 return common_chat_params_init_lfm2 (tmpl, params);
15391614 }
15401615
1616+ // LFM2.5 format detection: template uses plain "List of tools: [...]" with no special tokens
1617+ if (src.find (" List of tools: [" ) != std::string::npos &&
1618+ src.find (" <|tool_list_start|>" ) == std::string::npos) {
1619+ LOG_DBG (" Using specialized template: LFM2.5\n " );
1620+ return common_chat_params_init_lfm2_5 (tmpl, params);
1621+ }
1622+
15411623 // GigaChatV3 format detection
15421624 if (src.find (" <|role_sep|>" ) != std::string::npos &&
15431625 src.find (" <|message_sep|>" ) != std::string::npos &&
0 commit comments