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|>.
This commit is contained in:
2026-04-14 11:25:11 +00:00
parent 120c8d9d8d
commit 3ee933951c

View File

@@ -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
# ------------------------------------------------------------------