Skip to content

Fix review findings for chat-template repair (#5049)#5056

Merged
danielhanchen merged 2 commits into
mainfrom
fix/chat-template-review-fixes
Apr 16, 2026
Merged

Fix review findings for chat-template repair (#5049)#5056
danielhanchen merged 2 commits into
mainfrom
fix/chat-template-review-fixes

Conversation

@danielhanchen
Copy link
Copy Markdown
Member

Summary

Follow-up fixes for #5049 (merged as c5be8b1), addressing 4 issues found during code review:

  • Sandbox fallback in _VariantTokenizerProxy: The read-only tokenizer fallback path in apply_chat_template used a plain jinja2.Environment. Changed to SandboxedEnvironment to match the security posture of _derive_assistant_prefix_by_render, which already uses sandboxing for the same model-load-time rendering context.

  • Benign outer-If unwrapping in _template_ends_with_toplevel_for: Templates wrapped in harmless guards like {% if messages %}{% for ... %}{% endfor %}{% endif %} were rejected by the top-level-For check, preventing repair. Now unwraps outer-If nodes that have no else branch and don't test add_generation_prompt, while still rejecting structural wrappers like Qwen3-Guard.

  • Local-path diagnostics for dict/list variants: _VariantTokenizerProxy appended a variant label to name_or_path, breaking os.path.isdir() in _name_is_local_path. Added _source_path to preserve the raw path for local-path detection, passed via local_path_source to _format_chat_template_message.

  • Strict-mode message consistency: When UNSLOTH_STRICT_CHAT_TEMPLATE=1 is set and a RuntimeError is raised, the message no longer says "The model will still load" or "Set UNSLOTH_STRICT_CHAT_TEMPLATE=1 to raise instead of warn."

Test plan

  • Verified all 4 fixes with targeted pytest suite (30 test files, 200+ test cases)
  • Confirmed no regressions on ChatML, Llama-3, Gemma, Phi-4, ShareGPT template shapes
  • Confirmed dict/list/string/None chat_template paths all work correctly
  • Tested strict mode, warn mode, read-only tokenizer, and concurrent access paths

1. Sandbox fallback Jinja env in _VariantTokenizerProxy.apply_chat_template
   (use SandboxedEnvironment, matching _derive_assistant_prefix_by_render)
2. Unwrap benign outer-If guards in _template_ends_with_toplevel_for so
   templates like {% if messages %}{% for ... %}{% endfor %}{% endif %}
   are still repairable (preserves Qwen3-Guard rejection via else-branch
   and add_generation_prompt-name checks)
3. Preserve raw name_or_path in _VariantTokenizerProxy._source_path so
   local-path detection works for dict/list variant tokenizers
4. Context-aware strict-mode messages: omit "will still load" and
   "Set UNSLOTH_STRICT_CHAT_TEMPLATE=1" when already raising
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d736a12bc4

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

return None

node = _last_structural(ast.body)
while isinstance(node, jinja2.nodes.If) and not node.else_:
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Treat elif wrappers as structural in top-level loop gate

_template_ends_with_toplevel_for now unwraps outer If nodes whenever else_ is empty, but it does not check elif_. In Jinja, an if/elif/.../endif can have no else_ while still having alternate branches, so templates like {% if cond %}{% for ... %}{% endfor %}{% elif other %}...{% endif %} are incorrectly treated as “benign” and considered repairable. That lets _fix_chat_template inject an add_generation_prompt block after the outer endif, changing behavior for non-primary branches and potentially producing invalid prompts for those models.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request enhances the chat template repair logic in unsloth/tokenizer_utils.py. Key changes include refactoring _template_ends_with_toplevel_for to correctly identify repairable templates wrapped in simple if guards and updating _format_chat_template_message to provide more detailed feedback when templates are missing or have ineffective add_generation_prompt blocks. Additionally, the code now better tracks the source path of tokenizers for more accurate error reporting. I have no feedback to provide as there were no review comments to evaluate.

@danielhanchen danielhanchen merged commit ff23ce4 into main Apr 16, 2026
5 checks passed
@danielhanchen danielhanchen deleted the fix/chat-template-review-fixes branch April 16, 2026 15:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant