Skip to content

fix(core): emit enable_thinking on DashScope when reasoning is disabled#4505

Merged
doudouOUC merged 6 commits into
QwenLM:mainfrom
doudouOUC:fix/dashscope-enable-thinking
May 31, 2026
Merged

fix(core): emit enable_thinking on DashScope when reasoning is disabled#4505
doudouOUC merged 6 commits into
QwenLM:mainfrom
doudouOUC:fix/dashscope-enable-thinking

Conversation

@doudouOUC

@doudouOUC doudouOUC commented May 25, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • pipeline.ts:362 previously gated the qwen3 thinking-disable on 'enable_thinking' in typed — but provider buildRequest never auto-injects this qwen3-specific extension, so the check failed on QWEN_OAUTH defaults and any qwen3 model without extra_body.enable_thinking explicitly configured (e.g. qwen3.5-flash on the fast-model path). Side-queries silently burned 1.5–6 K reasoning tokens (24–95× over budget per bug(provider/dashscope): side-query thinking disable doesn't reach qwen3 series — 'enable_thinking' in typed check never fires #4501) despite sideQuery.applyThinkingDefault setting includeThoughts: false.
  • Replace the guard with a DashScopeOpenAICompatibleProvider.isDashScopeProvider-gated unconditional set, mirroring the existing isDeepSeekHostname branch immediately below.
  • Scope: qwen3 hybrid via DashScope's compatible-mode contract. GLM (extra_body.thinking.enabled) and DeepSeek-on-DashScope (thinking: { type: 'disabled' }) need different disable shapes — pre-existing gap, called out in the new in-code comments but not closed in this PR.

Test plan

  • Add 5 new tests covering: DashScope hostname + includeThoughts: false; DashScope + reasoning: false config opt-out; QWEN_OAUTH regardless of baseUrl; internal *.alibaba-inc.com; non-DashScope hostname (negative — must NOT emit enable_thinking)
  • Tighten existing override test (pipeline.test.ts:465) with explicit DashScope baseUrl so the hostname gate is exercised by intent rather than the !baseUrl default
  • pipeline.test.ts — 47/47 passing
  • Adjacent test files — 640/640 passing across 18 files (dashscope / deepseek / openrouter / converter / loggingContentGenerator / client / etc.)
  • tsc --noEmit clean
  • eslint clean on changed files

人工验证

setting不配置enable_thinking
之前:
image

之后:

image

Closes #4501

🤖 Generated with Qwen Code


📝 描述准确性更新(2026-05-31,作者自查)

更正:enable_thinking 设置除 isDashScopeProvider 外还有 model-name 门(model 以 qwen 开头或 coder-model),body 的 unconditional / mirroring deepseek 表述偏宽。(注:该门基于 request.model,即 side-query 的运行时模型,符合预期,非缺陷。)

The previous `'enable_thinking' in typed` guard only flipped the field
when pre-populated, but provider buildRequest never auto-injects this
qwen3-specific extension. So QWEN_OAUTH defaults and any qwen3 model
without `extra_body.enable_thinking` configured (e.g. qwen3.5-flash)
silently burned reasoning tokens on every side-query despite
includeThoughts: false. Hostname-gated unconditional set mirrors the
existing DeepSeek branch.

Closes QwenLM#4501

🤖 Generated with [Qwen Code](https://github.com/QwenLM/qwen-code)
Copilot AI review requested due to automatic review settings May 25, 2026 11:00

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR fixes a regression where “thinking disabled” signals were not reliably emitted for Qwen3 hybrid-thinking models when calling DashScope OpenAI-compatible endpoints, causing side-queries to unintentionally consume large reasoning token budgets. It updates the OpenAI pipeline to always emit enable_thinking: false when reasoning is disabled on DashScope endpoints, and adds targeted regression tests to prevent reintroducing the issue.

Changes:

  • Inject enable_thinking: false into the outgoing wire request when reasoning is disabled and the endpoint is identified as DashScope-compatible.
  • Update inline reasoning-shape documentation to reflect the DashScope/Qwen3 disable behavior.
  • Add/adjust unit tests to cover DashScope hostname gating (including QWEN_OAUTH and internal *.alibaba-inc.com) and negative cases.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

File Description
packages/core/src/core/openaiContentGenerator/pipeline.ts Adds DashScope-gated unconditional emission of enable_thinking: false when reasoning is disabled; updates related reasoning-shape commentary.
packages/core/src/core/openaiContentGenerator/pipeline.test.ts Adds regression tests for DashScope/Qwen3 thinking-disable propagation and tightens an existing override test to exercise hostname gating.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread packages/core/src/core/openaiContentGenerator/pipeline.ts Outdated
Comment thread packages/core/src/core/openaiContentGenerator/pipeline.test.ts Outdated
@github-actions

Copy link
Copy Markdown
Contributor

📋 Review Summary

This PR fixes a critical bug where Qwen3 hybrid-thinking models (e.g., qwen3.5-flash) were burning 24–95× more reasoning tokens than budgeted because the enable_thinking: false disable signal was never emitted on the wire. The fix replaces a flawed presence-check guard with a DashScope-provider-gated unconditional set, mirroring the existing DeepSeek hostname branch. Test coverage is comprehensive and well-structured.

🔍 General Feedback

  • Strong problem diagnosis: The PR body clearly explains the root cause (provider buildRequest never auto-injects enable_thinking, so the 'enable_thinking' in typed check failed on vanilla requests), the production impact (1.5–6K reasoning tokens burned on side-queries), and the fix strategy (hostname-gated unconditional set).
  • Excellent test coverage: Five new test cases cover the critical paths: DashScope hostname + includeThoughts: false, config-level reasoning: false opt-out, QWEN_OAUTH flow, internal Alibaba domains, and a negative test ensuring non-DashScope hostnames don't receive the qwen-specific field.
  • Scoped appropriately: The PR explicitly calls out that GLM and DeepSeek-on-DashScope need different disable shapes and documents this as a pre-existing gap rather than scope-creeping this fix.
  • Test plan tightened: The existing override test (pipeline.test.ts:465) was improved to explicitly set DashScope baseUrl so the hostname gate is exercised by intent rather than relying on the !baseUrl default.

🎯 Specific Feedback

🟢 Medium

  • File: pipeline.ts:362-383 - The comment block explaining the fix is comprehensive but could benefit from a brief mention of how the provider detection works (i.e., that isDashScopeProvider covers QWEN_OAUTH, dashscope.aliyuncs.com domains, and internal Alibaba domains). This would help future reviewers understand why the gate is trustworthy without jumping to the provider file.
    • Suggestion: Add a one-liner: "The provider detection covers QWEN_OAUTH authType, dashscope.aliyuncs.com hostnames, and internal *.alibaba-inc.com / *.aliyun-inc.com domains."

🔵 Low

  • File: pipeline.ts:489-495 - The updated comment for qwen3 series is accurate but slightly verbose for an inline comment. Consider tightening:

    • Current: "model-dependent; emitted as enable_thinking: false by buildRequest above when reasoning is disabled on DashScope endpoints (provider never auto-injects this field, so the wire body would otherwise lack the disable signal)"
    • Suggested: "model-dependent; emitted as enable_thinking: false when reasoning is disabled on DashScope endpoints (provider never auto-injects this field)"
  • File: pipeline.test.ts:761-810 - The first regression test (emits enable_thinking:false on DashScope hostname when includeThoughts is false) has excellent inline commentary explaining the production impact. Consider adding a similar one-liner to the second test (reasoning: false config opt-out) to clarify why this path matters (e.g., "Users can opt out via setup wizard's reasoning: false config, which should also disable qwen3 thinking").

  • File: pipeline.test.ts:18 - The import change from import type { ContentGeneratorConfig, AuthType } to import { AuthType, type ContentGeneratorConfig } is correct (AuthType is used as a runtime value in the test at line ~864), but adding a brief comment or keeping the import style consistent with other test files would improve maintainability.

✅ Highlights

  • Production-impacting fix: This closes a significant token-burn issue that affected side-queries (suggestions, recaps, etc.) on Qwen3 models, bringing actual token usage in line with user expectations.
  • Defensive test design: The negative test (does NOT emit enable_thinking on a non-DashScope hostname) is particularly valuable—it prevents future regressions where the enable_thinking field might accidentally leak to OpenAI or other providers.
  • QWEN_OAUTH coverage: Explicitly testing the QWEN_OAUTH path (line ~834) is smart—this is the default flow for first-time users and doesn't go through the wizard's extra_body setup, so it's a critical production scenario.
  • Clear scoping boundaries: The in-code comments acknowledging GLM and DeepSeek-on-DashScope as pre-existing gaps show good judgment about PR scope and set up future work cleanly.

The previous comment block restated mechanics already visible in the
code (overrides, mirrors DeepSeek, set-by-side-query) and pinned
"includeThoughts: false" as the trigger when reasoning: false also
qualifies. Keep only the two non-obvious whys (provider doesn't
auto-inject; gate prevents leaking the qwen-specific field) plus a
one-line scope note.

🤖 Generated with [Qwen Code](https://github.com/QwenLM/qwen-code)
@qwen-code-ci-bot

Copy link
Copy Markdown
Collaborator

Qwen Code Review (LIGHT)

What this PR does: Replaces the 'enable_thinking' in typed guard with a DashScopeOpenAICompatibleProvider.isDashScopeProvider() hostname/auth gate, so that enable_thinking: false is explicitly emitted on the wire for qwen3 models when reasoning is disabled — fixing a bug where side-queries silently burned reasoning tokens because the field was never present to be overridden.

Findings:

No issues found in the visible diff at this tier.

The logic change is correct and mirrors the existing isDeepSeekHostname branch pattern. The isDashScopeProvider gate correctly covers all four DashScope paths (QWEN_OAUTH, no-baseUrl default, dashscope hostname, internal Alibaba domains) and the negative test for non-DashScope hostnames confirms the field doesn't leak. The second commit trims the comment block to only the non-obvious rationale — good editorial judgment. Tests are thorough with both positive and negative coverage.


Qwen Code /review · qwen3.7-max · tier LIGHT

Comment thread packages/core/src/core/openaiContentGenerator/pipeline.ts
wenshao
wenshao previously approved these changes May 25, 2026

@wenshao wenshao left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

No review findings. LGTM! ✅ — qwen3.7-max via Qwen Code /review

@doudouOUC doudouOUC requested a review from pomelo-nwu May 25, 2026 12:23
@LaZzyMan LaZzyMan added the type/bug Something isn't working as expected label May 25, 2026
@wenshao

wenshao commented May 26, 2026

Copy link
Copy Markdown
Collaborator

🧪 本地构建验证报告 (维护者)

在隔离 worktree 中对 PR 合并后状态做了完整验证,结论是可以合并

验证环境

  • Worktree: /private/tmp/pr4505-merged
  • Base: PR 头 74bdd17ad merge 当前 origin/main (e8b79d772) → 合并 commit 61741763a
  • Node + npm ci + build + bundle: ✅ 全部成功 (packages/core & CLI bundle 均产出)
  • 构建工具: tmux session pr4505, 6 个 window 并行跑

静态检查

检查 范围 结果
tsc --noEmit packages/core 全包 ✅ EXIT_CODE=0
eslint 仅改动文件 (pipeline.ts + pipeline.test.ts) ✅ EXIT_CODE=0

单元测试 (与 PR 描述对齐)

套件 结果
pipeline.test.ts 47/47 通过 (含本 PR 新增 5 + 加固 1)
src/core/openaiContentGenerator/ 全目录 (15 文件) 431/431 通过, 0 跳过

🔬 回归证据 (核心)

为了验证新增测试是否真的能捕获原 bug,做了一组对比实验:把 PR 的 5 个新测试保留,仅把 pipeline.ts 还原回 origin/main (e8b79d772) 的旧实现 ('enable_thinking' in typed guard),再跑同一文件:

Tests  4 failed | 3 passed | 40 skipped (47)

4 个失败测试与 PR 修复点完全对应:

  • emits enable_thinking:false on DashScope hostname when includeThoughts is false → expected false, got undefined
  • emits enable_thinking:false on DashScope hostname when reasoning is configured to false → expected false, got undefined
  • emits enable_thinking:false on QWEN_OAUTH regardless of baseUrl → expected false, got undefined
  • emits enable_thinking:false on internal alibaba-inc.com hostname → expected false, got undefined

pipeline.ts 恢复到本 PR 的版本后, 同一组测试 7/7 全过。证明:

  1. issue bug(provider/dashscope): side-query thinking disable doesn't reach qwen3 series — 'enable_thinking' in typed check never fires #4501 描述的 bug 在 main 上确实存在, 旧代码完全没把 enable_thinking: false 发到线上 (字段是 undefined)
  2. 新增测试能够稳定捕获回归, 不是套娃 mock 自证
  3. 修复后 wire body 行为正确

🌐 端到端 wire-body 验证 (跳过 mock, 抓真实 HTTP)

为了越过单元测试的 mock 层, 启了一个本地 HTTP server 作为 mock 后端, 用 dist/ 里构建好的真实 createOpenAIContentGenerator 走完整路径 (DashScope provider + pipeline + OpenAI SDK), 抓取 server 实际收到的请求 body. 4 条路径全部符合预期:

场景 真实 wire body 的 enable_thinking 期望 结果
DASHSCOPE_PROXY_BASE_URL 匹配 + includeThoughts: false false false
QWEN_OAUTH (任意 baseUrl) + includeThoughts: false false false
QWEN_OAUTH + reasoning: false config false false
非 DashScope baseUrl (负向) undefined undefined

这是发到线上的真实 HTTP 请求体, 不是任何 mock 自证. QWEN_OAUTH 默认路径——也就是 issue #4501 在生产环境烧 token 的那条路径——现在确实带上了 enable_thinking: false.

范围检查 (符合 PR 自述)

  • ✅ 仅 DashScope/qwen3 路径被改动, DeepSeek 分支 (pipeline.ts:395-397) 保持原样
  • ✅ GLM (extra_body.thinking.enabled) 和 DeepSeek-on-DashScope (thinking: { type: 'disabled' }) 按 PR 自述未在本次范围内处理, 这是 pre-existing gap, 代码内注释 (pipeline.ts:368-369) 已明确标注
  • ✅ 负向测试覆盖了"非 DashScope 域名不得携带 qwen 私有字段"——避免向严格 OpenAI 兼容后端泄露未知字段触发 400

结论

修复点位准确, 与 issue #4501 根因匹配 (qwen3 enable_thinking 是 OpenAI-compatible 之外的非标扩展, provider buildRequest 不会自动注入, 旧 in typed guard 永远不命中); 实现走 DashScopeOpenAICompatibleProvider.isDashScopeProvider 与下面的 isDeepSeekHostname 分支对称, 边界覆盖到 dashscope.aliyuncs.com / dashscope-intl / token-plan.*.maas.aliyuncs.com / *.alibaba-inc.com / *.aliyun-inc.com / DASHSCOPE_PROXY_BASE_URL / QWEN_OAUTH; 新增测试既覆盖正向也覆盖负向, 且在 baseline 上能稳定 fail (避免占位 mock); diff 极小 (pipeline.ts +13/-2), 没有引入新依赖或重构.

建议:可以合并。 在我的环境里复现的回归与 issue 描述完全一致, 修复后所有 wire body 检查通过.

@LaZzyMan LaZzyMan left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Review

Targeted fix for the qwen3 default case where ${enable_thinking} in typed was a no-op because extra_body.enable_thinking is absent from default configs — side-queries on qwen3-hybrid models burned reasoning tokens despite includeThoughts: false. The new hostname-gated unconditional set mirrors the existing DeepSeek branch idiom, keeps the override semantics inside the reasoningDisabled block intact, and adds strong test coverage including a api.openai.com negative case that pins the gate.

One scope-widening note worth tightening before merge. The new gate is isDashScopeProvider-only, so when a user routes a non-Qwen model (GLM, DeepSeek) through DashScope's compatible endpoint and reasoning is disabled, the patch now sends enable_thinking: false regardless of model id. Practical impact depends on DashScope's leniency toward unknown extras: if it silently drops them, harmless; if it forwards them to the upstream model serving, the side-query may 400 where it previously succeeded. The PR description and inline comments already acknowledge that GLM uses extra_body.thinking.enabled and DeepSeek-on-DashScope uses thinking: { type: 'disabled' } — different shapes the qwen3 disable doesn't satisfy. ANDing the host check with a model-id regex (e.g. ^qwen[23]) restores the pre-PR conservative posture for non-Qwen routing without breaking the qwen3 fix.

Verdict

COMMENT — solves #4501 correctly with strong tests; the host-only gate widens the trigger surface beyond what the fix needs, worth restricting to qwen-prefixed models before the next provider lands the same shape.

Comment thread packages/core/src/core/openaiContentGenerator/pipeline.ts
Comment thread packages/core/src/core/openaiContentGenerator/pipeline.test.ts
DashScope's compatible-mode endpoint routes multiple model families
(qwen3, GLM, DeepSeek). The hostname-only gate would send
`enable_thinking: false` to all of them when reasoning is disabled —
fine for qwen, but GLM uses `extra_body.thinking.enabled` and
DeepSeek-on-DashScope uses `thinking: { type: 'disabled' }`. Sending
the qwen-specific field to those is at best a no-op and at worst
forwarded upstream and rejected. AND the gate with a `qwen` model-name
prefix to restore conservative posture for non-qwen routings.

Per review feedback from @LaZzyMan and @wenshao.

🤖 Generated with [Qwen Code](https://github.com/QwenLM/qwen-code)

@wenshao wenshao left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

R4: both R3 suggestions addressed in 7a7da7c — scope widening closed via startsWith('qwen') model-name filter, test gap filled with non-qwen negative test (glm-5 on DashScope). Multi-agent review (9 agents + reverse audit) across correctness, security, quality, performance, and test coverage found no high-confidence issues. Build clean, 48/48 tests pass, CI green 9/9. LGTM. ✅ — qwen3.7-max via Qwen Code /review

@longbinlai

Copy link
Copy Markdown

⚠️ Cross-PR Conflict Detected

This PR shares modified code with PR #4518 (fix(core): stabilize DeepSeek tool cache prefix).

Shared functions:

  • buildRequestpackages/core/src/core/openaiContentGenerator/pipeline.ts

Both PRs also modify files in the openaiContentGenerator directory:

Recommendation: These PRs both modify the OpenAI content generation pipeline. PR #4505 adds DashScope reasoning config while PR #4518 stabilizes the response converter. Test together before merging.


This conflict was detected automatically by CodeScope skills — structural analysis of call graphs and file modifications.

@longbinlai

Copy link
Copy Markdown

⚠️ Cross-PR Conflict Detected

This PR shares modified code with PR #4518 (fix(core): stabilize DeepSeek tool cache prefix).

Shared functions:

  • buildRequestpackages/core/src/core/openaiContentGenerator/pipeline.ts

Both PRs also modify files in the openaiContentGenerator directory:

Recommendation: These PRs both modify the OpenAI content generation pipeline. PR #4505 adds DashScope reasoning config while PR #4518 stabilizes the response converter. Test together before merging.


This conflict was detected automatically by CodeScope skills — structural analysis of call graphs and file modifications.

Comment thread packages/core/src/core/openaiContentGenerator/pipeline.ts Outdated
Comment thread packages/core/src/core/openaiContentGenerator/pipeline.ts
Comment thread packages/core/src/core/openaiContentGenerator/pipeline.ts
`coder-model` is `DEFAULT_QWEN_MODEL` (config/models.ts:7) — the
QWEN_OAUTH default model, aliased to Qwen 3.6 Plus hybrid. The previous
`startsWith('qwen')` gate didn't match it (string starts with `c`), so
QWEN_OAUTH default-flow users — the exact scenario QwenLM#4501 targets —
continued burning reasoning tokens on side-queries. The original
QWEN_OAUTH test masked this by using `qwen3-coder-flash`; switch it to
the real default `coder-model` so the regression is pinned.

Also add a `!baseUrl` regression test to cover the implicit-DashScope
default branch in `isDashScopeProvider` (dashscope.ts:49) — all other
positive tests explicitly set baseUrl.

Per @wenshao's [Critical] review.

🤖 Generated with [Qwen Code](https://github.com/QwenLM/qwen-code)

@wenshao wenshao left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

R6: No review findings — R5 Critical (coder-model gate miss) resolved in 4491a70. Downgraded from Approve to Comment: CI failing (Test (windows-latest, Node 22.x)). — qwen3.7-max via Qwen Code /review

@wenshao

wenshao commented May 27, 2026

Copy link
Copy Markdown
Collaborator

本地真实测试报告(merge 参考)

按 PR body 的 Test plan 完成静态检查 + 全套相关 vitest + 一段直接命中 ContentGenerationPipeline.execute() 的真磁盘 smoke。Issue #4501 的核心 bug——'enable_thinking' in typed 永远为 false 导致 qwen3 side-query 烧掉 1.5–6K reasoning token——在本 PR 下已修复,且 hostname / model 双闸口在 5 个正向 + 3 个负向场景下都按预期工作。可以合并。

测试环境

测试 commit 4491a70c7 (fix(core): cover coder-model in DashScope enable_thinking gate)
平台 macOS arm64 (Darwin 25.4.0)
Node v22.17.0
Worktree /Users/wenshao/Work/git/qwen-code/.qwen/tmp/review-pr-4505(基于 pull/4505/head

1. 静态检查与单测(PR body Test plan 三项)

命令 结果 PR body 声明
npx tsc --noEmit -p packages/core CORE=0 clean
npx tsc --noEmit -p packages/cli CLI=0
npx eslint pipeline.{ts,test.ts} ESLINT=0 clean
npx vitest run pipeline.test.ts 49/49 47/47(review 期间 +2 个 case:fix(core): restrict enable_thinking emission to qwen models on DashScopefix(core): cover coder-model in DashScope enable_thinking gate
npx vitest run packages/core/src/core/openaiContentGenerator/ 583 / 16 files 640 / 18 files(不完全对齐,因 PR body 还把 loggingContentGenerator.test.ts / client.test.ts / converter.test.ts 算进去;这两条我也一并跑通 → 算上后 ≥ 583,全绿)

2. 真磁盘 smoke——直接命中生产 ContentGenerationPipeline.execute()

写了一段 tsx 脚本,import worktree 里真实ContentGenerationPipeline + OpenAIContentConverter,挂一个 identity provider(buildRequest: req => req,复刻"用户 settings 没有 extra_body.enable_thinking 显式配置"的 #4501 现场)和一个抓取 wire body 的 stub OpenAI client,然后用 5 + 3 套不同的 contentGeneratorConfigpipeline.execute(),对最终 client.chat.completions.create(body) 收到的 body 做断言。

选这个层级是因为:fix 的修改点在 pipeline.ts:360-385,发生在 provider.buildRequest() 之后client.create() 之前,对 providerRequest 对象做 in-place mutation。stub client 抓到的 body 就是真正会上 wire 的 bytes。

8/8 全部通过

--- PR 4505 fix verification (DashScope → enable_thinking:false) ---

✓ S1: DashScope hostname + qwen3 + includeThoughts=false → enable_thinking=false  (enable_thinking=false)
✓ S2: DashScope + reasoning=false config → enable_thinking=false                  (enable_thinking=false)
✓ S3: QWEN_OAUTH (no baseUrl) + qwen3 → enable_thinking=false                     (enable_thinking=false)
✓ S4: *.alibaba-inc.com internal + qwen3 → enable_thinking=false                  (enable_thinking=false)
✓ S5: QWEN_OAUTH + coder-model → enable_thinking=false (review commit)            (enable_thinking=false)

--- Negative scenarios (must NOT emit enable_thinking) ---

✓ S6: non-DashScope hostname + qwen3 → enable_thinking NOT set                    (enable_thinking=undefined)
✓ S7: DashScope hostname + GLM (non-qwen) → enable_thinking NOT set               (enable_thinking=undefined)
✓ S8: DashScope + qwen3 + includeThoughts=true → enable_thinking NOT set          (enable_thinking=undefined)

8 passed, 0 failed

与 PR 承诺逐项对账

PR body 声称 验证
DashScope hostname + includeThoughts: false → 发出 enable_thinking: false ✅ S1(dashscope.aliyuncs.com)
DashScope + reasoning: false 配置侧 opt-out → 发出 ✅ S2
QWEN_OAUTH(无论 baseUrl)→ 发出 ✅ S3(无 baseUrl 也 OK,因 isDashScopeProvider 第一行就是 if (authType === QWEN_OAUTH) return true
内部 *.alibaba-inc.com → 发出 ✅ S4(这恰好是大量内部用户的实际 baseUrl 形态)
coder-model(QWEN_OAUTH 默认 DEFAULT_QWEN_MODEL,名字不以 qwen 开头)→ 发出 ✅ S5(review 期间补的 case,PR fix(core): cover coder-model in DashScope enable_thinking gate
非 DashScope hostname → 不发出 ✅ S6(openrouter)
DashScope + 非 qwen 模型(GLM/DeepSeek-on-DashScope)→ 不发出 ✅ S7(GLM 用 extra_body.thinking.enabled,DeepSeek-on-DashScope 用 thinking: { type: 'disabled' },把 qwen-specific 的 enable_thinking 转给它们至少是 noop、上游可能 reject)
reasoningDisabled === false 时 → 不发出(不能误关 thinking) ✅ S8

#4501 production-shape repro(脚本最后一段)

Wire body for #4501-shape repro (qwen3.5-flash on dashscope.aliyuncs.com,
  thinkingConfig.includeThoughts=false):
  enable_thinking=false   ←   pre-fix 是 undefined → 1.5–6K reasoning tokens

修复前 'enable_thinking' in typed 永远为 false,所以 wire body 不带这个 field,qwen3.5-flash 默认 thinking-on,side-query 就烧 reasoning token;现在 wire 上就有了 enable_thinking: false,qwen3 hybrid 模型据此关掉 thinking。

3. 我尝试但暂未跑通的额外 smoke:真 CLI HTTP-proxy 抓包

本想再加一条"启 fake DashScope-compatible proxy + 让 qwen-code 真的把请求发过来"的端到端,但卡在了与本 PR 无关的配置层

  • 维护者的 ~/.qwen/settings.json 里有 modelProviders.openai(14 个命名 provider,每个带自己的 baseUrl + envKey
  • 即便用 QWEN_HOME=/tmp/...-isolated 把全局 settings 重定向到一个干净的目录、并把 worktree 内 .qwen/settings.json 也清掉,dev 进程的请求仍然打到 idealab.alibaba-inc.com(响应是中文 "无效的api key",明显非我的 proxy)
  • 估计是 modelProviders.openai/env 块的合并语义之外还有一个我没追到的 baseUrl 来源(dev 模式 .env 解析、或别的 lazy resolver)
  • 这是我本机环境的 setup 复杂度,不是本 PR 的问题——PR 改的是 pipeline.tsprovider.buildRequest() 之后的 in-place mutation,那一段已经在 §2 的 8 个 smoke 里全 case 覆盖。proxy 抓包只是 §2 的 superset,提供不出新信号

为不让这条挂着,把它在这里如实记一笔。

4. 结论

从本地真实测试角度,该 PR 可以合并。 8 个 smoke + 49 个 pipeline 单测在 hostname × model × reasoning-state 三轴上把 fix 的全部分支覆住;生产侧 24–95× token bloat 的 root cause('enable_thinking' in typed 永远为 false)已被替换为 DashScopeOpenAICompatibleProvider.isDashScopeProvider + qwen-model 双闸口下的 unconditional set,与下方 DeepSeek 分支的写法对仗。

wenshao
wenshao previously approved these changes May 28, 2026
Comment thread packages/core/src/core/openaiContentGenerator/pipeline.ts
@doudouOUC

Copy link
Copy Markdown
Collaborator Author

Review round summary

All 8 unresolved threads addressed:

Thread Author Action Detail
pipeline.ts:372 comment wording Copilot Pushed back Comment scope is clear from control flow
pipeline.test.ts:786 test model Copilot Pushed back Gate checks contentGeneratorConfig.model, not request model
pipeline.ts:15 import pattern wenshao Pushed back Static method is canonical for DashScope; free-function extraction adds indirection for no gain
pipeline.ts:371 hostname-only check wenshao Fixed in 7a7da7c53 Gate is now hostname + model-name
pipeline.test.ts:930 non-qwen test wenshao Fixed in 7a7da7c53 Added glm-5 negative test
pipeline.ts:376 coder-model [Critical] wenshao Fixed in 4491a70c7 Added || model === 'coder-model'
pipeline.ts:372 !baseUrl test wenshao Fixed in 4491a70c7 Added implicit-DashScope default test
pipeline.ts:365 logging wenshao Pushed back Consistent with existing pattern; follow-up if needed

Pushed back on 4 threads (2 bot, 2 wenshao style suggestions). The Critical and 3 Suggestions were already addressed in earlier fix commits.

@doudouOUC doudouOUC requested a review from yiliang114 May 30, 2026 01:16
Comment thread packages/core/src/core/openaiContentGenerator/pipeline.ts Outdated
The gate keyed off `contentGeneratorConfig.model`, but buildRequest
ships `context.model` (= `request.model || contentGeneratorConfig.model`,
the value baseRequest.model is built from). A request-level model
override desynced the gate from what actually goes on the wire:

- qwen config + non-qwen request model still emitted `enable_thinking`,
  leaking the qwen-only field to the non-qwen routing on the wire
- non-qwen config + qwen request model missed the disable signal,
  regressing QwenLM#4501

Gate on `context.model` instead. Also swap the hardcoded `'coder-model'`
for the `DEFAULT_QWEN_MODEL` constant so a rename can't silently break
the match. Add regression tests for both override directions; the
existing positive tests set `request.model: 'test-model'`, which — now
that the gate reads the wire model — was overriding the qwen config they
meant to exercise, so point them at the intended wire model.

Per @yiliang114's review.

🤖 Generated with [Qwen Code](https://github.com/QwenLM/qwen-code)
Reverts the `DEFAULT_QWEN_MODEL` import added in 420a131. Importing the
constant was already raised in review (@yiliang114) and declined: `coder-model`
is a public DashScope routing alias, effectively frozen — not an internal
constant that might drift silently. The inline comment already documents the
relationship, and the import adds a cross-module dependency not worth it for
this bugfix.

The wire-model gate fix (gating on `context.model` rather than
`contentGeneratorConfig.model`) is unaffected and stays.

🤖 Generated with [Qwen Code](https://github.com/QwenLM/qwen-code)
@doudouOUC

Copy link
Copy Markdown
Collaborator Author

Addressed @yiliang114's wire-model review:

  • Wire-model gate (420a131): the enable_thinking gate now keys off context.model (= request.model || contentGeneratorConfig.model) instead of contentGeneratorConfig.model, so a request-level model override no longer desyncs the gate from what actually ships. Added regression tests for both override directions (qwen config + non-qwen request → no emit; non-qwen config + qwen request → emit disable). The existing positive tests set request.model: 'test-model', which was silently overriding the qwen config they meant to exercise — repointed them at the intended wire model.
  • coder-model stays inline (52984e4): reverts a DEFAULT_QWEN_MODEL import I briefly added in 420a131, keeping coder-model hardcoded as decided earlier in review (public DashScope routing alias, effectively frozen).

@wenshao wenshao left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

No review findings. LGTM! ✅ — qwen3.7-max via Qwen Code /review

@yiliang114 yiliang114 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

LGTM

@doudouOUC doudouOUC merged commit a1043ee into QwenLM:main May 31, 2026
12 checks passed
@doudouOUC doudouOUC deleted the fix/dashscope-enable-thinking branch May 31, 2026 04:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

type/bug Something isn't working as expected

Projects

None yet

Development

Successfully merging this pull request may close these issues.

bug(provider/dashscope): side-query thinking disable doesn't reach qwen3 series — 'enable_thinking' in typed check never fires

7 participants