dsh-adaptive-model-router
alaxrpg/dsh-adaptive-model-router
Adaptive model discovery, evaluation, tiering, and subagent routing for DeepSeek Harness.
설치
dsh plugin --profile web add github:alaxrpg/dsh-adaptive-model-routerREADME
DSH Adaptive Model Router
dsh-adaptive-model-router 是 DeepSeek Harness 的 Cordis/Loader 插件。它发现多个 Provider 的模型,综合元数据、外部评价和可选的受限真实探测生成评分,把候选模型分入 easy、normal、hard,再由 adaptive_subagent 为每次子任务选择模型。
这不是 Codex plugin。项目没有 .codex-plugin/plugin.json、Codex marketplace 条目、Codex skill 或 Codex hook,也不使用 plugin-creator。
设计边界
- 模型发现优先使用用户定义的模型清单:插件 Provider 配置中的
models显式列表 > DSH settings 中该 Provider 路由(如llm-pi-ai.providers.<route>.models)用户定义的模型;两者都没有时才回退端点发现(ctx.llm.discoverModels(...),再退 OpenAI-compatible/models)。 - 端点
/models只提供目录,不能证明能力;只有用户未定义任何模型清单时它才作为候选来源。 - 未知或不能可靠映射的模型保持
unrated,不会按模型名称猜测能力。 - 人工配置始终优先于自动策略;刷新只写插件数据目录,不反写
settings.yaml。 - 真实探测默认开启,并受到模型数、请求数、Token、超时和月预算硬限制;未知模型价格按
estimatedCostUsd: 0.01USD/请求预留。 - 刷新失败保留 last-known-good;新策略只影响之后启动的子代理。
- 默认拒绝公网评价源或 Provider URL 跳转到 loopback、私网、链路本地和云元数据地址。需要本机 Provider 时必须显式允许。
运行要求
- Node.js 20 或更高版本。
- 已能启动的 DeepSeek Harness profile。
- Harness 组合中已有
llm、tools、subagents以及对应的子代理 Provider。 - Provider 凭据继续由 DSH settings/credentials 服务管理;不要把 API Key 写进本插件配置。
安装与接入
开发目录中安装依赖、运行测试并生成 npm tarball:
npm install
npm test
npm pack
在 DSH profile 项目目录中安装刚生成的 tarball(路径按实际位置替换):
cd "$DSH_PROFILE_DIR"
npm install /absolute/path/to/dsh-adaptive-model-router-0.1.2.tgz
node -e "import('dsh-adaptive-model-router').then(m => console.log(m.name, typeof m.apply))"
最后一条命令应输出 adaptive-model-router function,说明 Loader 所用的包名可被 Node 正常 import。
从 GitHub 安装
也可以直接从 GitHub 安装(推荐固定到 tag,保证可复现):
dsh plugin --profile web add github:alaxrpg/dsh-adaptive-model-router#v0.2.3
不固定 ref(github:alaxrpg/dsh-adaptive-model-router)时拉取默认分支最新提交。peerDependencies 使用 >=0.1.1-rc.2 下界宽范围:独立安装时 pnpm 自动解析到最新可用版本;未来 @deepseek-ai 正式版发布后 pnpm update 即可跟进,无需再整组修改插件。
把插件 patch 作为当前 profile 的最后一层 overlay 合并。参考 examples/cordis.patch.yml:
- insert:
- id: adaptive-model-router
name: 'dsh-adaptive-model-router'
config:
dataDir: !!js dshHomePath('adaptive-model-router')
Cordis 通过命名导出的 apply(ctx, config) 加载插件。插件依赖相关 Harness 服务,并通过 ctx.tools.register(...) 注册 adaptive_subagent;卸载 Cordis fiber 时,工具注册、定时器和在途请求都会释放。
config.dataDir 表示插件的最终数据目录,不是共享根目录;因此上述 dshHomePath('adaptive-model-router') 实际写入 $DSH_HOME/adaptive-model-router/,不会再追加一层同名目录。未显式配置时,DSH_DATA_DIR 仍按 DSH 共享数据根目录处理。
不要直接用示例覆盖 $DSH_HOME/settings.yaml。请只把 examples/settings.yaml 中的 adaptive-model-router: 节合并到已有设置;首次接入前先保留原文件副本。
配置
可视化配置
在 DSH Web profile 中,插件在官方「设置 → 插件配置」页注册一张可折叠卡片——不再提供独立配置页,全部配置都在这张卡片内完成:
- 常用区直接可见:启用状态、难度模式、默认档位、Provider 列表(启用开关、档位模式与模型、锁定、回退模型、别名、新增/删除)。
- 刷新周期、路由回退、全局档位、分档阈值、评价权重、真实模型评价、外部评价源和 Provider discovery 非敏感连接参数等收在「高级设置」折叠区,默认隐藏,点击展开。
- 卡片底部提供「手动刷新评分」按钮:立即执行一次完整的发现 → 评分 → 分档流程(等同一次刷新任务,不受
refresh.enabled开关影响),完成后用最新评分与自动档位刷新显示。手动刷新与周期刷新共用refresh.timeoutSeconds超时,进行中的请求会去重复用,插件禁用或无已启用 Provider 时返回明确错误而不动缓存。
卡片通过插件自己的同源 API 读写:GET/PUT /api/adaptive-model-router/config 与 POST /api/adaptive-model-router/refresh。保存时卡片回传 API 返回的 revision,并依据 API 返回的 editable 状态启用编辑;过期 revision 会被拒绝(409)。提交后通过运行时 settings 热重载路由。
安全边界:
- 卡片和 API 只由当前 DSH 同源提供;修改请求必须使用 loopback Host(
127.0.0.1、localhost或[::1])、匹配的 Origin 和application/json,且请求体有大小上限。 apiKey、外部源headers以及内部toolName、subagentProvider、dataDir不会发送到浏览器,API 也拒绝写入这些字段;discovery 的settingsNs、provider、Base URL、API、地址策略和响应大小等非敏感字段可在卡片编辑,凭据只显示配置状态。- Provider 删除按层区分:settings.yaml 用户层中的 Provider 会被真正删除(
unset);由 profile 组合基座(cordis.patch.yml的插件config)定义的 Provider 无法通过卡片删除——删除请求会改写为enabled: false的用户层覆盖,避免“删不掉又悄悄复活”的假象,真正移除需修改基座配置。 - 悬挂或缓慢的请求体读取有超时上限;非预期错误只返回通用提示,不回显内部路径。
evaluation.probes的启用状态、单次运行上限、token/超时、月预算和未知价格预留均可在卡片编辑;默认开启,实际调用仍受月预算和单次运行上限保护。- CLI profile 没有
webServer时只不注册 API,路由工具和 settings 热重载不受影响。
DSH 0.1.0-rc.6 的内置“插件配置”页面并不会根据任意第三方 Schemastery schema 自动生成表单,Host API proxy 也对可从 Web 访问的 settings namespace 使用显式白名单。因此本插件通过官方 slots.inject('settings.plugin.item') 扩展点把自带 React 卡片注入设置页,并用自身 webServer 路由提供同源 API,不修改 DSH core 或安装目录。参见 DeepSeek Harness Architecture 和 Extension Cookbook。DSH 当前仍是 developer preview,升级后应重新验证此集成边界。
官方设置面板“插件配置”卡片
自 DSH 0.1.0-rc.7 起,Host API 已全量暴露第三方 settings 命名空间,官方设置面板新增“插件配置”标签页,通过 settings.plugin.item 槽以命名空间为键配对浏览器卡片。本插件随包提供浏览器半身(package.json 的 dsh.client 声明 + exports["./client"]),因此在 DSH Web profile 的 设置 → 插件 → 插件配置 中会显示“自适应模型路由”卡片,可编辑:启用开关、难度模式、默认档位,以及每个 Provider 的启用与 easy/normal/hard 三档模型(下拉含已发现模型,未指定时标注当前自动档位模型)。保存走 revision-fenced 的嵌套路径 mutate,与独立页面同一套写语义。
浏览器半身源码在 src-client/,构建产物为 lib/client.js(lazy-CJS factory 格式)。修改源码后需重新构建:
npm run build:client
构建依赖(esbuild)与宿主提供的 client 服务声明在 package.json 的 dsh.client.inject。插件集变更需重启 DSH 生效。
最小配置:
adaptive-model-router:
enabled: true
# providers 省略时默认启用 llm-pi-ai 的四个 Provider:
# opencode-go、qwen-token-plan-cn、zai-coding-cn、sensennova。
# 只有显式写 providers: {} 才表示不配置任何 Provider。
完整默认值和单 Provider 人工三档配置见 examples/settings.yaml。关键配置如下:
| 配置 | 默认值 | 说明 |
|---|---|---|
difficulty.defaultTier | normal | 自动难度无法可靠判断时使用 |
refresh.runOnStart | true | 启动后执行一次刷新 |
refresh.intervalMinutes | 360 | 周期刷新间隔 |
refresh.jitterMinutes | 15 | 最多附加的随机抖动 |
refresh.staleAfterHours | 72 | 自动策略过期界线 |
routing.unavailableAction | fallback | 显式 Provider/模型不可用时回退并记录原因,或用 error 直接报错 |
evaluation.minimumConfidence | 0.50 | 自动路由最低置信度;按评价源声明的维度计算覆盖率,LiveBench + 元数据通常约 0.87 |
evaluation.probes.enabled | true | 是否允许真实调用探测 |
evaluation.probes.estimatedCostUsd | 0.01 | 模型价格未知时,每个 probe 请求预留的最坏费用(USD) |
classification.strategy | thresholds | thresholds 或 Provider 内 percentile |
routing.staleAction | last-known-good | 策略过期后的动作 |
每个 Provider 的 tiers.easy/normal/hard 支持:
mode: auto:选择最新、未过期、置信度合格的自动评价结果。mode: prefer:优先指定model,不可用时回到自动选择。mode: manual:固定指定model。locked: true:周期刷新不能替换该档的人工结果。
自动置信度表示“已声明评价维度的证据可信度”,不是模型质量分数。LiveBench 只声明
coding/reasoning,Provider 元数据声明 context;缺失的 toolUse、延迟和价格不会再被
重复当作该评价源的缺失证据。Provider 只有一个或一组窄分数时,空的 easy/hard 档会使用
同一 Provider 内最近的已评价模型作为 automaticFallback;没有发现模型或没有有效证据的
Provider 不会被伪造为可用,界面应显示其凭据/发现状态。
同一模型可人工配置到全部三个档位;只有一个可用模型时无需伪造三个不同候选。
adaptive_subagent 工具
模型面对的调用参数:
{
"description": "分析认证模块",
"prompt": "检查调用链并提出修改方案",
"difficulty": "auto",
"provider": "auto"
}
description和prompt必填。difficulty可为auto | easy | normal | hard,默认auto。provider默认为auto,也可指定已启用 Provider。model可选;与provider一起作为本次调用的最高优先级覆盖。
路由元数据至少包含:
{
"difficulty": "hard",
"provider": "opencode-go",
"model": "deepseek-v4-flash",
"routeSource": "provider-manual-locked",
"policyVersion": "2026-08-14T16:00:00+08:00",
"score": 84.5,
"confidence": 0.78,
"stale": false,
"fallbackReason": null
}
Provider 和模型名称仅作为用户配置示例出现;源码默认值不会固定到该路由。
路由优先级
每次调用严格按以下顺序解析:
- 调用显式指定的
provider/model。 - Provider 当前档位的
manual + locked。 - 全局当前档位的人工锁定。
- Provider 当前档位的
prefer。 - Provider 最新有效自动结果。
- 跨 Provider 全局自动结果。
- last-known-good。
- Provider fallback 或显式 fallback。
- 返回清晰错误。
指定模型不存在、被禁用或不支持目标子代理 Provider 时不会被静默改写;是否回退由 routing.unavailableAction 控制,并在结果的 fallbackReason 中说明。调用显式指定 Provider 时,所有候选、last-known-good 和 fallback 都必须属于该 Provider;只有 provider: auto 可以使用全局或跨 Provider fallback。
当 policy 过期时,routing.staleAction: last-known-good 跳过过期自动结果并优先使用 last-known-good;fallback 同时跳过过期自动结果和 last-known-good,直接进入配置 fallback;error 则返回明确的 policy stale 错误。
数据文件与刷新
默认在 $DSH_HOME/adaptive-model-router/ 保存:
catalog.json
evaluations.json
policy.json
probe-budget.json
每个文件带 schemaVersion 与 generatedAt。保存过程先写同目录临时文件,校验后原子替换;文件损坏或 schema 不兼容时保留可读错误并尝试 last-known-good,不会清空人工设置。
刷新任务启动时运行一次,之后默认每 6 小时运行,附加最多 15 分钟抖动(仅当 refresh.enabled 为 true 时调度;runOnStart 控制启动时是否立即执行一次)。同一插件实例只允许一个刷新运行;失败按有界退避重试,卸载时取消 timer 与 AbortController。此外,配置页的「手动刷新评分」随时可触发一次同步,用于 refresh.enabled: false 或调整权重/阈值后立即重算评分分配。
外部评价源和 SSRF 防护
evaluation.externalSources 必须显式列出 URL。插件会限制协议、超时、响应体大小并校验 JSON schema;重定向后的每个地址仍需通过网络地址检查,且默认拒绝跨 origin 重定向。若显式允许跨 origin,插件会在下一跳剥离 Authorization、Cookie 和 API Key 类敏感头。默认拒绝:
localhost、loopback;- RFC 1918 私网、链路本地;
- IPv6 loopback、ULA、链路本地和 IPv4-mapped 私网地址;
- 常见云元数据终端地址。
日志会脱敏 Authorization、API Key、token 和凭据查询参数。若 Provider 确实运行在 127.0.0.1,仅在该 Provider 配置上显式启用本地地址,不要对所有外部源解除限制。
域名会在发起每一跳前解析并检查全部返回地址,但 Node 内置 fetch 不提供把已检查 IP 固定到连接的可移植接口,因此无法完全消除“检查后、连接前”的 DNS rebinding 时间窗。高风险部署应在出站代理/防火墙层再次禁止私网和元数据地址。
真实 probe 的月预算写入 probe-budget.json,按 UTC 年月累计并跨刷新、进程重启保留;新月份自动从零开始。每个请求在发送前先持久化预留最坏费用。模型未同时声明输入/输出单价时,使用 estimatedCostUsd(默认 0.01 USD/请求);用户可按实际价格显式调整。默认外部评价源使用 LiveBench 官方公开排行榜 CSV(https://raw.githubusercontent.com/LiveBench/new-livebench/main/public/table_2026_06_25.csv),卡片中可替换或删除;插件不会把凭据或敏感 headers 写入配置文件。
测试
测试使用 Node 内置 node:test,不会访问真实 Provider、不会读取真实凭据,也不会产生付费请求:
npm test
覆盖范围包括 Harness/OpenAI 两种 discovery、异常响应和超时、别名、评分与置信度、阈值/百分位分档、人工优先级、last-known-good、调度防重入与销毁、原子存储,以及 adaptive_subagent 的路由和资源释放。真实 Harness profile、网络 Provider、外部评价源和付费 probe 属于独立冒烟/集成验收边界。
本机冒烟验证
独立测试通过后,再合并 settings 与 patch 并启动当前 DSH web profile。建议验证:
- Loader 能加载插件且工具列表出现
adaptive_subagent。 - 手动把一个现有模型配置到三个档位,三次显式难度调用均选择该模型。
- 禁用或写错
prefer模型时,结果带回退原因且命中有效自动/last-known-good。 - 卸载插件后工具消失,数据文件保持有效,进程中没有遗留刷新定时器或子代理。
不要为冒烟恢复已停用 Provider,不要修改现有 Provider 定义,也不要保留旧子代理线程。
License
MIT,见 LICENSE。