Bundle
jolly-dsh-vision
ModLens-style vision bridge for DeepSeek Harness: deepseek-v4-pro as the brain, deepseek-v4-flash-vision-exp as the eyes.
- Source
- JollY-Life
- License
- MIT
- Updated
- Updated 3 days ago
Readme
# jolly-dsh-vision
ModLens 风格的视觉桥接插件:**一个纯文本大模型当大脑,一个视觉模型当眼睛**(默认 deepseek-v4-pro + deepseek-v4-flash-vision-exp)。
大脑看不到像素。本插件提供两条视觉通路:
1. **`vision` 工具**:传入图片路径或 URL,工具把图片存进附件库,通过 harness 的 LLM seam 调眼睛模型,返回一份 **modlens 式结构化证据 JSON**(summary / ocr / layout / semantics / visual / uncertainty),大脑引用证据作答而不是猜图。
2. **视觉版模型("(ds vision)" 孪生)**:在模型选择器里出现 `DeepSeek-V4-Pro (ds vision)` 等视觉孪生条目——选中后**可以直接在输入框粘贴/拖入图片**,插件在请求发出前自动把图片转成证据文本再交给大脑。
参考实现:[liustack/modlens](https://github.com/liustack/modlens)(MIT 许可;本项目的证据词汇与包装器机制派生自它,见 [THIRD_PARTY_NOTICES.md](./THIRD_PARTY_NOTICES.md))。
## 架构
```
通路 A — vision 工具(大脑主动调用)
大脑 (纯文本模型)
└─ 调用工具 vision(path, prompt?)
├─ 读本地文件 / 抓 http(s) URL(大小上限、魔数嗅探 png/jpeg/gif/webp)
├─ SSRF 防护:拒绝私有/环回/链路本地地址(可配置放开)
├─ ctx.attachments.saveImage() → 持久化图片引用
├─ ctx.llm.stream({ provider, model: 眼睛模型, system: 证据提示词, user: [文本+图片] })
├─ 装配文本流 → 解析证据 JSON(容忍 markdown 围栏)
└─ 返回证据对象(output.schema + render 转成模型可读 markdown)
通路 B — (ds vision) 孪生模型(贴图自动转换,modlens 的 Phase 3 机制)
输入框贴图 ──► 模型选择器选中 "DeepSeek-V4-Pro (ds vision)"
(孪生声明 image 输入 ⇒ 附件准入通过、缩略图正常)
└─ 请求到达插件注册的 vision 适配器
├─ 递归扫描消息(含 tool-result 嵌套)里的图片块
├─ 每张图(按 attachmentId 缓存,失败用恒定占位文本降级)
│ ctx.attachments.readImage → 眼睛模型 → 证据 markdown
├─ 图片块 ⇒ 文本块 "[Attached image, converted to evidence ...]"
├─ 自家 assistant 回合改标为上游 provider(保住推理连续性/replay state)
└─ 转发给真实上游路由(大脑只见文本,不见像素)
```
### 与 modlens 的差异
| | @liustack/modlens | jolly-dsh-vision |
|---|---|---|
| 视觉引擎调用 | spawn 自带 CLI 子进程 | 直接走 harness 的 `ctx.llm` seam |
| 引擎/密钥配置 | 独立 `~/.modlens/config.json`,自管多家引擎 | 复用 `settings.yaml`(模型目录)+ 凭据 seam(API key 环境变量) |
| 运行时依赖 | commander + undici | **零依赖**(纯 node 内置模块,无构建步骤) |
| 工具 | `modlens_read_image` | `vision`(可配置改名) |
| 视觉孪生 | 有(自动发现多条路由) | 有(单路由,默认包裹一条上游的纯文本模型) |
| 粘贴转路径(浏览器注入) | 有 | 无(孪生模型直接解锁贴图,无需转路径) |
| 大脑引导 | 无 | 有(系统提示词区段,教大脑何时调工具/何时引用已转换证据) |
| SSRF 防护 | 无 | 有(默认拒绝内网/环回/链路本地,可配置) |
零依赖是刻意的:树外 DSH 插件无法可靠解析 `@deepseek-ai/*` 包(modlens 的 dsh/index.js 注释也确认了这一点),所以工具定义走原始 JSON-Schema 注册路径、消息/流块/适配器全部鸭子类型化。
## 文件结构
```
package.json # dsh.bundle.patch 接入清单 + 发布元信息(files/repository)
cordis.patch.yml # 插件条目 + 默认配置(可在此覆盖)
src/index.js # 插件入口:name/inject/apply,注册工具+引导+包装器
src/pipeline.js # 眼睛调用管线(analyzeImageEvidence / runVision,可离线测试)
src/wrapper.js # (ds vision) 视觉孪生:适配器注册、图片→证据转换、缓存
src/evidence.js # 证据提示词、output schema、模型可读渲染
src/images.js # 路径/URL → 字节:大小上限、魔数嗅探、SSRF 防护
src/stream.js # 极简 BlockAssembler、finish 错误、JSON 解析
tests/offline.test.mjs # 离线逻辑测试(含 SSRF,不联网)
tests/wrapper.test.mjs # 视觉孪生适配器测试(假 harness)
tests/plugin-smoke.test.mjs # 插件注册冒烟测试
```
## 安装
### 从 npm(发布后)
```powershell
dsh plugin --profile web add jolly-dsh-vision
```
### 从本地路径(开发 / 未发布时)
```powershell
dsh plugin --profile web add <本地仓库路径>
```
`dsh plugin add` 会把依赖写入 profile 的 package.json;该包带 `dsh.bundle.patch` 清单,会被加入 `dsh.profile.bundles`(如未自动加入,手动确认 `dsh.profile.bundles` 包含 `jolly-dsh-vision`)。
用本地路径安装时为 `link:` 依赖,改代码后重启 dsh web 即生效(无需重装)。
> pnpm 11 的供应链策略默认要求"发布满 1 天";若安装时遇到
> `ERR_PNPM_MINIMUM_RELEASE_AGE_VIOLATION`,把相关包加入 profile 的
> `pnpm-workspace.yaml` 中 `minimumReleaseAgeExclude` 白名单即可。
## 前置条件
`~/.dsh/settings.yaml`:
```yaml
agent-default-model:
provider: deepseek-official
model: deepseek-v4-pro # 大脑
llm-deepseek:
models:
- id: deepseek-v4-flash-vision-exp
name: DeepSeek-V4-Flash-Vision-Exp
inputModalities:
- text
- image # 眼睛必须声明 image 输入,否则适配器拒收图片
```
凭据:凭据 seam 中的 API key(dsh-llm-deepseek 默认按 `apiKeyEnv: DEEPSEEK_API_KEY` 解析,即环境变量或 `.credentials.yaml`)。
## 配置
默认值在 `cordis.patch.yml`,均可覆盖:
| 键 | 默认值 | 含义 |
|---|---|---|
| `provider` | `deepseek-official` | 眼睛模型所在的路由 |
| `model` | `deepseek-v4-flash-vision-exp` | 眼睛模型 id |
| `toolName` | `vision` | 暴露给大脑的工具名 |
| `maxImageBytes` | `20971520` (20MB) | 单张图片大小上限 |
| `timeoutMs` | `120000` | 一次眼睛调用的协作式超时 |
| `maxOutputTokens` | `8192` | 证据输出上限 |
| `visionProvider` | `true` | 注册 `(ds vision)` 视觉孪生路由 |
| `wrapperProvider` | `deepseek-vision` | 孪生路由 id(模型选择器里的分组) |
| `sourceProvider` | `deepseek-official` | 被包裹的上游路由(其纯文本模型生成孪生) |
| `twinSuffix` | `(ds vision)` | 孪生模型名后缀 |
| `evidenceCacheMax` | `128` | 每图证据缓存容量(按 attachmentId 去重) |
| `guide` | `true` | 给大脑加系统提示词引导区段 |
| `allowPrivateUrls` | `false` | `false` 时拒绝私有/环回/链路本地 URL(SSRF 防护) |
## 使用
### 通路 A:路径/URL + vision 工具
1. 给大脑一张图:本地绝对/相对路径,或 http(s) URL,可带关注点。
2. 引导区段会让大脑在该调 `vision` 时调用它;大脑基于 OCR 全文与布局作答,看不清的地方会出现在 uncertainty 里。
### 通路 B:直接贴图 + (ds vision) 孪生模型
1. 在模型选择器里选带 `(ds vision)` 后缀的条目。
2. 直接在输入框粘贴/拖入图片——准入检查通过,缩略图正常显示。
3. 请求发出前,插件自动把图片转成证据文本(带 `[Attached image, converted to evidence by the jolly-dsh-vision bridge]` 标记)注入给大脑;大脑按引导直接引用,不会重复调用工具。
4. 同一张图在一次会话内只转换一次(附件内容寻址 + 缓存);转换失败会降级为恒定占位文本并继续对话,细节留在 harness 日志。
> 注意:模型选择器里的"普通版"纯文本模型依然不接受贴图——想要贴图就选带 `(ds vision)` 后缀的条目。
## 安全与隐私
- **API key 零接触**:本插件不读取、不记录、不转发任何密钥;密钥由 harness 凭据 seam 在每次调用时解析,代码不构造 `Authorization` 头、不读 `process.env`。
- **数据出网**:图片字节与 OCR 文本会发送到你配置的视觉 provider(这是功能本身)。除此之外插件不发出任何网络请求、不上报遥测。
- **SSRF 防护**:`vision` 工具默认拒绝私有网段 / 环回 / 链路本地 / 云元数据地址(含对主机名的 DNS 解析检查),防止恶意提示词诱导请求内网。确有内网需求时把 `allowPrivateUrls` 设为 `true`。
- **提示注入**:图片里的文字经 OCR 进入对话,恶意图片可能夹带指令。转换块已被框定为"证据,请引用",但无法完全免疫——请把图片视为不可信输入。
- **信任边界**:`vision` 能读取 harness 进程可读的任意图片文件并外发;图片路径(含解析后的绝对路径)会进入会话日志。多人共享会话导出时注意这一点。
- **零运行时依赖**:无第三方包,也就没有供应链投毒面。
## 测试
```powershell
node tests/offline.test.mjs # 嗅探/上限/提示词/解析/装配/管线/SSRF
node tests/wrapper.test.mjs # 视觉孪生:列表/解析/透传/转换/缓存/降级/重名
node tests/plugin-smoke.test.mjs # 插件导出、工具+引导+包装器注册
```
## 故障排查
- **工具没有出现**:确认 `dsh.profile.bundles` 含 `jolly-dsh-vision`,重启了 dsh web,且 `cordis.patch.yml` 中没有 `- id: vision / disabled: true`。
- **模型选择器里没有 `(ds vision)` 条目**:确认 `visionProvider: true`;重启后查看 harness 日志里有没有 `[jolly-dsh-vision] vision provider registration skipped`。
- **贴图报"模型不支持图片"**:说明当前选中的是纯文本条目——选带 `(ds vision)` 后缀的条目。
- **贴图后大脑收到占位文本**:harness 日志里会有 `[jolly-dsh-vision] image read failed (store|media|eyes)`:store=附件库异常,media=不支持的图片格式,eyes=眼睛模型调用失败(查凭据/配额)。
- **URL 报 refusing to fetch a private/internal address**:命中 SSRF 防护;确需内网时把 `allowPrivateUrls` 设为 `true`。
- **报 MISSING_CREDENTIAL**:凭据 seam 里没有可用的 API key。
- **报拒绝图片输入**:settings.yaml 里眼睛模型的 `inputModalities` 缺 `image`。
- **证据格式错误**:眼睛模型偶发输出围栏外的杂音,`parseEvidenceJson` 已做容错;持续失败可调小 `maxOutputTokens` 或加 `prompt` 收窄焦点。
- **与 modlens 的关系**:两者工具名不同(`vision` vs `modlens_read_image`)、孪生路由不同,可共存。
## 已知限制 / v2 想法
- 仅支持 png/jpeg/gif/webp;HEIC 需先转码。
- 视觉孪生为单路由实现;modlens 的自动发现多路由、上游重注册刷新(`llm/adapters-updated` 再扫描)暂未做。
- 未注册设置卡片;配置走 cordis.patch.yml。
## License
本项目代码采用 MIT 许可(Copyright (c) 2026 Jolly-Life,见 [LICENSE](./LICENSE))。
其中派生自 [@liustack/modlens](https://github.com/liustack/modlens) 的部分,其 MIT 声明与许可文本见 [THIRD_PARTY_NOTICES.md](./THIRD_PARTY_NOTICES.md)(Copyright (c) 2026 Leon Liu)。
Install
dsh plugin --profile web add github:JollY-Life/jolly-dsh-vision#49747d979dfec51338a1c393402c91d45dcf14cc
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 jolly-dsh-vision from the hub