[PR #15774] Harden Qwen-family tool payload rendering and fix Qwen 3.5 tool-block truncation integrity #77597

Open
opened 2026-05-05 10:15:58 -05:00 by GiteaMirror · 0 comments
Owner

📋 Pull Request Information

Original PR: https://github.com/ollama/ollama/pull/15774
Author: @B-A-M-N
Created: 4/23/2026
Status: 🔄 Open

Base: mainHead: qwen-family-hardening


📝 Commits (1)

  • 0159e10 Harden qwen-family tool payload handling

📊 Changes

9 files changed (+618 additions, -17 deletions)

View changed files

📝 model/renderers/qwen35.go (+3 -3)
📝 model/renderers/qwen35_test.go (+110 -0)
📝 model/renderers/qwen3coder.go (+91 -7)
📝 model/renderers/qwen3coder_test.go (+77 -0)
📝 model/renderers/qwen3vl.go (+1 -1)
📝 model/renderers/qwen3vl_nonthinking_test.go (+21 -0)
📝 model/renderers/qwen3vl_thinking_test.go (+20 -0)
📝 server/prompt.go (+27 -6)
📝 server/prompt_test.go (+268 -0)

📄 Description

Summary

This PR fixes multiple Qwen-family renderer correctness issues and one separate Qwen 3.5 truncation correctness issue.

Qwen-family renderer fixes

Across the Qwen renderer paths, tool payloads are now handled as untrusted text at the renderer boundary. This patch:

  • hardens tool-response rendering in:
    • qwen35
    • qwen3coder
    • qwen3vl
  • hardens tool-call argument rendering in the Qwen coder path
  • uses a shared entity-aware XML text escaper
  • preserves valid named and numeric entities
  • escapes malformed and partial entities safely
  • is idempotent
  • preserves JSON semantics inside tool-call argument payloads
  • aligns tool-response whitespace behavior across the Qwen-family renderers

Qwen 3.5-specific truncation fix

Separately, this PR fixes a Qwen 3.5 truncation correctness issue in server/prompt.go:

  • assistant messages with ToolCalls plus their immediately following contiguous tool messages are treated as a narrow unit during truncation
  • tool responses no longer survive truncation without their parent assistant tool-call turn
  • if that narrow block cannot fit, it is dropped as a unit rather than retaining an orphaned tool response

What changed

1. Shared Qwen-family renderer hardening

Files:

  • model/renderers/qwen3coder.go
  • model/renderers/qwen35.go
  • model/renderers/qwen3vl.go

Changes:

  • added shared entity-aware, idempotent XML text escaping for Qwen tool payload rendering
  • escaped Qwen tool-response payloads before embedding in <tool_response> blocks
  • escaped Qwen tool-call argument payloads in the coder path
  • preserved exact tool-response whitespace in qwen35 so behavior now matches the other Qwen-family renderers

This prevents structural prompt corruption from payloads such as:

  • </tool_response>
  • <tool_call>...
  • <think>...
  • mixed raw and entity payloads
  • JSON containing <, >, and &

2. Qwen 3.5-specific truncation integrity

File:

  • server/prompt.go

Changes:

  • if truncation would begin inside a Qwen 3.5 tool-response block, the retained window rolls back to the parent assistant message with ToolCalls
  • contiguous following tool messages are treated as part of that same narrow block
  • if the block still cannot fit, it is dropped as a unit

This preserves causal continuity without redesigning global truncation behavior.

Why this is split this way

This PR contains two distinct fix classes:

Shared Qwen-family renderer fixes

These are mechanical renderer-boundary hardening changes that apply across:

  • qwen35
  • qwen3coder
  • qwen3vl

Qwen 3.5-specific truncation fix

This is narrower and only addresses truncation integrity for the Qwen 3.5 tool-call path.

These are kept logically separate in this PR so the shared renderer changes and the model-path-specific truncation change can be reviewed independently.

Verification

Renderer hardening

Verified for:

  • XML structural safety
  • valid and malformed entity handling
  • idempotence
  • JSON preservation
  • whitespace fidelity
  • cross-renderer consistency across qwen35, qwen3coder, and qwen3vl

Truncation integrity

Verified that:

  • assistant ToolCalls plus contiguous following tool messages behave as one narrow unit
  • no orphaned tool responses survive truncation
  • oversized blocks are dropped as a unit
  • multiple blocks remain independent

Tests

Ran targeted tests covering:

  • renderer escaping and idempotence
  • JSON preservation in tool-call argument payloads
  • cross-renderer whitespace and entity behavior
  • Qwen 3.5 truncation behavior, including contiguous multi-tool responses and non-contiguous tool-message cases

Non-goals

This PR does not:

  • change parser behavior
  • change sampler behavior
  • redesign global truncation policy
  • harden non-Qwen renderer families in the same patch

Known out-of-scope follow-up

Similar raw tool-response insertion surfaces still exist outside the Qwen family in other renderer paths. I intentionally kept this PR scoped to Qwen-family renderer correctness plus the Qwen 3.5 truncation fix so the change remains minimal and reviewable.


🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.

## 📋 Pull Request Information **Original PR:** https://github.com/ollama/ollama/pull/15774 **Author:** [@B-A-M-N](https://github.com/B-A-M-N) **Created:** 4/23/2026 **Status:** 🔄 Open **Base:** `main` ← **Head:** `qwen-family-hardening` --- ### 📝 Commits (1) - [`0159e10`](https://github.com/ollama/ollama/commit/0159e10a0eafcbd615a2767f5dc687ae77fcf199) Harden qwen-family tool payload handling ### 📊 Changes **9 files changed** (+618 additions, -17 deletions) <details> <summary>View changed files</summary> 📝 `model/renderers/qwen35.go` (+3 -3) 📝 `model/renderers/qwen35_test.go` (+110 -0) 📝 `model/renderers/qwen3coder.go` (+91 -7) 📝 `model/renderers/qwen3coder_test.go` (+77 -0) 📝 `model/renderers/qwen3vl.go` (+1 -1) 📝 `model/renderers/qwen3vl_nonthinking_test.go` (+21 -0) 📝 `model/renderers/qwen3vl_thinking_test.go` (+20 -0) 📝 `server/prompt.go` (+27 -6) 📝 `server/prompt_test.go` (+268 -0) </details> ### 📄 Description ## Summary This PR fixes multiple Qwen-family renderer correctness issues and one separate Qwen 3.5 truncation correctness issue. ### Qwen-family renderer fixes Across the Qwen renderer paths, tool payloads are now handled as untrusted text at the renderer boundary. This patch: - hardens tool-response rendering in: - `qwen35` - `qwen3coder` - `qwen3vl` - hardens tool-call argument rendering in the Qwen coder path - uses a shared entity-aware XML text escaper - preserves valid named and numeric entities - escapes malformed and partial entities safely - is idempotent - preserves JSON semantics inside tool-call argument payloads - aligns tool-response whitespace behavior across the Qwen-family renderers ### Qwen 3.5-specific truncation fix Separately, this PR fixes a Qwen 3.5 truncation correctness issue in `server/prompt.go`: - assistant messages with `ToolCalls` plus their immediately following contiguous `tool` messages are treated as a narrow unit during truncation - tool responses no longer survive truncation without their parent assistant tool-call turn - if that narrow block cannot fit, it is dropped as a unit rather than retaining an orphaned tool response ## What changed ### 1. Shared Qwen-family renderer hardening Files: - `model/renderers/qwen3coder.go` - `model/renderers/qwen35.go` - `model/renderers/qwen3vl.go` Changes: - added shared entity-aware, idempotent XML text escaping for Qwen tool payload rendering - escaped Qwen tool-response payloads before embedding in `<tool_response>` blocks - escaped Qwen tool-call argument payloads in the coder path - preserved exact tool-response whitespace in `qwen35` so behavior now matches the other Qwen-family renderers This prevents structural prompt corruption from payloads such as: - `</tool_response>` - `<tool_call>...` - `<think>...` - mixed raw and entity payloads - JSON containing `<`, `>`, and `&` ### 2. Qwen 3.5-specific truncation integrity File: - `server/prompt.go` Changes: - if truncation would begin inside a Qwen 3.5 tool-response block, the retained window rolls back to the parent assistant message with `ToolCalls` - contiguous following `tool` messages are treated as part of that same narrow block - if the block still cannot fit, it is dropped as a unit This preserves causal continuity without redesigning global truncation behavior. ## Why this is split this way This PR contains two distinct fix classes: ### Shared Qwen-family renderer fixes These are mechanical renderer-boundary hardening changes that apply across: - `qwen35` - `qwen3coder` - `qwen3vl` ### Qwen 3.5-specific truncation fix This is narrower and only addresses truncation integrity for the Qwen 3.5 tool-call path. These are kept logically separate in this PR so the shared renderer changes and the model-path-specific truncation change can be reviewed independently. ## Verification ### Renderer hardening Verified for: - XML structural safety - valid and malformed entity handling - idempotence - JSON preservation - whitespace fidelity - cross-renderer consistency across `qwen35`, `qwen3coder`, and `qwen3vl` ### Truncation integrity Verified that: - assistant `ToolCalls` plus contiguous following `tool` messages behave as one narrow unit - no orphaned tool responses survive truncation - oversized blocks are dropped as a unit - multiple blocks remain independent ### Tests Ran targeted tests covering: - renderer escaping and idempotence - JSON preservation in tool-call argument payloads - cross-renderer whitespace and entity behavior - Qwen 3.5 truncation behavior, including contiguous multi-tool responses and non-contiguous tool-message cases ## Non-goals This PR does **not**: - change parser behavior - change sampler behavior - redesign global truncation policy - harden non-Qwen renderer families in the same patch ## Known out-of-scope follow-up Similar raw tool-response insertion surfaces still exist outside the Qwen family in other renderer paths. I intentionally kept this PR scoped to Qwen-family renderer correctness plus the Qwen 3.5 truncation fix so the change remains minimal and reviewable. --- <sub>🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.</sub>
GiteaMirror added the pull-request label 2026-05-05 10:15:58 -05:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github-starred/ollama#77597