Skip to content
dsh.fish
Bundle

dsh-image-bridge

DSH 图片理解桥接插件:挂 agent/pre-step,把消息中的图片块经视觉模型(默认智谱 GLM-4V-Flash 免费档)转成文字描述,让纯文本模型(DeepSeek)也能看图

Source
Seryta
License
MIT
Updated
Updated yesterday

Readme

# dsh-image-bridge — 图片理解桥接

DSH(DeepSeek Harness)插件:把用户在 Web GUI 里附加到消息中的图片,用视觉模型
(默认智谱 GLM-4V-Flash,免费)转成文字描述后注入对话,让 DeepSeek 这类纯文本
模型也能"看见"图片。

## 原理(挂载点)

- 拦截点:`agent/pre-step` 瀑布事件。它在模型请求组装前运行,返回的
  `PreStepDecision.messages` 就是进入本步的消息列表,可在进入前替换。
- 图片数据结构:消息内容块里 `{ type: 'image', attachment: ImageAttachmentRef }`;
  图片字节由附件服务(`ctx.attachments.readImage`)按内容寻址引用取出。
- 成功时把图片块替换为 `[图片描述] …` 文本块,文本块原样保留。
- 图片准入:若所选模型声明 `inputModalities: ['text']`,harness 会在消息进入
  循环前拒绝图片。需要配套一个补 image 模态声明的插件(见
  [llm-deepseek-image-admit](https://github.com/Seryta/llm-deepseek-image-admit),
  或任何同类 provider 包装)。

## 韧性策略

1. **单模型重试**:429/503 指数退避重试(默认 2 次:1s、2s);
2. **模型回退**:404、重试耗尽、其他 5xx 换下一个模型;默认免费档降级链
   `glm-4.6v-flash → glm-4.1v-thinking-flash → glm-4v-flash`;thinking 模型的
   `<think>` 推理块自动剥离;
3. **失败降级**:全部失败时图片块替换为 `[图片识别失败] …` 说明文本——本轮
   继续跑,模型如实告知用户;不放过图片块触发 `UNSUPPORTED_CONTENT` 整轮失败。

## 功能特性

- **attachmentId 转译缓存**(单图消息):同一张图在后续步骤不再重复识别;
- **体积/分辨率上限**:超 `maxImageBytes`(默认 10MB)或 `maxImagePixels`
  (默认 40MP)直接失败说明,不送重试链;
- **tool-result 嵌套图片递归转录**;
- **content parts 数组健壮性**:网关返回 `[{text}]` 数组时按序拼接;
- **四点式逐字转写 prompt**:逐字文字 → 版面结构 → 视觉元素 → 其他细节,
  完整优先于简洁;
- **多厂商凭证 env 名**:`ZHIPU_API_KEY / ZHIPUAI_API_KEY / VISION_API_KEY /
  DASHSCOPE_API_KEY` 依次尝试。

## 配置

| 项 | 环境变量 | 默认 | 说明 |
|---|---|---|---|
| 模型 | `IMAGE_BRIDGE_MODEL` | `glm-4v-flash` | 主模型名 |
| 回退模型 | `IMAGE_BRIDGE_FALLBACKS` | `glm-4.6v-flash,glm-4.1v-thinking-flash,glm-4v-flash` | 逗号分隔,依次尝试 |
| 重试次数 | `IMAGE_BRIDGE_RETRIES` | `2` | 单模型在 429/503 上的额外重试次数 |
| 描述语言 | `IMAGE_BRIDGE_LOCALE` | `zh` | `zh` 中文 / 其他英文 |
| 端点 | `IMAGE_BRIDGE_BASE_URL` | 智谱 OpenAI 兼容端点 | 换任意 OpenAI 兼容 VLM(配对应 key env 名) |
| 图片字节上限 | `IMAGE_BRIDGE_MAX_IMAGE_BYTES` | `10485760`(10MB) | 超限直接失败说明 |
| 图片像素上限 | `IMAGE_BRIDGE_MAX_IMAGE_PIXELS` | `40000000`(40MP) | width×height 超限直接失败说明 |
| 单次请求超时 | —(仅 cordis config `attemptTimeoutMs`) | `30000` ms | |
| 总预算 | —(仅 cordis config `budgetMs`) | `75000` ms | 单条消息识别总时间上限 |

凭证读取顺序:环境变量 → `ctx.credentials.resolve` → `~/.dsh/.credentials.yaml`
兜底解析(极简 `KEY: value` 行)。key 绝不进入日志。

## 安装

```sh
dsh plugin --profile web add github:Seryta/dsh-image-bridge
```

安装后重启 `dsh web`。纯手写 JS,零构建、零第三方依赖,仅用 Node 内置模块。

## 验证

```sh
# 单元自检(无网络、不读真实凭证,12 条路径)
node index.test.mjs
```

覆盖:成功替换、503 退避重试、404 回退、重试耗尽降级+notice、空响应降级、
无图片放行、无 key 降级、单图缓存命中、体积超限、`<think>` 剥离、
parts 数组拼接、tool-result 嵌套递归。

## 与同类插件的差异

| 项目 | 形态 | 本插件的取舍 |
|---|---|---|
| [dsh-vision-proxy](https://github.com/Flyvhidbwo/dsh-vision-proxy) | provider-route 包装 + 请求流内转录 | 本插件挂 `agent/pre-step` 重写消息,不改 provider 路由;多模型统一生效 |
| [dsh-vision](https://github.com/dsh-external/dsh-vision) | `view_image` 工具 | 工具形态模型需主动调用;本插件自动转录、模型无感 |
| [modlens](https://github.com/liustack/modlens) | read_image 工具 + 模型变体 + 结构化 evidence | 本插件不引入模型变体与 schema 约束,零配置零构建 |

## 已知限制

- 一次消息里多张图片合并为一次视觉 API 调用、一段描述;转译缓存按**有序
  attachmentId 组合键**命中(同组同序的图片每步不重复识别;换序视为不同
  组合、重新识别)。
- 缓存按 attachmentId 存描述文本(上限 100,FIFO 淘汰);失败结果不缓存。
- 图片不做压缩/缩放预处理;超限图片直接失败说明而非自动压缩。
- 免费档 VLM 可能限流;重试/回退后仍失败走失败说明路径,对话不受影响。

## License

MIT

Install

dsh plugin --profile web add github:Seryta/dsh-image-bridge

Profile: web

  • This source has no pinned commit, so a later push upstream changes what installs. Prefer pinning a commit.
Source