polan-prologue/dsh-reasoning-plus

dsh-reasoning-plus

DSH 推理等级补齐:为 API 地址非官方的自定义提供方实测出与官方同形的推理等级,每一档都在上游真实生效

Uncategorizedcordisdeepseek-harnessdsh

推理等级++(dsh-reasoning-plus)

为 API 地址非官方的自定义提供方,补齐与官方提供方同形的推理等级菜单, 并且保证每一档在上游真实生效——经实测探测,不可区分的档位不会出现在菜单里。

一句话:官方提供方的 Off / Low / High / Max 是适配器写死的;自定义提供方没有这份表, 所以菜单是空的。本插件把这份表实测出来再写回去。

项目地址:https://github.com/polan-prologue/dsh-reasoning-plus · 许可证:MIT


快速开始

# 从 GitHub 安装(推荐:锁定 tag,升级由你决定)
dsh plugin --profile desktop add github:polan-prologue/dsh-reasoning-plus#v0.2.0

# 从本地目录安装(二次开发时用这个)
dsh plugin --profile desktop add <插件目录>

装完重启 DSH(新增插件行必须重启才生效),然后到「设置 → 模型」的插件区找「推理等级++」卡片。

它解决什么问题

DSH 里官方 DeepSeek 提供方(@deepseek-ai/dsh-llm-deepseek)把 Off / Low / High / Max 写死在适配器里,所以模型选择器里永远有推理等级菜单。

自定义提供方走的是 @deepseek-ai/dsh-llm-pi-ai。它的 reasoningInfo(model, defaultLevel) 在 !model.reasoning 时返回 {}——手工声明的模型拿不到任何档位信息,于是:

  • 模型选择器里没有「推理等级」这一行;
  • 即使有,也没有任何东西会随档位变化发到上游。

根因是「等级表来自 profile 的 reasoningEfforts 声明」。所以真正的修复就是写配置—— 这一处写入同时喂给两条路径:菜单渲染(resolveModelInfo)与出网派发(wire dispatch)。 只做 UI 是假的,因为没有任何东西会真的改变请求。

它不做的事(重要)

  • 不硬编码档位表。 上游网关的行为差异极大,同一份表不可能同时正确: 实测中 off 在 OpenCode Zen 上必然 400,而在本地 wb 网关上是唯一可行的关闭方式。
  • 不猜。 每一档都必须经过同轮配对实测(见下)才会被声明。
  • 不碰官方提供方与目录路由。 只处理「API 地址不指向官方域名」的路由。

安装

插件通过 profile 的 bundle patch 接入。安装后需要重启 DSH 才生效 (新增插件行必须重启;改已有插件的客户端 bundle 才支持热重载)。

重启后在「设置 → 模型」的插件区会出现「推理等级++」卡片。

使用

  1. 打开插件卡片,它会列出所有可接管的路由(API 地址非官方的自定义提供方)。
  2. 打开页面时,插件会自动确保该有的实测结论已经存在:
    • 已有结论、且提供方配置没有改动 → 直接沿用,一个请求都不发;
    • 没有结论、或配置改过(端点/密钥引用/模型表/compat/输出上限等)→ 自动发起一轮实测。 每条路由都有一条常驻状态行(实测通过 / 实测进行中 / 提供方配置已变更… / 尚未实测), 不需要点任何按钮就能看到结果。
  3. 结论会给出哪些档位可区分。开了 autoApply 时自动接管;否则点「接管」把这些档位写进 profile。
  4. 模型选择器里的推理等级菜单随即与官方同形。
  5. 随时可点「交还」,把写入的声明移除(用 unset 移除,不会写回一个假的默认值)。
  6. 「实测」/「实测此模型」是强制路径:点了就一定重新打网关(即使配置没改)—— 自动路径才受"配置没改就不重测"约束。

实测结论怎么判断"还能不能用"

判据是配置指纹(内容),不是"什么时候测的"、更不是提供方的 ID / 显示名:

  • 指纹由 baseURL、api、apiKeyEnv、models[].id(按序)、compat(不含本插件自己 写进去的键)、probeMaxTokens、recommendWhenInconclusive 算出;
  • 指纹一致 → 结论有效,跨重启也照样复用(结论落在 <DSH_HOME>/storages/reasoning-plus/probe-memo.json);
  • 指纹不一致 → 结论标记为已失效(不是"从未实测"),界面如实说明并自动重测。
  • 想让结论定期过期,把 probeReuseMs 设成正数即可(0 = 不设期限,默认)。

判定方法:为什么不能只看「有没有变化」

实测踩过的坑,直接决定了探测器的设计:

  1. 绝对值会漂移。 同一个 prompt 的推理 token 在 285 ~ 4095 之间波动, 所以必须做同轮配对(同一次请求里比较 baseline 与候选档位),比例才有意义。
  2. 方向必须一致。 单次比例差异可能是噪声。判定要求多个轮次里方向一致, 且比值中位数超过阈值(MIN_RATIO_DELTA = 0.25)才算「可区分」。
  3. 必须先排除撞顶。 这是最隐蔽的失败:max_tokens=8192 时, baseline 与所有档位全部落在 8191/8192——测量无效,不是档位无用。 探测器会先标定 baseline,撞顶(baselineMedian ≥ 0.9 × maxTokens)时 沿提示词阶梯换更轻的提示词重试至多 3 次;仍撞顶则如实返回 ok:false, ceilingBound:true,不产出任何档位。

界面会显式标示撞顶状态并禁止接管——因为此时的结论不可信。

配置项

字段含义
enabled总开关
autoApply是否在插件加载时自动完成一轮接管(默认 false,生效时机见下)
officialHosts视为官方的域名(不接管这些)
only / skip白名单 / 黑名单路由
defaultEffort菜单的默认档位
manageDeclared已显式声明 reasoningEfforts 的模型是否也接管(交还时还原原值)
recommendWhenInconclusive测不出可区分档位时,是否仍按官方同形给出完整阶梯
probeEnabled是否允许发起真实探测请求
probeTimeoutMs单次探测超时(默认 90000:实测单次 6–48 秒)
probeMaxTokens探测请求的 max_tokens(撞顶判定基于它,默认 8192)
probeDeadlineMs整轮探测的总预算(默认 300000):超预算即停止发请求、用已有数据出结论
probeConcurrency并发度(默认 4):轮次各臂与关闭档候选共用这一个上限
probeReuseMs结论的复用期限。0(默认)= 只要配置指纹一致就一直复用

probeCacheMs 已被 probeReuseMs 取代(旧字段不再生效,配置里留着它会得到一条告警)。 改名的理由:旧字段的 0 是"每次都重测",新语义的 0 是"不设期限"——同一个名字下把 0 反过来解释,会让老配置静默变成相反的行为。

关于 autoApply 的生效时机

autoApply 的语义是在插件加载时自动完成一轮接管,而不是常驻监听配置变化:

  • 打开 autoApply 并重启 DSH 后,插件在加载时会自动筛出可接管的路由、按需实测、并把实测得出的档位写入 profile;
  • 之后想立即接管,仍可随时手动点「接管」——手动路径始终可用。

之所以不做成常驻监听:监听 volatile 配置变化需要额外的订阅与去重,还要处理"自动接管与手动接管互相打架"的情形,会引入新的失败面。当前实现让契约里"宿主逻辑在没有 webServer 时同样能自动接管"这一条真正成立(已在无 webServer 的组合下实测通过)。

注意:开启后每次 DSH 启动都会检查一遍,但只有"没有有效结论 / 配置改过"时才真的发探测请求 (结论持久化在备忘文件里)。不想产生任何探测流量可关闭 probeEnabled。

HTTP 接口

管理路由前缀 /api/dsh-reasoning-plus(仅本机来源可访问):

方法路径作用
GET/快照:路由列表、判定、已有探测结论、正在进行的实测、配置、警告
POST/probe对某条路由发起真实探测(force: true 表示不管指纹、强制重测)
POST/ensure确保该测的测了:立即返回 { started, skipped },实测在后台跑
POST/takeover写入实测得出的档位声明
POST/revert移除本插件写入的声明
PUT/settings更新插件配置

完整字段契约见 docs/接口契约.md(§13 是本节这批改动的契约)。

已知限制

  • 探测会产生真实请求与费用(每次 30–60 秒量级)。可在配置里关闭 probeEnabled。
  • 上游网关可能没有可用的「真关闭」写法。 例如 Zen 上 reasoning_effort: none 与 thinking:{type:"disabled"} 都返回 400,因此该路由不会提供 off 档。 这是如实反映上游能力,不是缺陷。
  • profile 的 cordis.patch.yml 可能被设置的恢复机制重写。 若接管结果被清掉, 重新进插件页点「接管」即可重建。
  • 探测结论是网关行为的时间快照。上游若改变参数语义,指纹无法感知(网关侧的改动不在你的 配置里)——想强制刷新请点「实测」,或把 probeReuseMs 设成正数定期失效。
  • 交还时如何认出本插件写的 compat:优先用进程内的"用户原值记录";记录不在时(插件重载 / 进程重启)改用持久备忘里记下的"我们写过哪些键、写的是什么值"——当前值与之逐键一致才 移除。两者都没有时才退回实测结论的指纹比对(那条路径的边界见下)。告警会列出被删的键与 值,可手工恢复。
  • 极少数情况下(持久备忘也被删掉 + 你自己设置的 compat 恰好与指纹完全相等),跨重启交还 会把它一并移除。取舍理由:反向选择(留下残留)会让插件写下的派发形态长期改变你后续自写 档位的线缆,危害更大。
  • 重启后未实测就交还且备忘也缺失时,插件无法计算指纹,会走 skipped(不改动任何配置)。 这种情况下告警文案里的「本插件未写入过这条路由」在少数情形下可能不准确(已记为低优先 文案问题)。

开发

# 全部测试
node --test "tests/*.test.mjs"

# 回溯注入:把"修复前的行为"逐条注入副本,确认新关卡真会红
node tests/falsify-injections.mjs

注意:本仓库的测试不能用 node --test <目录> 形式运行(该形式会把目录当模块路径而失败), 必须用上面的 glob 形式或显式文件列表。

开发环境相关的坑与实测数据见: