Repository navigation
Description
What happened? / 问题描述
DeepSeek 模型(无论官方端点还是 DeepSeek 系中转站)在开启思考后,请求里会被塞入上游不接受的 reasoning_effort 字段,导致 400 / invalid request。
关键点:detectCompat() 里 supportsReasoningEffort 的排除列表漏掉了 DeepSeek,其他已知不支持的厂商(Grok / Zai / Moonshot / Together / Cloudflare AI Gateway / Nvidia / AntLing)都排除了。
// out/main/chunks/openai-completions-C0fxjjC3.js:1133
// out/main/chunks/openai-completions-BNZB_AlW.js:1154
supportsReasoningEffort: !isGrok && !isZai && !isMoonshot && !isTogether && !isCloudflareAiGateway && !isNvidia && !isAntLing,
// ↑ 缺 !isDeepSeek影响面比看起来大:不只是 api.deepseek.com,所有 DeepSeek 模型中转渠道都会中招,因为 thinkingFormat 的判定是按 base_url 是否含 deepseek.com 来的:
const isDeepSeek = provider === "deepseek" || baseUrl.toLowerCase().includes("deepseek.com");中转站(new-api / one-api 之类,base_url 是自有域名)会落到 thinkingFormat: "openai" 分支,然后命中通用的 reasoning_effort 下发分支——即使 DeepSeek 上游本身不支持这个字段。
顺带一提,这里有个不一致:deepseek 分支里 thinking 和 reasoning_effort 是分开处理的(thinking 走 params.thinking,reasoning_effort 单独受 supportsReasoningEffort 控制),说明设计上本来就打算让某些端点只开思考、不传 effort——只是 DeepSeek 漏配了。
Steps to reproduce / 复现步骤
- 添加一个
openai_compatible供应商,api_style = chat_completions,base_url 指向一个 DeepSeek 系中转站(非deepseek.com,例如https://<relay>/v1) - 模型填
deepseek-v4.1-flash(或任意 DeepSeek 模型 id) - 把该模型的思考等级设为任意非 off 档位(
low/medium/high),例如thinkingLevels: ["off","low","medium","high"] - 发送任意消息 → 上游拒绝
reasoning_effort
对照组:思考等级保持 off 时正常——因为 reasoningEffort 为空时不会下发该字段,所以这个 bug 会一直藏着,直到用户打开思考。
Expected behavior / 预期行为
DeepSeek 系模型不应下发 reasoning_effort;开启思考时仅通过端点自身机制生效(或按端点能力下发 thinking),不影响其他厂商在同一中转站上的行为。
Actual behavior / 实际行为
请求体中携带 reasoning_effort,上游返回 400 / invalid request。
疑似根因 / Suspected root cause(代码级,已用打包代码验证)
说明:下面是我对 0.20.1 打包后的
app.asar做的静态分析 + 直接驱动被 patch 前后的真实buildParams()得到的 payload 对比。没有抓真实线上请求体(本地日志不记录该字段),所以请以维护者侧的请求日志为准。
1. 判定漏掉 DeepSeek
见上面的 supportsReasoningEffort 表达式。
2. 中转渠道走通用分支
detectCompat() 的 thinkingFormat 取值:
thinkingFormat: isDeepSeek ? "deepseek" : isZai ? "zai" : isTogether ? "together" : isAntLing ? "ant-ling" : isOpenRouter ? "openrouter" : "openai",isDeepSeek 只看 provider 与 base_url,所以中转站拿不到 "deepseek" 分支。
3. 我做的验证
我从打包代码里抽出 detectCompat / getCompat / buildParams(brace-match 抽取 + stub 掉无关 helper),直接调用真实的 buildParams(model, ctx, { maxTokens, reasoningEffort }, getCompat(model)),观察实际 payload:
| 场景 | 修补前 | 修补后 |
|---|---|---|
deepseek-v4.1-flash + 中转站 URL(thinkingFormat: "openai") |
reasoning_effort: "medium" |
字段不下发 |
官方 api.deepseek.com(thinkingFormat: "deepseek") |
reasoning_effort: "high" |
不下发,且 thinking: {"type":"enabled"} 保留 |
同中转站的 glm-5.2 / grok-4.7 |
下发 | 仍下发(未被误伤) |
4. 验证有效的最小改动
把 DeepSeek 识别从「只看 base_url」扩展到「也看模型 id / family」,然后加进排除列表:
const isDeepSeekModel = isDeepSeek || /deepseek/i.test(String(model.id ?? "")) || /deepseek/i.test(String(model.family ?? ""));
// ...
supportsReasoningEffort: !isDeepSeekModel && !isGrok && !isZai && !isMoonshot && !isTogether && !isCloudflareAiGateway && !isNvidia && !isAntLing,要注意只加 !isDeepSeekModel,不要直接放宽 isDeepSeek 本身——isDeepSeek 还参与 useMaxTokens / isNonStandard / supportsStore / requiresReasoningContentOnAssistantMessages,直接改会顺带改变中转站上 max_tokens 字段名等无关的线上行为。
5. 为什么没法用配置绕过
这点也许值得记一笔,因为对用户不直观:目前没有任何 UI 或供应商配置能关掉这个字段。
config_json.models[]由bindingFromGenericModel()(out/main/index.js)解析,它只回填固定字段(contextWindow/maxTokens/nativeWebSearch…),会丢弃compat- 用户供应商的目录来自
modelConfigFromModelsDev()(chunks/index-*.js),对非 models.dev 记录不设置compat modelConfigWithBinding()只把binding.thinkingProtocol映射到compat.forceAdaptiveThinking,不 gatereasoning_effort
也就是说 compat.supportsReasoningEffort 目前只能来自内置的 models.dev 条目,用户侧无法表达。建议至少放开其一(允许用户 per-model 覆盖该 compat 位),这样中转站用户不必改包。
Environment / 环境
- App 版本:
0.20.1 - OS:Windows 11 专业版(AMD64)
- 安装方式:NSIS 安装版(
D:\Program Files\PI-Desktop,含Uninstall PI-Desktop.exe;app-update.yml→provider: github,repo: vastsa/PI-Desktop) - 供应商类型:
openai_compatible+api_style = chat_completions - 复现模型:
deepseek-v4.1-flash(中转站),另见agentrouter渠道的deepseek-v4-flash(thinkingLevels: ["medium","high"])同样会下发
Logs / 日志
本地日志中未记录请求体,无法附上该字段的直接证据;provider.log 只有同期的 model discovery 401。已用上面的「驱动 buildParams()」方式替代取证。
备注 / Notes
我不是在要求上游支持 reasoning_effort——恰恰相反,诉求是不要下发它。如果维护者认为更合适的做法是「按 models.dev 的 reasoning_options 决定是否下发 effort」,那也能一并覆盖 Grok/Zai 那类已经硬编码排除的厂商,可能比继续往这个列表里加名字更稳。这条由你们判断。
Activity
vastsa commentedon Oct 14, 2026
感谢反馈,我们看看
muzimu217 commentedon Oct 14, 2026
补充一条对修复有用的调研结论,供参考(如果维护者已在处理可以忽略):
这个问题在 PI-Desktop 侧有一处现成的修复通路,不需要动 pi-ai。 核实链:
openai-completions.js:1265的排除列表确实缺 DeepSeek(:1236的isDeepSeek只匹配provider === "deepseek"或 base_url 含deepseek.com,所以所有中转站都落到thinkingFormat:"openai"的兜底分支,reasoning_effort由:714下发);:1317是model.compat.supportsReasoningEffort ?? detected.supportsReasoningEffort——模型级 compat 可以整体覆盖,且本仓库的 pi-ai patch 没有改这一行;deepseekRequestCompat(packages/shared/src/provider-presets.ts:497,按 vendorKey/URL/modelId/family 四路匹配,正是为了解决"PI-Desktop 存 UUID 作 provider 导致 pi-ai 探测不到"的同类问题),它返回的 compat 在provider-binding.ts:408展开进model.compat。所以最小修复是给
DeepSeekRequestCompat加supportsReasoningEffort: false(官方端点与中转两分支都带):false不是 undefined,在??合并下会正确压过自动探测值,阻断:714/:624/:634/:675/:701全部下发点;thinking字段照常走(官方端点思考不受影响),中转站则不再发不支持的字段。不影响其他厂商在同一中转站上的行为(判定按模型家族,gpt/glm 等不命中)。我本地已按这个方向起草了修复 + 回归测试(官方/中转/无关模型三分支断言,先红后绿验证过)。如果维护者还没动手,我可以整理成 PR 提交;如果你们已按别的方式处理,这些分析就当佐证。
vastsa commentedon Oct 16, 2026
已在 PR #1701 修复并合入 main(合并提交 )。DeepSeek 官方端点和中转模型现在会显式关闭通用 OpenAI 的 字段,同时保留原有 thinkingFormat 与 reasoning replay;回归测试已验证实际 Completions 请求不再携带该字段。
vastsa commentedon Oct 16, 2026
补充说明:上条评论中的代码标记因命令行转义被去掉,正确信息如下。
已在 PR #1701 修复并合入 main(合并提交 7403d76)。DeepSeek 官方端点和中转模型现在会显式关闭通用 OpenAI 的 reasoning_effort 字段,同时保留原有 thinkingFormat 与 reasoning replay;回归测试已验证实际 Completions 请求不再携带该字段。
fix(provider): suppress DeepSeek reasoning effort
fix(provider): send reasoning_effort to official DeepSeek endpoints (#…
perf(sessions): cache file-derived session summaries by file identity (…