@@ -1608,42 +1608,40 @@ static common_chat_params common_chat_params_init_kimi_k2(const common_chat_temp
16081608 return data;
16091609}
16101610
1611- // LFM2 format: uses <|tool_list_start|>[...]<|tool_list_end|> in system prompt
1612- // and <|tool_call_start|>[name(arg="val")]<|tool_call_end|> for tool calls.
1613- // - Reasoning: <think>{reasoning}</think> (optional)
1614- // - Content: text before a tool call (optional)
1615- // - Tool calls: Python-style, e.g. [function_name(arg1="value1", arg2="value2")]
1616- // Tool calls can appear multiple times (parallel tool calls supported)
1617- static common_chat_params common_chat_params_init_lfm2 (const common_chat_template & tmpl,
1618- const autoparser::generation_params & inputs) {
1611+ // LFM2/LFM2.5 parser. Tool calls are almost Python-style and parallel-capable
1612+ // (except dotted names and JSON literals true/false/null).
1613+ // Always wrapped in <|tool_call_start|>[name(args)]<|tool_call_end|> with optional <think> reasoning.
1614+ // tool_list_tokens preserves LFM2 system tool-list markers.
1615+ static common_chat_params common_chat_params_init_lfm2 (const common_chat_template & tmpl,
1616+ const autoparser::generation_params & inputs,
1617+ bool tool_list_tokens) {
16191618 common_chat_params data;
16201619
1621- data.prompt = common_chat_template_direct_apply_impl (tmpl, inputs);
1622- data.generation_prompt = common_chat_template_generation_prompt_impl (tmpl, inputs);
1623- data.format = COMMON_CHAT_FORMAT_PEG_NATIVE ;
1624- data.supports_thinking = true ;
1625- data.preserved_tokens = {
1626- " <|tool_list_start|>" ,
1627- " <|tool_list_end|>" ,
1628- " <|tool_call_start|>" ,
1629- " <|tool_call_end|>" ,
1630- " <think>" ,
1631- " </think>" ,
1632- };
1633-
1634- auto has_tools = inputs.tools .is_array () && !inputs.tools .empty ();
1635- auto extract_reasoning = inputs.reasoning_format != COMMON_REASONING_FORMAT_NONE ;
1636- auto include_grammar = has_tools && inputs.tool_choice != COMMON_CHAT_TOOL_CHOICE_NONE ;
1637-
16381620 const std::string TOOL_CALL_START = " <|tool_call_start|>" ;
16391621 const std::string TOOL_CALL_END = " <|tool_call_end|>" ;
1622+ const std::string TOOL_LIST_START = " <|tool_list_start|>" ;
1623+ const std::string TOOL_LIST_END = " <|tool_list_end|>" ;
16401624 const std::string THINK_START = " <think>" ;
16411625 const std::string THINK_END = " </think>" ;
16421626 const std::string GEN_PROMPT = " <|im_start|>assistant\n " ;
16431627
1628+ data.prompt = common_chat_template_direct_apply_impl (tmpl, inputs);
1629+ data.generation_prompt = common_chat_template_generation_prompt_impl (tmpl, inputs);
1630+ data.format = COMMON_CHAT_FORMAT_PEG_NATIVE ;
1631+ data.supports_thinking = true ;
1632+ data.preserved_tokens = { TOOL_CALL_START , TOOL_CALL_END , THINK_START , THINK_END };
1633+ if (tool_list_tokens) {
1634+ data.preserved_tokens .push_back (TOOL_LIST_START );
1635+ data.preserved_tokens .push_back (TOOL_LIST_END );
1636+ }
1637+
16441638 data.thinking_start_tag = THINK_START ;
16451639 data.thinking_end_tag = THINK_END ;
16461640
1641+ auto has_tools = inputs.tools .is_array () && !inputs.tools .empty ();
1642+ auto extract_reasoning = inputs.reasoning_format != COMMON_REASONING_FORMAT_NONE ;
1643+ auto include_grammar = has_tools && inputs.tool_choice != COMMON_CHAT_TOOL_CHOICE_NONE ;
1644+
16471645 if (inputs.has_continuation ()) {
16481646 const auto & msg = inputs.continue_msg ;
16491647
@@ -1670,7 +1668,7 @@ static common_chat_params common_chat_params_init_lfm2(const common_chat_templat
16701668 auto tool_calls = p.rule (" tool-calls" ,
16711669 p.trigger_rule (" tool-call" ,
16721670 p.literal (TOOL_CALL_START ) +
1673- p.python_style_tool_calls (inputs.tools , inputs.parallel_tool_calls ) +
1671+ p.python_style_tool_calls (inputs.tools , inputs.parallel_tool_calls , /* allow_json_literals = */ true ) +
16741672 p.literal (TOOL_CALL_END )
16751673 )
16761674 );
@@ -1697,93 +1695,6 @@ static common_chat_params common_chat_params_init_lfm2(const common_chat_templat
16971695 { COMMON_GRAMMAR_TRIGGER_TYPE_WORD , TOOL_CALL_START }
16981696 };
16991697 }
1700- return data;
1701- }
1702-
1703- // LFM2.5 format: uses plain "List of tools: [...]" in system prompt, no wrapper tokens.
1704- // Tool calls are bare [name(arg="val")], though model may optionally emit <|tool_call_start|>.
1705- // - Reasoning: <think>{reasoning}</think> (optional)
1706- // - Content: text before a tool call (optional)
1707- // - Tool calls: Python-style, e.g. [function_name(arg1="value1", arg2="value2")]
1708- // Tool calls can appear multiple times (parallel tool calls supported)
1709- static common_chat_params common_chat_params_init_lfm2_5 (const common_chat_template & tmpl,
1710- const autoparser::generation_params & inputs) {
1711- common_chat_params data;
1712-
1713- data.prompt = common_chat_template_direct_apply_impl (tmpl, inputs);
1714- data.generation_prompt = common_chat_template_generation_prompt_impl (tmpl, inputs);
1715- data.format = COMMON_CHAT_FORMAT_PEG_NATIVE ;
1716- data.supports_thinking = true ;
1717- data.preserved_tokens = {
1718- " <|tool_call_start|>" ,
1719- " <|tool_call_end|>" ,
1720- " <think>" ,
1721- " </think>" ,
1722- };
1723-
1724- auto has_tools = inputs.tools .is_array () && !inputs.tools .empty ();
1725- auto extract_reasoning = inputs.reasoning_format != COMMON_REASONING_FORMAT_NONE ;
1726- auto include_grammar = has_tools && inputs.tool_choice != COMMON_CHAT_TOOL_CHOICE_NONE ;
1727-
1728- const std::string THINK_START = " <think>" ;
1729- const std::string THINK_END = " </think>" ;
1730- const std::string GEN_PROMPT = " <|im_start|>assistant\n " ;
1731-
1732- data.thinking_start_tag = THINK_START ;
1733- data.thinking_end_tag = THINK_END ;
1734-
1735- if (inputs.has_continuation ()) {
1736- const auto & msg = inputs.continue_msg ;
1737-
1738- data.generation_prompt = GEN_PROMPT + THINK_START + msg.reasoning_content ;
1739- if (inputs.continue_final_message == COMMON_CHAT_CONTINUATION_CONTENT ) {
1740- data.generation_prompt += THINK_END + msg.render_content ();
1741- }
1742-
1743- data.prompt += data.generation_prompt ;
1744- }
1745-
1746- auto parser = build_chat_peg_parser ([&](common_chat_peg_builder & p) {
1747- auto generation_prompt = p.literal (GEN_PROMPT );
1748- auto end = p.end ();
1749-
1750- auto reasoning = p.eps ();
1751- if (extract_reasoning && inputs.enable_thinking ) {
1752- reasoning = p.optional (THINK_START + p.reasoning (p.until (THINK_END )) + THINK_END );
1753- }
1754-
1755- if (!has_tools || inputs.tool_choice == COMMON_CHAT_TOOL_CHOICE_NONE ) {
1756- return generation_prompt + reasoning + p.content (p.rest ()) + end;
1757- }
1758-
1759- auto tool_calls = p.rule (" tool-calls" ,
1760- p.trigger_rule (" tool-call" ,
1761- p.python_style_tool_calls (inputs.tools , inputs.parallel_tool_calls )
1762- )
1763- );
1764-
1765- auto content = p.content (p.until_one_of ({" <|tool_call_start|>" , " [" }));
1766- auto maybe_start = p.optional (p.literal (" <|tool_call_start|>" ));
1767- return generation_prompt + reasoning + content + maybe_start + tool_calls + end;
1768- });
1769-
1770- data.parser = parser.save ();
1771-
1772- if (include_grammar) {
1773- data.grammar_lazy = inputs.tool_choice == COMMON_CHAT_TOOL_CHOICE_AUTO ;
1774- data.grammar = build_grammar ([&](const common_grammar_builder & builder) {
1775- foreach_function (inputs.tools , [&](const json & tool) {
1776- const auto & function = tool.at (" function" );
1777- auto schema = function.at (" parameters" );
1778- builder.resolve_refs (schema);
1779- });
1780- parser.build_grammar (builder, data.grammar_lazy );
1781- });
1782- foreach_function (inputs.tools , [&](const json & tool) {
1783- const std::string name = tool.at (" function" ).at (" name" );
1784- data.grammar_triggers .push_back ({ COMMON_GRAMMAR_TRIGGER_TYPE_WORD , " [" + name + " (" });
1785- });
1786- }
17871698
17881699 return data;
17891700}
@@ -2298,14 +2209,14 @@ std::optional<common_chat_params> common_chat_try_specialized_template(
22982209
22992210 if (is_lfm2_template (src)) {
23002211 LOG_DBG (" Using specialized template: LFM2\n " );
2301- return common_chat_params_init_lfm2 (tmpl, params);
2212+ return common_chat_params_init_lfm2 (tmpl, params, /* tool_list_tokens = */ true );
23022213 }
23032214
23042215 // LFM2.5 format detection: template uses plain "List of tools: [...]" with no special tokens
23052216 if (src.find (" List of tools: [" ) != std::string::npos &&
23062217 src.find (" <|tool_list_start|>" ) == std::string::npos) {
23072218 LOG_DBG (" Using specialized template: LFM2.5\n " );
2308- return common_chat_params_init_lfm2_5 (tmpl, params);
2219+ return common_chat_params_init_lfm2 (tmpl, params, /* tool_list_tokens = */ false );
23092220 }
23102221
23112222 // GigaChatV3 format detection
0 commit comments