{"slug": "patched-jinja-template-for-qwen-3-5-27b-fixes-developer-role-crash-preserves-1", "title": "Patched Jinja template for Qwen 3.5 27B - fixes developer role crash + preserves thinking mode (thinking = 1). Drop-in replacement for agent tools (OpenCode, Claude Code, Continue, Cursor, Aider).", "summary": "This is a patched Jinja template for the Qwen 3.5 27B model that fixes a crash when using the \"developer\" role and preserves the model's thinking mode (thinking=1). It serves as a drop-in replacement for agent tools like OpenCode, Claude Code, Continue, Cursor, and Aider, addressing an issue where using the standard `--chat-template chatml` would silently disable thinking mode.", "body_md": "-\n-\nSave sudoingX/c2facf7d8f7608c65c1024ef3b22d431 to your computer and use it in GitHub Desktop.\n\n[Learn more about bidirectional Unicode characters](https://github.co/hiddenchars)\n\n| {%- set image_count = namespace(value=0) %} | |\n| {%- set video_count = namespace(value=0) %} | |\n| {%- macro render_content(content, do_vision_count, is_system_content=false) %} | |\n| {%- if content is string %} | |\n| {{- content }} | |\n| {%- elif content is iterable and content is not mapping %} | |\n| {%- for item in content %} | |\n| {%- if 'image' in item or 'image_url' in item or item.type == 'image' %} | |\n| {%- if is_system_content %} | |\n| {{- raise_exception('System message cannot contain images.') }} | |\n| {%- endif %} | |\n| {%- if do_vision_count %} | |\n| {%- set image_count.value = image_count.value + 1 %} | |\n| {%- endif %} | |\n| {%- if add_vision_id %} | |\n| {{- 'Picture ' ~ image_count.value ~ ': ' }} | |\n| {%- endif %} | |\n| {{- '<|vision_start|><|image_pad|><|vision_end|>' }} | |\n| {%- elif 'video' in item or item.type == 'video' %} | |\n| {%- if is_system_content %} | |\n| {{- raise_exception('System message cannot contain videos.') }} | |\n| {%- endif %} | |\n| {%- if do_vision_count %} | |\n| {%- set video_count.value = video_count.value + 1 %} | |\n| {%- endif %} | |\n| {%- if add_vision_id %} | |\n| {{- 'Video ' ~ video_count.value ~ ': ' }} | |\n| {%- endif %} | |\n| {{- '<|vision_start|><|video_pad|><|vision_end|>' }} | |\n| {%- elif 'text' in item %} | |\n| {{- item.text }} | |\n| {%- else %} | |\n| {{- raise_exception('Unexpected item type in content.') }} | |\n| {%- endif %} | |\n| {%- endfor %} | |\n| {%- elif content is none or content is undefined %} | |\n| {{- '' }} | |\n| {%- else %} | |\n| {{- raise_exception('Unexpected content type.') }} | |\n| {%- endif %} | |\n| {%- endmacro %} | |\n| {%- if not messages %} | |\n| {{- raise_exception('No messages provided.') }} | |\n| {%- endif %} | |\n| {%- if tools and tools is iterable and tools is not mapping %} | |\n| {{- '<|im_start|>system\\n' }} | |\n| {{- \"# Tools\\n\\nYou have access to the following functions:\\n\\n<tools>\" }} | |\n| {%- for tool in tools %} | |\n| {{- \"\\n\" }} | |\n| {{- tool | tojson }} | |\n| {%- endfor %} | |\n| {{- \"\\n</tools>\" }} | |\n| {{- '\\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>' }} | |\n| {%- if messages[0].role == 'system' %} | |\n| {%- set content = render_content(messages[0].content, false, true)|trim %} | |\n| {%- if content %} | |\n| {{- '\\n\\n' + content }} | |\n| {%- endif %} | |\n| {%- endif %} | |\n| {{- '<|im_end|>\\n' }} | |\n| {%- else %} | |\n| {%- if messages[0].role == 'system' or messages[0].role == 'developer' %} | |\n| {%- set content = render_content(messages[0].content, false, true)|trim %} | |\n| {{- '<|im_start|>system\\n' + content + '<|im_end|>\\n' }} | |\n| {%- endif %} | |\n| {%- endif %} | |\n| {%- set ns = namespace(multi_step_tool=true, last_query_index=messages|length - 1) %} | |\n| {%- for message in messages[::-1] %} | |\n| {%- set index = (messages|length - 1) - loop.index0 %} | |\n| {%- if ns.multi_step_tool and message.role == \"user\" %} | |\n| {%- set content = render_content(message.content, false)|trim %} | |\n| {%- if not(content.startswith('<tool_response>') and content.endswith('</tool_response>')) %} | |\n| {%- set ns.multi_step_tool = false %} | |\n| {%- set ns.last_query_index = index %} | |\n| {%- endif %} | |\n| {%- endif %} | |\n| {%- endfor %} | |\n| {%- if ns.multi_step_tool %} | |\n| {{- raise_exception('No user query found in messages.') }} | |\n| {%- endif %} | |\n| {%- for message in messages %} | |\n| {%- set content = render_content(message.content, true)|trim %} | |\n| {%- if message.role == \"system\" or message.role == \"developer\" %} | |\n| {%- if not loop.first %} | |\n| {{- '<|im_start|>user\\n' + content + '<|im_end|>\\n' }} | |\n| {%- endif %} | |\n| {%- elif message.role == \"user\" %} | |\n| {{- '<|im_start|>' + message.role + '\\n' + content + '<|im_end|>' + '\\n' }} | |\n| {%- elif message.role == \"assistant\" %} | |\n| {%- set reasoning_content = '' %} | |\n| {%- if message.reasoning_content is string %} | |\n| {%- set reasoning_content = message.reasoning_content %} | |\n| {%- else %} | |\n| {%- if '</think>' in content %} | |\n| {%- set reasoning_content = content.split('</think>')[0].rstrip('\\n').split('<think>')[-1].lstrip('\\n') %} | |\n| {%- set content = content.split('</think>')[-1].lstrip('\\n') %} | |\n| {%- endif %} | |\n| {%- endif %} | |\n| {%- set reasoning_content = reasoning_content|trim %} | |\n| {%- if loop.index0 > ns.last_query_index %} | |\n| {{- '<|im_start|>' + message.role + '\\n<think>\\n' + reasoning_content + '\\n</think>\\n\\n' + content }} | |\n| {%- else %} | |\n| {{- '<|im_start|>' + message.role + '\\n' + content }} | |\n| {%- endif %} | |\n| {%- if message.tool_calls and message.tool_calls is iterable and message.tool_calls is not mapping %} | |\n| {%- for tool_call in message.tool_calls %} | |\n| {%- if tool_call.function is defined %} | |\n| {%- set tool_call = tool_call.function %} | |\n| {%- endif %} | |\n| {%- if loop.first %} | |\n| {%- if content|trim %} | |\n| {{- '\\n\\n<tool_call>\\n<function=' + tool_call.name + '>\\n' }} | |\n| {%- else %} | |\n| {{- '<tool_call>\\n<function=' + tool_call.name + '>\\n' }} | |\n| {%- endif %} | |\n| {%- else %} | |\n| {{- '\\n<tool_call>\\n<function=' + tool_call.name + '>\\n' }} | |\n| {%- endif %} | |\n| {%- if tool_call.arguments is mapping %} | |\n| {%- for args_name in tool_call.arguments %} | |\n| {%- set args_value = tool_call.arguments[args_name] %} | |\n| {{- '<parameter=' + args_name + '>\\n' }} | |\n| {%- set args_value = args_value | tojson | safe if args_value is mapping or (args_value is sequence and args_value is not string) else args_value | string %} | |\n| {{- args_value }} | |\n| {{- '\\n</parameter>\\n' }} | |\n| {%- endfor %} | |\n| {%- endif %} | |\n| {{- '</function>\\n</tool_call>' }} | |\n| {%- endfor %} | |\n| {%- endif %} | |\n| {{- '<|im_end|>\\n' }} | |\n| {%- elif message.role == \"tool\" %} | |\n| {%- if loop.previtem and loop.previtem.role != \"tool\" %} | |\n| {{- '<|im_start|>user' }} | |\n| {%- endif %} | |\n| {{- '\\n<tool_response>\\n' }} | |\n| {{- content }} | |\n| {{- '\\n</tool_response>' }} | |\n| {%- if not loop.last and loop.nextitem.role != \"tool\" %} | |\n| {{- '<|im_end|>\\n' }} | |\n| {%- elif loop.last %} | |\n| {{- '<|im_end|>\\n' }} | |\n| {%- endif %} | |\n| {%- else %} | |\n| {{- '<|im_start|>user\\n' + content + '<|im_end|>\\n' }} | |\n| {%- endif %} | |\n| {%- endfor %} | |\n| {%- if add_generation_prompt %} | |\n| {{- '<|im_start|>assistant\\n' }} | |\n| {%- if enable_thinking is defined and enable_thinking is false %} | |\n| {{- '<think>\\n\\n</think>\\n\\n' }} | |\n| {%- else %} | |\n| {{- '<think>\\n' }} | |\n| {%- endif %} | |\n| {%- endif %} |\n\nUsage\n\nDrop this file next to your model and run:\n\nllama-server -m Qwen3.5-27B-Q4_K_M.gguf -ngl 99 -c 262144 -np 1 -fa on --cache-type-k q4_0\n\n--cache-type-v q4_0 --chat-template-file qwen3.5_chat_template.jinja\n\nWhat this fixes: Qwen 3.5 GGUF templates reject the developer role sent by OpenCode, Claude Code, and\n\nother modern agent tools. The common workaround (--chat-template chatml) silently kills thinking mode\n\n(thinking = 0). This template handles all roles and preserves full thinking (thinking = 1).\n\nNote: Not needed for Qwopus (Claude Opus distilled version) which uses Hermes 2 Pro template natively.\n\nThanks a lot for this, I'm using it on 9B Q4_K_M with an RTX 5080 and the results are pretty good too!\n\nThanks a lot for this, I'm using it on 9B Q4_K_M with an RTX 5080 and the results are pretty good too!\n\nthanks for confirming! what tok/s are you seeing on the 5080? curious how it compares to 3090 numbers.\n\nit fixes tool calling mostly, but it seems like there is some bug in either model or llamacpp that causes `<tool_call>`\n\nleak into `<think>`\n\nblock too.\n\nI'm still getting tool call errors \"Failed to generate tool call\" and \"Api Error 500\" but I'm using Claude Code with LM Studio 0.4.7 + Qwen3.5-27b. Thought this jinja template would fix but it's not. Any clues what the heck I should do?\n\ncouldnt get it to work and switched from \"LM Studio\" to \"llama-server\" but why are you guys not using the flags \"--temp 0.6 --top-p 0.95 --top-k20...\" with Qwen3.5 in llama-server like recommended from unsloth? I thought they're mandatory for good reasoning? I always used them in LM Studio but Im new to llama-server. Did I miss something?\n\nFyi theres a localllama thread regarding empty think blocks silently losing some of the kv cache reuse.\n\nr/LocalLLaMA/comments/1sg076h/i_tracked_a_major_cache_reuse_issue_down_to_qwen\n\n[https://gist.github.com/sudoingX/c2facf7d8f7608c65c1024ef3b22d431#file-qwen3-5_chat_template-jinja-L100](https://gist.github.com/sudoingX/c2facf7d8f7608c65c1024ef3b22d431#file-qwen3-5_chat_template-jinja-L100)\n\n1 Liner in this revision Initial runs look pretty good on hermes+Qwen3.5-35B-A3B-GPTQ-Int4 on 2x4060\n\n[https://gist.github.com/chuyqa/e44bda6b471792758849971c7cca519c/revisions](https://gist.github.com/chuyqa/e44bda6b471792758849971c7cca519c/revisions)\n\n.. Hermes is stopping less and my cache %s are looking higher..\n\n```\n(APIServer pid=1) INFO 04-10 01:40:19 [loggers.py:259] Engine 000: Avg prompt throughput: 0.0 tokens/s, Avg generation throughput: 85.8 tokens/s, Running: 1 reqs, Waiting: 0 reqs, GPU KV cache usage: 3.8%, Prefix cache hit rate: 96.7%, MM cache hit rate: 33.3%\n(APIServer pid=1) INFO 04-10 01:40:29 [loggers.py:259] Engine 000: Avg prompt throughput: 0.0 tokens/s, Avg generation throughput: 85.5 tokens/s, Running: 1 reqs, Waiting: 0 reqs, GPU KV cache usage: 3.8%, Prefix cache hit rate: 96.7%, MM cache hit rate: 33.3%\n(APIServer pid=1) INFO:     192.168.101.54:51875 - \"POST /v1/chat/completions HTTP/1.1\" 200 OK\n\n(APIServer pid=1) INFO 04-10 01:40:37 [qwen3xml_tool_parser.py:1178] vLLM Successfully import tool parser Qwen3XMLToolParser !\n(APIServer pid=1) INFO 04-10 01:40:39 [loggers.py:259] Engine 000: Avg prompt throughput: 0.0 tokens/s, Avg generation throughput: 66.5 tokens/s, Running: 1 reqs, Waiting: 0 reqs, GPU KV cache usage: 5.2%, Prefix cache hit rate: 96.6%, MM cache hit rate: 33.3%\n(APIServer pid=1) INFO 04-10 01:40:53 [qwen3xml_tool_parser.py:1178] vLLM Successfully import tool parser Qwen3XMLToolParser !\n(APIServer pid=1) INFO 04-10 01:40:59 [loggers.py:259] Engine 000: Avg prompt throughput: 5003.2 tokens/s, Avg generation throughput: 63.1 tokens/s, Running: 1 reqs, Waiting: 0 reqs, GPU KV cache usage: 12.7%, Prefix cache hit rate: 96.6%, MM cache hit rate: 33.3%\n```\n\nThanks a lot for this, I'm using it on 9B Q4_K_M with an RTX 5080 and the results are pretty good too!\n\nSamesies! Results are pretty good so far, this was an annoying bug for opencode & claude-code", "url": "https://wpnews.pro/news/patched-jinja-template-for-qwen-3-5-27b-fixes-developer-role-crash-preserves-1", "canonical_source": "https://gist.github.com/sudoingX/c2facf7d8f7608c65c1024ef3b22d431", "published_at": "2026-03-07 11:57:32+00:00", "updated_at": "2026-05-21 14:13:08.277912+00:00", "lang": "en", "topics": ["large-language-models", "developer-tools", "open-source"], "entities": ["Qwen", "OpenCode", "Claude Code", "Continue", "Cursor", "Aider", "Jinja"], "alternates": {"html": "https://wpnews.pro/news/patched-jinja-template-for-qwen-3-5-27b-fixes-developer-role-crash-preserves-1", "markdown": "https://wpnews.pro/news/patched-jinja-template-for-qwen-3-5-27b-fixes-developer-role-crash-preserves-1.md", "text": "https://wpnews.pro/news/patched-jinja-template-for-qwen-3-5-27b-fixes-developer-role-crash-preserves-1.txt", "jsonld": "https://wpnews.pro/news/patched-jinja-template-for-qwen-3-5-27b-fixes-developer-role-crash-preserves-1.jsonld"}}