class KimiK3Renderer(BaseRenderer[HfTokenizer]):
"""Render chat prompts with Kimi K3's Python XTML encoding.
K3 ships no Jinja chat template; its tokenizer renders messages through
``encoding_k3`` instead. We tokenize eagerly so the structural markers keep
their special-token ids while user- and tool-supplied text stays ordinary.
"""
def __init__(self, config: VllmConfig, tokenizer: HfTokenizer | None) -> None:
super().__init__(config, tokenizer)
self._apply_chat_template_async = make_async(
self._apply_chat_template, executor=self._executor
)
def _apply_chat_template(
self,
conversation: list[dict[str, Any]],
params: ChatParams,
) -> list[int]:
# Tokenize eagerly: K3 encodes structural markers as special tokens and
# user/tool text as ordinary tokens, so we cannot defer to a plain
# re-tokenization of the rendered string downstream.
kwargs = params.get_apply_chat_template_kwargs()
_apply_k3_thinking_kwargs(kwargs)
if params.tool_choice not in (None, "auto"):
kwargs["tool_choice"] = _dump_k3_template_value(params.tool_choice)
if params.response_format is not None:
kwargs["response_format"] = _dump_k3_template_value(params.response_format)
kwargs["tokenize"] = True
return self.get_tokenizer().apply_chat_template(conversation, **kwargs)
def render_messages(
self,
messages: list[ChatCompletionMessageParam],
params: ChatParams,
) -> tuple[list[ConversationMessage], DictPrompt]:
conversation, mm_data, mm_uuids = parse_chat_messages(
messages,
self.model_config,
content_format="string",
media_io_kwargs=_merge_k3_media_io_kwargs(params.media_io_kwargs),
mm_processor_kwargs=params.mm_processor_kwargs,
)
rendered_conversation = _normalize_k3_tool_messages(conversation)
prompt = parse_dec_only_prompt(
self._apply_chat_template(rendered_conversation, params)
)
if mm_data is not None:
prompt["multi_modal_data"] = mm_data
if mm_uuids is not None:
prompt["multi_modal_uuids"] = mm_uuids
return cast(list[ConversationMessage], rendered_conversation), prompt
async def render_messages_async(
self,
messages: list[ChatCompletionMessageParam],
params: ChatParams,
) -> tuple[list[ConversationMessage], DictPrompt]:
conversation, mm_data, mm_uuids = await parse_chat_messages_async(
messages,
self.model_config,
content_format="string",
media_io_kwargs=_merge_k3_media_io_kwargs(params.media_io_kwargs),
mm_processor_kwargs=params.mm_processor_kwargs,
)
rendered_conversation = _normalize_k3_tool_messages(conversation)
token_ids = await self._apply_chat_template_async(rendered_conversation, params)
prompt = parse_dec_only_prompt(token_ids)
if mm_data is not None:
prompt["multi_modal_data"] = mm_data
if mm_uuids is not None:
prompt["multi_modal_uuids"] = mm_uuids
return cast(list[ConversationMessage], rendered_conversation), prompt