@leaf233/dsh-model-gateway

modelnpmApache-2.0

Exposes the models already configured in DeepSeek Harness through an OpenAI-compatible gateway, so other local projects can call them with any standard OpenAI client. Multiple provider candidates share one named group with ordered, balanced or random dispatch; upstream failures before any output swi

下载量 / Downloads
695

Install

dsh plugin --profile web add @leaf233/dsh-model-gateway

Sandbox test

This plugin has not been tested yet. Submit a test →

README

模型中转站 (@leaf233/dsh-model-gateway)

给 DeepSeek Harness 挂一个 OpenAI 兼容的 /v1 接口,把 harness 里已注册的大模型(比如 @tnnevol/dsh-codebuddy 里的 CodeBuddy 模型)反代出去,其他项目用标准 OpenAI 客户端就能调用。

在 设置 → 模型中转站 里可以看到所有接口地址、分组、创建和管理 API 密钥、开关鉴权、查看可用模型。

为什么不是"再登一次"

这个插件不碰任何凭据。它自己不会读写token等,而是把请求交给 DSH 的 llm 服务转发——也就是直接复用已注册的 provider 适配器。

这是因为一些事实性存在的问题:CodeBuddy 的 refresh token 会轮换,而且 @tnnevol/dsh-codebuddy 内部用单飞(single-flight) 保证并发刷新只发生一次。如果本插件自己再建一个 session,两次刷新会互相作废,结果是 Web 界面里的登录被踢掉。走 ctx.llm 就不可能出现这种情况。

由此还顺带复用了上游插件的账号故障转移、额度感知、图片序列化和模型目录。

本仓库是 qilin-zhu/dsh-model-relay 的 fork,包名 @leaf233/dsh-model-gateway。上游 npm 包 dsh-model-relay(0.1.1)与本 fork 无关、也不兼容 0.1.2-rc.1,见下文「安装」。

DSH 版本支持

0.1.2-rc.1、0.1.7-alpha.1+ 与 0.2.0-rc.1+ 为本 fork 新增的支持范围,0.1.2-rc.1 需要下面三处修复;0.1.1 及更早不在声明范围内。0.2.0 上的设置页图标做了跨版本回退,0.1.5–0.2.0 均可正常渲染

本仓库的 package.json 声明:

"engines": { "dsh": "0.1.2-rc.1 || ^0.1.5-rc.2 || ^0.1.6-alpha.1 || ^0.1.7-alpha.1 || ^0.2.0-rc.1" }

六个 @deepseek-ai/* peer 依赖同样带上这五段范围。

0.1.2-rc.1 上必须的三处修复

上游 0.1.1 的代码在 0.1.2-rc.1 上装得上但跑不起来,本 fork 修的正是这三处:

1. 系统提示词:0.1.2-rc.1 的 @deepseek-ai/dsh-llm 不再导出 createSystemMessage,MessageSourceMap 里也没有 system。原代码的顶层命名导入会直接抛 SyntaxError,插件完全加载不了。正确形状是请求级的 options.system(GenerateOptions.system),适配器会把它映射到 provider 自己的 system slot。

2. Tag 组件:dsh-client-ui-primitives 在 0.1.2-rc.1 里不导出 Tag,primitives.Tag 是 undefined,渲染设置页时抛错、整个区块挂不上。改用实际存在的 Pill(注意它接的是 active,不是 tone)。

3. connection patch:见上文「配置」里的说明,已删除。

compatibility.json 里的 dshPluginApi.version 也改了,但那是惰性元数据——DSH 运行时不读这个字段,真正起作用的是 package.json 的 engines.dsh 和 peer 范围。

安装

命令行安装:

# 从 npm 安装
dsh plugin --profile web add @leaf233/dsh-model-gateway

# 本地开发(file: 链接)
dsh plugin --profile web add /绝对路径/relay-upstream

装完重启 Web profile,服务地址是:

http://127.0.0.1:3080/v1
这个 fork 用 @leaf233 作用域名,不是上游的 dsh-model-relay。
npm 上那个不带 scope 的 dsh-model-relay 属于上游作者(maintainer jarvistop,仓库 qilin-zhu/dsh-model-relay),本 fork 无权发布它,而且它在 0.1.2-rc.1 上会直接崩(详见下文「DSH 版本支持」)。
所以如果需要版本支持或上游不一样的功能,需要使用 @leaf233/dsh-model-gateway(本 fork)。
注意区分三个名字
| 名字 | 值 | 用途 |
|---|---|---|
| 包名 | @leaf233/dsh-model-gateway | npm 安装、bundles 列表 |
| patch 行的 id | dsh-model-gateway | 你在自己 profile 里覆盖配置时的锚点 |
| provider id | dsh-model-gateway | DSH 模型选择器里的分组命名空间(<provider>_<模型>) |
只有包名带 scope。id 和 provider id 保持不带 scope,所以已有的配置覆盖和分组名不受影响。

用法

任意 OpenAI 客户端都可以,base_url 指向上面的地址。

鉴权是可选的:还没创建密钥、也没在配置里写固定密钥时,接口对所有人开放(随便填一个 sk-xxx 或留空都行)。创建第一个密钥后自动开始校验。

from openai import OpenAI

client = OpenAI(base_url="http://127.0.0.1:3080/v1", api_key="sk-dshgw-...")

resp = client.chat.completions.create(
    model="codebuddy_deepseek-v4.1-flash",
    messages=[{"role": "user", "content": "你好"}],
)
print(resp.choices[0].message.content)
curl http://127.0.0.1:3080/v1/models

curl http://127.0.0.1:3080/v1/chat/completions \
  -H 'content-type: application/json' \
  -d '{"model":"codebuddy_deepseek-v4.1-flash","messages":[{"role":"user","content":"你好"}]}'

支持的接口:

方法 · 路径 · 说明

GET · /v1/models · 列出所有已注册 provider 的模型,以及你的模型分组

POST · /v1/chat/completions · 对话,支持 stream: true

请求体与 OpenAI Chat Completions 一致:model、messages、stream、tools、tool_choice、temperature、max_tokens、stop、reasoning_effort。支持 role: "tool" 的工具结果回传、assistant 的 tool_calls 回放、以及 image_url 图片输入(data URL 或远程 URL)。

流式响应是标准 SSE,以 data: [DONE] 结束。

模型分组

分组 = 一组模型的候选列表 + 一套调度方式。 给这组模型起个名字,之后用这个名字调用,网关按调度方式决定先试谁,第一个能应答的就被采用。

默认就是"按顺序逐个尝试",也就是"额度轮换"最直接的用法:把同一个模型在不同供应商上的实例排成一组,一个用不了了自动落到下一个。

# 直接调用分组名
resp = client.chat.completions.create(
    model="fast-chat",
    messages=[{"role": "user", "content": "你好"}],
)

在 设置 → 模型中转站 → 模型分组 里创建和管理,用 ↑ ↓ 调候选顺序。

调度方式

设置页提供四个预设:

预设 · 行为

顺序(默认) · 按候选列表从上到下依次尝试,遇到不可用就顺延。

均衡 · 每次请求轮换起点,把负载摊到所有候选上。

随机 · 每次请求打乱顺序。

顺序 + 限流重试 · 按顺序尝试;某条腿遇到限流(429)时在同一条腿上重试若干次,再顺延到下一个。

存储上其实是两个正交的字段:strategy(sequential / round-robin / random)决定顺序,retry429(0–3)决定同一条腿重试几次。上面四个预设是这两者里最常用的四个组合,不是全部——存储本身能表达 12 种,比如"均衡 + 重试"(round-robin + retry429: 1)可以直接写进 model-relay-groups.json,只是设置页暂时没有对应的按钮。分成两个字段而不是一个枚举,就是为了让这些组合不必为每种顺序各复制一份重试逻辑。

均衡和随机都是完整排列,不是子集。 被排到后面的候选仍然是回退链的一部分,所以任何调度方式都不会缩短分组的兜底能力。

⚠️ 均衡和随机会牺牲会话粘性。 顺序模式下同一个会话始终落在同一条腿上,上游的 prompt cache 因此命中;换成均衡或随机后,多轮对话会在供应商之间跳,prompt cache 基本失效,成本和首字延迟都会上升。另外不同供应商的 tokenizer 不同,同一段文本的 token 计数会漂移,usage 对账会对不上。并非bug。

在 DSH 本体里也能用

本插件会把自己注册成一个名为 dsh-model-gateway 的 DSH provider,它的"模型目录"就是你的分组列表。打开 设置 → 模型,在 dsh-model-gateway 下面就能像选普通模型一样选到分组名。

分组名同样出现在 /v1/models 里,所以外部客户端和 DSH 两边都能用。

DSH 的模型选择器是全局的——注册之后,所有会话都能选到这些分组。
底层模型仍然可以直接调用(codebuddy_deepseek-v4-flash 这种全名照常可用)。想让外部只看到分组,把 providers 配成 [dsh-model-gateway] 即可——但那是对外的过滤,不影响 DSH 界面里能看到什么。

回退只在"还没有任何输出"之前发生

唯一需要理解的行为约束:

  • 上游还没吐出任何内容就失败(连不上、凭证失效、上游报错)→ 自动换下一个候选,客户端完全察觉不到。
  • 上游已经吐出内容之后再断 → 不换,流被截断。

第二条不是bug:已经吐出去的内容客户端已经收到了,此时换腿会造成重复输出。所以分组能兜住"这个供应商现在用不了",但兜不住"响应写到一半断了"。

"吐出内容"的判定见上面的排查小节——只有 usage 分片不算。

限流重试(retry429)

开启后,某条腿遇到限流会在同一条腿上重试,而不是立刻换下一条腿。三条约束:

  • 只重试限流。 判据是失败码 RATE_LIMIT,不是 HTTP 状态。因为 statusForCode 把 RATE_LIMIT、QUOTA、QUOTA_EXCEEDED 全都映射成 429,但额度用尽重试也没用——再问一次还是同一个答案,只是白花调用方的时间

…(truncated)

Rendered from the README published on npm. Formatted only, not edited.

Versions

Published on npm::5,最后更新 2026-11-04

Links

GitHub · Homepage · npm

Same category