jolly-dsh-vision
ModLens 风格的视觉桥接插件:一个纯文本大模型当大脑,一个视觉模型当眼睛(默认 deepseek-v4-pro + deepseek-v4-flash-vision-exp)。
大脑看不到像素。本插件提供两条视觉通路:
vision工具:传入图片路径或 URL,工具把图片存进附件库,通过 harness 的 LLM seam 调眼睛模型,返回一份 modlens 式结构化证据 JSON(summary / ocr / layout / semantics / visual / uncertainty),大脑引用证据作答而不是猜图。- 视觉版模型("(ds vision)" 孪生):在模型选择器里出现
DeepSeek-V4-Pro (ds vision)等视觉孪生条目——选中后可以直接在输入框粘贴/拖入图片,插件在请求发出前自动把图片转成证据文本再交给大脑。
参考实现:liustack/modlens(MIT 许可;本项目的证据词汇与包装器机制派生自它,见 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(发布后)
dsh plugin --profile web add jolly-dsh-vision
从本地路径(开发 / 未发布时)
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:
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 工具
- 给大脑一张图:本地绝对/相对路径,或 http(s) URL,可带关注点。
- 引导区段会让大脑在该调
vision时调用它;大脑基于 OCR 全文与布局作答,看不清的地方会出现在 uncertainty 里。
通路 B:直接贴图 + (ds vision) 孪生模型
- 在模型选择器里选带
(ds vision)后缀的条目。 - 直接在输入框粘贴/拖入图片——准入检查通过,缩略图正常显示。
- 请求发出前,插件自动把图片转成证据文本(带
[Attached image, converted to evidence by the jolly-dsh-vision bridge]标记)注入给大脑;大脑按引导直接引用,不会重复调用工具。 - 同一张图在一次会话内只转换一次(附件内容寻址 + 缓存);转换失败会降级为恒定占位文本并继续对话,细节留在 harness 日志。
注意:模型选择器里的"普通版"纯文本模型依然不接受贴图——想要贴图就选带
(ds vision)后缀的条目。
安全与隐私
- API key 零接触:本插件不读取、不记录、不转发任何密钥;密钥由 harness 凭据 seam 在每次调用时解析,代码不构造
Authorization头、不读process.env。 - 数据出网:图片字节与 OCR 文本会发送到你配置的视觉 provider(这是功能本身)。除此之外插件不发出任何网络请求、不上报遥测。
- SSRF 防护:
vision工具默认拒绝私有网段 / 环回 / 链路本地 / 云元数据地址(含对主机名的 DNS 解析检查),防止恶意提示词诱导请求内网。确有内网需求时把allowPrivateUrls设为true。 - 提示注入:图片里的文字经 OCR 进入对话,恶意图片可能夹带指令。转换块已被框定为"证据,请引用",但无法完全免疫——请把图片视为不可信输入。
- 信任边界:
vision能读取 harness 进程可读的任意图片文件并外发;图片路径(含解析后的绝对路径)会进入会话日志。多人共享会话导出时注意这一点。 - 零运行时依赖:无第三方包,也就没有供应链投毒面。
测试
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 的关系:两者工具名不同(
visionvsmodlens_read_image)、孪生路由不同,可共存。
已知限制 / v2 想法
- 仅支持 png/jpeg/gif/webp;HEIC 需先转码。
- 视觉孪生为单路由实现;modlens 的自动发现多路由、上游重注册刷新(
llm/adapters-updated再扫描)暂未做。 - 未注册设置卡片;配置走 cordis.patch.yml。
License
本项目代码采用 MIT 许可(Copyright (c) 2026 Jolly-Life,见 LICENSE)。
其中派生自 @liustack/modlens 的部分,其 MIT 声明与许可文本见 THIRD_PARTY_NOTICES.md(Copyright (c) 2026 Leon Liu)。
No comments yet. Be the first to write one.