[中文](./README.md) · [English](./README.en.md) · [日本語](./README.ja.md) # dsh-deepseek-relay — DeepSeek 中継サーバー(リレー)の思考強度アダプター > deepseek-harness(`dsh`)に**中継サーバー**(OneAPI / new-api / one-hub などのサードパーティ製 OpenAI 互換ゲートウェイ、いわゆる「API 転送」サービス)経由で導入した DeepSeek モデルに対しても、公式 API と同様に Web UI で**推論レベル(思考強度)**を設定できるようにします。Off / Low / High / Max の 4 段階で、かつ中継サーバーが認識できる形式で思考パラメーターを送信します。 ## ⚠️ アップグレード時の注意(v0.1.1、重要) 古いバージョンからアップグレードする場合、手動で確認・修正が必要なのは**ここだけ**です: > **手動で直す必要があるのはこの 1 箇所だけ** > > 古い設定に「実際には画像認識(vision)に対応しているモデル」(例:`deepseek-v4-flash-vision-exp`)があり、かつ以前は `id` しか書かず能力フィールドを一切書いていなかった場合——アップグレード後は**デフォルトで「画像非対応」になります**。画像を受け取らせたい場合は、次の 1 行を手動で追加してください: > > ```yaml > - id: deepseek-v4-flash-vision-exp > supportsVision: true # ← アップグレード後に画像認識を使うならこの行を自分で追加 > ``` > > その他のモデル(テキストのみ)は変更不要です。`supportsVision` は省略時 `false` として扱われます。これは仕様です(画像を誤って送った際に host が親切な「このモデルは画像非対応」エラーを出すため)。 ## 機能 - ✅ Web UI に Off / Low / High / Max の推論レベルを表示(公式 `llm-deepseek` と完全同一) - ✅ 思考パラメーターを中継サーバーの方言(`openai` / `deepseek`)に自動変換 - ✅ モデルごとの wire 値上書き(`ultra` のようなカスタム値をゲートウェイが要求する場合にも対応) - ✅ モデルごとの画像認識能力宣言(`supportsVision`)、画像付与時の親切な拒否 - ✅ 1 つのプラグインインスタンスで複数の中継ルートを装着可能 - ✅ 純プラグインで動作、**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 互換アダプターのルーティング**(中継サーバー 1 つにつき 1 ルート)を登録し、さらに: - **常に Off / Low / High / Max の 4 段階を UI に公開**(公式 `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: '/absolute/path/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 キー**:環境変数 `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 キーの環境変数名 | `RELAY_API_KEY` | | `displayName` | モデルセレクターの表示名 | ルートキー | | `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 ではない**ため、画像添付時に host が親切な「モデルは画像非対応」エラーを表示します) | `false` | > 💡 **アップグレード者の方へ**:古い設定で実際に画像認識に対応しているモデルに `id` しか書いていなかった場合、アップグレード後は `supportsVision: false` として扱われます——画像認識を使うには上の「アップグレード時の注意」の通り `supportsVision: true` の 1 行を追加してください。 ## 公式仕様への準拠 リポジトリの `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` コード、`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 等)では、公式 API をそのまま転送する場合を除き、`thinkingFormat: deepseek` ではなく `thinkingFormat: openai` を使うべきです。 ## コード構成 ``` 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:4 段階推論レベル + fetch/SSE stream ├── serialize.ts # メッセージ+思考パラメーターのシリアライズ(openai/deepseek 方言) ├── translate.ts # SSE chunk → harness StreamChunk ├── sse.ts # SSE 解析(eventsource-parser) └── types.ts # wire 型 ``` ## 検証 - `tsc --noEmit` 完全型チェック:0 エラー(公式 npm パッケージ `@deepseek-ai/dsh-llm` 等 0.1.1-rc.2 に対して)。 - シリアライズロジックテスト(`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`(「画像転送は未実装」)を返します——実装は今後の予定です。 - 1 つのプラグインインスタンスで複数の中継ルートを設定可能(`providers` 辞書に追加するだけ)。 ## 免責事項 本プラグインは DeepSeek 公式とは無関係のサードパーティ製非公式アダプター層です。中継サーバー/サードパーティ製ゲートウェイを利用する際は、その利用規約ならびに現地の法規に従ってください。**本プラグインは「現状有姿」で提供され、すべてのゲートウェイへの適合を保証するものではなく、インストール・利用・アップグレードの過程で生じたいかなるデータ消失・設定破損・アカウント上のリスクについても責任を負いません。** アップグレード前に `$DSH_HOME`(`settings.yaml` および `.credentials.yaml` を含む)を各自でバックアップしてください。本リポジトリのリンクをクリックした時点で、以上の内容を了承したものとみなします。