DSH XD Pool
XDTrees/dsh-xdpool把本机 WorkBuddy 桌面 App 登录过的所有账号自动并入 DSH 模型池:多账号轮换自动容错、实时积分,模型目录按模型标注积分倍率与限时免费 / 夜间折扣。
DeepSeek Harness 多供应商模型路由插件:规则路由 + 首 token 前无感故障切换 + cooldown 熔断 + 用量记账 + 状态接口。
DSH 多供应商模型路由插件(v1.0.2):规则路由 + 首 token 前无感故障切换 + cooldown 熔断 + 用量记账 + 每日 API 报告(结构化 + Markdown) + 模型全自动测试(独立 phase + 退避 + 短路) + 模型档案窗口限额(5h / 1周 / 自定义) + 面板信息分级 + Windows 适配 + 状态接口 + WebUI 面板 + 诊断页签(按 seqId 分组的切换明细) + 凭据失败黄条 + match 收紧密(allowLegacyMatch) + free-tier 跨请求节流 + context 窗口感知重排 + 每日报告一键启用与生成 toast。
| 阶段 | 版本链 | 主题 |
|---|---|---|
| v0.9.10 A | v0.9.10 → v0.9.16 + v0.9.17 收口 | 渠道档案 schema + 声明通道扩能(限额/重置/TPM/来源/备注)+ UI 编辑器 |
| v0.9.10 A 续 | v0.9.18 → v0.9.19 | TPM 节流激活 + hop 内联声明 UI |
| v0.9.9 审核收口 | v0.9.20 → v0.9.21 | 审核修复(1 Critical + 2 Important + I3/S1–S4):model-test 落盘解耦、writeAtomic tmp 清理、档案按 mtime 排序、hourCycle:'h23' |
| v0.9.9 审核续 | v0.9.22 → v0.9.23 | 节流数据源修复(Metrics.snapshot 不再截断到 50)+ 真实数据驱动分析 + 档案重访阈值告警 |
| v1.0 阶段 B | v1.0.0 → v1.0.2 | 契约冻结与交付收口:三类契约文档 / 配置 schema 版本化 + 迁移链 / 集成测试层(13 条契约)/ 人工测试用例集(46 例)/ 数据丢失 Critical 修复(/state 改部分更新)/ CI 首次全绿(3 平台 × Node 22/24)/ v1.0.2 安装包 |
重要审查:docs/v0.9.9-审核摘要.md(30 行 TL;DR)+ v0.9.9-代码审核报告.md(完整 331 行)—— 审核了 v0.9.9 阶段的 1 Critical + 3 Important + 4 Suggestion,全部于 v0.9.20/v0.9.21 处理完毕(含等价性穷举 960 组 0 差异、import 图零环、实测推翻原判断 3 项回归审核)。
agent 发起流式调用
└─ llm/stream waterfall 包装层(本插件)
├─ 首选路由:原样 next() 透传(prepared 绑定不动,行为与无插件时完全一致)
├─ 首分片前收到 error finish 且错误码 ∈ failoverSignals
│ → 失败路由进 cooldown → 取候选链下一项重发(对 agent loop 无感)
├─ 挂起不产分片 → TTFT 看门狗超时 → abort 并按 TIMEOUT 切换
├─ 候选耗尽 → 合成 error finish(保留真实错误码)交 agent/request-error
└─ 首分片之后:绝不切换(commit-on-first-chunk,流文法硬约束)
安全边界:
agent/request(会话级提议)与 llm/stream 包装层两处正规入口propose: false(默认)时对会话模型选择零干预,只做故障切换当前请用方式二。 本插件的
npm包尚未发布、awesome-dsh-plugin市场条目尚未提交, 因此下面的市场/命令行安装当前会失败。上架完成后本节会更新为可用状态。
打开 dsh 设置 → 插件市场,搜索 dsh-model-router,点安装。
或命令行:
dsh plugin --profile web add @botton/dsh-model-router
# macOS / Linux(bash):
# 安装(备份 patch → 拷包 → 幂等追加条目;不自动重启)
scripts/deploy.sh
# 卸载(先摘条目后删包,自动备份 patch)
scripts/undeploy.sh
# 重载生效(确认后手动执行):
# macOS(launchd 守护):
# launchctl kickstart -k gui/$(id -u)/com.deepseek.dsh
# Windows 10+(PowerShell 5.1+,无需 Git Bash):
# 安装(逻辑与 deploy.sh 一致:备份 patch → 拷包 → 幂等追加条目;不自动重启)
scripts\deploy.ps1
# 卸载(先摘条目后删包,自动备份 patch)
scripts\undeploy.ps1
# 重载生效:Windows 无 launchd——停止 dsh 进程后重新运行 dsh 即可
配置在 profile patch(~/.deepseek-harness/home/profiles/web/cordis.patch.yml)的 insert 条目 config: 块中,改完重载生效。
状态接口(只读,仅限本机回环访问):
curl --noproxy '*' -s http://127.0.0.1:3081/api/model-router/status
返回:规则与策略快照、cooldown 状态表、最近 50 次尝试(含每次 attemptIndex/TTFT/e2eMs/outcome;失败项附错误码)、按路由聚合统计、用量记账(滚动 5h/1w 窗口)、包装层计数器(wraps/passthroughs/failovers/timeouts/forced/exhaustions/userAborts)。
每日报告(reports.enabled=true 后启用;未启用时接口存在但返回 503 明确提示):
curl --noproxy '*' -s 'http://127.0.0.1:3081/api/model-router/reports' # 最近摘要 + 今日实时快照
curl --noproxy '*' -s 'http://127.0.0.1:3081/api/model-router/reports?l1=1' # 轻量摘要(面板概览卡 5s 轮询)
curl --noproxy '*' -s 'http://127.0.0.1:3081/api/model-router/reports?day=2026-09-06' # 指定日报告
curl --noproxy '*' -s -X POST 'http://127.0.0.1:3081/api/model-router/reports/generate' # 立即生成昨日报告
v0.9.4 每日报告 UX 补丁(解决「不知道在哪开」「点击无提示」「不知道落盘在哪」):
POST /state 提交 reports.enabled=true(无需手改 patch);保存后提示需重启 dsh 让 daily 实例装配(macOS:launchctl kickstart -k gui/$(id -u)/com.deepseek.dsh)/status 顶层回显 reports: { enabled, reportDir, hour }(默认报告目录 ~/Documents/dsh-model-router-reports/),面板已启用态顶部直接显示v0.9.5 报告格式变更(Markdown):
<day>.report.md(不再生成 JSON 副本),首行内嵌 ``,可直接用 IDE 或访达双击阅读GET /reports?day= 与 POST /reports/generate 响应额外附 markdown 字段(仅已生成才返),面板「查看 Markdown」按钮一键渲染全文.report.json 文件不再被读取,可安全删除模型全自动测试(v0.9.5 §9;需先启用 reports.enabled=true,共享报告目录):
curl --noproxy '*' -s 'http://127.0.0.1:3081/api/model-router/model-test' # 运行快照 + 最近一次结果
curl --noproxy '*' -s -X POST 'http://127.0.0.1:3081/api/model-router/model-test' \
-H 'content-type: application/json' \
-d '{"targets":[{"provider":"minimax-cn","model":"MiniMax-M3","tier":"free"}]}' # 启动跑批
curl --noproxy '*' -s -X DELETE 'http://127.0.0.1:3081/api/model-router/model-test' # 中止
curl --noproxy '*' -s 'http://127.0.0.1:3081/api/model-router/model-test/list' # 历史报告列表
(provider, model) 清单串行跑完整 4 相(probe / rpm / context / quota-group),
产出 probe.ttftMs、rpm.first429Rpm、context.maxAccepted、quotaGroup 与
verdict(primary / backup / exclude + score + quotaRisk)<reportDir>/<runId>.model-test.{json,md} 双份;POST /model-test/manual 可补人工字段
(如上游文档标称的 manualTpm),与实测差异 >5× 时记 verdict.warnings 但不阻断(provider, model) 自动去重,空列表禁止提交route[0])/ 加入备用(追加到 route 末尾)/
排除(写 providerMeta.<provider>.exclude=true)free 的 target 必须显式带 "confirmPaidBurn": true,否则 400singleRaw 带 PROBE_MARK 直透——不 failover、不写 cooldown/metrics/quota/dailyrules[0])——面板暂未提供「选择哪条规则」的选择器;需作用于其它规则时,请先在「切换规则」页签调整规则顺序配额窗口(模型档案)(v0.9.5 §11):
curl --noproxy '*' -s 'http://127.0.0.1:3081/api/model-router/quota/windows?provider=minimax-cn'
curl --noproxy '*' -s -X POST 'http://127.0.0.1:3081/api/model-router/quota/sync' \
-H 'content-type: application/json' \
-d '{"provider":"minimax-cn","windows":{"fiveHour":{"limit":1000000,"resetAt":"2026-09-15T10:00:00Z"}}}'
curl --noproxy '*' -s -X POST 'http://127.0.0.1:3081/api/model-router/quota/reset' \
-H 'content-type: application/json' -d '{"provider":"minimax-cn","windowId":"five-hour"}'
providerMeta.<provider>.quotaWindows 声明窗口上限(fiveHour / weekly)与自定义窗口
(customWindows),wrapper 在入口前置拦截:窗口耗尽就跳过该 provider、不打上游 APIlimit 单位 = token。成功调用按 tokens 累加窗口用量(v0.9.6 起),达到 limit 即主动避让;
上游返回 429/QUOTA 时自动 markBurnedOut 置满兜底;resetAt 到期自动滑窗清零used / 滑窗后的 resetAt)独立持久化到 ~/.deepseek-harness/home/quota-state.json
(30s 周期 flush + 进程退出同步兜底),与配置态 model-router-state.json 两层分离used/limit 使用率quotaWindows 的 provider 不受拦截(不会误伤),面板会提示「建议补填窗口限额」exclude —— 排除某 provider(v0.9.6):
providerMeta.<provider>.exclude 设 true,该 provider 不再进入注册表自动展开的候选池
(same-model / same-provider / exclude-current 三策略均受影响)rule.route 的条目,也不否决当前会话 seed——
即「别再自动选它」,而非「全局禁用该 provider」providerMeta 限额声明字段(v0.9.10 / v0.9.16;方向性方案 §6-A 阶段 A 落地):
| 字段 | 类型 | 语义 | 进路由 |
|---|---|---|---|
rpmLimit | 正整数 | 每分钟请求上限 | ✓ 节流(90% 软上限) |
tpmLimit | 正整数 | 每分钟 token 上限 | ✓(待 Task 3c 提供 token 数据) |
resetPolicy.window | minute | hour | day | rolling | 上游限额重置窗口 | ✗(仅声明) |
resetPolicy.at | HH:MM | hour/day 时必填 | ✗ |
resetPolicy.rollingSec | 1-86400 | rolling 时必填 | ✗ |
tpmLimitSourceUrl | http(s) URL | TPM 依据链接 | ✗(仅声明) |
resetPolicySourceUrl | http(s) URL | 重置规则依据链接 | ✗ |
notes | ≤500 字 | 备注 | ✗ |
/status 与抽屉冲突徽章)rules[].route[].hop 里也写 rpmLimit/tpmLimit/resetPolicy,
优先级 > providerMeta[provider].*(与既有 quotaGroup/tier 一致)javascript: / data: / file: 一律 400(XSS 边界)/status 的 providerMeta 原样回传时,normalizeConfig 自动丢弃
派生字段(observed/conflicts),无污染按需负载测试(手动触发,复用 probe 原语;默认只测 free tier,不烧付费 token):
curl --noproxy '*' -s 'http://127.0.0.1:3081/api/model-router/loadtest' # 运行快照
curl --noproxy '*' -s -X POST 'http://127.0.0.1:3081/api/model-router/loadtest' -H 'content-type: application/json' -d '{"phase":"probe"}'
curl --noproxy '*' -s -X POST 'http://127.0.0.1:3081/api/model-router/loadtest' -H 'content-type: application/json' -d '{"phase":"rpm","tiers":["free"]}'
curl --noproxy '*' -s -X DELETE 'http://127.0.0.1:3081/api/model-router/loadtest' # 中止
probe(存活/延迟)/ rpm(QPS 阶梯找 RPM 边界,触发限流自动等待恢复)/ context(多尺寸上下文接受度)/ quota-group(同组共享速率池联动)把默认规则的首选改为一个故意写错的 model id,备用为真实可用路由:
route:
- { provider: apikey-202606301659, model: wrong-model-id } # 故意错
- { provider: apikey-202608290333, model: glm-5.2 } # 真实可用
重载后正常对话。预期:
metrics.recent 出现 p=apikey-202606301659 outcome=failed errorCode=* 后紧跟 p=apikey-202608290333 outcome=committed;wrapper.failovers 计数 +1;冷却表出现错误路由记录。删掉错误 model 恢复正常配置,正常对话多轮,对比 status:wrapper.failovers 不增长、对话行为与装插件前一致。
把 fallbackPolicy.failureThreshold 调成 1,重复场景 A 三次以上,观察冷却表路由进入 open 状态、cooldownSec 后转 half-open。
agent/request-error/retry 恢复(本插件不接手)——这是设计约束(防内容拼接错乱),不是 bug。exhaustionWindowSec(默认 120s)内候选链收敛为单候选,防止与 dsh-llm-retry 叠加造成尝试次数乘法爆炸;status 的 router.converged 字段可见。firstTokenTimeoutMs(默认 30000ms = 30s)内首选未产任何分片 → 判 TIMEOUT 切换;用户主动取消不受影响。| 字段 | 默认 | 说明 |
|---|---|---|
propose | false | true 时经 agent/request 提议会话级 (provider, model) |
rules[] | [] | 自上而下首个 match 生效;match 支持 provider/model/default |
providerMeta | {} | v0.8.0 A-2:provider → {quotaGroup?, tier?, exclude?, quotaWindows?, customWindows?} 元数据(自家配置域,不跨插件读 settings);route hop 可内联覆盖 quotaGroup/tier(单条特例优先)。exclude:true(v0.9.6)= 该 provider 不再进入注册表自动展开的候选池(不否决手工 route 与当前 seed);quotaWindows {fiveHour?, weekly?} 与 customWindows [{id, windowMs, limit?, used?, resetAt?}](v0.9.5)= 窗口上限声明,limit 单位 = token |
fallbackPolicy.maxRetries | 2 | 首选之后最多切换次数(首分片前);v0.8.0 A-3:未显式声明时自动调优为 max(quotaGroupCount×2, 5),显式声明(含显式 2)一律尊重 |
fallbackPolicy.failureThreshold | 3 | 连续失败进 open 的阈值 |
fallbackPolicy.cooldownSec | 60 | open → half-open 冷却秒数 |
fallbackPolicy.quotaFailureThreshold | 1 | QUOTA(workspace 配额耗尽)触发阈值;v0.6.1 起配额型错误单独阈值 |
fallbackPolicy.quotaCooldownSec | 600 | QUOTA 冷却秒数(10 分钟);half-open 放行试探后自动回归 |
fallbackPolicy.failoverSignals | 14 个错误码 | 可触发切换的 failure.code:QUOTA QUOTA_EXCEEDED RATE_LIMIT TRANSPORT SERVER UNKNOWN INVALID_CREDENTIAL MISSING_CREDENTIAL EMPTY_RESPONSE TIMEOUT + v0.8.0 INVALID_REQUEST(DSH httpErrorCode(400, 非 quota/context) 归此类)+ v0.9.4 追加 context 类 CONTEXT_LENGTH CONTEXT_WINDOW TOO_MANY_TOKENS(累计上下文超窗 400) |
fallbackPolicy.allCooldownFallback | force-first | 候选全冷却时:force-first 强制重试首选候选 / fail 回退透传 |
fallbackPolicy.switchAfterFirstChunk | false | 硬约束:首分片之后绝不切换(commit-on-first-chunk,防内容拼接错乱) |
firstTokenTimeoutMs | 30000 | 首分片看门狗毫秒(≥1000,30s) |
failoverBudgetMs | 90000 | v0.6.1 一次请求内全部尝试(含看门狗)的总耗时上限;防挂起候选叠加拖死会话 |
exhaustionWindowSec | 120 | 链耗尽后单候选收敛时间窗 |
mode | balanced | v0.7.0 模式预设:stable / balanced / fast,覆盖 watchdog/budget/cooldown/quotaCooldown;不动规则链与 maxRetries |
registryRefreshSec | 300 | 注册表(provider/模型目录)刷新周期秒数 |
probe.enabled | false | v0.4.0 健康探测开关;默认关闭避免无授权打真实 API |
reports | {enabled:false, hour:'01:00'} | v0.8.0 G1 每日报告开关;enabled:true 后日账本记账 + 每日 hour 生成昨日报告(v0.9.5 起为 .report.md,默认目录 ~/Documents/dsh-model-router-reports/);v0.9.4 起面板「每日报告」页签可一键开启(POST /state),/status 回显 reports.enabled/reportDir/hour。可选 modelTestThreshold: {primary, backup}(v0.9.5,默认 {0.7, 0.4})控制模型测试 verdict 的 primary/backup 评分阈值,须满足 0<=backup<primary<=1 |
statusPath | /api/model-router/status | 状态接口路径 |
已实现(逐版本履历见 CHANGELOG.md):
rules 声明候选链,四种候选扩展策略(explicit / same-model / same-provider / exclude-current,源自 dsh 模型注册表)。v0.9.1 起为「纯选择驱动」——规则 = 命名「规则包」,在对话框模型选择器选中即完整接管;不选任何包 = 透传直连、插件不介入。rules 支持面板热更新(store JSON 持久化,免改 patch 免重启)providerMeta 声明窗口限额(5h / 1周 / 自定义)与重置规则;free-tier 跨请求节流(RPM 与 TPM 各自独立判定,v0.9.18/0.9.22)model-test / loadtest / probe)统一列表与详情timeWindows 按 timeZone 分段,peak / valley 各自候选链(段判定与显示统一 hourCycle:'h23')?l1=1)deploy.ps1 / undeploy.ps1(PS5.1 兼容、幂等)Host 拒绝)、runId 格式白名单 + 路径前缀双校验、notes 长度上限;/status 不含任何凭据材料state.json 版本化 + 迁移链(v1.0 起);/state 为部分更新(只覆盖请求体出现的键,v1.0.1 修复了原「整体替换导致静默擦除」缺陷)未实现(后续分期):配额剩余预算参与路由排序/排除(P3)、SQLite 聚合与评分卡(P4)、多 key 池轮换(P5)、浏览器看板(P4 可选)、前端模块化拆分(client.js 仍为单文件,v1.0 验收项 #6)、升级/回滚脚本(#3)、多 provider 段场景实机验证(#5)。
Apache-2.0