{# =========================
   Qwen 3.8 Multimodal Chat Template (radiance)

   Verified byte-identical to the released Qwen/Qwen3.8-27B-FP8 chat_template.jinja on every
   case except the one it deliberately fixes (2 below).

   1. Vision. Unchanged from the official 27B template (<|vision_start|> / <|image_pad|> /
      <|video_pad|>), plus tolerance for the raw OpenAI video_url item shape.
   2. tool_call.arguments as a JSON *string*  (the reason this file exists at all).
      Stock 3.8 still does `tool_call.arguments|items` directly. Over the OpenAI API
      vLLM replays an assistant tool call with `arguments` as a STRING, so `|items`
      raises `TypeError: Can only get item pairs from a mapping` and stalls every
      multi-turn tool loop. render_tool_arguments() parses it back to a mapping via the
      `from_json` filter (registered by patch_from_json_filter.py) and re-renders the
      exact <parameter=..> XML the model was trained on. Same fix as qwen3.6-enhanced.jinja.
   3. Self-healing for an unclosed <think> that swallowed a <tool_call> (carried over from
      qwen3.6-enhanced.jinja), and the reasoning_content fallback for clients that inline
      <think>..</think> in content instead of sending reasoning_content.

   Qwen3.8 semantics kept as-is from the official template:
   - reasoning_effort: xhigh (default) | medium | low, injected as a system instruction.
   - preserve_thinking: the knob is NOT new (3.6 and qwen3.6-enhanced.jinja both honour it) --
     what changed in 3.8 is the DEFAULT. 3.6: `is defined and is true`, i.e. off unless asked.
     3.8: `is undefined or is true`, i.e. ON unless disabled, so <think> blocks from ALL
     historical assistant turns are kept. Better decision consistency for agents and a more
     stable prefix for --enable-prefix-caching, at the cost of longer prompts. Set
     chat_template_kwargs={"preserve_thinking": false} for the old interleaved behaviour
     (only turns after the last real user query keep their think block).
   - The official tool preamble is used verbatim. The hardened tool instructions from
     qwen3.6-enhanced.jinja are deliberately NOT carried over: 3.8 is trained on the
     official wording and stronger agent execution is its headline change. Graft them back
     only if tool-calling actually misbehaves.

   Checked against Qwen/Qwen3.8-27B-FP8 (2026-08-14). Re-diff if Qwen revises the template.
   ========================= #}
{%- set image_count = namespace(value=0) -%}
{%- set video_count = namespace(value=0) -%}

{# Macro to render individual tool parameters #}
{%- macro render_tool_parameter(args_name, args_value) -%}
    {{- '<parameter=' ~ args_name ~ '>\n' -}}
    {# strings verbatim, everything else via tojson -- matches the official template. Do NOT
       use `| string` for non-strings: it renders booleans as True/False, not true/false. #}
    {%- set args_value = args_value | string if args_value is string else args_value | tojson(ensure_ascii=False) | safe -%}
    {{- args_value -}}
    {{- '\n</parameter>\n' -}}
{%- endmacro -%}

{# Macro to render all tool arguments (mapping OR JSON string -- see header note 2) #}
{%- macro render_tool_arguments(arguments) -%}
    {%- if arguments is undefined or arguments is none -%}
    {%- elif arguments is mapping -%}
        {%- for args_name, args_value in arguments|items -%}
            {{- render_tool_parameter(args_name, args_value) -}}
        {%- endfor -%}
    {%- elif arguments is string -%}
        {%- set trimmed_arguments = arguments | trim -%}
        {%- if trimmed_arguments -%}
            {%- set parsed = trimmed_arguments | from_json -%}
            {%- if parsed is mapping -%}
                {%- for args_name, args_value in parsed | items -%}
                    {{- render_tool_parameter(args_name, args_value) -}}
                {%- endfor -%}
            {%- else -%}
                {{- trimmed_arguments -}}
                {%- if not trimmed_arguments.endswith('\n') -%}
                    {{- '\n' -}}
                {%- endif -%}
            {%- endif -%}
        {%- endif -%}
    {%- else -%}
        {{- raise_exception('Unexpected tool_call.arguments type.') -}}
    {%- endif -%}
{%- endmacro -%}

{# Core content renderer (Handles Text + Vision) #}
{%- macro render_content(content, do_vision_count, is_system_content=false) -%}
    {%- if content is string -%}
        {{- content -}}
    {%- elif content is iterable and content is not mapping -%}
        {%- for item in content -%}
            {%- if 'image' in item or 'image_url' in item or item.type == 'image' -%}
                {%- if is_system_content -%}
                    {{- raise_exception('System message cannot contain images.') -}}
                {%- endif -%}
                {%- if do_vision_count -%}
                    {%- set image_count.value = image_count.value + 1 -%}
                {%- endif -%}
                {%- if add_vision_id -%}
                    {{- 'Picture ' ~ image_count.value ~ ': ' -}}
                {%- endif -%}
                {{- '<|vision_start|><|image_pad|><|vision_end|>' -}}
            {# 'video_url' mirrors the image branch's 'image_url'; stock Qwen templates omit it
               and raise on the raw OpenAI video_url form. vLLM normalises to type=='video'
               before templating, so this only matters for direct apply_chat_template use. #}
            {%- elif 'video' in item or 'video_url' in item or item.type == 'video' -%}
                {%- if is_system_content -%}
                    {{- raise_exception('System message cannot contain videos.') -}}
                {%- endif -%}
                {%- if do_vision_count -%}
                    {%- set video_count.value = video_count.value + 1 -%}
                {%- endif -%}
                {%- if add_vision_id -%}
                    {{- 'Video ' ~ video_count.value ~ ': ' -}}
                {%- endif -%}
                {{- '<|vision_start|><|video_pad|><|vision_end|>' -}}
            {%- elif 'text' in item -%}
                {{- item.text -}}
            {%- else -%}
                {{- raise_exception('Unexpected item type in content.') -}}
            {%- endif -%}
        {%- endfor -%}
    {%- elif content is none or content is undefined -%}
        {{- '' -}}
    {%- else -%}
        {{- raise_exception('Unexpected content type.') -}}
    {%- endif -%}
{%- endmacro -%}

{%- if not messages -%}
    {{- raise_exception('No messages provided.') -}}
{%- endif -%}

{# -------- Qwen3.8 reasoning_effort --------
   Gated on thinking being enabled, matching the released template: with
   enable_thinking=false no reasoning instruction is injected at all. #}
{%- set reasoning_instructions = '' -%}
{%- if enable_thinking is undefined or enable_thinking is true -%}
    {%- set resolved_reasoning_effort = reasoning_effort|default('xhigh') -%}
    {%- if resolved_reasoning_effort not in ('xhigh', 'medium', 'low') -%}
        {{- raise_exception('Unexpected reasoning effort ' ~ reasoning_effort ~ '. Supported types are xhigh (default), medium, and low.') -}}
    {%- endif -%}
    {%- if resolved_reasoning_effort == 'xhigh' -%}
        {%- set reasoning_instructions = 'Reasoning effort is set to xhigh. Please think carefully through the task, validate key assumptions, consider plausible alternatives, and prioritize correctness, consistency, and clarity in the final answer.' -%}
    {%- elif resolved_reasoning_effort == 'low' -%}
        {%- set reasoning_instructions = 'Reasoning effort is set to low. Keep your thinking brief and focused, moving directly to the conclusion without unnecessary elaboration.' -%}
    {%- endif -%}
{%- endif -%}

{# -------- Tools & System Header -------- #}
{%- if tools and tools is iterable and tools is not mapping -%}
    {{- '<|im_start|>system\n' -}}
    {%- if reasoning_instructions -%}
        {{- reasoning_instructions ~ '\n\n' -}}
    {%- endif -%}
    {{- "# Tools\n\nYou have access to the following functions:\n\n<tools>" -}}
    {%- for tool in tools -%}
        {{- "\n" -}}
        {{- tool | tojson(ensure_ascii=False) -}}
    {%- endfor -%}
    {{- "\n</tools>" -}}
    {{- '\n\nIf you choose to call a function ONLY reply in the following format with NO suffix:\n\n<tool_call>\n<function=example_function_name>\n<parameter=example_parameter_1>\nvalue_1\n</parameter>\n<parameter=example_parameter_2>\nThis is the value for the second parameter\nthat can span\nmultiple lines\n</parameter>\n</function>\n</tool_call>\n\n<IMPORTANT>\nReminder:\n- Function calls MUST follow the specified format: an inner <function=...></function> block must be nested within <tool_call></tool_call> XML tags\n- Required parameters MUST be specified\n- You may provide optional reasoning for your function call in natural language BEFORE the function call, but NOT after\n- If there is no function call available, answer the question like normal with your current knowledge and do not tell the user about function calls\n</IMPORTANT>' -}}
    {%- if messages[0].role == 'system' -%}
        {%- set sysc = render_content(messages[0].content, false, true)|trim -%}
        {%- if sysc -%}
            {{- '\n\n' ~ sysc -}}
        {%- endif -%}
    {%- endif -%}
    {{- '<|im_end|>\n' -}}
{%- else -%}
    {%- if messages[0].role == 'system' -%}
        {%- set sysc = render_content(messages[0].content, false, true)|trim -%}
        {%- if sysc -%}
            {{- '<|im_start|>system\n' ~ (reasoning_instructions ~ '\n\n' if reasoning_instructions else '') ~ sysc ~ '<|im_end|>\n' -}}
        {%- elif reasoning_instructions -%}
            {{- '<|im_start|>system\n' ~ reasoning_instructions ~ '<|im_end|>\n' -}}
        {%- endif -%}
    {%- elif reasoning_instructions -%}
        {{- '<|im_start|>system\n' ~ reasoning_instructions ~ '<|im_end|>\n' -}}
    {%- endif -%}
{%- endif -%}

{# -------- Thinking boundary: index of the last REAL user query --------
   Only consulted when preserve_thinking is explicitly false (3.8 preserves by default). #}
{%- set ns = namespace(last_user_index=-1) -%}
{%- for m in messages -%}
    {%- if m.role == "user" -%}
        {%- set uc = render_content(m.content, false)|trim -%}
        {%- if not (uc.startswith('<tool_response>') and uc.endswith('</tool_response>')) -%}
            {%- set ns.last_user_index = loop.index0 -%}
        {%- endif -%}
    {%- endif -%}
{%- endfor -%}

{# -------- Render messages -------- #}
{%- for message in messages -%}
    {%- set content = render_content(message.content, true)|trim -%}
    {%- if message.role == "system" -%}
        {%- if not loop.first -%}
            {{- raise_exception('System message must be at the beginning.') -}}
        {%- endif -%}
    {%- elif message.role == "user" -%}
        {{- '<|im_start|>user\n' ~ content ~ '<|im_end|>\n' -}}
    {%- elif message.role == "assistant" -%}
        {# Self-heal an unclosed <think> that swallowed a <tool_call>: close it before the
           tool call so the tool markup is never nested inside the reasoning block. #}
        {%- if '<tool_call>' in content and '<think>' in content -%}
            {%- set last_think = content.rfind('<think>') -%}
            {%- set last_close = content.rfind('</think>') -%}
            {%- set tool_pos = content.find('<tool_call>') -%}
            {%- if last_close < last_think or last_close == -1 -%}
                {%- if tool_pos > last_think -%}
                    {%- set content = content[:tool_pos] + '</think>' + content[tool_pos:] -%}
                {%- else -%}
                    {%- set content = content + '</think>' -%}
                {%- endif -%}
            {%- endif -%}
        {%- endif -%}

        {%- set reasoning_content = '' -%}
        {%- if message.reasoning_content is string -%}
            {%- set reasoning_content = message.reasoning_content -%}
        {%- else -%}
            {# Fallback for clients that inline <think>..</think> in content instead of
               sending reasoning_content (stock 3.8 dropped this; keep it, it is strictly
               more forgiving and prevents a double-wrapped think block). #}
            {%- if '</think>' in content -%}
                {%- set reasoning_content = content.split('</think>')[0].rstrip('\n').split('<think>')[-1].lstrip('\n') -%}
                {%- set content = content.split('</think>')[-1].lstrip('\n') -%}
            {%- endif -%}
        {%- endif -%}
        {%- set reasoning_content = reasoning_content|trim -%}

        {# Qwen3.8: preserve_thinking defaults to TRUE (undefined == on). #}
        {%- if preserve_thinking is undefined or preserve_thinking is true or loop.index0 > ns.last_user_index -%}
            {{- '<|im_start|>assistant\n<think>\n' ~ reasoning_content ~ '\n</think>\n\n' ~ content -}}
        {%- else -%}
            {{- '<|im_start|>assistant\n' ~ content -}}
        {%- endif -%}

        {%- if message.tool_calls and message.tool_calls is iterable and message.tool_calls is not mapping -%}
            {%- for tool_call in message.tool_calls -%}
                {%- if tool_call.function is defined -%}
                    {%- set tool_call = tool_call.function -%}
                {%- endif -%}
                {%- if loop.first -%}
                    {%- if content|trim -%}
                        {{- '\n\n<tool_call>\n<function=' ~ tool_call.name ~ '>\n' -}}
                    {%- else -%}
                        {{- '<tool_call>\n<function=' ~ tool_call.name ~ '>\n' -}}
                    {%- endif -%}
                {%- else -%}
                    {{- '\n<tool_call>\n<function=' ~ tool_call.name ~ '>\n' -}}
                {%- endif -%}
                {{- render_tool_arguments(tool_call.arguments) -}}
                {{- '</function>\n</tool_call>' -}}
            {%- endfor -%}
        {%- endif -%}
        {{- '<|im_end|>\n' -}}
    {%- elif message.role == "tool" -%}
        {%- if loop.index0 == 0 or messages[loop.index0 - 1].role != "tool" -%}
            {{- '<|im_start|>user' -}}
        {%- endif -%}
        {{- '\n<tool_response>\n' ~ content ~ '\n</tool_response>' -}}
        {# no leading newline before <|im_end|> -- matches the official template exactly #}
        {%- if loop.last or messages[loop.index0 + 1].role != "tool" -%}
            {{- '<|im_end|>\n' -}}
        {%- endif -%}
    {%- else -%}
        {{- raise_exception('Unexpected message role.') -}}
    {%- endif -%}
{%- endfor -%}
{# -------- Generation prompt -------- #}
{%- if add_generation_prompt -%}
    {{- '<|im_start|>assistant\n' -}}
    {%- if enable_thinking is defined and enable_thinking is false -%}
        {{- '<think>\n\n</think>\n\n' -}}
    {%- else -%}
        {{- '<think>\n' -}}
    {%- endif -%}
{%- endif -%}
