DeepSeek Harness Hub
← 返回列表

视觉模型桥接aijunjiang/dsh-plugin-vision

DeepSeek Harnessspec-screened在 GitHub 查看 ↗
未验证

让纯文本 agent 借视觉模型看懂会话图片

尚未跑自动兼容性验证,可查看页面内的依赖与入口分析。 · 最近上游提交 2026/9/3 · 已提供中文文档

通过任何兼容 OpenAI 的视觉模型为你的 DSH 智能体装上眼睛——11 个提供商预设(豆包/Qwen-VL/GLM-V/OpenAI/Gemini/Ollama……),能力复选框会注入实时提示词引导,并分析用户拖入聊天的图像;智能体自行编写观察提示词,而 base64 永远不会进入其上下文。

综合分
29.4
GitHub 分
29.4
用户评分
★ Stars
1
周下载量
安装插件(需先安装 dsh CLI 引擎:npm install -g @deepseek-ai/dsh)
dsh plugin --profile web add aijunjiang/dsh-plugin-vision
该插件未发布到 npm,走 GitHub 源安装(pnpm 若拦截 prepare 脚本,按其提示在 pnpm-workspace.yaml 的 allowBuilds 中放行后重跑)
数据截至 2026/9/16(元数据每日更新 · 实装验证按队列轮转,单条结论的验证时间见上方)
依赖的 DSH / Cordis 模块
@deepseek-ai/cordis@deepseek-ai/schemastery@deepseek-ai/dsh-settings@deepseek-ai/dsh-tools
用户评分
还没有人投票,来当第一个
订阅周报,不错过优质插件更新
每周一封 · 高评分插件 + 新用户活动

README

dsh-plugin-vision

English · 简体中文

给 DSH 的 agent 装一双眼睛。

主模型是纯文本的时候,会话里的图片对它来说只是一行
[image omitted because this model accepts text only; attachment sha256:1a2b3c4d]。
这个插件把那道缺口补上:agent 自己写观察指令,把图片交给在线视觉大模型(VLM)去看,拿回文字结论。

- 多供应商:统一按 OpenAI 兼容的 POST {baseUrl}/chat/completions 调用,内置 11 家预设,
也可填任意自定义 endpoint。
- 能力勾选即提示词:在设置卡片里勾选该模型的特色能力(检测框 / OCR / 文档图表 / 视频 / GUI /
深度思考 / 多图对比…),这些说明会实时注入 agent 的系统提示,告诉它「这台模型还能干什么、
该怎么提要求」。
- 提示词归 agent:插件不预置「请描述这张图片」之类的模板。观察什么、要多细、输出什么格式,
由 agent 现场撰写。
- 看得见会话里的图:用户在输入框上传的图片,agent 可以直接用 latest 或占位符里那串 8 位摘要点名分析。
- 密钥进凭据库:API Key 存在 ~/.dsh/.credentials.yaml,界面只显示「已配置 / 未配置」,永不回显字面量。
- 界面双语:设置卡片跟随 DSH 自身的语言设置切换(中文 / English)。

安装

方式 A:官方 dsh plugin add(推荐)

⚠️ 不要执行 dsh plugin add dsh-plugin-vision。 这个裸包名会去公共 npm registry 解析,而
npm 上的 dsh-plugin-vision 是另一位作者的无关项目,不是本插件,且与当前版本的 DSH 不兼容。
请始终从本仓库安装。

dsh plugin --profile web add github:aijunjiang/dsh-plugin-vision
或从本地检出安装:dsh plugin --profile web add

内部 @deepseek-ai 依赖全部走 DSH 基线的可选 peer,npm 一个包都不会拉;
声明了 dsh.bundle 的包会自动注册进 profile 的 bundles 层。
卸载:dsh plugin --profile web remove dsh-plugin-vision。

装完重启一次 dsh web,设置卡片才会出现。

方式 B:本地开发热装载

把仓库以包名链接进目标 profile 的 node_modules,再在该 profile 的 cordis.patch.yml 里插一行:

Windows(web profile 为例)
New-Item -ItemType Junction
-Path  "$env:USERPROFILE\.dsh\profiles\web\node_modules\dsh-plugin-vision"
-Target "C:\path\to\dsh-plugin-vision"

~/.dsh/profiles/web/cordis.patch.yml
- insert:
- id: vision-bundle
name: dsh-plugin-vision
config:
provider: ark
apiKeyEnv: ARK_API_KEY

web profile 是 live-reload:保存补丁文件,配置改动(供应商、模型、baseUrl…)立刻生效,不用重启。
但 插件源码(index.js / catalog.js)的改动不会热更——模块被 ESM 缓存住了,改完要重启 dsh web。
设置卡片属于客户端半,需要刷新一次页面;首次安装后如果卡片没出现,重启 dsh web 即可。

⚠️ Windows 上改这个 YAML 千万别用 PowerShell 5.1 的 Get-Content / Set-Content -Encoding utf8:
前者按 GBK 解码会毁掉中文注释,后者会写入 BOM,两者都会让整个补丁层解析失败、所有插件一起掉线。
用 [System.IO.File]::ReadAllText/WriteAllText($p, $t, (New-Object System.Text.UTF8Encoding($false)))。

配置

设置 → 插件 → 视觉能力。

| 项 | 说明 |
| --- | --- |
| 供应商 | 切换即带出该家的默认 endpoint、模型与能力勾选 |
| 模型 ID | 留空 = 用预设模型。模型 ID 会随厂商迭代变化,过期了就在这里改 |
| Base URL | 留空 = 用预设 endpoint;私有部署 / 代理在这里填 |
| API Key | 写进 DSH 凭据库;留空表示不改动已存密钥 |
| 特色能力 | 勾选项注入 agent 系统提示;不勾就不会告诉 agent 该模型有这个本事 |
| 高级 → 细节档位 | auto / high(看小字更准)/ low(更快更省) |
| 高级 → 深度思考 | 不传 / auto / enabled / disabled,仅对支持的供应商生效 |
| 高级 → 单次最多图片数 | 默认 6 |
| 高级 → 超时(毫秒) | 默认 120000 |
| 高级 → 凭据引用名 | 密钥在凭据库里的键名,也可直接 export 同名环境变量 |

密钥解析顺序:设置里的 apiKey 字段 → DSH 凭据域(apiKeyEnv 指向的引用)→ 同名环境变量。

内置供应商预设

| id | 供应商 | 默认 endpoint | 默认模型 |
| --- | --- | --- | --- |
| ark | 火山方舟(豆包 Doubao) | https://ark.cn-beijing.volces.com/api/v3 | doubao-seed-1-6-vision-250815 |
| dashscope | 阿里百炼(通义千问 Qwen-VL) | https://dashscope.aliyuncs.com/compatible-mode/v1 | qwen3-vl-plus |
| zhipu | 智谱 BigModel(GLM-V) | https://open.bigmodel.cn/api/paas/v4 | glm-4.6v |
| openai | OpenAI | https://api.openai.com/v1 | gpt-4o |
| gemini | Google Gemini(OpenAI 兼容端点) | https://generativelanguage.googleapis.com/v1beta/openai | gemini-2.5-flash |
| moonshot | 月之暗面 Kimi | https://api.moonshot.cn/v1 | kimi-k3 |
| stepfun | 阶跃星辰 StepFun | https://api.stepfun.com/v1 | step-1o-turbo-vision |
| siliconflow | 硅基流动 SiliconFlow | https://api.siliconflow.cn/v1 | Qwen/Qwen2.5-VL-72B-Instruct |
| openrouter | OpenRouter(聚合) | https://openrouter.ai/api/v1 | qwen/qwen2.5-vl-72b-instruct |
| ollama | 本地 Ollama / vLLM(OpenAI 兼容) | http://127.0.0.1:11434/v1 | qwen2.5vl:7b |
| custom | 自定义(任意 OpenAI 兼容服务) | (自己填) | (自己填) |

预设是起点不是牢笼:任何一家的 endpoint / 模型都能在卡片里改。模型 ID 更新很快,以厂商文档为准。

agent 怎么用

vision_analyze

| 参数 | 必填 | 说明 |
| --- | --- | --- |
| images | ✅ | 图片来源数组 |
| prompt | ✅ | 观察指令,由 agent 自己撰写 |
| system | | 给 VLM 的角色设定 |
| model | | 临时覆盖本次模型 |
| detail | | auto / high / low |
| thinking | | auto / enabled / disabled |
| max_tokens | | 本次回复上限 |

images 每一项可以是:

| 写法 | 含义 |
| --- | --- |
| C:\pic\a.png、/home/u/a.jpg | 本地文件;经 ctx.fs 读取,尊重沙箱与远端路由;无扩展名也能按魔数识别 |
| https://… | 远程 URL,原样交给 VLM 自己抓 |
| data:image/png;base64,… | 直传 |
| json:/路径/bundle.json | JSON 图包:base64 存在文件里,一次可带多张,见下节 |
| sha256:1a2b3c4d | 会话附件摘要,支持前缀——文本模型占位符里那 8 位就够 |
| latest / latest:3 | 本会话最近上传的 1 张 / 3 张图 |

vision_list_images

无参数。列出当前会话里出现过的全部图片附件(名称、尺寸、类型、摘要),
让 agent 在只看到 [image omitted …] 时也知道有哪些图可看、该点名哪一张。

传图纪律(已写进 agent 的系统提示,并在运行时强制)

| 别这么干 | 该这么干 | 为什么 |
| --- | --- | --- |
| 起个本地/局域网图片服务,把 http://192.168.1.7/a.png 交给在线模型 | 直接把本地路径写进 images | 在线 VLM 在公网,它回不到你的内网。插件会在发请求前拦下私有地址(127.0.0.1 / localhost / 10.x / 192.168.x / 172.16–31.x / 169.254.x / .local)并告诉 agent 怎么改 |
| 把远端设备上的路径直接丢给模型 | 先取回本地,再传本地路径 | 远端主机的本地路径对公网模型同样不可达 |
| 自己 read 出 base64 拼成 data URL 传进来 | 传路径,或用 sha256: 摘要 / latest 点名 | 那串字节会完整落进 agent 的上下文,既挤占上下文又白花 token;插件读同一张图不占 agent 一个 token。真塞了大段 base64(≥20000 字符)时,工具会在结果末尾提醒它下次换传法 |

例外:当 baseUrl 本身就指向本机模型(Ollama / vLLM)时,模型与图片在同一张网里,私有地址是合法的,插件不会拦。

JSON 图包:手里只有 base64 时的正道

有时确实拿不到文件——远端 API 返回的、数据库字段里的、脚本现算的。这时不要让 agent 把 base64 打印出来,
而是在产生它的那一侧写成 JSON 文件,agent 只传一行路径:

{
"images": [
{ "name": "首帧", "data": "iVBORw0KGgoAAAANSUhEUg..." },
{ "name": "尾帧", "data": "iVBORw0KGgoAAAANSUhEUg..." }
]
}

images: ["json:/tmp/bundle.json"]        整包
images: ["json:/tmp/bundle.json#2"]      只要第 2 张
images: ["json:/tmp/bundle.json#2-4"]    第 2 到第 4 张

省了多少:实测 3 张 200×150 的 PNG,

| | 字符数 | 约合 token |
| --- | ---: | ---: |
| 拼成 data URL 走 images | 297,373 | ~74,000 |
| json:/tmp/bundle.json | 75 | ~20 |

模型收到的图一模一样,省掉的是 agent 上下文里那份副本。

解析器刻意写得宽松(Postel 原则),下面这些生产方写法都认:

| 位置 | 接受的写法 |
| --- | --- |
| 顶层 | {"images":[…]}、{"items"/"list"/"data"/"frames":[…]}、顶层直接是数组、单张时直接一个对象 |
| 条目 | 对象,或直接一串 base64 字符串 |
| 图片字段 | data / base64 / b64 / content / bytes / image / dataUrl;值可带 data:image/...;base64, 前缀、可带换行、可是 URL-safe base64 |
| 替代来源 | path(相对路径按 JSON 文件所在目录解析)或 url(同样受私有地址拦截约束) |
| 类型 | mediaType / mime / type 等可以完全省略——插件按字节魔数嗅探;声明错了以嗅探为准,并在标签里标注 |
| 名字 | name / label / title / id 等,会写进图片标签,方便在 prompt 里用「第 2 张」指代 |

生产方一行就能写出来:

远端:把当前目录若干 PNG 打成图包
python3 -c "
import base64, glob, json
print(json.dumps({'images':[{'name':p,'data':base64.b64encode(open(p,'rb').read()).decode()} for p in sorted(glob.glob('.png'))]}))
" > bundle.json

// Node:把内存里的 Buffer 直接落盘成图包
fs.writeFileSync('bundle.json', JSON.stringify({
images: buffers.map((b, i) => ({ name: frame-${i}, data: b.toString('base64') })),
}))

约束:单图仍受单图字节上限约束,展开后的总张数仍受单次最多图片数约束,整包读取上限 128MB;
图包里不能再引用另一个图包(禁止套娃)。

坐标纪律

任何涉及位置的问题,都要在 prompt 里写死输出格式并要求模型回报它处理的图像宽高,否则坐标无法还原:

只输出 JSON:[{"label":"...","bbox_2d":[x1,y1,x2,y2]}]
并在 JSON 前用一行说明你处理的图像像素宽高,以及坐标是绝对像素还是归一化值。

各家约定并不一致(已知:Qwen-VL 返回处理后图像的绝对像素 bbox_2d;GLM-V 返回 0–1000 归一化
且包在  …  里),所以「让模型自报基准」比「假设某种基准」可靠。

排错

| 现象 | 原因 / 处理 |
| --- | --- |
| 未配置视觉模型 API Key | 卡片里保存密钥,或 export apiKeyEnv 指定的环境变量 |
| HTTP 401 AuthenticationError | 密钥取到了但不被该 endpoint 认可——注意「搜索 API 密钥」和「模型 API 密钥」通常不是同一把 |
| HTTP 404 / model not found | 模型 ID 过期或该账号无权限,在卡片里换一个 |
| 只有思考内容、没有正文 | 思考型模型把 token 全花在推理上了:调大 max_tokens,或把 thinking 设为 disabled |
| 按摘要 xxx 没找到附件对象 | 先调 vision_list_images 确认摘要;跨会话的图不在当前会话事件里 |
| 字节不是受支持的图片格式 | 只支持 PNG / JPEG / WebP / GIF / BMP |
| 卡片不出现 | 客户端半需要刷新页面;仍无则重启 dsh web |
| 所有插件一起消失 | 多半是 profile 的 cordis.patch.yml 被写坏(BOM / 编码),见上文警告 |

开发

npm run sync    # 用 catalog.js 重新生成 client.js 里的 CATALOG 镜像
npm test        # 宿主逻辑 + 执行路径 + 卡片离线渲染(不开浏览器)
npm run check   # 语法检查 + 校验镜像同步 + 跑全部测试

- catalog.js —— 供应商与能力清单,唯一真相源。加一家供应商只改这里,然后 npm run sync。
- index.js —— 宿主半:两个工具、配置命名空间、密钥解析、图片解析、动态系统提示段落。
- client.js —— 客户端半:设置卡片与它的 zh / en 词典。客户端 bundle 不允许 import,
故 CATALOG 以生成的方式内联。
- tests/session-images.test.mjs —— 会话图片枚举(latest / 摘要点名的地基):覆盖宿主
session-controller 走的全部五条事件路径(data.content、data.message.content、data.inserted[]、
assistant/chunk 的 block-end、嵌套 tool-result),外加去重、顺序、脏数据与配置归一化、提示词注入。
- tests/execute.test.mjs —— 起一个本地 OpenAI 兼容假端点,把 execute() 完整跑通:断言请求体
(鉴权头、各家 detail/thinking 映射、多图标注、extraBody 合并)与全部响应/错误分支
(分片 content、思考截断、只有思考没有正文、模型拒答、HTTP 500、非 JSON、缺 Key、张数超限、中止)。
- tests/client-card.test.cjs —— 用 react-dom/server 把卡片渲染成静态 HTML,断言官方卡片结构、
中英两种语言、zh/en 键集对齐、高级选项默认收起,以及保存设置与写凭据的调用序列。

三个测试都会在依赖缺失时自动跳过(退出码 0):index.js 需要 @deepseek-ai/schemastery,
卡片测试需要 react / react-dom(浏览器端由 DSH 平台注入,本地可从 DSH profile 的
node_modules 解析)。

想在没有真实密钥时联调?

起一个 OpenAI 兼容的假端点,把 baseUrl 指过去、apiKey 随便填,就能验证请求体与全链路:

// mock.cjs —— 收到什么就落盘什么,然后回一段固定答复
require('node:http').createServer((req, res) => {
const chunks = []
req.on('data', c => chunks.push(c))
req.on('end', () => {
require('node:fs').writeFileSync('last-request.json', Buffer.concat(chunks).toString())
res.writeHead(200, { 'Content-Type': 'application/json' })
res.end(JSON.stringify({ choices: [{ message: { content: 'MOCK OK' }, finish_reason: 'stop' }] }))
})
}).listen(8791, '127.0.0.1')

License

MIT

上游仓库有新提交时邮件通知你(每天最多一封,无更新不打扰),随时一键退订。

💬 加入 DPharness 群聊

插件用法、部署报错、新插件第一时间同步——群里问,比一个人翻文档快。

点击加入 QQ 群
DPharness 群聊二维码,手机 QQ 扫码进群
扫码进群