dsh-free-tier-router
DSH 插件:自带代理内核的多平台 LLM 渠道池。
把好几个平台的免费额度合成一个"看起来用不完"的池子:谁限流了就换下一个,按实测成本自动排序, 按任务自动挑渠道(要识图就用能识图的、要调工具就用支持工具的),付费渠道永不自动参与。
自包含 —— 不依赖任何外部项目目录,换台电脑解压即装。
它做什么 / 不做什么
做:
- 一个可用的渠道池 + 自动 fallback + 冷却熔断
- 按任务自动选渠道:请求带图片 → 只选能识图的;带 tools → 只选支持工具调用的;都没有 → 挑最省的
- 按实测 token 成本自动排序优先级(不是靠猜,是拿真实
total_tokens排) - 面板上一个平台一张卡:平台信息 + API 管理地址 + key 的增删改
- 显示每个渠道的状态、冷却/恢复时间、累计 token、最近错误
- 额度熔断:在额度用完之前就把渠道降级或停用 —— 软阈值降排序、硬阈值停用; 信息源优先用平台自报(响应头剩余额度 / 官方余额接口),其次才是本地累计
- 从 OpenRouter 自动发现新的零价模型
不做(这些界限是刻意的):
- ❌ 不产生额度。免费额度要你自己去各平台注册领取
- ❌ 不替你注册账号(要手机号 / 实名 / 验证码)
- ❌ 不做风控对抗(不伪造设备指纹、不做 IP 轮换、不自动注册)
- ⚠️ 不是所有平台都能查余额。目前只有 OpenRouter 有官方余额接口(插件会自动查,见
「额度熔断」);其余平台要么自己填额度数字,要么去控制台看。
但限流时不少平台会把恢复时间写在错误消息里(如火山
It will reset at ...), 插件会把它抓出来显示成「14:35 恢复」
⚠️ 同一个账号建多把 key 是共享账号额度的,多把 key 只在「不同账号」时才是独立额度。 多账号轮换免费额度普遍违反平台条款,有封号风险,后果自负。
路由规则(核心行为)
一次请求怎么选渠道:
- 能力硬过滤:请求带图片 → 只留声明了
image的渠道;带 tools → 只留声明了tools的。 (往纯文本渠道发图片,火山/通义这类会返回 200 然后瞎编,比报错危险得多。) - 跳过
manualOnly:标记为手动专用的渠道(通常是付费的)在auto模式下永不参与, 只有显式写providerId::model才会用到 —— 防止自动 fallback 撞上付费渠道产生费用。 - 额度熔断:达到硬阈值的渠道直接剔除(后面的兜底也不会把它捞回来); 达到软阈值的只排到队尾 —— 还有额度,不该浪费。
- 按 priority 升序,跳过正在冷却的。
- 失败就换下一个,并给失败的渠道设冷却。
额度熔断(额度用超之前就切走)
冷却(health.mjs)是事后的:收到 429/402 才知道这个渠道不行了。
对免费额度来说只是浪费一次请求;但按量付费渠道的欠费发生在响应返回之前 ——
事后切换救不回来。所以另做一层事前判断:
| 阈值 | 默认 | 行为 |
|---|---|---|
softRatio |
0.9 | 只把该渠道排到候选队尾:别人能用就先用别人,额度不浪费 |
hardRatio |
1.0 | 真正停用,连"全部冷却时兜底"也不选它 |
判定用的信息源(从准到糙,没有就不判定):
- 平台自报 —— 响应头
x-ratelimit-remaining-tokens(Groq / Cerebras 这类), 或配了balance: true时定时查的官方余额(目前 OpenRouter 支持); - 本地累计 —— 你填的
tokens/cost额度 ÷ 本代理记的用量; - 都没有 → 完全不干预,行为跟以前一样。
"limits": { "tokens": 1000000, "window": "90d", "softRatio": 0.9, "hardRatio": 1.0 }
只有 note 的老配置行为完全不变。完整字段见 docs/CONFIG.md。
多模型渠道(limits.perModel,如火山方舟一把 key 下几十个模型):额度按模型各自记,
一个烧完自动换下一个,同一次请求内就会连试;而且 429/404 只冷掉那一个模型,
不会因为某个模型被限流就放弃整个渠道。
⚠️ 本地累计只统计经过本代理的流量;DSH 主对话若直连上游,那部分看不到。 用量存在
~/.dsh/llm-router/usage.json,跨重启保留(否则重启一次就等于额度回满)。
自动调优优先级
面板上的「探测并优化优先级」跑完会按实测 token 开销自动重排 priority(越省越靠前):
node lib/probe.mjs --tune # 单独跑也行
排序依据是同一条探测请求的 total_tokens(同分比耗时)。manualOnly 的渠道不参与排序。
实测示例:千帆 6 token → 百炼 14 → OpenRouter 42 → 硅基 174 → 智谱(思考型,几百)。
一个渠道配多把 key(多账号)
node add-account.mjs qianfan-text "bce-v3/ALTAK-xxx/yyy"
node add-account.mjs --list # 看每个渠道有几把
配置形态:apiKeyEnvs: ["QIANFAN_API_KEY", "QIANFAN_API_KEY_2", ...]。
请求遇到 401/403/429 时,代理会在同一渠道内换下一把 key 重试。
⚠️ 同一个账号建多把 key 是共享额度的 —— 多把 key 只在「不同账号」时才有独立额度。 多账号轮换免费额度普遍违反平台条款,有封号风险,后果自负。
安装
方式一:从 GitHub 装(推荐)
dsh plugin --profile <你的profile名> add github:EiyoV/dsh-free-tier-router
装完重启 DSH,然后在「设置 → 渠道池」打开面板,或直接访问 http://127.0.0.1:19387/llm-router
方式二:用 zip 包离线装
- 拿到
dsh-free-tier-router-1.0.0.zip(在项目dist-package/下,或自己跑node dsh-plugin/pack.mjs生成) - 解压到任意目录
- 在该目录执行:
(没装 Node 也行 —— 装了 DSH 就有 Node,脚本会自动找到 DSH 安装目录和它自带的 pnpm)node install.mjs - 重启 DSH
- 打开面板填 key:http://127.0.0.1:19387/llm-router
配置和密钥住在 ~/.dsh/llm-router/,首次运行自动生成。想带着配置换机就把这个目录一起拷走。
装完在哪看
| 位置 | 内容 | 生效 |
|---|---|---|
| 设置 → 渠道池 | 独立选项面板(client 侧) | 需重启 DSH |
| 设置 → 插件 | 列表里一条 sourceType=local 的 dsh-free-tier-router,可启停 |
安装后即有 |
/llm-router |
同一份面板的直达地址 | 重启后可用 |
/api/llm-router/* |
status / catalog / action / save-key |
重启后可用 |
面板能干什么
- 看每个渠道:是否可用、冷却剩多久、请求 / 成功 / 失败次数、累计 token、最近错误
- 看每个渠道的额度用量与熔断状态(未设额度的会明确标出「未设额度 · 不熔断」,不装作有护栏)
- 一键:解除全部冷却、重置额度用量、探测渠道(真实请求)、发现新免费模型、发现并写入配置、重新加载配置
- 看「待你注册」渠道清单:注册地址、需要什么材料、我核实到的坑
- 直接粘贴 key 保存(不用手改文件)
数据目录
~/.dsh/llm-router/
config.json 渠道清单与策略(首次从内置模板生成,可自行改)
.env 各渠道密钥(首次生成带注释的模板)
catalog.json 渠道目录(注册指引 / 已核实额度 / 坑)
usage.json 各渠道累计用量(额度熔断;跨重启保留,可手动删掉重置)
logs/ 预留
config.json 里的能力声明(capabilities)很关键:往纯文本渠道发图片,火山/通义这类会返回 200 然后开始瞎编,比报错危险得多。所以请求带图时只会选声明了 image 的渠道。
端口冲突
代理默认监听 127.0.0.1:8787。如果该端口已经有另一个 llm-router 在跑(比如独立项目的 start.cmd),插件会复用它而不是重复启动。要改端口就改 cordis.patch.yml 里的 port,或 config.json 里的 server.port。
目录结构
package.json 声明 dsh.bundle.patch(自动挂载)与 dsh.client(web 平台)
cordis.patch.yml bundle 层:insert 自己
dist/index.js Host:启动内嵌代理 + 注册路由 + 面板
dist/client.js client:IIFE + __ModuleLoader__.load,注册 settings.section
dist/panel.html 面板本体(纯 HTML/CSS/JS,无构建)
lib/ 代理内核(config / health / pool / server / discover / probe / paths)
lib/default-config.json 内置默认渠道配置
test/smoke.mjs 冒烟测试(不需要 key)
install.mjs 安装脚本
pack.mjs 打 zip
migrate-env.mjs 把项目 .env 的 key 迁进数据目录(本机一次性用)
dist/*.js 和 lib/*.mjs 都是手写纯 JS,没有 TypeScript、没有打包步骤 —— 改完跑 install.mjs 即可。
开发
node dsh-plugin\test\smoke.mjs # 冒烟测试(不需 key,会初始化数据目录)
node dsh-plugin\install.mjs # 同步到 profile(会先清掉旧副本)
node dsh-plugin\pack.mjs # 重新打 zip
node test\verify-package.mjs # 解压到临时目录验证包是自包含的
改 Host 侧(dist/index.js、lib/)→ 跑 install.mjs + 重启 DSH。
改 client 侧(dist/client.js)→ 必须重启 DSH(index 注入在 web server 启动时生成)。
注意
cordis.patch.yml里的路径用正斜杠:YAML 双引号里的反斜杠是转义符,中文路径必踩。package.json的name必须与 patch 里的name完全一致。file:依赖会被 pnpm 按files字段打包复制 —— 改了files或加了新目录(如lib/)后, 必须先删掉node_modules/dsh-free-tier-router再装,否则 pnpm 认为"无事可做"(实测输出Packages: -10), 新文件不会同步过去。install.mjs已经自动处理这一步。- 用 Node 改 JSON,不要用 PowerShell
Set-Content:这台机器的pwsh是 Windows PowerShell 5.1,写无 BOM 的 UTF-8 会出问题。 - 安装必须用 DSH 自带的 pnpm:
dsh plugin --profile desktop ...会被 CLI 硬拒绝(源码写死 desktop 由 Electron 独占管理)。
No comments yet. Be the first to write one.