From 3ee933951cb36d61f9a459bd5a2e4e2e66176c2e Mon Sep 17 00:00:00 2001 From: biondizzle Date: Tue, 14 Apr 2026 11:25:11 +0000 Subject: [PATCH] Tool parser: fallback to <|tool_call_begin|> when no section marker Some Kimi K2.5 model variants (nvidia/Kimi-K2.5-NVFP4) omit <|tool_calls_section_begin|> and go directly to <|tool_call_begin|>. The tool parser was only looking for section-level markers, so these tool calls were forwarded as raw content text instead of being parsed. Fix: _find_section_start and _find_section_start_end now fall back to <|tool_call_begin|> as a section start when no section-level marker is found. The section end falls back to <|tool_call_end|>. --- kimi_k2_tool_parser.py | 33 ++++++++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/kimi_k2_tool_parser.py b/kimi_k2_tool_parser.py index 6b1d298..75a764e 100644 --- a/kimi_k2_tool_parser.py +++ b/kimi_k2_tool_parser.py @@ -109,6 +109,9 @@ class KimiK2ToolParser(ToolParser): "<|tool_calls_section_end|>", "<|tool_call_section_end|>", ] + # Some model variants omit the section-level marker and go + # directly to <|tool_call_begin|>. Treat it as a fallback. + self._fallback_section_start: str = "<|tool_call_begin|>" # Primary variant for ToolParser base class / adjust_request self.tool_calls_start_token: str = "<|tool_calls_section_begin|>" self.tool_calls_end_token: str = "<|tool_calls_section_end|>" @@ -209,12 +212,23 @@ class KimiK2ToolParser(ToolParser): return function_name, raw_id def _find_section_start(self, text: str) -> int: - """Return the index of the first section-start marker, or -1.""" + """Return the index of the first section-start marker, or -1. + + Falls back to <|tool_call_begin|> if no section-level marker + is found. Some model variants skip <|tool_calls_section_begin|> + and go directly to <|tool_call_begin|>. + """ best = -1 for variant in self.tool_calls_section_start_variants: idx = text.find(variant) if idx != -1 and (best == -1 or idx < best): best = idx + # Fallback: if no section-level marker found, look for + # <|tool_call_begin|> directly. + if best == -1 and self._fallback_section_start: + idx = text.find(self._fallback_section_start) + if idx != -1: + best = idx return best def _find_section_start_end(self, text: str) -> tuple[int, int]: @@ -223,6 +237,9 @@ class KimiK2ToolParser(ToolParser): *start_of_inner* points just past the section-start marker. *end_of_inner* is the index of the section-end marker, or -1 if the section is still open. + + Falls back to <|tool_call_begin|> if no section-level marker + is found. """ for variant in self.tool_calls_section_start_variants: idx = text.find(variant) @@ -234,6 +251,20 @@ class KimiK2ToolParser(ToolParser): if end_idx != -1: return inner_start, end_idx return inner_start, -1 + + # Fallback: no section-level marker found. Look for + # <|tool_call_begin|> directly as the section start. + if self._fallback_section_start: + idx = text.find(self._fallback_section_start) + if idx != -1: + inner_start = idx + len(self._fallback_section_start) + # Look for <|tool_call_end|> as the section end + end_marker = self._fallback_section_start.replace("begin", "end") + end_idx = text.find(end_marker, inner_start) + if end_idx != -1: + return inner_start, end_idx + return inner_start, -1 + return -1, -1 # ------------------------------------------------------------------