Skip to content

fix(core): size the qwen-latest-series invite aliases at 1M - #15758

Merged
wenshao merged 1 commit into
mainfrom
fix/qwen-latest-series-context-window
Oct 28, 2026
Merged

wenshao merged 1 commit into
mainfrom
fix/qwen-latest-series-context-window

Conversation

@wenshao

@wenshao wenshao commented Oct 28, 2026

Copy link
Copy Markdown
Collaborator

What this PR does

Teaches the context-window detector to recognize the invite-only qwen-latest-series- model aliases as 1M-context models. The series prefix is matched ahead of the Qwen family defaults, with a trailing hyphen so the prefix cannot claim ids that merely share its letters. Nothing else changes: output ceilings, input modalities and model-id normalization are untouched, and the models.dev catalog is still consulted first, so the new rule answers only for ids no upstream source describes and steps aside once the catalog carries one.

Why it's needed

An id such as qwen-latest-series-invite-202610-m4 names no model generation that the curated 1M rules key on, and models.dev does not list it, so it fell through to the 256K Qwen family default. The model was then treated as having 262,144 tokens instead of 1,000,000: auto-compaction fired at 222,822 tokens — 22% of the real window — and output requests were sized against the 32K family ceiling. Nothing failed loudly, so the loss showed up only as conversations compacting far earlier than they needed to.

Because the whole series shares the 1M window, a prefix rule also covers later invite codes without another code change, as well as the beta-series spelling that was already in use.

Reviewer Test Plan

How to verify

Point an OpenAI-compatible provider at qwen-latest-series-invite-202610-m4 and run /context. The header should read Context window: 1000.0k tokens; before this change the same model reported Context window: 262.1k tokens. Note that the detector is reached only on the API-key auth paths — Qwen OAuth admits coder-model alone and substitutes it before any window is computed, so that auth type is unaffected either way.

At the unit level, from packages/core:

npx vitest run src/core/tokenLimits.test.ts

Both new assertions were checked to redden on their own, one mutant at a time, so neither passes vacuously:

  • Removing the new rule: expected 262144 to be 1000000 on the reported id.
  • Dropping the trailing hyphen from the rule (^qwen-latest-series): the boundary case reports expected 1000000 to be 262144.

The surrounding suites were run as well — tokenLimits, modalityDefaults, model-catalog, model-catalog-refresh, modelConfigResolver — 314 passed. prettier --check, eslint and tsc --noEmit are clean for the core package.

Evidence (Before & After)

Not captured against a live endpoint: I have no credentials for this invite-only model, so the before/after below is the window and the thresholds computed from it at the source level, not a terminal capture.

Before After
Detected context window 262,144 1,000,000
/context header 262.1k tokens 1000.0k tokens
Auto-compaction threshold 222,822 850,000
Hard threshold 239,144 977,000

Tested on

OS Status
🍏 macOS ✅
🪟 Windows ⚠️
🐧 Linux ⚠️

Environment (optional)

Unit tests plus source-level evaluation of the detector and the threshold computation. No live model endpoint was exercised.

Risk & Scope

  • Main risk or tradeoff: the rule takes the whole series to be 1M, which is maintainer-reported rather than something the code or models.dev can confirm. If a future member of the series ships a smaller window, compaction would fire late and a request could be rejected for length. That stays recoverable — the server's own reported ceiling already shrinks the reactive compaction target — but it costs one failed request. Encoding an internal invite-only naming family in the shared detector is the other tradeoff: an internal catalog mirror served through the existing models.dev URL override would avoid it, at the cost of standing up that mirror.
  • Not validated / out of scope: no end-to-end run against the real invite endpoint, for lack of credentials. The output ceiling is deliberately left at the 32K family default, since only the context window was reported. A bare qwen-latest is not covered: normalization strips its trailing -latest, so no prefix rule can reach it, and changing that shared step would reach catalog keys and modality detection too.
  • Breaking changes / migration notes: none. The only ids whose answer changes are those beginning with qwen-latest-series-, and only when the catalog has no entry for them.

Linked Issues

None.

中文说明

这个 PR 做了什么

让上下文窗口识别逻辑把邀测专用的 qwen-latest-series- 系列别名识别为 1M 上下文模型。该系列前缀在 Qwen 家族默认值之前匹配,并带尾部连字符,使这个前缀不会误认那些仅仅字母相同的 id。其余一切不变:输出上限、输入模态与模型 id 归一化都未触及,并且仍然优先查询 models.dev 目录,因此新规则只为那些上游数据源没有描述的 id 作答,一旦目录收录了它们,规则就自动让位。

为什么需要

像 qwen-latest-series-invite-202610-m4 这样的 id 不含任何可供既有 1M 规则据以判断的型号代际信息,models.dev 也没有收录它,于是它落到了 256K 的 Qwen 家族默认值上。这个模型因此被当作只有 262,144 而非 1,000,000 tokens 来处理:自动压缩在 222,822 tokens 处就触发 —— 只有真实窗口的 22% —— 而输出请求则按 32K 的家族上限设定。没有任何显式报错,所以损失只表现为对话远比必要的时机更早被压缩。

由于整个系列共享 1M 窗口,一条前缀规则还能覆盖后续新的邀测代号而无需再次改码,同样覆盖此前已在使用的 beta 系列拼写。

评审验证计划

如何验证

把一个 OpenAI 兼容 provider 指向 qwen-latest-series-invite-202610-m4,然后执行 /context。头部应显示 Context window: 1000.0k tokens;改动之前同一模型显示的是 Context window: 262.1k tokens。注意识别逻辑只在 API key 类认证路径上才会被走到 —— Qwen OAuth 仅准入 coder-model,并在计算任何窗口之前就完成替换,因此该认证类型无论改动前后都不受影响。

单测层面,在 packages/core 目录下执行:

npx vitest run src/core/tokenLimits.test.ts

两条新断言都逐一做过变异验证,每次只改一处,确认它们不是空跑通过:

  • 移除新规则:报告的 id 上出现 expected 262144 to be 1000000。
  • 去掉规则的尾部连字符(^qwen-latest-series):边界用例出现 expected 1000000 to be 262144。

周边套件也一并跑过 —— tokenLimits、modalityDefaults、model-catalog、model-catalog-refresh、modelConfigResolver —— 314 项通过。core 包的 prettier --check、eslint 与 tsc --noEmit 均干净。

证据(改动前后)

未在真实端点上采集:我没有这个邀测模型的凭据,因此下面的前后对比是在源码层面由窗口值及其推算出的阈值,而不是终端截图。

改动前 改动后
识别出的上下文窗口 262,144 1,000,000
/context 头部 262.1k tokens 1000.0k tokens
自动压缩阈值 222,822 850,000
硬阈值 239,144 977,000

测试环境

操作系统 状态
🍏 macOS ✅
🪟 Windows ⚠️
🐧 Linux ⚠️

运行环境(可选)

单元测试,加上对识别逻辑与阈值计算的源码层面求值。未调用任何真实模型端点。

风险与范围

  • 主要风险或权衡:该规则把整个系列都当作 1M,这一点来自维护者的说明,而非代码或 models.dev 所能确认。若该系列将来某个成员的窗口更小,压缩就会触发过晚,请求可能因长度被拒。这种情况仍可恢复 —— 服务端自己报出的上限已经会收缩反应式压缩的目标值 —— 但代价是一次失败请求。另一个权衡是把内部邀测命名族写进共享的识别逻辑:通过既有的 models.dev URL 覆盖项提供一个内部目录镜像可以避开这一点,代价是要把那个镜像建起来。
  • 未验证 / 超出范围:没有对真实邀测端点做端到端运行,原因是缺少凭据。输出上限有意保留在 32K 的家族默认值上,因为此次只报告了上下文窗口。裸 qwen-latest 不在覆盖范围内:归一化会剥掉它尾部的 -latest,因此任何前缀规则都到不了它,而改动那个共享步骤还会波及目录键与模态识别。
  • 破坏性变更 / 迁移说明:无。取值发生变化的只有以 qwen-latest-series- 开头、且目录中没有对应条目的那些 id。

关联 Issue

无。

The invite-only series aliases name no model generation the curated 1M rows key on, and models.dev does not carry them, so they fell through to the 256K Qwen family row. A model with a 1M window then compacted at 222,822 tokens — roughly a quarter of the window it actually has — and its output requests were sized against the 32K family ceiling.

Match the series prefix ahead of the family rows. The trailing hyphen keeps the prefix off ids that merely share its letters. The catalog is still consulted first, so this answers only for ids no upstream source describes, and it steps aside once models.dev carries one.

Output limits, modalities and normalization are untouched. Both new assertions were checked to redden alone: removing the row reports 262144 against the expected 1000000, and dropping the trailing hyphen makes the boundary case report 1000000 against the expected 262144.
@qwen-code-review-bot

qwen-code-review-bot commented Oct 28, 2026 •

Copy link
Copy Markdown
Collaborator

✅ Qwen Triage finished — view run. See the stage comments in this thread for the result.

✅ Qwen Triage 已完成 —— 查看运行。结果见本线程中的各阶段评论。

@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.

Review: APPROVE ✅

Scope: packages/core/src/core/tokenLimits.ts (+5) + tokenLimits.test.ts (+18)
Depth: default → escalated (auth-adjacent: API-key auth path reaches this detector; Qwen OAuth skips it)

Correctness

The new rule [/^qwen-latest-series-/, LIMITS['1m']] is placed correctly in the PATTERNS array — after the Qwen-family 1M rules (qwen3.x, qwen-plus-latest, qwen-flash-latest) and before coder-model, which sit ahead of the 256K fallback rows. Order is load-bearing here: without this position the series ids would still match the 256K Qwen family default below.

The trailing hyphen in ^qwen-latest-series- is the boundary guard. Removing it would claim ids like qwen-latest-seriesfoo — the test at line 288 (expect(tokenLimit('qwen-latest-seriesfoo')).toBe(262144)) proves that this guard works and that the test itself is not a vacuous pass.

Tests

Six test cases — the reported id, a future invite code, the beta-series spelling, the code-stripped form, the openai/ prefix, and the boundary rejection. Each was mutation-tested by the author (removing the rule → 262144 on the reported id; dropping the hyphen → 1000000 on seriesfoo). The surrounding suites (tokenLimits, modalityDefaults, model-catalog, model-catalog-refresh, modelConfigResolver) pass — 314 total.

Risk

The author correctly identifies the tradeoff: 1M is maintainer-reported, not confirmed from models.dev. If a future series member ships a smaller window, compaction fires late and one request may fail. This is recoverable (the server's own ceiling shrinks the reactive compaction target) and the alternative (standing up an internal catalog mirror) has its own cost. No breaking changes.

Verdict

No P0, P1, or P2 findings. The change is minimal, correctly ordered, boundary-guarded, and mutation-tested. APPROVE.


Reviewed at head dce317f — only review on this PR.

@wenshao wenshao left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Reviewed commit dce317f68584c45c0743355301af53ff7b2dfbdc. No actionable correctness or regression findings in this diff.

The series rule is ordered before the Qwen family fallback, and the tested invite/date/version/provider-prefix forms survive normalization as intended. Usable catalog context values and explicit context-window settings retain precedence. The output-limit and modality paths are unchanged.

Independent validation:

  • Reproduced the old behavior through the installed Qwen CLI's /context: 262.1k tokens, with no model API request. The PR source resolves the same model ID to 1,000,000.
  • Ran the unchanged tokenLimits.test.ts: 104/104 passed, using an isolated Vitest configuration that retains the package test setup and omits the workspace-build prerequisite check.
  • Also passed 63 existing modality tests and 3 focused probes covering aliases, unchanged output limits, catalog precedence, and unrelated Qwen IDs: 170 passing tests in total.
  • Verified both new regression tests independently: removing the rule fails with 262144 versus 1000000; removing its trailing hyphen fails the prefix-boundary test with 1000000 versus 262144. These mutations were applied in memory; the reviewed source and test files remain byte-identical to the commit.

Validation limits: the separate model-catalog.test.ts suite could not collect because the shared local dependencies lack ajv/dist/2020.js; it is not counted as passing. No full workspace build/typecheck or live invite-model endpoint test was performed. The series-wide 1M capacity remains the maintainer-provided premise documented in the PR.

中文评审结论

已评审提交 dce317f68584c45c0743355301af53ff7b2dfbdc,未发现本次改动引入的、需要修复的正确性或回归问题。

新规则位于 Qwen 家族兜底规则之前;测试覆盖的邀测代号、日期、版本和 provider 前缀均能在归一化后正确匹配。可用的目录上下文值和显式窗口配置仍优先,输出上限与模态识别路径未变。

独立验证:全局 CLI 的 /context 复现了旧值 262.1k tokens,未发起模型 API 请求;PR 源码对同一模型返回 1,000,000。原有 tokenLimits.test.ts 在保留包级测试 setup、跳过工作区构建前置检查的隔离 Vitest 配置下 104/104 通过。移除规则和移除尾部连字符的两项内存变异,分别使新增的正向测试和边界测试按预期失败;被评审的源码和测试文件与提交内容完全一致。

另有 63 项原有模态测试及 3 项别名、输出上限、目录优先级和无关 Qwen 名称探针通过,共 170 项通过。

验证范围:单独的 model-catalog.test.ts 因共享本地依赖缺少 ajv/dist/2020.js 未完成收集,不计为通过。未运行完整工作区构建/typecheck,也未调用真实邀测模型端点;整个系列具有 1M 容量仍以 PR 中维护者提供的信息为前提。

@qqqys qqqys 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.

Critical-only scan at head dce317f6. Verdict: APPROVE — no prior blocking findings exist on this PR (no reviews, no threads), and the current scan found no provable blocking defect.

The change

One row added to the input table: [/^qwen-latest-series-/, LIMITS['1m']] at packages/core/src/core/tokenLimits.ts:272, placed after the specific Qwen 1M rows and before the /^qwen/ 256K family fallback at :279, plus 18 lines of test.

What I verified in the code

The rule is reachable and correctly ordered. PATTERNS is read at exactly one site — the first-match loop at :403 — and no row above :272 can match these ids: the Qwen 1M rows are ^qwen3-coder-plus, ^qwen3-coder-flash, ^qwen3\.\d, ^qwen-plus-latest$, ^qwen-flash-latest$, and the other families are anchored to gemini-, gpt-, o\d, claude-. Before this change such an id fell to /^qwen/ → 262144, which is the reported symptom; after it, :272 wins.

The catalog still answers first, so the rule only fills a gap. findTokenLimit consults lookupModelCatalog(norm) at :389 and returns at :395-397 whenever the catalog reports a context ≥ MIN_AUTO_DETECTED_CONTEXT_WINDOW, before the loop is reached. A future models.dev entry for any of these ids therefore overrides the new row rather than fighting it.

Output limits are structurally untouched. For type === 'output' the function returns at :400 (curatedOutputLimit(norm) ?? fromCatalog), which iterates the separate OUTPUT_PATTERNS table at :375; the loop at :403 is never reached. The row was added to PATTERNS, not OUTPUT_PATTERNS, so hasExplicitOutputLimit and defaultOutputCeiling cannot change behaviour — matching the description's decision to leave the output ceiling at the family default.

normalize() cannot defeat the prefix. Traced against :147-227 for each asserted id: the provider-prefix strip (^.*\/) turns openai/qwen-latest-series-invite-202610-m4 into the bare id; the trailing-suffix strip at :219-221 is $-anchored and its guard list (^qwen-(?:plus|flash|vl-max)-latest$, ^kimi-k2-\d{4}$, ^deepseek-v4) does not exempt this family, so -v28 is stripped from …-invite-beta-v28 and -202610 from …-invite-202610, leaving qwen-latest-series-invite-beta and qwen-latest-series-invite — both still carry the qwen-latest-series- prefix. -m4/-m5 match no alternative in that strip and survive. The -latest the description mentions as unreachable is only stripped when trailing, and here it is not.

The trailing hyphen does the boundary work it claims. qwen-latest-seriesfoo normalizes unchanged, fails ^qwen-latest-series-, and falls through to /^qwen/ → 262144, which tokenLimits.test.ts asserts. The new row is anchored at ^, so no id outside this prefix can change its answer.

CI at this head

Nothing red. The routing, classification, authorization and Managed Agent tier gates pass; Lint & Static (ubuntu-latest, Node 22.x) and Test (ubuntu-latest, Node 22.x) — the lane that runs tokenLimits.test.ts — are still pending, so the new assertions have not yet been executed by CI. No failure is attributable to this change.

@qwen-code-review-bot

Copy link
Copy Markdown
Collaborator

Thanks for the PR!

Template looks good ✓ — every section is filled in, including an honest account of what was not verified.

Problem: Observed, and verifiable statically. I traced normalize() on qwen-latest-series-invite-202610-m4 by hand: the trailing -m4 matches none of the suffix-strip alternatives, so the id survives intact, models.dev has no entry for it, and it lands on the ^qwen family row at 262,144. The four numbers in your Evidence table also check out against computeThresholds — 222,822 / 239,144 at 256K and 850,000 / 977,000 at 1M are exactly min(0.85·window, window − 33,000) and window − 23,000. Beyond that, the id family is corroborated in this repo independently of your report: docs/e2e-tests/2026-05-19-qwen-runtime-diagnostics-benchmark-report.md records a 41.9-minute interactive benchmark run on qwen-latest-series-invite-beta-v28, so this is a model the project's own E2E work has actually driven, not a hypothetical spelling. The one input nobody can check from here is the 1M figure itself — that's maintainer knowledge, and you say so plainly.

Direction: Aligned. Correct window detection is what every compaction threshold downstream is computed from, so a silent 256K on a 1M model is precisely the kind of quiet loss worth fixing. CHANGELOG has no direct reference to a prefix-series rule, but the area is clearly live in the reference agent — it carries entries for a [1m] alias suffix breaking model resolution and for an alias failing to resolve to its model, which is the same failure shape from the other side.

Size: Core paths touched (packages/core/src/**). Breakdown: 5 production lines (one regex row plus four comment lines) in tokenLimits.ts, 18 test lines, 0 generated/schema lines. Below every threshold in the core gate, and maintainer-authored besides, so no escalation on size.

Approach: Minimal, and it's the path I'd have picked independently. I considered the alternatives before reading the diff: extending normalize() to fold these ids onto a known generation is worse (that step feeds catalog keys and modality detection too — you note this yourself), per-user contextWindowSize config pushes the burden onto everyone who touches the model, and a catalog mirror is the interesting one. That one is real, not hypothetical — QWEN_CODE_MODELS_DEV_URL already exists for exactly this ("Replaces the models.dev URL, e.g. with a corporate mirror"). But it trades one regex row for standing up and maintaining a mirror, and it only helps users who set the env var. Your call to reject it is the right one. The trailing hyphen is the detail I'd have wanted: it keeps the prefix from claiming ids that merely share its letters, and you pinned that with a test rather than leaving it to review. Placement is correct too — after every row it cannot overlap, and ahead of the ^qwen fallback it has to beat.

Risk: No elevated risk signals — neither changed file matches the revert-correlated path list. Two things I checked that are worth stating, because they're what make this safe to be this small: the new row lives only in PATTERNS, so OUTPUT_PATTERNS, curatedOutputLimit and hasExplicitOutputLimit are untouched and the 32K output ceiling genuinely stays where it was; and your mitigation for an over-sized assumption is real, not aspirational — chatCompressionService does Math.min(contextLimit, observedServerCeiling) when a server reports its own ceiling, so a future smaller-window series member costs one rejected request and then retargets.

Moving on to code review. 🔍

中文说明

感谢贡献!

模板完整 ✓ —— 各节都填写了,包括对"哪些没有验证"的坦诚说明。

问题: 已观测,且可从源码静态验证。我手工跟踪了 normalize() 处理 qwen-latest-series-invite-202610-m4 的过程:尾部的 -m4 不匹配任何后缀剥离分支,因此该 id 完整保留,models.dev 没有对应条目,最终落到 ^qwen 家族行的 262,144。你证据表里的四个数字也与 computeThresholds 吻合 —— 256K 下的 222,822 / 239,144 与 1M 下的 850,000 / 977,000,正好是 min(0.85·window, window − 33,000) 与 window − 23,000。此外,这个 id 家族在本仓库中还有独立于你描述的佐证:docs/e2e-tests/2026-05-19-qwen-runtime-diagnostics-benchmark-report.md 记录了一次在 qwen-latest-series-invite-beta-v28 上运行 41.9 分钟的交互式基准测试,说明这是项目自己的 E2E 工作实际用过的模型,而不是假想的拼写。唯一无法从这里核实的是 1M 这个数值本身 —— 那属于维护者的领域知识,你也明确说明了这一点。

方向: 对齐。上下文窗口识别是下游所有压缩阈值的计算基准,因此 1M 模型被静默当作 256K,正是那种值得修复的隐性损失。CHANGELOG 中没有关于前缀系列规则的直接条目,但这个领域在参考 agent 中显然很活跃 —— 它有 [1m] 别名后缀破坏模型解析、以及别名无法解析到对应模型的条目,属于同一类失效形态的另一面。

规模: 触及核心路径(packages/core/src/**)。明细:tokenLimits.ts 中 5 行生产代码(一行正则加四行注释)、18 行测试、0 行生成/schema。低于核心门禁的所有阈值,且为维护者本人提交,因此不因规模升级。

方案: 最小改动,也正是我独立会选的路径。在看 diff 之前我考虑过其他方案:扩展 normalize() 把这些 id 折叠到某个已知代际上更糟(那个步骤还会影响目录键与模态识别 —— 你自己也指出了这点);让用户各自配置 contextWindowSize 会把负担推给每个接触该模型的人;目录镜像是最有意思的一个,而且它是真实存在的机制而非假想 —— QWEN_CODE_MODELS_DEV_URL 就是为此而设("Replaces the models.dev URL, e.g. with a corporate mirror")。但那要用"搭建并维护一个镜像"来换"一行正则",而且只对设置了该环境变量的用户生效。你否决它的判断是对的。尾部连字符是我会想要的细节:它让这个前缀不会误认仅仅字母相同的 id,而且你用测试把它钉住了,而不是留给评审去发现。插入位置也正确 —— 在所有不可能重叠的行之后,并且排在它必须胜过的 ^qwen 兜底行之前。

风险: 无升级风险信号 —— 两个改动文件都不匹配与回滚相关的路径清单。有两点我核实过,值得说明,因为它们正是这个改动能做得这么小还安全的原因:新行只存在于 PATTERNS 中,因此 OUTPUT_PATTERNS、curatedOutputLimit 与 hasExplicitOutputLimit 都未被触及,32K 输出上限确实保持原样;你对"窗口假设偏大"的缓解措施是真实的而非纸面上的 —— 当服务端报出自己的上限时,chatCompressionService 会执行 Math.min(contextLimit, observedServerCeiling),所以将来某个窗口更小的系列成员只会付出一次被拒请求的代价,随后就会重新校准目标值。

进入代码审查 🔍

— Qwen Code · qwen3.8-max-2026-09-02

Reviewed at dce317f68584c45c0743355301af53ff7b2dfbdc · re-run with @qwen-code /triage

@qwen-code-review-bot

Copy link
Copy Markdown
Collaborator

Code review

No critical blockers and no AGENTS.md violations. The change is one row in an existing ordered table, and I could satisfy myself about all of it by reading.

Before looking at the diff I worked out what I'd do myself: add a row to PATTERNS, because that table is already where the Qwen 1M carve-outs live (qwen3-coder-plus, qwen-plus-latest, qwen-flash-latest) and it's first-match-wins, so a prefix row is the idiom. The PR does exactly that — no simpler path was missed.

The assertions are correct, and they're not vacuous. I hand-traced normalize() for each of the five ids rather than trusting the expected values, because the interesting part is how the suffix-stripper interacts with the new prefix:

  • -invite-202610-m4 survives intact (-m4 matches none of the strip alternatives), so the rule sees the full id.
  • -invite-beta-v28 loses -v28 to the v\d+ alternative and still matches on qwen-latest-series-invite-beta.
  • -invite-202610 loses the date to \d{4,} and still matches on qwen-latest-series-invite.
  • openai/… is reduced by the leading ^.*\/ strip before any of the above.

Each of those is a distinct branch of the stripper, and the tests pin all of them plus the provider-prefixed spelling. That's better coverage than the change strictly needs.

Ordering is right. The row sits after every pattern it cannot overlap and ahead of the ^qwen 256K fallback it has to beat. PATTERNS is module-private and read only by findTokenLimit, so there is no second iteration site to keep in sync.

The output path is genuinely untouched, which is the claim I most wanted to check, since an accidental output change would resize every request to these models. The new row is in PATTERNS only; curatedOutputLimit, hasExplicitOutputLimit and defaultOutputCeiling all read OUTPUT_PATTERNS, where ^qwen still yields 32K. hasExplicitOutputLimit returning false for these ids is also correct — a user's explicit max_tokens should not be clamped by a row nobody wrote for output.

Consumers, all of which move together off the same tokenLimit/knownTokenLimit entry point: modelConfigResolver (stamps contextWindowSize as "auto-detected from model"), modelsConfig and modelRegistry, config.ts and client.ts (the /context read), baseLlmClient (window-clamped output sizing), chatCompressionService via computeThresholds, acpAgent (contextLimit advertised to ACP hosts), serve/workspace-providers-status, and vscode-ide-companion/acpModelInfo — where the derived value is third priority behind the wire-reported legacy and _meta limits, so a host that reports its own number still wins. No site can disagree with another about the window, which is the property you want from a table like this.

One non-blocking suggestion. The trailing hyphen is the right precision guard, but it interacts with the date-strip in a way that leaves one shape uncovered: an id that normalizes down to a bare qwen-latest-series falls through to 256K again. Concretely, qwen-latest-series-202610 strips to qwen-latest-series — no trailing hyphen left — and gets 262,144, which is the exact bug this PR fixes. /^qwen-latest-series(?:-|$)/ would cover that shape while keeping the qwen-latest-seriesfoo boundary test green (seriesfoo has neither a hyphen nor end-of-string after series). Whether it's worth it depends on whether a date-only spelling of the series will ever exist; if you know it won't, leaving the stricter regex is the better call and this is noise.

One second-order effect worth naming, with its mitigation verified. Moving the window from 262,144 to 1,000,000 moves auto-compaction from 222,822 to 850,000, so sessions on these ids will retain roughly 3.8× more history before token-compaction fires. I checked this specifically because the in-repo E2E report that mentions this model family (docs/e2e-tests/2026-05-19-…) is a heap/OOM investigation whose recorded lesson is that "token threshold alone is not a reliable memory guard". The backstops are still there and are not keyed on the detected window: useMemoryMonitor compacts UI history when heapUsed crosses MEMORY_UI_COMPACT_THRESHOLD(), and memoryPressureMonitor drives microcompactHistory off v8 heap statistics. So the change doesn't trade a memory guard for context headroom.

Testing

This is an unattended CI run, so per the triage rules I did not build, run, or execute anything from this PR — the evidence below is the PR's own CI, read through the API for commit dce317f68584c45c0743355301af53ff7b2dfbdc. Nothing was reproduced locally and no terminal capture is attached; tmux real-scenario testing is N/A on this path.

Zero checks are red at the time of writing. Lint & Static has already landed green. The one that matters most here — Test (ubuntu-latest, Node 22.x), which runs the suite containing tokenLimits.test.ts — is still in progress, so the new assertions are not yet confirmed by CI; I verified them by static trace only. I did not poll for it. The Qwen Triage Finalize job rewrites the table below in place once CI settles.

Check Conclusion
Test (ubuntu-latest, Node 22.x) in progress
Lint & Static (ubuntu-latest, Node 22.x) success
Integration Tests (no-AK, No Sandbox) in progress
web-shell E2E Smoke (ubuntu-latest, Node 22.x) in progress
TUI parity snapshots (ink vs opentui) in progress
OpenTUI no-flicker gate in progress
Installed npm Node26 private Bun acceptance queued
Check TUI impact success
Classify PR success
Managed Agent gate success
Select the Managed Agent tier success
route / label / assign / authorize / delay-automatic-review success
review-pr queued
triage in progress (this run)
118 further checks skipped (not applicable to this diff)

Sandboxed verification would settle the part CI cannot: @qwen-code /verify — the before/after in the PR description is source-level arithmetic, not a captured run, and while I confirmed the arithmetic against computeThresholds, nobody has observed the reported id actually resolve to 1M through the full config path or watched /context render 1000.0k tokens instead of 262.1k. That needs no invite credentials, since the detector is a pure string→number function, so an A/B against the base build would prove the row is load-bearing rather than merely present. @qwen-code /tmux would capture the rendered header for the user-visible half of the claim, and is available here since you have write access. What neither lane can settle is whether the true window really is 1,000,000 — that rests on your maintainer knowledge of an invite-only model, and it's the one input in this PR that no reviewer can check from the outside. You've said so plainly in Risk & Scope, which is the right way to carry it.

中文说明

代码审查

没有关键阻塞项,也没有违反 AGENTS.md 的地方。改动就是既有有序表里的一行,全部可以靠阅读确认。

在看 diff 之前我先想清了我会怎么做:往 PATTERNS 加一行 —— 因为 Qwen 的 1M 特例(qwen3-coder-plus、qwen-plus-latest、qwen-flash-latest)本来就住在这张表里,而它是首个匹配胜出,所以前缀行就是这里的惯用写法。PR 正是这么做的,没有漏掉更简的路径。

断言正确,而且不是空跑。 我对这五个 id 逐一手工跟踪了 normalize(),而不是直接相信期望值,因为真正有意思的是后缀剥离器与新前缀的交互:-invite-202610-m4 完整保留(-m4 不匹配任何剥离分支);-invite-beta-v28 被 v\d+ 分支剥掉 -v28,仍在 qwen-latest-series-invite-beta 上匹配;-invite-202610 被 \d{4,} 剥掉日期,仍在 qwen-latest-series-invite 上匹配;openai/… 则先被开头的 ^.*\/ 削掉前缀。这几条分别覆盖剥离器的不同分支,测试把它们连同带 provider 前缀的拼写都钉住了,覆盖度超出这个改动本身的需要。

顺序正确。 新行位于所有不可能重叠的模式之后、位于它必须胜过的 ^qwen 256K 兜底之前。PATTERNS 是模块私有的,只被 findTokenLimit 读取,因此不存在第二处遍历点需要同步。

输出路径确实未被触及 —— 这是我最想核实的一点,因为误改输出会改变发给这些模型的每个请求的大小。新行只在 PATTERNS 中;curatedOutputLimit、hasExplicitOutputLimit 与 defaultOutputCeiling 读取的都是 OUTPUT_PATTERNS,那里的 ^qwen 仍然给出 32K。这些 id 上 hasExplicitOutputLimit 返回 false 也是对的 —— 用户显式设置的 max_tokens 不该被一行本不为输出而写的规则钳制。

下游消费方全部同源联动,都走 tokenLimit/knownTokenLimit 这一个入口:modelConfigResolver(把 contextWindowSize 标记为 "auto-detected from model")、modelsConfig 与 modelRegistry、config.ts 与 client.ts(/context 的读取)、baseLlmClient(按窗口钳制输出大小)、经由 computeThresholds 的 chatCompressionService、acpAgent(向 ACP 宿主通告 contextLimit)、serve/workspace-providers-status,以及 vscode-ide-companion/acpModelInfo —— 后者的推导值排在 wire 上报的 legacy 与 _meta 之后第三位,因此宿主自报数值仍然优先。没有任何一处能对窗口给出与别处不一致的答案,这正是这类表应有的性质。

一条非阻塞建议。 尾部连字符是对的精度防护,但它与日期剥离的交互留下一个未覆盖形态:归一化后正好落到裸 qwen-latest-series 的 id 会重新跌回 256K。具体说,qwen-latest-series-202610 会被剥成 qwen-latest-series —— 尾部连字符没了 —— 于是得到 262,144,恰好就是本 PR 要修的那个 bug。改成 /^qwen-latest-series(?:-|$)/ 可以覆盖这一形态,同时保持 qwen-latest-seriesfoo 的边界测试为绿(seriesfoo 在 series 之后既不是连字符也不是串尾)。值不值得改,取决于这个系列是否真会出现只有日期的拼写;如果你确定不会,保留更严格的写法反而更好,这条建议就可以忽略。

一个值得点名的二阶效应,其缓解措施已核实。 窗口从 262,144 提到 1,000,000,会把自动压缩点从 222,822 推到 850,000,也就是说这些 id 上的会话在触发按 token 压缩之前会多保留约 3.8 倍的历史。我特意核查了这点,因为仓库内提到这个模型族的 E2E 报告(docs/e2e-tests/2026-05-19-…)正是一次堆内存/OOM 调查,它记下的结论是"仅靠 token 阈值并不是可靠的内存防护"。这些兜底仍然在,而且都不以识别出的窗口为依据:useMemoryMonitor 在 heapUsed 越过 MEMORY_UI_COMPACT_THRESHOLD() 时压缩 UI 历史,memoryPressureMonitor 则依据 v8 堆统计驱动 microcompactHistory。所以这个改动并没有用内存防护去换上下文余量。

测试

这是一次无人值守的 CI 运行,因此按照 triage 规则,我没有构建、运行或执行本 PR 的任何代码 —— 下面的证据是通过 API 读取的、针对提交 dce317f68584c45c0743355301af53ff7b2dfbdc 的 PR 自身 CI 结果。没有任何本地复现,也没有附终端截图;本路径下 tmux 真实场景测试为 N/A。

截至撰写时没有任何检查为红。Lint & Static 已经绿了。而这里最关键的一项 —— 运行包含 tokenLimits.test.ts 的套件的 Test (ubuntu-latest, Node 22.x) —— 仍在进行中,因此新增断言尚未经 CI 确认;我只做了静态跟踪验证。我没有轮询等待。CI 结束后,Qwen Triage Finalize 任务会就地重写上面的表格。

沙箱化验证可以补上 CI 补不了的那部分:@qwen-code /verify —— PR 描述里的前后对比是源码层面的推算,而非实际运行的采集;虽然我对照 computeThresholds 确认了这些算术,但还没有人真正观测到所报告的 id 走完整配置路径后解析为 1M,也没有人看到 /context 渲染出 1000.0k tokens 而不是 262.1k。这不需要邀测凭据,因为识别逻辑是纯粹的字符串→数字函数,所以与 base 构建做 A/B 就能证明这一行是真正起作用的,而不只是存在于代码里。@qwen-code /tmux 则可以为该主张中用户可见的那一半采集渲染出的头部,而且因为你有写权限,这里是可以触发的。两条通道都无法确认的是:真实窗口是否确实为 1,000,000 —— 那依赖于你对一个邀测模型的维护者知识,也是本 PR 中唯一外部评审无法核实的输入。你在"风险与范围"里明确说明了这一点,这是承载它的正确方式。

— Qwen Code · qwen3.8-max-2026-09-02

Reviewed at dce317f68584c45c0743355301af53ff7b2dfbdc · re-run with @qwen-code /triage

@qwen-code-review-bot

Copy link
Copy Markdown
Collaborator

Confidence: 4/5 — correct, minimal, and better tested than it needs to be; the single reservation is that CI hasn't landed yet and the 1M figure itself is one reviewer-uncheckable input.

Going back to what I'd have written myself: the same one row in the same place. The PR doesn't just match that proposal, it exceeds it — I'd probably have written two assertions (the reported id and the family default) and called it done, whereas these tests pin all four branches of normalize()'s suffix-stripper that the prefix can interact with, plus the provider-prefixed spelling. That's the part that would have bitten someone later: a rule keyed on a prefix looks obvious right up until a date suffix gets stripped out from under it, and now it can't regress silently.

On whether users care: yes, and it's the annoying kind of bug. Nothing errors, nothing logs, the session just compacts at 22% of its real window — so the user experiences it as the assistant forgetting things early and has no way to trace it to a context-window table. Fixing a silent degradation like that is worth five lines.

I went looking for reasons to say no, because that's the job. I checked the output path (untouched — OUTPUT_PATTERNS is a separate table and the three functions that read it never see the new row), checked pattern ordering, checked for any existing test asserting 256K on a qwen-latest* id (none), enumerated every consumer of tokenLimit/knownTokenLimit across four packages, and checked the one second-order effect that actually worried me: raising the window 4× moves auto-compaction out to 850,000 tokens, and the in-repo E2E report mentioning this model family is a heap/OOM investigation. The memory backstops turned out to be independent of the detected window, so that concern dissolved rather than being waved off. What's left is one non-blocking suggestion about the bare post-normalization form, which may well be noise depending on whether a date-only spelling of the series will ever exist.

The hesitation I did sit with is the one you name yourself: encoding an internal invite-only naming family in a shared detector. I landed on it being fine, for a concrete reason rather than a shrug — PATTERNS already carries coder-model, which is an internal alias of exactly the same character, so this isn't a new category of thing entering the file. And the row is self-retiring: findTokenLimit consults the catalog first, so the moment models.dev describes one of these ids the rule stops answering for it. It doesn't accumulate as permanent debt. The catalog-mirror alternative would avoid the coupling, but it trades one regex for infrastructure plus an env var every affected user has to know about, which is a worse deal for a strictly smaller benefit.

In six months I'd thank whoever wrote this, not curse them: the comment records that the value is maintainer-reported rather than derived, so the next person knows which fact to re-check if a series member ever behaves oddly.

Verdict: approve — deferred until CI lands green on dce317f68584c45c0743355301af53ff7b2dfbdc. Two PR workflow runs are still in flight (Qwen Code CI, tui-parity), and the unit suite that would actually execute the new assertions is among them, so I'm not approving against a result that doesn't exist yet. Nothing is red. No approval is posted in this run; the finalize job posts the commit-pinned approval once every check on that SHA completes green, and withholds it if anything lands red or the head moves.

中文说明

Confidence: 4/5 —— 正确、最小,且测试覆盖超出必要程度;唯一的保留意见是 CI 尚未结束,而 1M 这个数值本身是一个评审方无法核实的输入。

回到我原本会怎么写:同一位置、同一行。这个 PR 不只是与我的方案一致,而是超出了它 —— 我大概只会写两条断言(所报告的 id 与家族默认值)就算完事,而这些测试钉住了前缀可能与之交互的 normalize() 后缀剥离器的全部四个分支,外加带 provider 前缀的拼写。这正是日后可能咬人的地方:一条基于前缀的规则看上去理所当然,直到某个日期后缀在它底下被剥掉;现在它不会再悄悄回退了。

至于用户是否在意:在意,而且是那种讨厌的 bug。不报错、不打日志,会话只是在真实窗口的 22% 处就压缩 —— 于是用户的体验是助手过早开始遗忘,却完全无从追溯到一张上下文窗口表。修掉这种静默降级,值这五行代码。

我主动去找了反对理由,因为这就是门禁该做的事。我核查了输出路径(未被触及 —— OUTPUT_PATTERNS 是独立的表,读取它的三个函数都看不到新行)、核查了模式顺序、核查了是否存在断言 qwen-latest* 为 256K 的既有测试(没有)、列举了跨四个包的所有 tokenLimit/knownTokenLimit 消费方,并核查了唯一真正让我担心的二阶效应:窗口提高 4 倍会把自动压缩点推到 850,000 tokens,而仓库内提到这个模型族的 E2E 报告正是一次堆内存/OOM 调查。结果内存兜底机制与识别出的窗口无关,所以这个顾虑是被化解掉的,而不是被搪塞过去的。剩下的只有一条关于归一化后裸形态的非阻塞建议,而它是否算噪音,取决于这个系列是否真会出现只有日期的拼写。

我确实停下来想过的一处犹豫,正是你自己点出的那一点:把内部邀测命名族写进共享识别逻辑。我的结论是可以接受,理由是具体的而非耸肩 —— PATTERNS 里本来就有 coder-model,那是性质完全相同的内部别名,所以这并不是有新类别的东西进入这个文件。而且这一行是会自我退休的:findTokenLimit 先查目录,因此一旦 models.dev 描述了这些 id,规则就不再为它作答。它不会累积成永久债务。目录镜像方案可以避免这种耦合,但那要用"一套基础设施加上每个受影响用户都得知道的一个环境变量"去换一行正则,收益严格更小而代价更差。

半年之后我会感谢写这行的人,而不是骂他:注释记下了这个数值来自维护者的说明而非推导得出,因此下一个人知道,万一某个系列成员行为异常,该重新核实的是哪一个事实。

结论:批准 —— 但推迟到 CI 在 dce317f68584c45c0743355301af53ff7b2dfbdc 上全绿之后。 仍有两个 PR workflow 在运行(Qwen Code CI、tui-parity),而真正会执行新增断言的单元测试套件就在其中,所以我不会针对一个尚不存在的结果给出批准。目前没有任何红项。本次运行不发布批准;finalize 任务会在该 SHA 上所有检查全绿后发布绑定到该提交的批准,若有任一项变红或 head 发生移动则不予发布。

— Qwen Code · qwen3.8-max-2026-09-02

Reviewed at dce317f68584c45c0743355301af53ff7b2dfbdc · re-run with @qwen-code /triage

@pomelo-nwu

Copy link
Copy Markdown
Collaborator

建议这个 PR 顺手把 qwen4 家族也一起设为 1M。

现在 PATTERNS 里 Qwen 的 1M 规则到 qwen3\.\d 为止,没有任何 qwen4 规则,models.dev 和内置 catalog 也都还没有 qwen4 条目。在 head(dce317f)上实测:qwen4-max、qwen4-plus、qwen4.5-max、qwen4-coder-plus 全部落到 ^qwen 的 256K 兜底——和本 PR 为 invite 系列修的是同一个症状(自动压缩在真实窗口约 22% 处就触发)。omni 的测试夹具已经在用 qwen4-omni-120b-think,这些 id 出现只是时间问题。

qwen4 同样整族 1M,在 qwen3\.\d 旁边加一行就能覆盖整个家族,后续 qwen4.x 也不用再改码:

[/^qwen4/, LIMITS['1m']],

在副本上验证过:qwen4 各 id(含裸 qwen4、日期后缀、openai/ 前缀)都返回 1M,qwen3-max / qwen-plus / qwen-latest-seriesfoo 等既有边界不受影响;catalog 优先的语义也不变,models.dev 一旦收录 qwen4 就自动让位给目录值。

@wenshao

wenshao commented Oct 28, 2026

Copy link
Copy Markdown
Collaborator Author

Local verification — real /context A/B: 1000.0k vs 262.1k

I built both arms and drove the product's real /context path against an OpenAI-compatible endpoint. Verified head dce317f68584c45c0743355301af53ff7b2dfbdc (unchanged from round start to publication) against base 9acb3a5a1d68aad0705ad4d420830affb59cc95a, on macOS 26.6.2 / Apple M1 Max, Node 22.23.4.

Local verdict: merge-ready — no blocking findings. One non-blocking boundary note (N1) is at the end. CI was still running the Node 22 test jobs when this was published; this round covers tokenLimits.test.ts and its four neighbour suites locally.

How it was verified

  • Two detached worktrees, each with its own full npm run build (Node 22.23.4, 8 GB heap, EXIT=0). The built core dist carries the new rule in head (grep -c qwen-latest-series- → 2) and not in base (→ 0).
  • Headless CLI: the same command on both arms, qwen -p '/context' --auth-type openai --model qwen-latest-series-invite-202610-m4 --openai-base-url <fake>/v1 --openai-api-key fake-key, with a scratch QWEN_HOME and QWEN_CODE_MODELS_DEV_REFRESH=off.
  • Interactive TUI: a real model turn (hi → fake reply carrying usage.prompt_tokens=12000) followed by /context, captured from tmux at 150×48. Same env and flags on both arms.
  • The endpoint is the repository's own integration-tests/fake-openai-server.ts. No invite-model credentials were used, so this exercises the full config → detector → /context/threshold path, not the real invite endpoint — the same limitation the PR states.

The user-visible result

Observation Base Head
Headless /context header Context window: 262.1k tokens Context window: 1000.0k tokens
TUI effective window 242.1k 980.0k
TUI warn threshold 202.8k 830.0k
TUI auto threshold 222.8k 850.0k
TUI hard threshold 239.1k 977.0k
12.0k-token prompt shown as 4.6% used 1.2% used

The threshold ladder matches computeThresholds exactly (85% auto, 13k ceiling buffer, 3k hard buffer), so the change moves compaction timing by the amount the PR claims, not just the header text.

Headless, head:

Head headless /context

Headless, base:

Base headless /context

Interactive TUI, head — a real turn then /context:

Head TUI /context

Interactive TUI, base, same flow:

Base TUI /context

Unit gates and mutation matrix

Cell Result
tokenLimits.test.ts, head 104 passed (104)
tokenLimits.test.ts, base 102 passed (102)
Head test file on base source 1 failed / 103 passed — expected 262144 to be 1000000
Head 5-suite gate (tokenLimits, modalityDefaults, model-catalog, model-catalog-refresh, modelConfigResolver) 314 passed (314) in 5 files
Same 5 suites, base (A/A control) 312 passed (312) — exactly the 2 new tests fewer
tsc --noEmit (core), prettier, eslint 0 / clean / 0
Mutant (head source, focused file) Result Assertion that reddened
M1 drop the new PATTERNS row KILLED expected 262144 to be 1000000
M2 drop the trailing hyphen KILLED expected 1000000 to be 262144 (boundary test)
M3 LIMITS['1m'] → ['256k'] KILLED expected 262144 to be 1000000
M4 narrow to one invite id KILLED expected 262144 to be 1000000
M5 move the row below ^qwen KILLED expected 262144 to be 1000000
C0 unmutated control GREEN 104 passed

Every cell was restored with git checkout HEAD -- and verified clean (blob 40bf899db71cf291ff9e90b9b836e3ec93e36bea before and after). M2 is the cell that proves the precision guard: on base the boundary test passes vacuously (nothing is claimed), and only the over-claiming mutant reddens it.

Unit gates and mutations

No collateral change across 48 model ids

I compared the two built dists directly over 48 ids — every Qwen row, the boundary spellings, and representatives of Gemini/OpenAI/Claude/DeepSeek/GLM/MiniMax/Kimi/Seed-OSS/unknown.

  • Exactly 9 ids changed input, and every one normalizes into qwen-latest-series-: the five ids in the tests (four suffix shapes plus the provider-prefixed form), …-invite, …-beta, the :free suffix form, and the uppercase form.
  • qwen-latest-seriesfoo, qwen-latest-seriesx-invite-202610, bare qwen-latest-series stay 262,144 on both arms.
  • Output limit, hasExplicitOutputLimit and defaultOutputCeiling are identical for all 48 ids; series ids stay at 32,768 output.
  • qwen-plus resolves to 1,000,000 in both arms — its models.dev catalog entry outranks the 256K family row, which is the catalog-first precedence the PR relies on, observed through the real loader.

Corpus comparison

N1 (non-blocking) — ids that normalize to a bare qwen-latest-series still fall back

Confirmed at both arms: qwen-latest-series-202610 and qwen-latest-series-v28 have their date/version suffix stripped by normalize() and become bare qwen-latest-series, which the trailing-hyphen rule deliberately does not claim, so they stay at 262,144. This is the shape the triage bot's non-blocking suggestion names; it is outside the PR's stated invite-alias scope, so I am not counting it as a defect.

I probed the suggested variant as a merge input, not as a requested change: changing the row to /^qwen-latest-series(?:-|$)/ keeps all 104 focused tests green, moves bare/date/version forms to 1,000,000, and still leaves qwen-latest-seriesfoo and qwen-latest-seriesx-… at 262,144. Whether a bare series id should answer 1M is a product call; the stricter regex is consistent with the PR's current scope.

What this does not settle

The detector and every downstream consequence are verified, but whether the real invite model truly has a 1,000,000-token window rests on maintainer knowledge, not on anything observable from outside — the PR says so, and I could not add evidence there without credentials.

中文摘要(点击展开)

本地验证 —— 真实 /context A/B:1000.0k vs 262.1k

我构建了两臂,并用产品真实的 /context 路径对接一个 OpenAI 兼容端点。被验证 head 为 dce317f68584c45c0743355301af53ff7b2dfbdc(轮次开始到发布未变化),base 为 9acb3a5a1d68aad0705ad4d420830affb59cc95a;机器为 macOS 26.6.2 / Apple M1 Max,Node 22.23.4。

本地结论:merge-ready,无阻塞发现。 末尾有一条非阻塞边界说明(N1)。发布时 CI 的 Node 22 测试任务仍在运行;本轮已在本地覆盖 tokenLimits.test.ts 及其四个邻接套件。

验证方式

  • 两个 detached worktree,各自全量 npm run build(Node 22.23.4、8 GB 堆、EXIT=0)。head 的 core dist 带新规则(grep -c qwen-latest-series- → 2),base 为 0,证明构建确实带上了改动。
  • 无头 CLI:两臂执行同一命令 qwen -p '/context' --auth-type openai --model qwen-latest-series-invite-202610-m4 --openai-base-url <fake>/v1 --openai-api-key fake-key,使用独立 QWEN_HOME 与 QWEN_CODE_MODELS_DEV_REFRESH=off。
  • 交互式 TUI:先发一条真实模型请求(hi → 伪服务回复并携带 usage.prompt_tokens=12000),再执行 /context,用 tmux 150×48 抓取。两臂环境与参数一致。
  • 端点使用仓库自带的 integration-tests/fake-openai-server.ts。未使用邀测模型凭据,因此验证的是完整的 配置 → 识别逻辑 → /context/阈值路径,而不是真实邀测端点 —— 这与 PR 自述的限制相同。

用户可见结果

观测项 base head
无头 /context 头部 Context window: 262.1k tokens Context window: 1000.0k tokens
TUI 有效窗口 242.1k 980.0k
TUI warn 阈值 202.8k 830.0k
TUI auto 阈值 222.8k 850.0k
TUI hard 阈值 239.1k 977.0k
12.0k token 提示词的占用显示 4.6% 1.2%

阈值阶梯与 computeThresholds 完全一致(85% auto、13k 上限缓冲、3k hard 缓冲),说明改动确实按 PR 所述移动了压缩时机,而不只是头部文字变化。

单测门禁与变异矩阵

格子 结果
tokenLimits.test.ts,head 104 passed (104)
tokenLimits.test.ts,base 102 passed (102)
head 测试文件跑在 base 源码上 1 failed / 103 passed —— expected 262144 to be 1000000
head 五套件(tokenLimits、modalityDefaults、model-catalog、model-catalog-refresh、modelConfigResolver) 314 passed (314),5 个文件
同样五套件在 base(A/A 对照) 312 passed (312) —— 正好少两个新测试
tsc --noEmit(core)、prettier、eslint 0 / clean / 0
变异(head 源码、聚焦测试文件) 结果 变红的断言
M1 删除新增 PATTERNS 行 KILLED expected 262144 to be 1000000
M2 去掉尾部连字符 KILLED expected 1000000 to be 262144(边界测试)
M3 LIMITS['1m'] → ['256k'] KILLED expected 262144 to be 1000000
M4 收窄为单一 invite id KILLED expected 262144 to be 1000000
M5 把该行移到 ^qwen 之后 KILLED expected 262144 to be 1000000
C0 未变异对照 GREEN 104 passed

每格都用 git checkout HEAD -- 还原并校验干净(变异前后 blob 均为 40bf899db71cf291ff9e90b9b836e3ec93e36bea)。M2 是证明精度防护承重的那一格:在 base 上边界测试是空过的(base 不会认领任何 id),只有"过度认领"的变异会让它变红。

48 个模型 id 无附带变化

我直接对比两臂构建产物中的 48 个 id —— 覆盖全部 Qwen 行、边界拼写,以及 Gemini/OpenAI/Claude/DeepSeek/GLM/MiniMax/Kimi/Seed-OSS/未知模型的代表项。

  • 输入值恰好 9 个发生变化,且全部归一化后落在 qwen-latest-series-:测试里的四种拼写、…-invite、…-beta、:free 后缀形式、openai/… 前缀形式和全大写形式。
  • qwen-latest-seriesfoo、qwen-latest-seriesx-invite-202610、裸 qwen-latest-series 在两臂都保持 262,144。
  • 输出上限、hasExplicitOutputLimit 与 defaultOutputCeiling 在全部 48 个 id 上完全一致;系列 id 的输出仍为 32,768。
  • qwen-plus 在两臂都解析为 1,000,000 —— 它的 models.dev 目录条目胜过 256K 家族行,这正是 PR 依赖的"目录优先",且是通过真实加载器观测到的。

N1(非阻塞)—— 归一化为裸 qwen-latest-series 的 id 仍会回落

两臂实测:qwen-latest-series-202610 与 qwen-latest-series-v28 的日期/版本后缀会被 normalize() 剥掉,变成裸 qwen-latest-series,而尾部连字符规则有意不认领它,因此仍是 262,144。这正是 triage bot 非阻塞建议里点名的形态;它超出 PR 明示的邀测别名范围,所以我不把它计为缺陷。

我把建议的正则当作合并决策输入做了探针(不是要求改动):把该行改成 /^qwen-latest-series(?:-|$)/ 后,104 个聚焦测试仍全绿,裸/日期/版本形态变为 1,000,000,qwen-latest-seriesfoo 与 qwen-latest-seriesx-… 仍为 262,144。裸系列 id 是否应为 1M 属于产品判断;更严格的写法与 PR 当前范围自洽。

本轮未覆盖

识别逻辑及其全部下游后果都已验证;但真实邀测模型是否确有 1,000,000 token 窗口,取决于维护者知识,无法从外部观测 —— 这一点 PR 已明说,我也无法在缺少凭据的情况下补充证据。

Artifacts: wenshao/qwen-code@assets-pr15758 under pr15758/ (six PNGs, all raw URLs checked 200). Harness, logs and the full report live in tmp/pr15758-verify-20261028-114320/. Verified at head dce317f6; base 9acb3a5a.

@wenshao
wenshao merged commit 79a9b75 into main Oct 28, 2026
142 of 143 checks passed
@wenshao

wenshao commented Oct 28, 2026

Copy link
Copy Markdown
Collaborator Author

感谢建议,已按此落地。本 PR 已合并,改动另开 #15762(基于 main 79a9b753c60)。

基线复核:在 79a9b753c60 上实测,qwen4、qwen4-max、qwen4-plus、qwen4.5-max、qwen4-coder-plus、qwen4-max-2026-10-01、openai/qwen4-max、qwen4-omni-120b-think 全部返回 262144,与你描述一致。

改动:就是建议的那一行 [/^qwen4/, LIMITS['1m']],放在 ^qwen3\.\d 旁边、256K 家族兜底之前。

真实 /context A/B:用仓库自带的 integration-tests/fake-openai-server.ts 驱动产品真实路径,不需要 qwen4 凭据。同一命令、同一 id、两臂退出码均为 0:

base: Model: qwen4-max  Context window: 262.1k tokens
head: Model: qwen4-max  Context window: 1000.0k tokens

同一路径上 qwen3-max 仍为 262.1k,边界未受影响。

变异见证:两条新用例逐一验证过,每次只改一处 —— 把该行改成 256K,家族用例红(106 项中其余 105 项仍绿);跳过 catalog 分支让家族表先作答,catalog 覆盖用例在 400,000 上红。catalog 优先语义未变,并新增一条用例把它钉住,models.dev 收录 qwen4 后自动让位。

范围:输出上限仍保留在 32K 的 Qwen 家族默认值上,本次只动输入窗口;qwen4 的输出上限待明确后再单独钉定。整族 1M 这一点在 PR 正文的风险段里如实标注为评审者提供、代码与 models.dev 当前无法确认。

wenshao added a commit that referenced this pull request Oct 28, 2026
The Qwen 1M rules stopped at qwen3.x, so every qwen4 spelling fell through to the 256K family row and auto-compaction fired at 22% of the real window. Add a family row for the generation, as suggested in review of #15758.
@qwen-code-review-bot

Copy link
Copy Markdown
Collaborator

Released in v0.26.3.

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.

5 participants