Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 12 additions & 5 deletions python/sglang/srt/entrypoints/openai/serving_chat.py
Original file line number Diff line number Diff line change
Expand Up @@ -1138,11 +1138,7 @@ def _process_tool_call_id(
history_tool_calls_cnt: int,
) -> str:
"""Process for generating a new and unique `tool_call_id`"""
if self.tool_call_parser != "kimi_k2":
# A simple uuid is sufficient for all models except for Kimi-K2.
tool_call_id = f"call_{uuid.uuid4().hex[:24]}"
return tool_call_id
else:
if self.tool_call_parser == "kimi_k2":
# Align with Kimi-K2 format: functions.{name}:{index}
# Kimi-K2 allows multiple tool_calls in one message; SGLang sets call_item.tool_index to the *local* position inside that message.
# Therefore, the index must be corrected by using `history_tool_calls_cnt + call_item.tool_index` to ensure globally unique and properly ordered.
Expand All @@ -1151,6 +1147,17 @@ def _process_tool_call_id(
f"Process tool call idx, parser: {self.tool_call_parser}, tool_call_id: {tool_call_id}, history_cnt: {history_tool_calls_cnt}"
)
return tool_call_id
if self.tool_call_parser == "kimi_k2_raw_id":
# RL training needs the model-emitted tool_call_id round-tripped verbatim,
# so we skip the history-based renumbering above and return whatever the
# detector captured. Fall back to the canonical Kimi-K2 reconstruction
# (without history offset) if for any reason the detector did not record
# a raw id — the raw id field is best-effort but the format is stable.
if call_item.tool_call_id:
return call_item.tool_call_id
return f"functions.{call_item.name}:{call_item.tool_index}"
# A simple uuid is sufficient for all other models.
return f"call_{uuid.uuid4().hex[:24]}"

def _process_tool_calls(
self,
Expand Down
6 changes: 6 additions & 0 deletions python/sglang/srt/function_call/core_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,12 @@ class ToolCallItem(BaseModel):
tool_index: int
name: Optional[str] = None
parameters: str # JSON string
# The tool_call_id string emitted by the model, captured verbatim.
# Only populated by detectors whose downstream consumers need the exact
# model-emitted id (e.g. RL training trajectories). Existing detectors
# leave this as None and the serving layer falls back to its usual id
# generation strategy.
tool_call_id: Optional[str] = None


class StreamingParseResult(BaseModel):
Expand Down
6 changes: 5 additions & 1 deletion python/sglang/srt/function_call/function_call_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,10 @@
from sglang.srt.function_call.gpt_oss_detector import GptOssDetector
from sglang.srt.function_call.hermes_detector import HermesDetector
from sglang.srt.function_call.internlm_detector import InternlmDetector
from sglang.srt.function_call.kimik2_detector import KimiK2Detector
from sglang.srt.function_call.kimik2_detector import (
KimiK2Detector,
KimiK2RawIdDetector,
)
from sglang.srt.function_call.lfm2_detector import Lfm2Detector
from sglang.srt.function_call.llama32_detector import Llama32Detector
from sglang.srt.function_call.mimo_detector import MiMoDetector
Expand Down Expand Up @@ -54,6 +57,7 @@ class FunctionCallParser:
"glm47": Glm47MoeDetector,
"gpt-oss": GptOssDetector,
"kimi_k2": KimiK2Detector,
"kimi_k2_raw_id": KimiK2RawIdDetector,
"lfm2": Lfm2Detector,
"llama3": Llama32Detector,
"mimo": MiMoDetector,
Expand Down
22 changes: 22 additions & 0 deletions python/sglang/srt/function_call/kimik2_detector.py
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ def detect_and_parse(self, text: str, tools: List[Tool]) -> StreamingParseResult
tool_index=function_idx,
name=function_name,
parameters=function_args,
tool_call_id=function_id,
)
)

Expand Down Expand Up @@ -177,6 +178,7 @@ def parse_streaming_increment(
tool_index=self.current_tool_id,
name=function_name,
parameters="",
tool_call_id=function_id,
)
)
self.current_tool_name_sent = True
Expand Down Expand Up @@ -253,3 +255,23 @@ def get_info(name: str) -> StructureInfo:
)

return get_info


class KimiK2RawIdDetector(KimiK2Detector):
"""
Variant of KimiK2Detector that preserves the model-emitted tool_call_id verbatim.

The default kimi_k2 path renumbers ids via `history_tool_calls_cnt + tool_index`
in the serving layer so that multi-turn conversations get globally unique,
monotonically increasing ids (see PR #10600). That is the right behavior for
chat use cases.

RL training has the opposite requirement: the trajectory must round-trip the
exact tool_call_id the model produced (e.g. `functions.foo:5`), so that the
follow-up tool result turn references the same id the policy emitted. This
subclass exists purely as a marker so the serving layer can branch on the
parser name and use `ToolCallItem.tool_call_id` directly. Parsing logic
is identical to KimiK2Detector.
"""

pass
Loading