dsh-vision-plugin
> 给 DeepSeek Harness 的纯文本模型装上"眼睛"。
English | 中文
v1.4.0 · MIT License · 兼容 dsh v0.1.0-rc.8(及 rc.7)
---
deepseek-v4 系列是纯文本模型:你可以在对话框里粘贴截图、照片,但它看不见。这个插件在中间加了一层——图片先交给视觉模型(qwen3.7-plus、kimi-k3 等)转写成文字描述,再把描述交给主模型。从此粘贴图片、切换任何文本模型,都能正常"看图"。
效果
你:这是什么? (粘贴了一张 Clash Verge Logo)
助手:这是 Clash Verge 的标志(Logo)。左侧是一个黑色的猫头剪影,
右侧是文字 "Clash Verge"。它是一款基于 Clash 内核的图形化
代理客户端软件……图片由视觉模型自动转写,主模型看到的是文字描述,回答和读图效果一样自然。
三个能力
1. 粘贴图片直接问 —— 对话框粘贴或拖入图片,任意文本模型都能回答图片内容,不需要任何特殊指令。 2. vision_analyze 工具 —— 模型可以主动调用它分析本地图片文件,还能指定问题("图中表格第三行数据是多少?")和具体视觉模型。 3. 设置页配置视觉模型 —— DSH 设置面板新增"视觉模型"页:选默认视觉模型、看当前路由,中英双语、跟随界面语言。
快速开始
永久安装 —— 每次 dsh 启动自动载入
本仓库根目录就是一个 dsh bundle 包(dsh-vision-plugin):宿主半部 [lib/index.js](lib/index.js)、浏览器半部 [lib/client.js](lib/client.js)、 插件行 [cordis.patch.yml](cordis.patch.yml)。把 bundle 注册进你的 profile, dsh 启动时就会自动挂载插件——不需要 cordis_define/cordis_run,重启也不丢。
① 用 dsh plugin 安装。 包已发布到 npm,一条命令搞定——dsh plugin 用 pnpm 装好包后,因为包声明了 dsh.bundle,会自动把它追加进 dsh.profile.bundles:
dsh plugin --profile web add dsh-vision-plugin正在开发插件本身?改用本地检出安装——下次重启 dsh 即生效,无需走 registry:
dsh plugin --profile web add /绝对路径/deepseek-harness-plug
# 或在检出目录里直接:
dsh plugin --profile web add .然后确认行已合成:
dsh --profile web --dump-config # 合成树里应出现 "vision" 行② 声明图片能力。 宿主在消息进入会话前会检查当前模型的输入能力。为了让文本模型收下图片(收下后由插件转写),在 ~/.dsh/settings.yaml 里给它们声明:
llm-pi-ai:
providers:
opencode-go:
apiKeyEnv: OPENCODE_GO_API_KEY
modelOverrides:
deepseek-v4-flash:
input: [text, image]
deepseek-v4-pro:
input: [text, image]
# 其他可能切换到的文本模型同理;原生视觉模型无需声明文件会被自动热加载,不用重启。建议把每个你可能用到的文本模型都写上——否则切到未声明的模型后粘贴图片,会在插件转写之前就被拒绝。
> dsh v0.1.0-rc.8 及以上另有原生方案:内置的 deepseek-official 路由(llm-deepseek)支持配置启用原生图片请求,DeepSeek 模型可以直接在它自己的配置里声明图片能力: > > ``yaml > llm-deepseek: > models: > - id: deepseek-v4-flash > inputModalities: [text, image] > ` > > 插件信任这条路由:在该路由上声明图片能力的模型,设置页里会标记「原生视觉」、以兜底梯队加入自动选择、且图片不再转写(rc.8 适配器会自行强制校验所声明的能力)。pi-ai 路由的 modelOverrides` 仍然只是"放行"机制——这类模型照旧转写,行为与之前完全一致。
③ 重启 dsh。 启动时 loader 会挂载 vision 行:注册 vision_analyze 工具、开启 llm/stream 图片转写瀑布、/vision/api/state 服务设置页,浏览器半部由 /plugins/dsh-vision-plugin/client.js 提供。验证:设置面板出现"视觉模型"页、工具列表里有 vision_analyze,然后粘贴图片开聊。
设置界面
打开 DSH 左下角 ⚙️ 设置 → "视觉模型"页:
- 状态徽章显示运行状态和版本号
- 下拉框选择默认视觉模型(自动选择,或指定某个模型)——保存立即生效,且选择会被记住(浏览器本地存储),下次启动自动恢复
- 底部说明当前生效的模型和路由
界面语言跟随 DSH 设置(通用 → 语言),中英文自动切换。
常见问题
切到其他文本模型后粘贴图片被拒? settings.yaml 里没有给那个模型声明图片能力。按上面步骤 ② 补上即可,热加载立即生效。
没有配置 opencode-go 怎么办? 不影响。v1.1.0 起插件会遍历所有已配置的 provider,自动找到第一个带视觉模型的路由;一个都没有时才会降级(对话继续,提示图片不可用)。
我的 provider 拒绝图片,报 unknown variant \image_url\, expected \text\`? 这个错误说明图片块被发给了只接受文本的模型。自动选择绝不会这么做:只会自动挑受信任的模型——原生视觉白名单(lib/engine.js 里的 NATIVE_VISION_MODELS),以及 dsh v0.1.0-rc.8 起在内置 deepseek-official 路由上声明了图片能力的模型。但设置页会列出所有 provider 上目录声明支持图片的全部模型——受信任的和你在 settings.yaml 里用 modelOverrides 声明图片能力的都算,显式选择(设置页或 vision_analyze 的 model` 参数)会被采纳。如果选中的模型上游实际拒绝图片,调用会自动回退到受信任的视觉模型(一个都没有时降级为占位提示)。如果你用的模型确实支持原生视觉但还没进白名单,把它加进去,自动选择就会优先用它。
DSH 重启后插件还在吗? 会。永久安装把 bundle 注册进 profile 的 dsh.profile.bundles,每次启动都会自动载入——这正是它的目的。
会多花钱吗? 转写会调用一次视觉模型;同一张图、同一个问题有缓存,多轮追问不会重复调用。视觉模型优先级:工具显式参数(model/provider)> 设置页配置 > 自动选择。
我选的视觉模型会被记住吗? 会。v1.2.0 起设置页的选择保存在浏览器(localStorage),下次启动 dsh 自动恢复;自动转写瀑布也走同一路由。宿主侧的路由本身是进程内的。
视觉模型出错了会怎样? 不会中断对话:输出了一部分就保留一部分;完全失败就换备用模型再试一次;视觉调用挂起超过两分钟会被掐断并视为失败(从而触发备用模型重试);还不行就告诉模型"图片暂时不可用",对话照常继续。
Bundle 结构
- [
lib/engine.js](lib/engine.js) —— 共享引擎,单一事实源:vision_analyze工具、llm/stream转写瀑布、路由发现、缓存与超时。刻意零 import(pnpm 不会为link:profile 插件安装依赖)。 - [
lib/index.js](lib/index.js) —— bundle 宿主适配器(组合插件行vision):注册工具与瀑布;/vision/api/state、/vision/api/modelJSON 接口通过ctx.inject(['webServer'], …)条件注册,因此没有 Web 服务的栈(headless——rc.8 的启动审计会直接判挂起等待缺失服务的行)也能正常激活。 - [
lib/client.js](lib/client.js) —— bundle 浏览器半部(dsh.client名册条目):"视觉模型"设置页,由 web shell 在/plugins/dsh-vision-plugin/client.js提供。 - [
cordis.patch.yml](cordis.patch.yml) —— 挂载该 bundle 的 loader 补丁行(dsh.bundle.patch)。 - [
scripts/check.js](scripts/check.js) —— 一致性检查(npm run check):校验版本号在各处一致、lib/engine.js保持零 import。 - [
test/engine.test.js](test/engine.test.js) —— 引擎测试套件(npm test):路由发现、override 优先级、缓存/去重、超时、瀑布、工具。
开发插件本身:改 [lib/engine.js](lib/engine.js)(宿主逻辑)或 [lib/client.js](lib/client.js)(界面),然后 npm run check && npm test。
深入细节(可选阅读)
工作流程:图片进入会话 → llm/stream 监听器判断目标模型——原生视觉模型直接看原图;文本模型则先由视觉模型转写成文字再派发。"原生视觉"指任意路由上的白名单模型,或(dsh v0.1.0-rc.8 起)宿主自有 deepseek-official 路由上目录声明支持图片的模型——该路由的适配器会自行强制校验声明的能力。图片检测是递归的(与宿主一致):嵌套在 tool-result 里的图片、以及助手消息里的图片(来自视觉模型会话的历史)同样会被转写或替换,保证任何图片块都不会到达纯文本模型。转写请求带 Symbol 标记防递归,结果按"图片 + 问题"缓存(TTL + FIFO 上限,并发同图共享一次在途调用)。
设置页会列出所有 provider 上目录声明支持图片的全部模型——不限于 opencode 路由:受信任的原生视觉模型(白名单 + rc.8 deepseek-official),以及你通过 modelOverrides 声明图片能力的模型都显示。只有自动选择走信任门控:resolveVisionRoute 只会挑受信任的模型,因为 pi-ai 路由上仅凭目录的"图片能力"无法和 modelOverrides 广告区分,纯文本模型会在上游直接拒绝图片块(unknown variant \image_url\``)。显式选择则信任用户配置;若所选模型上游失败,现有重试逻辑会自动回退到受信任的视觉模型。
可调常量都在 [lib/engine.js](lib/engine.js) 顶部:DEFAULT_MODEL(兜底模型)、NATIVE_VISION_MODELS(自动选择用的原生视觉白名单)、DEEPSEEK_OFFICIAL_PROVIDER(rc.8 声明图片能力即受信任的路由)、MODEL_PRIORITY(自动选择顺序)、STREAM_TIMEOUT_MS(视觉调用挂起超时)、CACHE_TTL_MS / CACHE_MAX(转写缓存)、CATALOG_TTL_MS(模型目录缓存)。
License
MIT