Source code for kani.model_specific.qwen3

import json
import logging
import re

from kani.models import FunctionCall, ToolCall
from .base import BaseToolCallParser
from ..engines.huggingface import ChatTemplatePromptPipeline

log = logging.getLogger(__name__)


# ===== PROMPT PIPELINE =====
def build_prompt_pipeline(tokenizer, **kwargs):
    # set default chat_template_reasoning_content_key to "reasoning_content"
    kwargs["chat_template_reasoning_content_key"] = (
        kwargs.get("chat_template_reasoning_content_key") or "reasoning_content"
    )
    return ChatTemplatePromptPipeline(tokenizer, **kwargs)


# ===== OUTPUT PARSER =====
[docs] class Qwen3Parser(BaseToolCallParser): r""" Tool calling + reasoning adapter for Qwen3 models:: Qwen/Qwen3-* Reasoning segments are returned as :class:`.ReasoningPart`\ s. """ def __init__( self, *args, tool_call_start_token="<tool_call>", tool_call_end_token="</tool_call>", reasoning_start_token="<think>", reasoning_end_token="</think>", reasoning_always_at_start=False, **kwargs, ): super().__init__( *args, tool_call_start_token=tool_call_start_token, tool_call_end_token=tool_call_end_token, reasoning_start_token=reasoning_start_token, reasoning_end_token=reasoning_end_token, reasoning_always_at_start=reasoning_always_at_start, **kwargs, ) def parse_tool_calls(self, content: str): tool_calls = [] tool_content_matches = re.finditer( rf"{re.escape(self.tool_call_start_token)}(.+?){re.escape(self.tool_call_end_token)}", content, re.IGNORECASE | re.DOTALL, ) # do this in reverse so we can edit the str directly for tool_content_match in reversed(list(tool_content_matches)): log.debug(f"Found tool content while parsing: {tool_content_match.group(1)}") try: data = json.loads(tool_content_match[1]) except json.JSONDecodeError: log.error( f"Could not decode tool content! Skipping this tool call:\n{tool_content_match[0]!r}!", exc_info=True, ) continue tool_name = data.get("name", "undefined") tool_args = data.get("arguments", {}) if not isinstance(tool_args, dict): log.warning(f"Could not decode tool arguments! Skipping this tool call:\n{tool_content_match[0]!r}!") continue tool_call = ToolCall.from_function_call(FunctionCall.with_args(tool_name, **tool_args)) tool_calls.append(tool_call) content = content[: tool_content_match.start()] + content[tool_content_match.end() :] tool_calls.reverse() # since we parsed backwards return content.strip(), tool_calls
class Qwen3ThinkingParser(Qwen3Parser): def __init__(self, *args, reasoning_always_at_start=True, **kwargs): super().__init__(*args, reasoning_always_at_start=reasoning_always_at_start, **kwargs)