dsh-model-proxy:按模型名走出站代理,代理不可用時直連回退

前言

在 DeepSeek Harness(DSH)裏,一個網關往往掛多種模型,背後可能是不同上游。同一 host、同一 API key、同一請求形態下,有的模型在本地區正常,有的會因區域限制、限速或只能經公司代理訪問而失敗——這與「隔壁模型」無關。

進程級環境變量代理按 host 路由:HTTP_PROXY 無法區分同一 host 上的不同模型。整網關走代理會把本來正常的模型一起拖進代理;完全不設代理則受限模型一直失敗。下面介紹社區插件 dsh-model-proxy,它從每次請求體裏讀出 model 字段,只把你指定的模型名發往代理,其餘保持原有出站路徑。

這是什麼

dsh-model-proxy(GitHub:windwhiterain/dsh-model-proxy,維護者 windwhiterain)是 DSH 的 bundle 插件,分類爲模型推理。一句話:在 Harness 內做按模型名的出站代理路由,並在代理 TCP 不可達時可選地回退到插件加載前的直連路徑。

插件以當前 dsh 進程權限運行;安裝前應自行查看倉庫源碼與 MIT 許可證。社區目錄頁見 SkillHub 插件頁,與 DeepSeek / 幻方無官方從屬關係。

路由方式

DSH、pi-ai 與各類 provider SDK 通過 globalThis.fetch 發請求,最終落到 undici 的全局 dispatcher 槽位(Symbol.for('undici.globalDispatcher.1'))。本插件佔用該槽位,按順序決策:

  1. Origin:目標 host 不在 origins 內,或請求無 body,則保持插件加載前的路由。
  2. 代理可達性:在開啓 fallbackToDirect 且代理 host:port TCP 拒絕連接時,候選請求仍走上述「先前路由」,避免代理停掉後拖垮全進程調用。
  3. 模型名:讀取 body 頭部 JSON 的 "model" 字段;若匹配 models 中的模式,則經 proxy 出站。

爲解析模型名,插件只緩衝 body 的前若干 chunk,並在解析前 yield 已讀 chunk,不改變 chunk 邊界、背壓與流式響應行爲。配置裏的 direct 指進程在裝插件前已有的路由;若啓動環境未設代理,則未匹配請求與未裝插件時行爲一致。

核心配置項

bundle 自帶的 cordis.patch.yml 會插入 id: model-proxy 行;也可在 <DSH_HOME>/profiles/<profile>/cordis.patch.yml 熱重載編輯,或通過 settings 層覆蓋同 id。無效配置會導致激活失敗,而不是靜默走錯路由。

- id: model-proxy
  config:
    enabled: true
    proxy: http://127.0.0.1:10793
    origins:
      - opencode.ai
    models:
      - grok-*
      - gpt-*-luna
    fallbackToDirect: true
    probeTimeoutMs: 300
    probeCacheMs: 2000
    peekBytes: 16384
字段 含義
enabled 爲 false 時不安裝攔截,所有請求保持原路由
proxy 匹配模型使用的代理端點,支持 http: / https: 及可選憑據
origins 僅對這些 host 上的請求做模型匹配
models 模型名模式,* 匹配任意字符段
fallbackToDirect 代理不可連時,匹配請求改走先前路由
probeTimeoutMs 可達性探測 TCP 連接超時
probeCacheMs 探測結果緩存時長,突發請求只探一次
peekBytes 爲找 model 字段最多窺視的 body 字節數

安裝與啓用

在目標 profile 的 package.json(路徑:<DSH_HOME>/profiles/<profile>/package.json)註冊依賴與 bundle:

{
  "dependencies": {
    "dsh-model-proxy": "github:windwhiterain/dsh-model-proxy"
  },
  "dsh": {
    "profile": {
      "bundles": ["@deepseek-ai/dsh-base", "dsh-model-proxy"]
    }
  }
}

本地開發可用 link:C:/path/to/dsh-model-proxy 等形式。隨後安裝該 profile 並重啓 dsh 一次;bundle 的 cordis.patch.yml 會寫入默認 model-proxy 配置。

不要同時爲 dsh 進程設置 HTTP_PROXY / HTTPS_PROXY。啓動環境中的代理會讓 dsh 安裝進程級 dispatcher,本插件會把該路徑當作「非代理模型」的保留路由,與「僅部分模型走代理」的目標相反。啓動環境保持直連,由本插件單獨處理需要代理的流量。

典型用法

同一網關內,部分模型需翻牆或區域代理

README 示例:OpenCode Go 在同一網關提供 grok-* 與 gpt-*-luna,在受限地區上游返回 unsupported_country_region_territory,而 GLM、Kimi、DeepSeek 等同 key 同 region 正常。用上文 YAML 只把兩類受限模型經支持地區的代理發出,其餘直連;關閉代理後,在 fallbackToDirect: true 下直連模型不受影響。

直接調該網關時,請求仍需帶 x-opencode-session 頭(否則 400 MissingSessionID),與是否啓用本插件無關。

單一 provider 走公司代理

- id: model-proxy
  config:
    proxy: http://proxy.corp.example:3128
    origins: [api.example.com]
    models: ['*']

整段網關經不同出口,進程其他 host 不變

與上例相同,將 models 設爲 ['*'];路由仍按請求決策,之後在別處新增的 host 不會被這一條連帶代理。

診斷與自測

決策會寫入宿主 logger,例如:

dsh-model-proxy: models grok-*, gpt-*-luna on opencode.ai via http://127.0.0.1:10793, direct while it is down
dsh-model-proxy: route=proxy via=http://127.0.0.1:10793 reason=model:grok-4.7
dsh-model-proxy: route=direct via=direct reason=model-other:glm-5.3-flash
dsh-model-proxy: route=direct via=direct reason=proxy-down
dsh-model-proxy: route=direct via=direct reason=origin

卸載時會恢復被替換的 dispatcher,便於後續插件佔用同一槽位。

倉庫提供 probe 腳本(需配置可讀憑據):

node probe/smoke.mjs              # 代理可達
node probe/smoke.mjs --proxy-down # 回退路徑

適用場景與注意

適合誰:在同一 DSH 網關混用多模型、且只有部分模型需要獨立出站代理的智能體或網關維護者;需要代理宕機時其餘模型仍可用的部署。

進程內生效:路由邏輯只在 dsh 進程內生效;shell 工具拉起的 curl、git 等仍遵循啓動環境,本插件刻意不修改後者。

模型名來源:僅從 body 讀取 model;僅寫在 header、或 model 出現在 peekBytes 之後的請求不會匹配,保持直連。

代理探測範圍:探測回答「端口是否在監聽」,不驗證上游是否可用;代理能連但上游失敗時,該請求仍會失敗。

單條配置一個 proxy:不同模型族要走不同代理,需要多條 model-proxy 行(README 說明當前實現對此有約束,截斷處未展開完整表述)。

依賴版本:插件將 undici 固定爲 ~8.10.0;8.11.0 在 Node 24 下接管全局 dispatcher 時可能破壞 content-encoding 解碼。與 @deepseek-ai/dsh-http-proxy 的 undici 約束同類,鎖文件升級需謹慎並重跑 probe 斷言。

兼容性:依賴 Node 自帶 fetch 所暴露的全局 dispatcher 槽位;除「帶 fetch 的 Node」外 README 未假定更細版本。

結尾

dsh-model-proxy 解決的是「同一 API host、不同模型不同可達性」下的出站問題:用模型名模式選擇代理,可選在代理不可連時回退,並避免整進程 HTTP_PROXY 的一刀切副作用。目錄與源碼:

羽毛球分组比赛记分
小程序二维码

欢迎使用《羽毛球分组比赛记分》微信小程序

小夜