dsh-llm-gateway
把 DSH 已配置的模型共享给本机程序——无需重复配置,用 OpenAI 兼容接口直接调用。
Python、Node、curl、Notebook 都能直接用,不需要额外的 SDK 适配层;模型目录从你的 DSH 配置里自动发现。
特性
- OpenAI 兼容:
/v1/models、/v1/chat/completions(流式与非流式)、/healthz - 真实路由:模型目录从 DSH 的
llm服务自动发现;请求里的model决定实际调哪个 provider,不是被忽略后照常回答 - 工具调用:
tools完整转发,tool_calls按 OpenAI 约定分片返回;role:'tool'回执与assistant.tool_calls历史都能正确落回 DSH 的消息形状 - 图片输入:
image_url支持 data URL 与 http(s) 链接,图片经 DSH 的 attachments 服务落盘后交给模型;/v1/models里input_modalities含image的模型可直接用 - thinking 双向:非流式响应带
reasoning_content,请求也可回传它——DeepSeek 系 thinking 模型的多轮工具对话靠这个才续得上 - usage 带明细:除三个基本数字外,还给出
prompt_tokens_details.cached_tokens与completion_tokens_details.reasoning_tokens - 没有去路的参数会报错:DSH 的调用配置只承载
temperature/max_tokens/reasoning_effort,其余(top_p、seed、response_format等)一律400说清,不静默忽略;stop会被透传,但当前 DSH 的适配器不吃它(见「已知边界」) - 未知模型会报错:打错一个字母得到的是
404 model_not_found,不会静默换成另一个模型的答案 - 歧义会报错:某个裸 model id 同时属于多个 provider 时返回
400 ambiguous_model,提示改用provider/model - 错误码有据可依:按 DSH 的稳定 code 映射状态(401 / 429 / 400 / 404),
Retry-After一并透传 - 可选鉴权:配置
apiKeys后校验Authorization: Bearer或x-api-key(sha256 + 定长比较) - 可在界面配置:宿主侧声明了 Config schema(每个字段都必须带
.volatile()——dsh-settings的volatileForm()会把「一个 volatile 字段都没有」的条目整条丢掉,配置区也就不会出现),浏览器半侧把配置表单注册进 Plugins 页,端口与默认模型都能在界面上改,改端口不需要重启 DSH - 零运行时依赖:只用 Node 内置模块(另加 DSH 自身提供的 peer
@deepseek-ai/schemastery),不需要npm install
快速开始
安装
本插件必须作为 profile 依赖安装:dependencies 交给 pnpm 解析,dsh.profile.bundles 交给 loader 组合,两处都要登记。
只把目录丢进 <profile>/node_modules/、不写 dependencies 的「游离安装」不受支持,后果是:
- DSH 插件页看不到它 —— 那里的
installed由 profile 的dependencies决定,启停、版本、卸载都无从操作; - 插件页那张包卡片的详情页里不会出现「配置」块,端口 / 默认模型这些参数只能改配置文件;
/healthz会返回installation.declared: false,宿主日志同时打一条「游离安装」警告。
建议安装方式:打包安装(tarball)—— profile 与源码目录解耦,内容被复制进 profile,
Node 的依赖解析基准留在 profile 的 node_modules 内,不依赖符号链接。
bump
package.json的version,然后打包并登记:powershell -NoProfile -ExecutionPolicy Bypass -File .\build-and-pack.ps1脚本做三件事:
pnpm pack出<name>-<version>.tgz、把它复制进 profile、 把 profile 的dependencies.dsh-llm-gateway改写成file:<name>-<version>.tgz。在 profile 目录里安装:
pnpm install重启 DSH —— bundle 清单变更必须重启(
patchReload: live只管已加载插件的 config)。 改动插件代码后同样要重启:重装只换磁盘上的文件,跑着的进程不会把已载入的模块换掉; Windows 上重装前最好先卸载,否则 pnpm 会撞EPERM(旧目录还被进程占着,改不了名)。
别在 profile 的
cordis.patch.yml里再写一条id: llm-gateway的insert—— 会和包内 patch 撞 id。 参数一律在设置页改:写在组合层(patch 里的 config override)的值会让设置页里那个字段显示成 「已覆盖」,反而挡住正常改法。
验证
curl http://127.0.0.1:8790/healthz能通就说明起来了(宿主日志落不落盘取决于部署, 别把日志当验收依据):ok: truecurl http://127.0.0.1:8790/healthz→catalog.models应等于你在 profile 里配的模型总数- 非流式与流式各调一次,
finish_reason应是字符串 - 发一个不存在的 model → 应返回 404(不是 200)
- 带
tools发一次 → 应返回finish_reason: "tool_calls",且message.tool_calls非空
调用
from openai import OpenAI
c = OpenAI(base_url="http://127.0.0.1:8790/v1", api_key="any") # 未配 apiKeys 时不校验
print([m.id for m in c.models.list()])
print(c.chat.completions.create(
model="provider/model",
messages=[{"role": "user", "content": "ping"}],
).choices[0].message.content)
curl http://127.0.0.1:8790/v1/chat/completions \
-H 'Content-Type: application/json' \
-d '{"model":"provider/model","messages":[{"role":"user","content":"ping"}]}'
接口
| 方法 | 路径 | 说明 |
|---|---|---|
| GET | /v1/models |
全部 provider/model;某个裸 model id 只属于一个 provider 时,同时以裸 id 列出 |
| GET | /healthz |
网关状态 + 目录统计(provider 数 / 模型数 / 别名数 / 目录年龄)+ installation(profile 声明的 spec、是否登记在 dependencies 与 dsh.profile.bundles 里) |
| POST | /v1/chat/completions |
OpenAI 兼容,stream 真/假都支持 |
/models、/chat/completions、/v1/healthz 是等价别名。
请求体支持的参数:model、messages、stream、max_tokens(或新名 max_completion_tokens;
超出 200000 会被截断到 200000,不是报错)、temperature、stop(最多 4 条,见下面的警告)、
tools、reasoning_effort。
⚠️
stop在当前 DSH 上不可用:网关会把它原样透传,但 DSH 的适配器不支持GenerateOptions.stop,带stop的请求会以502 UNSUPPORTED_OPTION失败 (上游原话:llm-pi-ai does not support GenerateOptions.stop;opencode-go 与 niko-api 实测一致, 不带stop的同请求返回 200)。需要截断请用提示词自行约束。
不在这份名单里的一律 400 unsupported_parameter。 例外只有 n=1、stream_options、user 和
tool_choice:"auto" —— 这几个等于不表态,却几乎每个 SDK 都会默认带上,拒绝等于自断门路。
messages[].content 可以是字符串,也可以是 part 数组:
| part | 说明 |
|---|---|
{type:'text', text} |
文本 |
{type:'image_url', image_url:{url}} |
图片;url 支持 data:image/png;base64,… 与 http(s)://(后者由网关代下载) |
消息角色除 user / assistant / system / developer 外,还支持工具对话的两个形状:
{role:'tool', tool_call_id, content}—— 工具回执{role:'assistant', tool_calls:[…], reasoning_content}—— 带工具调用或思考过程的历史
system / developer 消息会合并成 DSH 的 options.system。
model 解析顺序:规范 id provider/model → 唯一的裸 model id → 请求缺 model 时用 defaultModel。
配置
| 键 | 默认 | 说明 |
|---|---|---|
host |
127.0.0.1 |
别改成 0.0.0.0,除非同时配 apiKeys |
port |
8790 |
监听端口;改动会重建 HTTP 服务 |
defaultModel |
(空) | 请求没带 model 时使用;留空则强制显式指定 |
apiKeys |
[] |
非空则校验 Authorization: Bearer 或 x-api-key(定长比较) |
excludeProviders |
[] |
从目录剔除的 provider id |
excludeModels |
[] |
从目录剔除的 model id 或 provider/model |
catalogTtlMs |
30000 |
目录缓存时长;未命中会强制刷新一次 |
reasoningAsContent |
true |
上游只出 reasoning 不出 text 时,把 reasoning 兜底当 content |
requestLog |
true |
每个请求打一行 方法 路径 -> 状态 (耗时) |
maxBodyBytes |
8388608 |
请求体上限 |
已知边界
- 默认无鉴权:只绑回环所以安全;一旦把
host改到局域网或公网,必须同时配apiKeys。 - 图片要靠 attachments 服务:DSH 没挂载 attachment provider 时返回
501。图片的类型、张数、 总字节由那个 provider 的策略决定,它拒绝时网关把原话(如INVALID_IMAGE)直接透传,不替它改口。 - 音频、视频不支持:
input_audio之类的 part 返回400 unsupported_content。 - 采样参数没有去路:DSH 的调用配置只有六个字段,
top_p/seed/response_format/logprobs/ penalties 等都返回400—— 宁可报错,也不静默忽略。 stop当前不可用:它会被原样透传给 DSH,但适配器不支持GenerateOptions.stop,带stop的请求 以502 UNSUPPORTED_OPTION失败 —— 这一层上游不支持,不是网关的取舍;同请求去掉stop即 200。max_tokens是软上限:超过 200000 不报错,而是被截断到 200000(DSH 的调用配置就收这个数)。- thinking 模型要多传一个字段:多轮请求必须把上一轮的
reasoning_content带回,否则上游会以400拒绝。这是模型的要求,不是网关的。 reasoning_tokens未必有:字段实现了,但取不取得到取决于上游是否上报;成文时测到的 provider 都没给这个数。- 单轮:每次请求独立调用
llm.stream,不维护服务端会话;多轮靠客户端把历史塞回messages。 - 目录是快照:
catalogTtlMs到期或 id 未命中时重建,新增模型建议重启 DSH。空目录不算有效快照, 适配器就绪后会自愈。 - 中途失败改不回状态码:SSE 头一旦发出就只能下发错误数据帧(OpenAI 同样做法);开流前的失败会给 真实的错误状态。
- 插件加载失败不会拖垮 DSH:单个插件异常只影响本插件。
文件清单
dsh-llm-gateway/
package.json # 包描述,dsh.bundle.patch 与 dsh.client 声明
cordis.patch.yml # bundle 层:插入 llm-gateway 条目
build-and-pack.ps1 # 打包安装脚本:pnpm pack → 放进 profile → 改写 dependencies
.gitignore # 打包产物(*.tgz)不入库
lib/
index.js # 宿主半侧:HTTP 服务、模型目录、OpenAI 兼容层、Config schema
client.js # 浏览器半侧:Plugins 页上的配置表单
宿主半侧依赖 @deepseek-ai/schemastery(由 DSH 运行时提供);浏览器半侧通过 DSH 的客户端模块表使用
@deepseek-ai/dsh-client-ui-primitives。除此外没有第三方依赖。
许可
MIT
No comments yet. Be the first to write one.