Skip to content
dsh.fish
Bundle

dsh-deepseek-relay

DeepSeek 中转站(OpenAI 兼容网关)思考强度适配插件:让 UI 出现 Off/Low/High/Max 推理等级选项,并按中转站格式发送 reasoning_effort / thinking

Source
LXFLGH
stars
1 stars
License
MIT
Updated
Updated 5 days ago

Readme

[中文](./README.md) · [English](./README.en.md) · [日本語](./README.ja.md)

# dsh-deepseek-relay — DeepSeek 中转站思考强度适配插件

> 让 deepseek-harness(`dsh`)里通过**中转站**(第三方 OpenAI 兼容网关,如 OneAPI / new-api / one-hub 或各种「API 转发」服务)接入的 DeepSeek 模型,也能像官方 API 一样在 Web UI 里设置**推理等级(思考强度)**:Off / Low / High / Max 四档,并把思考参数按中转站认识的格式发出去。

## ⚠️ 升级须知(v0.1.1,重要)

如果你是从旧版升级的**老板**,只有一处需要你手动确认:

> **唯一需要你手动改的地方**
>
> 如果你的老配置里有个模型实际能识图(比如 `deepseek-v4-flash-vision-exp`),但之前只写了 `id`、没写任何能力字段——升级后它**默认变为「不支持识图」**。想让它能收图,需要手动补一行:
>
> ```yaml
> - id: deepseek-v4-flash-vision-exp
>   supportsVision: true   # ← 升级后想用识图,这行要自己加
> ```
>
> 其余模型(纯文本)无需改动,保持原样即可;`supportsVision` 缺省按 `false` 处理,这是设计使然——让 harness 在误传图片时给出友好的「该模型不支持图片」提示,而不是把请求发出去再报错。

## 特性

- ✅ 在 Web UI 暴露 **Off / Low / High / Max** 四档推理等级(与官方 `llm-deepseek` 完全一致)
- ✅ 自动按中转站方言(`openai` / `deepseek`)序列化思考参数
- ✅ 支持按模型覆盖 wire 值,应对网关要求 `ultra` 之类自定义取值
- ✅ 支持按模型声明识图能力(`supportsVision`),收图时给友好拦截
- ✅ 一个插件实例可挂多条中转站路由
- ✅ 纯插件即可生效,**无需改动 harness 本体**

## 问题

官方 API 走内置 `llm-deepseek` 适配器,`resolveModel` 会返回 `reasoning.efforts = [Off, Low, High, Max]`,所以 UI 有推理等级选项。

中转站通常走 `llm-pi-ai` 的「自定义提供方」(`openai-completions`)。但:

1. Web UI 的表单没有 `reasoningEfforts` 字段,手写模型没有推理元数据,UI 里**根本不出现推理等级选项**;
2. 即使声明了,发往中转站的思考参数格式也不一定对——DeepSeek 官方格式(`thinking: {type: "enabled"}` + `reasoning_effort`)和 OpenAI 格式(只有 `reasoning_effort`)在中转站上不通用。

## 解决方式

本插件注册**独立的 OpenAI 兼容适配器路由**(每条中转站一条路由),并且:

- **始终向 UI 暴露 Off / Low / High / Max 四档推理等级**(与官方 `llm-deepseek` 完全一致,见 `src/adapter.ts` 的 `REASONING_EFFORTS`);
- 按 `thinkingFormat` 把思考参数写成中转站认识的方言(见 `src/serialize.ts`):

| 档位 | `openai`(默认) | `deepseek` |
| --- | --- | --- |
| off | 不发送任何思考字段(网关默认) | `thinking: {type:"disabled"}` |
| low | `reasoning_effort: "low"` | `thinking:{type:"enabled"}` + `reasoning_effort:"low"` |
| high | `reasoning_effort: "high"` | `thinking:{type:"enabled"}` + `reasoning_effort:"high"` |
| max | `reasoning_effort: "max"` | `thinking:{type:"enabled"}` + `reasoning_effort:"max"` |

- `thinkingFormat: auto` 时按 baseURL 域名猜:含 `deepseek.com/ai/cn` 用 `deepseek`,其余默认 `openai`(多数中转站)。
- 支持按模型覆盖 wire 值(`reasoningEfforts`),应对「网关要求 `ultra` 之类自定义取值」的场景。

## 使用

两种方式都符合官方插件规范(见下文「官方规范符合性」):

### 方式 A:从源码运行,`--patch` 本地加载(开发 / 自用)

在 deepseek-harness 仓库根目录(已 `pnpm install` 过):

```sh
pnpm dsh web --patch ./dsh-deepseek-relay/cordis.yml
```

启动前先编辑 `cordis.yml`,把 `providers.relay` 换成你的中转站信息:

```yaml
- insert:
    - id: dsh-deepseek-relay
      name: '/绝对路径/deepseek-harness/dsh-deepseek-relay/src/index.ts'
      config:
        providers:
          my-relay:
            baseURL: https://your-relay.example.com/v1
            apiKeyEnv: RELAY_API_KEY
            thinkingFormat: auto
            models:
              - id: deepseek-v4-flash
              - id: deepseek-v4-flash-vision-exp
                supportsVision: true   # 能识图的模型记得开
```

- **API key**:设置环境变量 `RELAY_API_KEY`,或启动后在 Web UI **设置 → 模型** 里选中该提供方填入密钥(密钥存 `$DSH_HOME/.credentials.yaml`)。
- **模型**:`models` 列表即模型选择器里出现的模型;`contextWindow` / `maxTokens` 会作为上下文 / 输出上限信息。
- 保存后,在模型选择器选到该模型的会话里,输入框旁就会出现 **Off / Low / High / Max** 推理等级下拉(和官方 API 一样)。

### 方式 B:作为组合包(bundle)安装(可分发)

```sh
# 本地目录 / git / tarball 均可;首次会初始化 profile
dsh plugin --profile demo add ./dsh-deepseek-relay
# 或 dsh plugin --profile demo add github:you/dsh-deepseek-relay
```

包内 `cordis.patch.yml` 声明了插件行(`name: dsh-deepseek-relay` 按包名解析,加载 `lib/index.js`),`prepare` 脚本(esbuild)会在安装时自动构建 `lib/`。安装后插件处于 **dormant(空配置可安全装载,不注册任何路由 / 目录,适配器与 configurable-provider 目录两侧都有空数组守卫,见 `src/index.ts` 的 `ensureDirectory` / `ensureRegistrationFacts`)**,在 profile 的 `cordis.patch.yml` 或 `$DSH_HOME/cordis.patch.yml` 里覆盖同 id 行填入中转站配置(参考 `cordis.patch.yml` 顶部注释),或随后在 Web UI 设置中热更新生效。

## 配置项

| 字段 | 说明 | 默认 |
| --- | --- | --- |
| `baseURL` | 中转站 OpenAI 兼容地址,`/chat/completions` 自动拼接 | 必填 |
| `apiKeyEnv` | API key 环境变量名 | `RELAY_API_KEY` |
| `displayName` | 模型选择器显示名 | 路由 key |
| `thinkingFormat` | `auto` / `openai` / `deepseek` | `auto` |
| `reasoningEffort` | 默认推理等级 `off`/`low`/`high`/`max` | `high` |
| `maxTokensField` | 输出上限字段 `max_tokens`/`max_completion_tokens` | `max_tokens` |
| `maxTokens` | 路由级默认输出上限 | 256000 |
| `models[].id` | 发往网关的模型 id | 必填 |
| `models[].name` | 选择器显示名 | id |
| `models[].contextWindow` | 上下文容量 | 262144 |
| `models[].maxTokens` | 该模型输出上限 | 路由级值 |
| `models[].reasoningEfforts` | 按档位覆盖 wire 值(`null`=支持但不发送) | 方言默认 |
| `models[].supportsVision` | 模型是否支持识图(Web UI 中是模型行的「支持识图」勾选框)。`true` → `inputModalities:['text','image']`;`false` 或省略 → `['text']`(**显式非 undefined**,让 harness 在提交图片时给出友好的「模型不支持图片」提示) | `false` |

> 💡 **升级用户看这里**:老配置里若某个模型实际能识图但只写了 `id`,升级后它会按 `supportsVision: false` 处理——想用识图就按上方「升级须知」补一行 `supportsVision: true`。

## 官方规范符合性

对照仓库 `docs/user/develop/basic/{index,config,publish}.zh.md` 与 `docs/user/develop/practice/llm-adapter.zh.md`:

| 官方要求 | 本插件 |
| --- | --- |
| 插件模块导出 `name` + `apply(ctx, config)` | ✅ `src/index.ts` |
| 声明 `inject`(本插件依赖 `llm` 服务) | ✅ `inject = ['llm']` |
| 导出 `Config` 类型 + 同名 Schemastery schema,默认值写在 schema 中 | ✅ `src/index.ts` |
| `--patch` overlay 加载本地插件(源码路径 `.ts`) | ✅ `cordis.yml`(方式 A) |
| 组合包 `dsh.bundle` manifest + `cordis.patch.yml`(按包名引用) | ✅ `package.json` + `cordis.patch.yml`(方式 B) |
| git 安装的 TS 包必须自带 `prepare` 构建(产出 `lib/`) | ✅ `scripts/build.mjs` + `tsconfig.build.json` |
| `LlmAdapter` 契约:实现 `stream()`、`resolveModel` 返回 `reasoning` 元数据、`attributionHeaders()`、`LlmError` 稳定 code、透传 `options.signal` | ✅ `src/adapter.ts` |

注:`--patch` 从源码路径加载 `.ts` 是官方教程支持的**开发方式**;正式分发(npm / git / tarball)走方式 B,`prepare` 构建出 `lib/index.js` 后与源码运行等价。两种方式共用同一份 `src/`。

## 备选:不装插件,直接用官方 llm-pi-ai 手工配置

官方 `llm-pi-ai` 本身支持同样的能力,只是要手写 `$DSH_HOME/settings.yaml`。如果不想装插件,可以这样写(等效于 `thinkingFormat: deepseek`):

```yaml
llm-pi-ai:
  providers:
    my-relay:
      apiKeyEnv: RELAY_API_KEY
      api: openai-completions
      baseURL: https://your-relay.example.com/v1
      compat:
        thinkingFormat: deepseek
      models:
        - id: deepseek-v4-flash
          reasoningEfforts:
            off:
            low: low
            high: high
            max: max
```

> 注意:`reasoningEfforts` 里 `off:` 冒号后留空表示「支持该档、不发送」;其余档必须给 wire 值,否则配置被拒。多数网关(OneAPI 等)应使用 `thinkingFormat: openai` 而不是 `deepseek`,除非网关原样转发官方 API。

## 代码结构

```
dsh-deepseek-relay/
├── cordis.yml          # --patch 本地加载配置示例(方式 A)
├── cordis.patch.yml    # 组合包配置层(方式 B,dsh.bundle.patch 指向它)
├── package.json        # dsh.bundle manifest + scripts(prepare 构建)
├── scripts/build.mjs   # esbuild 构建单文件 lib/index.js(external @deepseek-ai/*)
├── tsconfig.json       # 类型检查(tsc --noEmit)
└── src/
    ├── index.ts      # 插件入口:Config schema、路由注册、settings 热更新
    ├── adapter.ts    # RelayAdapter:四档推理等级 + fetch/SSE stream
    ├── serialize.ts  # 消息 + 思考参数序列化(openai/deepseek 方言)
    ├── translate.ts  # SSE chunk → harness StreamChunk
    ├── sse.ts        # SSE 解析(eventsource-parser)
    └── types.ts      # wire 类型
```

## 验证

- `tsc --noEmit` 完整类型检查:0 错误(对 `@deepseek-ai/dsh-llm` 等 0.1.1-rc.2 官方 npm 包)。
- 序列化逻辑测试(`resolveThinking` / `resolveThinkingFormat` / `serializeRequest`,openai/deepseek 方言 × off/low/high/max × 模型级覆盖 × session-title × maxTokensField):全部通过。
- SSE 转换测试(`reasoning_content` 流、tool_calls 增量拼接、usage 去重、`[DONE]` 收尾、空响应):全部通过。
- 验证依赖通过 npm 平铺安装(`C:/Users/29261/Downloads/relay-verify`,测试脚本可复用)。

## 注意事项

- 中转站若在 `/chat/completions` 之外还有 `/v1` 后缀,`baseURL` 写完整路径,例如 `https://relay.example.com`(若网关恰好要求不带 `/v1`)。
- 插件默认只支持文本输入(与官方 `llm-deepseek` 一致);图片输入会以 `UNSUPPORTED_CONTENT` 拒绝。若某模型已声明 `supportsVision: true`,但图片传输功能(attachments store 集成)尚未实现,会给出 `UNSUPPORTED_CONTENT`(「图片传输待实现」),届时再补。
- 一个插件实例可配多条中转站路由(`providers` dict 里加即可)。

## 免责声明

本插件为第三方非官方适配层,与 DeepSeek 官方无隶属关系。使用中转站 / 第三方网关请遵守其服务条款与当地法律法规;**本插件按「现状」提供,不保证适配所有网关,也不对安装、使用、升级过程中可能造成的任何数据丢失、配置损坏或账号风险负责**。升级前请自行备份 `$DSH_HOME`(含 `settings.yaml` 与 `.credentials.yaml`)。点击本仓库链接即视为已知晓并同意上述条款。

Install

dsh plugin --profile web add github:LXFLGH/dsh-deepseek-relay

Profile: web

  • This package builds from source on install. pnpm will ask you to allow its build script — that is permission to run the package’s code on your machine, outside the agent sandbox. Only allow sources you trust.
  • This source has no pinned commit, so a later push upstream changes what installs. Prefer pinning a commit.
Source