Bundle
dsh-vision-bridge
DeepSeek Harness 多模态视觉桥:贴图自动转文字描述(llm/stream 代理)+ view_image/ocr_image 主动视觉工具 + 原生多模态路由自动跳过(rc.7 适配),让 text-only 的 DeepSeek 模型看见图片
- Source
- DreamRift
- License
- MIT
- Updated
- Updated 5 hours ago
Readme
# DeepSeek 多模态视觉桥(dsh-vision-bridge)
`dsh-vision-bridge` 是 DeepSeek Harness(DSH)的多模态视觉插件。DeepSeek 官方接口是纯文本的:
在 Web UI 贴图后,`dsh-llm-deepseek` 适配器会因消息中的图片内容块直接抛 `UNSUPPORTED_CONTENT`。
本插件借鉴 [Qwen-MM-Plugins](https://github.com/QwenLM/Qwen-MM-Plugins) 的思路(VL 描述注入 +
主动视觉工具),以 DSH 原生插件实现「让纯文本的 DeepSeek 模型看见图片」:
- **贴图自动代理**:拦截 `llm/stream` 请求,把消息中的 `ImageBlock` 交给 OpenAI 兼容的视觉模型
(默认 DashScope `qwen-vl-max`)生成结构化中文描述(含图中文字逐字转录),替换为文本块后放行。
同图跨轮次缓存、描述文本稳定(保 KV cache 前缀);VL 失败进入失败冷却并降级为占位文本,
不阻塞会话;会话标题请求自动跳过视觉解析。
- **主动视觉工具**:注册 `view_image`(查看本地图片 / 按问题回答)与 `ocr_image`(逐字转录),
模型可主动查看文件系统中的截图、设计稿、图表。
- **设置页卡片**(v0.2.0 起):设置 → 插件 → 插件配置 → 「视觉模型」,可修改端点(来源)/ 模型 /
OCR 模型并保存,**热更新生效**(无需重启);密钥与 harness 官方一致——write-only 不回显明文,
只显示「已配置 / 未配置」徽标,留空保持。
- **模型能力桥接**(v0.4.0 重构):运行时探测原生多模态路由并自动跳过视觉桥。
DSH rc.7 起官方 `llm-pi-ai` 适配器(pi-ai 库)**原生支持图片**,本插件在请求到达时
经 `ctx.llm.resolveModelInfo()` 查询 `inputModalities`,含 `image` 即直通(不调 VL、
不改写消息),让 pi-ai 原生多模态链路零开销工作;text-only 路由(`deepseek-official`)
仍走 VL 代理。旧版「给 pi-ai 模型补 image 声明」行为已移除(rc.7 中既有害又无必要)。
零硬 npm 依赖(`dsh-settings`/`schemastery`/`dsh-tools` 动态 import,缺包仅降级对应功能)、
不改 DSH 核心源码、不写会话事件,Windows / Linux / macOS 原生可用(无 Python / uvx / WSL 要求)。
- 设计依据与决策记录:[`DeepSeek多模态视觉桥-开发计划.md`](DeepSeek多模态视觉桥-开发计划.md)
- 挂载与配置:[`docs/挂载指南.md`](docs/挂载指南.md)
- Qwen-MM-Plugins 调研:[`docs/Qwen-MM-Plugins调研报告.md`](docs/Qwen-MM-Plugins调研报告.md)
- 许可证:[`LICENSE`](LICENSE)(MIT)
## 部署
### 方式一:官方安装(推荐)
仓库已打 `dsh-plugin` topic,可被 [DSH 插件商店](https://dshpluginstore.com) 等目录自动收录。
一条命令安装:
```bash
dsh plugin --profile web add "github:DreamRift/dsh-vision-bridge"
```
### 方式二:手动安装(tgz + bundle)
1. 克隆本仓库,进入项目目录并生成可安装包:
```bash
cd dsh-vision-bridge
npm pack # 产出 dsh-vision-bridge-0.4.0.tgz
```
2. 在 `$DSH_HOME/profiles/web/package.json` 声明依赖与 bundle(包自带
`cordis.patch.yml`,无需在 profile patch 手写 insert):
```json
{
"dsh": { "profile": { "bundles": ["dsh-vision-bridge"] } },
"dependencies": {
"dsh-vision-bridge": "file:<本仓库绝对路径>/dsh-vision-bridge-0.4.0.tgz"
}
}
```
3. 安装 profile 依赖:
```bash
cd "$DSH_HOME/profiles/web"
pnpm install
```
4. 默认配置(DashScope `qwen-vl-max` 等)在包内 `cordis.patch.yml`;改配置优先走设置页
「视觉模型」卡片(热更新),高级字段见 [`docs/挂载指南.md`](docs/挂载指南.md) §3.2。
5. 设置页自动暴露(rc.7 起无需 patch):`settings.describe()` 列出所有已注册 namespace,
注册即暴露。**旧版 `scripts/patch-api-proxy-namespace.mjs` 已删除**(rc.7 删除了
`WEB_SETTINGS_NAMESPACES` 白名单)。
6. **内置路由图片准入豁免**(deepseek-official 等 `inputModalities` 被 DSH 硬编码为 `["text"]`
的路由贴图被拒时必须;DSH 升级后需重跑一次):
```bash
node scripts/patch-api-proxy-image-admission.mjs
```
详见 [`docs/挂载指南.md`](docs/挂载指南.md) §3.6:`deepseek-official` 无法声明 image,
会被 api-proxy 的 `MODEL_DOES_NOT_SUPPORT_IMAGES` 准入检查拦截;本脚本对
`providerRoutes` 内已接管的 provider 跳过该准入(图片由视觉桥代理转文字)。
改完需**重启 DSH 后端**生效。
7. 把 VL 服务的 key 写入 `$DSH_HOME/.credentials.yaml`(明文不进任何配置文件 / 仓库;
也可重启后直接在设置页「视觉模型」卡片里填,效果相同):
```yaml
QWEN_MM_VISION_API_KEY: sk-<your-key>
```
8. 重启 `dsh web`,启动日志应出现 `[vision-bridge] 已启用:…` 与 `[vision-bridge] 设置页已就绪:…`。
## 工作原理
DSH 的贴图链路本身完整(拖放 → `ctx.attachments` → 消息中的 `ImageBlock`),卡在 DeepSeek 适配器
拒绝图片内容。本插件监听 `llm/stream` waterfall:由于 cordis 的 waterfall `next` 不接收替换参数,
插件采用 **veto + 重入**(DSH 官方插件 `dsh-session-checkpoint-policy` 示范的合法模式)——
不调用 `next`,而是把替换后的请求打上 `Symbol.for` 标记重新送入 `ctx.llm.stream()`。
不含图片的请求走同步直通,零开销。会话日志中的 `ImageBlock` 保持原样(替换只发生在请求侧,
聊天历史仍显示原图)。rc.7 起在重写前先探测当前路由是否原生支持图片(`ctx.llm.resolveModelInfo`
的 `inputModalities`):原生多模态(llm-pi-ai 等)直接直通、不调 VL。机制细节与源码依据见
开发计划 §3.1/§5.2 与 `src/dsh/proxy.js` 头注。
## 目录结构
```
dsh-vision-bridge/
├── DeepSeek多模态视觉桥-开发计划.md # 设计与决策记录(含 DSH 机制调研来源)
├── docs/
│ ├── 挂载指南.md # 挂载到 DSH + 配置 + 验证清单 + 常见问题
│ └── Qwen-MM-Plugins调研报告.md # 上游项目调研
├── package.json # dsh-vision-bridge 包(out-of-tree 插件)
├── src/
│ ├── core/ # 核心库(纯 JS,零依赖,可单测)
│ │ ├── vision-client.js # OpenAI 兼容 VL 客户端(超时 / 429 重试 / 错误分类)
│ │ ├── prompts.js # describe / ocr / ask 三类中文 prompt
│ │ ├── image-utils.js # 格式白名单、扩展名+魔数识别、base64 dataURL
│ │ ├── message-rewrite.js # 不可变重写:ImageBlock → 文本块
│ │ └── describe-cache.js # attachmentId → 描述 LRU(保 KV cache 前缀稳定)
│ └── dsh/ # DSH 适配层
│ ├── index.js # 插件入口 apply(ctx, config)
│ ├── proxy.js # llm/stream 拦截(veto + 重入 + 原生多模态直通)
│ ├── tools.js # view_image / ocr_image 工具注册与执行
│ ├── runtime.js # 共享运行时(日志 / fetch / 凭据解析 / VL 客户端)
│ ├── model-bridge.js # 运行时探测原生多模态路由(llm.resolveModelInfo)并跳过
│ ├── settings.js # 设置页桥接(schema + 热更新)
│ └── config.js # 配置解析(默认值 + 归一化,零依赖)
└── tests/ # node:test,66 个用例(mock fetch,无需 VL key)
```
## 测试
```bash
node --test tests/*.test.js # 66 个单元/集成测试全绿
```
覆盖:消息不可变重写(含冻结输入与 tool-result 内嵌)、缓存稳定性与 LRU、
VL 客户端(成功/401/429 重试/超时/取消/畸形响应/网络错误)、veto+重入链路
(直通/替换/降级/strict/失败冷却/标题占位/原生多模态探测直通)、
模型桥探测(resolveModelInfo 命中/TTL 缓存/降级/热更新清缓存/旧字段兼容)、
工具执行(正常/缺凭据指引/OCR 回退)。
## 致谢
- [QwenLM/Qwen-MM-Plugins](https://github.com/QwenLM/Qwen-MM-Plugins):核心思路
(VL 描述注入、结构化转述、主动视觉工具)的来源;本项目未复用其代码(Python/MCP 实现改为
DSH 原生 Node 插件)。
## 许可
[MIT](LICENSE)
Install
dsh plugin --profile web add github:DreamRift/dsh-vision-bridge#a9501e68ee8aeb7187034e78e6ae9f3fc2f4ba56
Profile: web
With the hub plugin installed, ask your agent to install it by name — it resolves the same plan shown here.
dsh plugin --profile web add github:stvlynn/dsh.fish#path:packages/dsh-plugin-hub
install dsh-vision-bridge from the hub