{%- set template_version = "qwen-3.8-27b-chromix+froggerick.v1" %} {#- ------------------------------------------------------------------------- qwen-3.8-27b-chromix+froggerick.v1 Tool-handling fork of qwen3.8-safe-v2.1-reasoning-compat. Scope is intentionally limited to tool definitions, tool-call wire format, tool-call history, tool arguments, parallel calls, and tool responses. Non-tool conversation behavior, reasoning policy, role policy, multimodal content handling, and generation-prompt behavior are kept aligned with the safe template. ------------------------------------------------------------------------- -#} {%- if not messages %} {{- raise_exception('No messages provided.') }} {%- endif %} {#- ------------------------------------------------------------------------- Configuration ------------------------------------------------------------------------- -#} {%- set _requested_tool_format = tool_call_format if tool_call_format is defined else 'auto' %} {%- if _requested_tool_format not in ('auto', 'xml', 'json') %} {{- raise_exception( 'Unexpected tool_call_format ' ~ _requested_tool_format ~ '. Supported values are auto (default), xml, and json.' ) }} {%- endif %} {%- set add_vision_id = add_vision_id if add_vision_id is defined else false %} {%- set enable_thinking = enable_thinking if enable_thinking is defined else true %} {%- set auto_disable_thinking_with_tools = auto_disable_thinking_with_tools if auto_disable_thinking_with_tools is defined else false %} {%- if preserve_reasoning is defined and preserve_reasoning is not none and preserve_thinking is defined and preserve_thinking is not none and preserve_reasoning != preserve_thinking %} {{- raise_exception( 'preserve_reasoning and preserve_thinking were both provided with conflicting values.' ) }} {%- endif %} {%- if preserve_reasoning is defined and preserve_reasoning is not none %} {%- set _preserve_thinking = preserve_reasoning %} {%- elif preserve_thinking is defined and preserve_thinking is not none %} {%- set _preserve_thinking = preserve_thinking %} {%- else %} {%- set _preserve_thinking = true %} {%- endif %} {%- set max_tool_arg_chars = max_tool_arg_chars if max_tool_arg_chars is defined else 0 %} {%- set max_tool_response_chars = max_tool_response_chars if max_tool_response_chars is defined else 0 %} {%- if max_tool_arg_chars is not number or max_tool_arg_chars < 0 %} {{- raise_exception('max_tool_arg_chars must be a non-negative number.') }} {%- endif %} {%- if max_tool_response_chars is not number or max_tool_response_chars < 0 %} {{- raise_exception('max_tool_response_chars must be a non-negative number.') }} {%- endif %} {%- if tools is defined and tools is not none and tools %} {%- if tools is string or tools is mapping or tools is not iterable %} {{- raise_exception('tools must be an iterable list of tool definitions.') }} {%- endif %} {%- set _has_tools = true %} {%- else %} {%- set _has_tools = false %} {%- endif %} {#- ------------------------------------------------------------------------- Resolve tool-call wire format. The safe template already switches auto mode to JSON when historical OpenAI-compatible tool calls carry function.arguments as a JSON string. That behavior is preserved. This fork also switches auto mode to JSON when a historical tool call or advertised tool name cannot be represented safely in Qwen's pseudo-XML syntax, or when a string argument contains pseudo-XML control delimiters that could terminate a parameter/function/tool_call block early. Forced XML remains strict: structurally unsafe XML data raises instead of being silently rewritten or corrupted. ------------------------------------------------------------------------- -#} {%- set format_scan = namespace( has_json_string_arguments=false, has_xml_unsafe_data=false ) %} {%- if _has_tools %} {%- for tool in tools %} {%- if tool.function is defined and tool.function is not none %} {%- set _tool_def = tool.function %} {%- else %} {%- set _tool_def = tool %} {%- endif %} {%- if _tool_def.name is defined and _tool_def.name is not none and _tool_def.name is string %} {%- set _tool_def_name = _tool_def.name %} {%- if '<' in _tool_def_name or '>' in _tool_def_name or '\n' in _tool_def_name or '\r' in _tool_def_name or '\t' in _tool_def_name or ' ' in _tool_def_name or '=' in _tool_def_name %} {%- set format_scan.has_xml_unsafe_data = true %} {%- endif %} {%- endif %} {%- endfor %} {%- endif %} {%- for message in messages %} {%- if message.role == 'assistant' and message.tool_calls is defined and message.tool_calls is not none and message.tool_calls and message.tool_calls is not string and message.tool_calls is not mapping and message.tool_calls is iterable %} {%- for tool_call in message.tool_calls %} {%- if tool_call.function is defined and tool_call.function is not none %} {%- set _tc = tool_call.function %} {%- else %} {%- set _tc = tool_call %} {%- endif %} {%- if _tc.name is defined and _tc.name is not none and _tc.name is string %} {%- set _scan_name = _tc.name %} {%- if '<' in _scan_name or '>' in _scan_name or '\n' in _scan_name or '\r' in _scan_name or '\t' in _scan_name or ' ' in _scan_name or '=' in _scan_name %} {%- set format_scan.has_xml_unsafe_data = true %} {%- endif %} {%- endif %} {%- if _tc.arguments is defined and _tc.arguments is not none %} {%- if _tc.arguments is string and _tc.arguments | trim and _tc.arguments | trim != '{}' %} {%- set format_scan.has_json_string_arguments = true %} {%- elif _tc.arguments is mapping %} {%- for _scan_arg_name, _scan_arg_value in _tc.arguments | items %} {%- set _scan_arg_name = _scan_arg_name | string %} {%- if not _scan_arg_name or '<' in _scan_arg_name or '>' in _scan_arg_name or '\n' in _scan_arg_name or '\r' in _scan_arg_name or '\t' in _scan_arg_name or ' ' in _scan_arg_name or '=' in _scan_arg_name %} {%- set format_scan.has_xml_unsafe_data = true %} {%- endif %} {%- if _scan_arg_value is string and ( '' in _scan_arg_value or '' in _scan_arg_value or '' in _scan_arg_value or '' in _scan_arg_value ) %} {%- set format_scan.has_xml_unsafe_data = true %} {%- endif %} {%- endfor %} {%- endif %} {%- endif %} {%- endfor %} {%- endif %} {%- endfor %} {%- if _requested_tool_format == 'auto' %} {%- if format_scan.has_json_string_arguments or format_scan.has_xml_unsafe_data %} {%- set _tool_format = 'json' %} {%- else %} {%- set _tool_format = 'xml' %} {%- endif %} {%- else %} {%- set _tool_format = _requested_tool_format %} {%- endif %} {%- set _thinking_enabled = enable_thinking %} {%- if auto_disable_thinking_with_tools and _has_tools %} {%- set _thinking_enabled = false %} {%- endif %} {%- set _effort_raw = reasoning_effort if reasoning_effort is defined else 'medium' %} {%- if _effort_raw == 'none' %} {%- set _reasoning_effort = 'none' %} {%- set _thinking_enabled = false %} {%- elif _effort_raw == 'high' or _effort_raw == 'xhigh' %} {%- set _reasoning_effort = 'xhigh' %} {%- elif _effort_raw == 'medium' %} {%- set _reasoning_effort = 'medium' %} {%- elif _effort_raw == 'low' %} {%- set _reasoning_effort = 'low' %} {%- else %} {{- raise_exception( 'Unexpected reasoning effort ' ~ _effort_raw ~ '. Supported values are none, high/xhigh, medium, and low.' ) }} {%- endif %} {%- set reasoning_instructions = '' %} {%- if _thinking_enabled %} {%- if _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 _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 %} {#- ------------------------------------------------------------------------- Content rendering ------------------------------------------------------------------------- -#} {%- set image_count = namespace(value=0) %} {%- set video_count = namespace(value=0) %} {%- 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 item is not mapping %} {{- raise_exception( 'Content-list items must be mappings with a supported text, image, or video type.' ) }} {%- endif %} {%- set _item_type = item.type if 'type' in item else none %} {%- if _item_type is not none and _item_type not in ('text', 'image', 'image_url', 'video') %} {{- raise_exception( 'Unexpected content item type ' ~ _item_type ~ '.' ) }} {%- endif %} {%- set _is_image = _item_type == 'image' or _item_type == 'image_url' or 'image' in item or 'image_url' in item %} {%- set _is_video = _item_type == 'video' or 'video' in item %} {%- if _is_image and _is_video %} {{- raise_exception( 'A content item cannot contain both image and video data.' ) }} {%- elif _item_type == 'text' and (_is_image or _is_video) %} {{- raise_exception( 'A text content item cannot also contain image or video data.' ) }} {%- elif _is_image %} {%- if is_system_content %} {{- raise_exception( 'System and developer messages 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|>' }} {%- elif _is_video %} {%- if is_system_content %} {{- raise_exception( 'System and developer messages 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 _item_type == 'text' or 'text' in item %} {%- if 'text' not in item or item.text is not string %} {{- raise_exception( 'Text content items must contain a string text field.' ) }} {%- endif %} {{- item.text }} {%- else %} {{- raise_exception( 'Unexpected item in content list. Expected text, image, or video content.' ) }} {%- endif %} {%- endfor %} {%- elif content is none or content is undefined %} {{- '' }} {%- else %} {{- raise_exception('Unexpected content type.') }} {%- endif %} {%- endmacro %} {#- ------------------------------------------------------------------------- Validate and merge leading system/developer messages. Keep this strict: agent resilience must not silently reorder authority. ------------------------------------------------------------------------- -#} {%- set head = namespace(count=0, seen_non_system=false) %} {%- for message in messages %} {%- set _is_system = message.role == 'system' or message.role == 'developer' %} {%- if _is_system %} {%- if head.seen_non_system %} {{- raise_exception( 'System and developer messages must appear before all user, assistant, and tool messages.' ) }} {%- endif %} {%- set head.count = head.count + 1 %} {%- else %} {%- set head.seen_non_system = true %} {%- endif %} {%- endfor %} {%- set system_state = namespace(content='') %} {%- for message in messages[:head.count] %} {%- set _part = render_content(message.content, false, true) | trim %} {%- if _part %} {%- if system_state.content %} {%- set system_state.content = system_state.content ~ '\n\n' ~ _part %} {%- else %} {%- set system_state.content = _part %} {%- endif %} {%- endif %} {%- endfor %} {%- set _system_content = system_state.content %} {%- set _msgs = messages[head.count:] %} {#- ------------------------------------------------------------------------- System prompt and tool instructions ------------------------------------------------------------------------- -#} {%- if _has_tools %} {{- '<|im_start|>system\n' }} {%- if reasoning_instructions %} {{- reasoning_instructions ~ '\n\n' }} {%- endif %} {{- '# Tools\n\nYou have access to the following functions:\n\n' }} {%- for tool in tools %} {{- '\n' }} {{- tool | tojson }} {%- endfor %} {{- '\n' }} {%- if _tool_format == 'json' %} {{- '\n\nIf you choose to call a function ONLY reply in the following format with NO suffix:\n\n\n{"name": "example_function_name", "arguments": {"example_parameter_1": "value_1", "example_parameter_2": "value_2"}}\n\n\n\nReminder:\n- Use only function names and parameters defined in \n- Required parameters MUST be specified\n- Function calls MUST be a single JSON object with "name" and "arguments" inside \n- You may provide optional reasoning in natural language BEFORE the first tool call, but NOT after a tool call\n- For multiple or parallel calls, output one fully closed block per function, in the order they should be executed\n- Do NOT nest blocks\n- Do NOT output conversational text after a tool call\n- If no function call is needed, answer normally with your current knowledge\n' }} {%- else %} {{- '\n\nIf you choose to call a function ONLY reply in the following format with NO suffix:\n\n\n\n\nvalue_1\n\n\nvalue_2\n\n\n\n\n\nReminder:\n- Use only function names and parameters defined in \n- Required parameters MUST be specified\n- Function calls MUST use an inner block inside \n- You may provide optional reasoning in natural language BEFORE the first tool call, but NOT after a tool call\n- and tags MUST begin at the start of a new line with no indentation\n- For multiple or parallel calls, output one fully closed block per function, in the order they should be executed\n- Do NOT nest blocks\n- Do NOT output conversational text after a tool call\n- If no function call is needed, answer normally with your current knowledge\n' }} {%- endif %} {%- if _system_content %} {{- '\n\n' ~ _system_content }} {%- endif %} {{- '<|im_end|>\n' }} {%- else %} {%- if _system_content %} {{- '<|im_start|>system\n' ~ (reasoning_instructions ~ '\n\n' if reasoning_instructions else '') ~ _system_content ~ '<|im_end|>\n' }} {%- elif reasoning_instructions %} {{- '<|im_start|>system\n' ~ reasoning_instructions ~ '<|im_end|>\n' }} {%- endif %} {%- endif %} {#- ------------------------------------------------------------------------- Find the last real user query. Preserve the safe-template behavior for normal conversations. The only exception is a retained tool continuation where context truncation has removed the original user query. In that tool-specific case, use the last wrapped tool_response/user turn when available, otherwise the final retained message index. This prevents tool loops from dying solely because the initiating user turn fell out of context. ------------------------------------------------------------------------- -#} {%- set tool_history = namespace( has_activity=false, last_wrapped_tool_response_index=none ) %} {%- set ns = namespace( multi_step_tool=true, last_query_index=(_msgs | length) - 1 ) %} {%- for message in _msgs %} {%- if message.role == 'tool' %} {%- set tool_history.has_activity = true %} {%- elif message.role == 'assistant' and message.tool_calls is defined and message.tool_calls is not none and message.tool_calls %} {%- set tool_history.has_activity = true %} {%- elif message.role == 'user' %} {%- set _tool_scan_user = render_content(message.content, false) | trim %} {%- if _tool_scan_user.startswith('') and _tool_scan_user.endswith('') %} {%- set tool_history.has_activity = true %} {%- set tool_history.last_wrapped_tool_response_index = loop.index0 %} {%- endif %} {%- endif %} {%- endfor %} {%- for message in _msgs[::-1] %} {%- set index = (_msgs | length - 1) - loop.index0 %} {%- if ns.multi_step_tool and message.role == 'user' %} {%- set _rendered_user = render_content(message.content, false) | trim %} {%- if not ( _rendered_user.startswith('') and _rendered_user.endswith('') ) %} {%- set ns.multi_step_tool = false %} {%- set ns.last_query_index = index %} {%- endif %} {%- endif %} {%- endfor %} {%- if ns.multi_step_tool %} {%- if tool_history.has_activity %} {%- if tool_history.last_wrapped_tool_response_index is not none %} {%- set ns.last_query_index = tool_history.last_wrapped_tool_response_index %} {%- else %} {%- set ns.last_query_index = (_msgs | length) - 1 %} {%- endif %} {%- set ns.multi_step_tool = false %} {%- else %} {{- raise_exception('No user query found in messages.') }} {%- endif %} {%- endif %} {#- ------------------------------------------------------------------------- Conversation rendering ------------------------------------------------------------------------- -#} {%- set history_state = namespace(previous_role='') %} {%- for message in _msgs %} {%- set content = render_content(message.content, true) | trim %} {%- if message.role == 'user' %} {{- '<|im_start|>user\n' ~ content ~ '<|im_end|>\n' }} {%- elif message.role == 'assistant' %} {%- set reasoning_content = '' %} {%- if message.reasoning_content is defined and message.reasoning_content is not none %} {%- if message.reasoning_content is string %} {%- set reasoning_content = message.reasoning_content %} {%- else %} {%- set reasoning_content = message.reasoning_content | string %} {%- endif %} {%- elif message.thinking is defined and message.thinking is not none %} {%- if message.thinking is string %} {%- set reasoning_content = message.thinking %} {%- else %} {%- set reasoning_content = message.thinking | string %} {%- endif %} {%- elif message.reasoning is defined and message.reasoning is not none %} {#- vLLM's OpenAI-compatible reasoning parser may return the historical thinking text under `reasoning`. Accept it only as a fallback so the established reasoning_content/thinking precedence and rendered prompt format remain unchanged. -#} {%- if message.reasoning is string %} {%- set reasoning_content = message.reasoning %} {%- else %} {%- set reasoning_content = message.reasoning | string %} {%- endif %} {%- endif %} {%- set reasoning_content = reasoning_content | trim %} {#- Preserve the original invariant: when thinking is preserved, always reconstruct the thinking block, even when it is empty. -#} {%- if _preserve_thinking or loop.index0 > ns.last_query_index %} {{- '<|im_start|>assistant\n\n' ~ reasoning_content ~ '\n\n\n' ~ content }} {%- else %} {{- '<|im_start|>assistant\n' ~ content }} {%- endif %} {%- if message.tool_calls is defined and message.tool_calls is not none and message.tool_calls %} {%- if message.tool_calls is string or message.tool_calls is mapping or message.tool_calls is not iterable %} {{- raise_exception( 'assistant.tool_calls must be an iterable list of tool calls.' ) }} {%- endif %} {%- for tool_call in message.tool_calls %} {%- if tool_call.function is defined and tool_call.function is not none %} {%- set tc = tool_call.function %} {%- else %} {%- set tc = tool_call %} {%- endif %} {%- if tc.name is not defined or tc.name is none or tc.name is not string or not tc.name %} {{- raise_exception( 'Every tool call must have a non-empty string name.' ) }} {%- endif %} {%- set tc_name = tc.name %} {%- if _tool_format == 'json' %} {%- if loop.first %} {%- if content | trim %} {{- '\n\n\n' }} {%- else %} {{- '\n' }} {%- endif %} {%- else %} {{- '\n\n' }} {%- endif %} {%- set _json_arguments = '{}' %} {%- if tc.arguments is defined and tc.arguments is not none %} {%- if tc.arguments is mapping %} {%- set _json_arguments = tc.arguments | tojson %} {%- elif tc.arguments is string %} {%- set _raw_arguments = tc.arguments | trim %} {%- if _raw_arguments %} {%- if not ( _raw_arguments.startswith('{') and _raw_arguments.endswith('}') ) %} {{- raise_exception( 'JSON tool-call arguments must be a JSON object string.' ) }} {%- endif %} {%- set _json_arguments = _raw_arguments %} {%- endif %} {%- else %} {{- raise_exception( 'JSON tool-call arguments must be a mapping or a JSON object string.' ) }} {%- endif %} {%- endif %} {{- '{"name": ' }} {{- tc_name | tojson }} {{- ', "arguments": ' }} {{- _json_arguments }} {{- '}\n' }} {%- else %} {%- if '<' in tc_name or '>' in tc_name or '\n' in tc_name or '\r' in tc_name or '\t' in tc_name or ' ' in tc_name or '=' in tc_name %} {{- raise_exception( 'XML tool-call names cannot contain angle brackets, spaces, equals signs, or control whitespace.' ) }} {%- endif %} {%- if loop.first %} {%- if content | trim %} {{- '\n\n\n\n' }} {%- else %} {{- '\n\n' }} {%- endif %} {%- else %} {{- '\n\n\n' }} {%- endif %} {%- if tc.arguments is defined and tc.arguments is not none %} {%- if tc.arguments is mapping %} {%- for args_name, args_value in tc.arguments | items %} {%- set _argument_name = args_name | string %} {%- if not _argument_name or '<' in _argument_name or '>' in _argument_name or '\n' in _argument_name or '\r' in _argument_name or '\t' in _argument_name or ' ' in _argument_name or '=' in _argument_name %} {{- raise_exception( 'XML tool parameter names must be non-empty and cannot contain angle brackets, spaces, equals signs, or control whitespace.' ) }} {%- endif %} {{- '\n' }} {%- if args_value is string %} {%- set _argument_value = args_value %} {%- if _tool_format == 'xml' and ( '' in _argument_value or '' in _argument_value or '' in _argument_value or '' in _argument_value ) %} {{- raise_exception( 'XML tool argument contains a reserved tool-control delimiter. Leave tool_call_format unset so auto mode can select JSON, or use tool_call_format="json".' ) }} {%- endif %} {%- else %} {%- set _argument_value = args_value | tojson %} {%- endif %} {%- if max_tool_arg_chars > 0 and _argument_value | length > max_tool_arg_chars %} {{- _argument_value[:max_tool_arg_chars] }} {{- '\n[TRUNCATED — original length ' ~ (_argument_value | length | string) ~ ' chars]' }} {%- else %} {{- _argument_value }} {%- endif %} {{- '\n\n' }} {%- endfor %} {%- elif tc.arguments is string %} {%- set _raw_arguments = tc.arguments | trim %} {%- if _raw_arguments and _raw_arguments != '{}' %} {{- raise_exception( 'tool_call_format="xml" requires tool-call arguments to be mappings, but received a JSON string. Parse the JSON string into a mapping before applying the template, or leave tool_call_format unset so the template can select the JSON tool-call format automatically.' ) }} {%- endif %} {%- else %} {{- raise_exception( 'XML tool-call arguments must be a mapping. Parse JSON-string arguments before applying the template.' ) }} {%- endif %} {%- endif %} {{- '\n' }} {%- endif %} {%- endfor %} {%- endif %} {{- '<|im_end|>\n' }} {%- elif message.role == 'tool' %} {%- if history_state.previous_role != 'tool' %} {{- '<|im_start|>user' }} {%- endif %} {%- if max_tool_response_chars > 0 and content | length > max_tool_response_chars %} {%- set content = content[:max_tool_response_chars] ~ '\n[TRUNCATED — original length ' ~ (content | length | string) ~ ' chars]' %} {%- endif %} {{- '\n\n' }} {{- content }} {{- '\n' }} {%- if loop.last %} {{- '<|im_end|>\n' }} {%- elif _msgs[loop.index0 + 1].role != 'tool' %} {{- '<|im_end|>\n' }} {%- endif %} {%- elif message.role == 'system' or message.role == 'developer' %} {{- raise_exception( 'System and developer messages must appear only at the beginning of the conversation.' ) }} {%- else %} {{- raise_exception( 'Unexpected message role ' ~ message.role ~ '.' ) }} {%- endif %} {%- set history_state.previous_role = message.role %} {%- endfor %} {#- ------------------------------------------------------------------------- Generation prompt ------------------------------------------------------------------------- -#} {%- if add_generation_prompt %} {{- '<|im_start|>assistant\n' }} {%- if not _thinking_enabled %} {{- '\n\n\n\n' }} {%- else %} {{- '\n' }} {%- endif %} {%- endif %}