机制解析)
qwen-code 输出 Token 上限自适应升级Adaptive Output Token Escalation机制解析【免费下载链接】qwen-codeAn open-source AI coding agent that lives in your terminal.项目地址: https://gitcode.com/GitHub_Trending/qw/qwen-code本文以docs/design/adaptive-output-token-escalation/adaptive-output-token-escalation-design.md为骨架深入 qwen-code 源码packages/core/src/core/下多个模块剖析其“默认采用模型声明输出上限、截断时先升级再多轮续写”的输出 Token 治理方案。读完你将掌握max_tokens的解析优先级、QWEN_CODE_MAX_OUTPUT_TOKENS环境变量的作用、64K 升级下限与 3 次续写恢复的实现细节以及这套机制在 OpenAI 兼容、DashScope、Anthropic 三类 provider 上的落地差异与设计取舍。背景为什么“低默认值”会伤害正确性qwen-code 面临一个典型的 LLM API 容量矛盾每一次 API 请求都会按max_tokens比例预留 GPU 槽位GPU slot。把默认值设低例如 8K确实能降低槽位预留、提升后端吞吐但代价是正常的大篇幅回复更容易被截断在文件写入类工作流中截断会产出不完整的 tool-call 参数迫使工具调度器tool scheduler拒绝这次不完整的写入最坏情况下一次普通请求会退化成“截断 → 重试”的往返甚至陷入重试循环。因此该设计把正确性放在首位默认信任模型声明的输出上限仅在真正触顶时进行升级与续写同时保留让运维显式压低预留量的逃生口。解决方案总览默认、升级、恢复三层策略方案的核心逻辑可以浓缩为引自 设计文档 的架构图Request (max_tokens user/env value or model output limit) │ ▼ ┌─────────────────────────┐ │ Response truncated? │──── No ──▶ Done ✓ │ (MAX_TOKENS) │ └───────────┬──────────────┘ │ Yes ▼ ┌──────────────────────────────────────────────────┐ │ Layer 1: Escalate to model output limit │ │ ┌────────────────────────────────────────────┐ │ │ │ Pop partial response from history │ │ │ │ RETRY (isContinuation: false → reset UI) │ │ │ │ Re-send at max(64K, model output limit) │ │ │ └────────────────────────────────────────────┘ │ └───────────┬──────────────────────────────────────┘ │ ▼ ┌─────────────────────────┐ │ Still truncated? │──── No ──▶ Done ✓ │ (MAX_TOKENS) │ └───────────┬──────────────┘ │ Yes ▼ ┌──────────────────────────────────────────────────┐ │ Layer 2: Multi-turn recovery (up to 3×) │ │ ┌────────────────────────────────────────────┐ │ │ │ Keep partial response in history │ │ │ │ Push user message: Resume directly... │ │ │ │ RETRY (isContinuation: true → keep UI buf) │ │ │ │ Re-send with updated history │ │ │ │ Model continues from where it left off │ │ │ └──────────────────────────────┬─────────────┘ │ │ │ │ │ ┌──────────────┬───────┴──────┐ │ │ │ Succeeded? │ Yes ──▶ Done ✓ │ │ └──────┬──────┘ │ │ │ No (still truncated) │ │ ▼ │ │ attempt 3? ── Yes ──▶ loop back ↑ │ └───────────┬──────────────────────────────────────┘ │ No (exhausted) ▼ ┌──────────────────────────────────────────────────┐ │ Layer 3: Tool scheduler fallback │ │ ┌────────────────────────────────────────────┐ │ │ │ Reject truncated Edit/Write tool calls │ │ │ │ Return guidance: You MUST split into │ │ │ │ smaller parts — write skeleton first, │ │ │ │ then edit incrementally. │ │ │ └────────────────────────────────────────────┘ │ └──────────────────────────────────────────────────┘三条关键语义默认不使用固定低值而是采用模型声明的输出上限若当前上限低于 64K升级时以 64K 为下限。升级截断后把部分响应从历史中弹出以更大的max_tokens整体重发UI 重置缓冲区。恢复升级后仍截断则保留部分响应在历史中注入续写消息让模型从断点继续最多 3 次耗尽后回落到工具调度器的截断指导拒绝不完整的 Edit/Write 调用并提示拆分输出。需要更低预留量的运维场景并未被丢弃只是改为显式开启设置QWEN_CODE_MAX_OUTPUT_TOKENS环境变量即可且该显式值会被尊重。max_tokens的解析优先级与“已知模型”判定有效max_tokens按以下优先级解析表格引自设计文档并可与源码逐条对应优先级来源已知模型取值未知模型取值升级行为1最高用户配置samplingParams.max_tokensmin(userValue, modelLimit)userValue不升级2环境变量QWEN_CODE_MAX_OUTPUT_TOKENSmin(envValue, modelLimit)envValue不升级3最低模型/默认输出上限modelLimitDEFAULT_OUTPUT_TOKEN_LIMIT 32K升级到模型上限64K 下限 恢复其中“已知模型”指在OUTPUT_PATTERNS中有显式条目的模型通过 tokenLimits.ts 的hasExplicitOutputLimit()判定。已知模型的生效值始终被裁剪到模型声明的输出上限以内以避免input max_output contextWindowSize引发部分 API 的 400 错误未知模型自定义部署、自托管端点则直接把用户值透传因为后端可能支持更大的上限。这一逻辑落地在三个内容生成器中对应关系与设计文档一致DefaultOpenAICompatibleProvider.applyOutputTokenLimit()—— OpenAI 兼容 provider见 default.tsDashScopeProvider—— 继承默认 provider 的applyOutputTokenLimit()见 dashscope.tsAnthropicContentGenerator.buildSamplingParameters()—— Anthropic provider见 anthropicContentGenerator.ts。OpenAI 兼容分支的判定顺序default.tsapplyOutputTokenLimit()的实际执行顺序是若samplingParams已设置直接原样返回请求采样参数是线上形态的唯一真源不注入默认值有显式request.max_tokens已知模型取min(userMaxTokens, modelLimit)未知模型直接采用用户值无显式配置先查QWEN_CODE_MAX_OUTPUT_TOKENS经parsePositiveIntegerEnvValue校验为纯正整数再落到defaultOutputCeiling(model)。代码注释中给出了几个可复现的示例用户设 4K、已知模型上限 64K → 用 4K尊重用户偏好用户设 100K、已知模型上限 64K → 用 64K裁剪防 API 报错用户设 100K、未知模型 → 用 100K后端可能支持用户未设、模型上限 64K → 用 64K用户未设、模型上限 4K → 用 4K取模型较低值用户未设、QWEN_CODE_MAX_OUTPUT_TOKENS16000→ 用 16K。Anthropic 分支anthropicContentGenerator.tsbuildSamplingParameters()与 OpenAI 分支保持同一套“用户 max_tokens 是天花板而非豁免券”的约定当请求同时携带已被窗口裁剪的maxOutputTokens时通过reconcileMaxTokens(configMaxTokens, requestMaxTokens)取两者较小值上线路从而保证prompt max_tokens ≤ window对 samplingParams 用户同样成立tokenLimits.ts。源码纵深tokenLimits.ts 的模型上限表与裁剪管线tokenLimits.ts 是整套机制的“数据库”关键常量与函数符号值含义DEFAULT_TOKEN_LIMIT200,000输入上下文默认值DEFAULT_OUTPUT_TOKEN_LIMIT32,000未知模型的输出默认值ESCALATED_MAX_TOKENS64,000升级下限OUTPUT_TOKEN_CEILING64,000同ESCALATED_MAX_TOKENS自动非用户配置输出请求的上限升级目标不会超过它MIN_CLAMPED_OUTPUT_TOKENS4,000窗口裁剪时输出请求的下限值得注意的设计自动路径的请求值即使模型声明更高也会被裁剪到 64K 上限——defaultOutputCeiling()的唯一例外是CLAUDE_OPUS_EXTENDEDOpus 4.6–4.8 与 5.x直接放行 128K真正需要更多输出的用户必须显式设置max_tokens仍受模型真实上限约束。这与设计文档“升级到模型上限64K 下限”的表意一致升级请求也走clampOutputTokensToWindow(OUTPUT_TOKEN_CEILING, ...)绝不会越过 64K 上限见 llm-chat.ts 中escalatedLimit的计算。OUTPUT_PATTERNStokenLimits.ts按“最具体优先、首个命中生效”排序部分代表性条目Gemini 3.x64KGemini 其余8KGPT-5.x / o 系列128KGPT 其余16KClaude Opus 4.6–4.8 / 5.x128KSonnet 4.664KClaude 其余64KQwen3.x / coder-model64KQwen 其余32KDeepSeek V4384Kreasoner/R164Kchat8KGLM-5 系列128KGLM-4.716KKimi K3128KMiniMax-M2.564K。normalize()负责把形形色色的模型 IDprovider 前缀、authType:model、qwen3-coder:free这类右侧 tag、claude-opus-4.8点分小版本、日期/量化后缀等归一化防止未知变体悄悄落到默认 32K 分支。升级机制Escalation放在重试循环之外设计文档特别强调升级逻辑刻意放在主重试循环之外。原因有三重试循环处理的是瞬时错误限流、无效流、内容校验失败截断不是错误——它是“成功但被截短”的响应升级流自身的错误限流、网络失败应直接抛给调用方而不是被重试逻辑用错误的参数静默重试。从源码结构看设计文档所指的geminiChat.ts在当前仓库中即 llm-chat.ts升级/恢复相关常量均定义于此。升级步骤与源码守卫条件一一对应1. 流成功完成lastError null 2. 最后一个 chunk 的 finishReason MAX_TOKENS 3. 守卫检查通过 - maxTokensEscalated false防止无限升级见 llm-chat.ts#L3408 - hasUserMaxTokensOverride false尊重用户意图见 llm-chat.ts#L3412-L3415 同时覆盖 samplingParams.max_tokens 与 QWEN_CODE_MAX_OUTPUT_TOKENS - shouldEscalateMaxOutputTokens true初始有效上限 升级后上限见 llm-chat.ts#L3425-L3426 4. 计算升级上限clampOutputTokensToWindow(OUTPUT_TOKEN_CEILING, ...)等价于 max(64K, 模型上限) 的窗口裁剪版本 5. 从聊天历史弹出部分模型响应 6. 产出 RETRY 事件isContinuation: false→ UI 丢弃部分输出并重置缓冲区 7. 以 maxOutputTokens 升级后上限重新发送同一请求测试 llm-chat.test.ts 验证了这条路径should escalate thought-only MAX_TOKENS responses after a tool result断言第二次generateContentStream调用的config.maxOutputTokens大于第一次且事件流中出现携带maxOutputTokensEscalated的RETRY反例测试should not escalate ... when max tokens are user-setllm-chat.test.ts则验证了samplingParams: { max_tokens: 1024 }时不触发升级。恢复机制Recovery保留部分响应、多轮续写若升级后的响应仍被截断finishReason MAX_TOKENS恢复循环运行至多MAX_OUTPUT_RECOVERY_ATTEMPTS3次1. 部分模型响应已在历史中由 processStreamResponse 推入 2. 推入恢复用户消息OUTPUT_RECOVERY_MESSAGE 3. 产出 RETRY 事件isContinuation: true→ UI 保留文本缓冲区以便续写 4. 用更新后的历史重发模型能看到自己的部分输出 恢复指令 5. 若仍截断且次数未用完回到步骤 1 6. 若恢复尝试抛错空响应、网络错误 - 从历史弹出悬空的恢复消息 - 跳出恢复循环相关常量llm-chat.tsMAX_OUTPUT_RECOVERY_ATTEMPTS 3RECOVERY_RESUME_INSTRUCTIONResume directly — no apology, no recap of what you were doing. Pick up mid-thought if that is where the cut happened. Break remaining work into smaller pieces.——明确禁止道歉与复述鼓励从断点续写、拆分剩余工作OUTPUT_RECOVERY_MESSAGE Output token limit hit. RECOVERY_RESUME_INSTRUCTIONOUTPUT_RECOVERY_TAIL_CHARS 1200嵌入恢复消息的上一响应尾部最大字符数给模型足够的续写锚点约 200–400 token 的散文或一张多行 Markdown 表格又不撑爆输入预算恢复文本去重由getRecoveryContinuationSuffix/findContainedRecoveryPrefixReplayLength完成RECOVERY_OVERLAP_MIN_BYTES 6并对 CJK 有码点级下限防止我们这类常见两字重合被误判。步骤 6 的“弹出悬空恢复消息”有明确的索引守卫rollbackRecoveryAttempt()先弹出部分model[fc]若processStreamResponse已推入再弹出恢复用户消息顺序颠倒会把OUTPUT_RECOVERY_MESSAGE残留成一条真实的用户消息llm-chat.ts。RETRY 时的状态清理turn.ts当Turn类收到 RETRY 事件时会清空上一轮累积状态防止不一致turn.tspendingToolCalls.length 0——若首次截断响应里已包含完整 tool call升级响应会重复它们必须清空避免重复调用pendingCitations.clear()——避免重复引用finishReason undefined——让新响应的 finish reason 生效。isContinuation标志被透传给 UI供其决定是重置文本缓冲区升级false还是保留续写true。此外截断时Turn会给待处理 tool call 打上wasOutputTruncated true供下游区分“截断”与“真实参数错误”turn.ts。常量汇总与各模型升级上限定义于 llm-chat.ts 与 tokenLimits.ts常量值用途ESCALATED_MAX_TOKENS64,000模型上限较低时的升级下限MAX_OUTPUT_RECOVERY_ATTEMPTS3升级后的最大多轮续写次数有效升级上限为max(ESCALATED_MAX_TOKENS, tokenLimit(model, output))模型升级上限Claude Opus 4.6131,072128KGPT-5 / o 系列131,072128KQwen3.x65,53664K未知模型64,000下限注在发送路径上该值还会再经clampOutputTokensToWindow按上下文窗口剩余空间裁剪因此实际线路值以窗口约束为准。设计决策复盘四个为什么为什么不用 8K 默认值8K 默认是槽位预留/容量优化不是正确性需求它以“大响应截断”为代价换取后端吞吐请求按max_tokens比例预留 GPU 槽位低值少预留大文件生成与编辑类 tool call 完全可能超过 8K8K 默认会把正常请求变成“截断 → 升级”的往返最坏形成重试循环对照实现Claude Code 保留了 8K 上限但把它藏在默认对第三方关闭的特性开关tengu_otk_slot_v1not validated on Bedrock/Vertex之后——即其非第一方服务下的默认行为正是“使用模型声明上限”。qwen-code 的 provider 全部是第三方 / OpenAI 兼容 / 自托管因此对齐“默认关闭”是安全选择容量权衡并未丢失只是改为opt-in容量受限的自托管后端可通过QWEN_CODE_MAX_OUTPUT_TOKENS例如8000恢复低预留量同时刻意不重新引入 GrowthBook 式特性开关——qwen-code 没有此类基础设施环境变量已覆盖需求。为什么升级到模型上限而不是固定 64K输出上限更高的模型Claude Opus 128K、GPT-5 128K此前被无谓地限制在 64K用模型真实上限能覆盖绝大多数长输出避免第二次重试ESCALATED_MAX_TOKENS64K仅作为未知模型tokenLimit()返回默认 32K的下限。为什么多轮续写而不是渐进升级渐进升级如 16K → 32K → 64K每次都需重新生成完整响应多轮续写保留部分响应让模型继续省 token 与延迟恢复消息成本极低每次约 40 token远低于重新生成大响应3 次尝试上限既防止死循环又覆盖绝大多数实际场景。为什么升级放在重试循环之外截断是成功场景而非错误升级流的错误限流、网络失败应直接传播而不是用错误参数静默重试让重试循环专注于其原始职责瞬时错误恢复恢复错误被单独捕获避免中止整个会话。实操建议与适用前提常规使用无需任何配置系统默认采用模型声明输出上限自动裁剪到 64K 天花板截断时自动升级 最多 3 次续写容量受限的自托管后端设置QWEN_CODE_MAX_OUTPUT_TOKENS8000或任意正整数恢复低槽位预留该值在已知模型上仍会被裁剪到模型上限以内注意无效值非正整数会被parsePositiveIntegerEnvValue静默忽略需要超长输出如 DeepSeek V4 的 384K 输出上限在samplingParams.max_tokens中显式设置未知模型直接透传、已知模型按声明上限裁剪并受窗口裁剪约束前提与边界以上行为以当前仓库packages/core源码为准不同 provider 对max_tokens的约束不同已知/未知模型的裁剪差异正是为了适配这些差异而设计。延伸阅读设计文档adaptive-output-token-escalation-design.md上限表与裁剪管线tokenLimits.tsOpenAI 兼容 providerdefault.tsDashScope providerdashscope.tsAnthropic provideranthropicContentGenerator.ts升级/恢复主逻辑llm-chat.tsRETRY 状态清理turn.ts行为测试llm-chat.test.ts、tokenLimits.test.ts【免费下载链接】qwen-codeAn open-source AI coding agent that lives in your terminal.项目地址: https://gitcode.com/GitHub_Trending/qw/qwen-code创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考