Skip to content

vllm.parser.kimi_k3

Classes:

  • KimiK3Parser

    Compose the Kimi K3 reasoning and tool parsers for XTML output.

KimiK3Parser

Bases: DelegatingParser

Compose the Kimi K3 reasoning and tool parsers for XTML output.

Source code in vllm/parser/kimi_k3.py
class KimiK3Parser(DelegatingParser):
    """Compose the Kimi K3 reasoning and tool parsers for XTML output."""

    # TODO: Switch Kimi K3 to the parser engine once its XTML reasoning/tool
    # path is covered there.
    def _extract_tool_calls(
        self,
        content: str | None,
        request: ChatCompletionRequest | ResponsesRequest,
        enable_auto_tools: bool = False,
    ) -> tuple[list[FunctionCall] | None, str | None]:
        if self._tool_parser is None or not enable_auto_tools:
            return super()._extract_tool_calls(content, request, enable_auto_tools)

        tool_call_info = self.extract_tool_calls(content or "", request=request)
        if request.tool_choice == "none":
            return [], tool_call_info.content
        if not tool_call_info.tools_called:
            return None, tool_call_info.content

        tool_calls = [
            FunctionCall(
                id=tool_call.id,
                name=tool_call.function.name,
                arguments=tool_call.function.arguments,
            )
            for tool_call in tool_call_info.tool_calls
        ]
        parsed_content = tool_call_info.content
        if parsed_content and parsed_content.strip() == "":
            parsed_content = None
        return tool_calls, parsed_content

    def _extract_tool_calls_streaming(
        self,
        previous_text: str,
        current_text: str,
        delta_text: str,
        previous_token_ids: Sequence[int],
        current_token_ids: Sequence[int],
        delta_token_ids: Sequence[int],
        request: ChatCompletionRequest | ResponsesRequest,
        tool_call_idx: int | None = None,
        tool_call_id_type: str = "random",
        function_name_returned: bool = False,
    ) -> tuple[DeltaMessage | None, bool]:
        if request.tool_choice != "none":
            return super()._extract_tool_calls_streaming(
                previous_text,
                current_text,
                delta_text,
                previous_token_ids,
                current_token_ids,
                delta_token_ids,
                request,
                tool_call_idx=tool_call_idx,
                tool_call_id_type=tool_call_id_type,
                function_name_returned=function_name_returned,
            )

        delta_message = self.extract_tool_calls_streaming(
            previous_text,
            current_text,
            delta_text,
            previous_token_ids,
            current_token_ids,
            delta_token_ids,
            request,
        )
        if delta_message is not None:
            delta_message.tool_calls = []
        return delta_message, False

    def parse_delta(
        self,
        delta_text: str,
        delta_token_ids: list[int],
        request: ChatCompletionRequest | ResponsesRequest,
        prompt_token_ids: list[int] | None = None,
        *,
        finished: bool,
    ) -> DeltaMessage | None:
        state = self._stream_state
        previous_content = state.previous_text if state.reasoning_ended else ""
        delta_message = super().parse_delta(
            delta_text,
            delta_token_ids,
            request,
            prompt_token_ids,
            finished=finished,
        )

        if (
            self._tool_parser is not None
            or not isinstance(self._reasoning_parser, KimiK3ReasoningParser)
            or not state.reasoning_ended
            or delta_message is None
        ):
            return delta_message

        stripped = self._reasoning_parser.strip_content_streaming(
            previous_text=previous_content,
            current_text=state.previous_text,
        )
        delta_message.content = stripped.content if stripped is not None else None
        if (
            delta_message.role is None
            and delta_message.content is None
            and delta_message.reasoning is None
            and not delta_message.tool_calls
        ):
            return None
        return delta_message