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:
@@ -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
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user