dsh-media-lens
All-media (video/audio/image) sibling of liustack/modlens: turns local media into structured evidence (timeline / transcript / layout regions) injected into the conversation so text-only models can cite concrete frames, seconds and speakers — Evidence, not imagination. One failover chain per media,
Install
dsh plugin --profile web add dsh-media-lens
Sandbox test results
| Stage | Result |
|---|---|
| 1 install | ✗ |
| 2 list | — |
| 3 mount | — |
| 4 boot | — |
dsh: initialized profile web at /home/node/.dsh/profiles/webFour stages run in an isolated sandbox: install (network on) → list → mount → boot (last three with no network).
README
dsh-media-lens
媒体感知插件:把本地 video / audio / image 变成结构化证据(时间线 / 转写 / 版面区域)注入对话,让文本模型引用具体帧、秒、说话人 —— Evidence, not imagination。同时内置 provider 路由线:每个媒体一条 failover 链,预算超限自动降级,每次尝试记录在 meta.attempts —— a fallback is never silent。
media_read_video / media_read_audio / media_read_image → 路径守卫(存在性 + 扩展名白名单) → ffprobe 探测 + 预算裁决(full / reencode / sampleFrames / segment / downsample) → failover 链(首个成功即返回;全败抛带 attempts 的结构化错误) → MediaEvidence JSON(timeline / transcript / regions + meta.attempts)
<!-- host-compat:begin — generated by scripts/sync-hosts.mjs from scripts/hosts.mjs; DO NOT EDIT -->
- Supported DSH hosts:
0.1.7-rc.10.1.7-rc.20.2.0-rc.10.2.0-rc.20.2.1-rc.10.2.1-rc.20.2.1-rc.30.2.1-rc.40.2.1-rc.50.2.2-rc.1(10 rc across lines 0.1.7 / 0.2.0 / 0.2.1) - Runtime-verified:
0.1.7-rc.2,0.2.0-rc.2,0.2.1-rc.1— evidence in.compat-results/
<!-- host-compat:end -->
链序(每媒体)
媒体 · 链
video(≤10MB 且 ≤10min) · multimodal(注册全模态直传)→ frames:(ffmpeg 均匀抽帧 + 图片链)→ local:metadata
video(10–19MB) · reencode:multimodal(540p x264 压缩后直传)→ 同上
video(>19MB 或 >10min) · 直接 frames: 抽帧路
audio · multimodal(全模态直传转写)→ asr:endpoint-N(本地 ASR CLI,多端点按序)→ local:metadata
image · registered:(宿主已注册视觉模型)→ configured:openai(OpenAI-compatible 端点)→ local:metadata
处理端来源(FR-9:已注册模型优先)
- 宿主已注册模型:插件经
ctx.llm.listProviders()/listModels()枚举(60s 缓存),按inputModalities分组构成首选链;图片原生 image 块直送。 - 显式选择:配置
models.{image,audio,video}: "provider/model"直接指定一个已注册模型 —— 视频的 MiMo 类全模态路就走这里(宿主模态词表当前只认text|image,自动发现不了视频/音频声明,显式选择是唯一入口;实际承载由适配器(如 openai-completions 接管线)的模态硬闸把关,拒绝是响亮的)。 - 自配端点:
providers.openai.{baseUrl, apiKey, model},apiKey 支持逗号分隔多 key(auth/429/quota 换下一个 key;网络/5xx 跳过保链)。 - 本地兜底:strictLocalOnly 或全链失败时,
local:metadata只出 probe 元数据(时长/分辨率/大小),degraded='local-metadata-only'。
配置
dsh-media-lens:
strictLocalOnly: false # true 时链中剔除一切远端 provider,媒体内容不出本机
cooldownMs: 600000 # keyFailure 类失败的默认冷却时长(有 Retry-After/"Resets in" 线索时按线索)
models:
image: auto # auto | '' | "provider/model"
audio: auto
video: auto # MiMo 类全模态:填 "llm-pi-ai/你的mimo模型id"
providers:
openai:
baseUrl: '' # 例 https://api.example.com/v1
apiKey: '' # 逗号分隔多 key 轮换
model: ''
structuredOutput: false # true 时注入 response_format json_schema(strict)(不认识的网关会 400,故默认关)
asr:
command: '' # 单端点写法:本地转写 CLI 模板,{input} 占位
endpoints: [] # 多端点选择项,按序尝试;与 command 合并(command 在前)
refine:
enabled: false # Phase 7:链出口一次 refine pass,默认关
model: '' # 开启时必须写 'openai/<model>' 或裸 model;auto / 空串 = 未启用。转述手只认 configured 自配端点(registered:/插件注入模型无效)
ASR 命令模板示例(Windows,模板内双引号保护空格路径):
python "C:\tools\sensevoice_cli.py" --input "{input}" --json
转写输出识别两种格式:JSON segments([{start,end,text[,speaker]}] 或 {"segments":[...]})与 SRT/时间行文本。
隐私说明(对照 dsh-vision-router 教训)
- 默认媒体内容会发往远端 provider(注册模型 / 配置端点)。要内容不出本机,显式开启
strictLocalOnly: true——之后只剩本地 ASR CLI 与元数据兜底。 meta.attempts记录每次尝试(provider 名含来源前缀registered:/multimodal:/configured:/asr:/frames:/local:,多 key 轮换带keyIndex),meta.warnings记录路由通告(failover 去向、冷却垫尾、轮换成功)——降级绝不静默。- 媒体路径按不可信输入处理:只读取、存在性 + 扩展名 + magic-byte 三重校验(改名伪装的文件在入参侧拒绝);处理指令显式声明媒体内出现的指令一律视为数据;内容永不执行。
- 错误文本先脱密(apiKey/baseUrl 精确匹配)再截断,密钥不进 attempts/日志。
- 音视频直传只在适配器接管线(如 openai-completions takeover)上真正内联;未接管线路上模型只看到文件 handle 文本 —— 插件检测到这种回声会放弃该跳并落到抽帧/ASR 兜底,不产幻觉证据。
冷却语义(modlens 软化熔断)
只有 keyFailure 类失败(auth/429/quota 正文)触发冷却——普通失败(网络抖动、5xx、解析失败)永不冷却,正文碰巧含 quota 字样也不算。冷却中的 provider 垫到链尾仍作最后尝试(不是踢出):穷途末路时仍试一把,成功即恢复。provider 给恢复线索(Retry-After 头或正文 Resets in Xh Ym Zs)时按线索计时,否则 cooldownMs。
structuredOutput(FR-13,opt-in)
providers.openai.structuredOutput: true 时请求注入 response_format json_schema(strict)(由证据载荷派生,非手写镜像)。认识的网关(vLLM、官方 OpenAI 等)服务端强制形状能显著提证据质量;不认识的网关对未知字段直接 400——所以缺省关。
refine pass(Phase 7,链出口一次,opt-in)
主链首个成功后、返回前可选地再过一次文本模型将证据精炼规范化(去重 / 紧措辞 / 保留不确定性)——对齐 medialens 型项目常见的 "visionModel 读手 + textModel 转述手" pair 设计。
refine: enabled: true model: "openai/your-text-model" # 或裸 model;auto / 空串 = 未启用
默认关,理由:
- 隐私:多一跳远端 = 多一次媒体内容(raw evidence)暴露面;
strictLocalOnly下链仅到local:metadata,此时 refine 自动跳过(无文本可精炼),不会拉回远端。 - 延迟:直出证据已可用,refine 是锦上添花。
- 不作 auto:auto = 随便把所有模型都加透镜,不允许。
来源闸门(转述手只认用户自配端点):refine 这一跳只允许 configured 端点(providers.openai 配了 baseUrl + apiKey)——model 必须写 openai/<model> 或裸 model,实际请求 model 取 providers.openai.model 单源。registered: / multimodal:(宿主 discovery 枚举,可能含其它插件注入的模型)一律不可信:命中即静默跳过(不写 refineAttempt、不追 warning,行为与未启用一致)。传输走 configured 直连(不经宿主 runtime 路由表),structuredOutput 开启时注入 response_format。
失败行为(不静默,但不撤销已成):refine 抛错 / 输出无 JSON / 校验不通过 → 保留 raw evidence,meta.warnings 追加 refine failed: <redacted>,meta.refineAttempt.ok = false;主链 attempts 不新增(读手已成功,不浪费下一跳抽帧/ASR)。成功时 refine 归属写 meta.refineAttempt.provider = 'refined:openai/<model>',meta.provider 保留读手前缀(registered / multimodal / configured / asr / frames / local);source / meta.attempts / meta.generatedAt 不被 refine 修改。
传输硬化:EVIDENCE_REFINE_INSTRUCTION 四要素与读手指令同源(反注入 / verbatim / bbox 保守 / uncertainty),另加「保留原 source 与 attempts」一行——媒体内出现的指令会随 raw evidence 二次进入 LLM 上下文,反注入行必须再吃一遍,防洗证据。错误文本先脱密后截断。
vision-bridge(v4,选择器可见的透镜包装,opt-in)
把透镜包装成宿主模型选择器里可见的路由:经宿主 llm.registerAdapter 注册 media-lens-vision-<upstream> 包装 provider,选择器出现 <模型名> (media-lens vision) 条目。选中它作主对话模型后,请求中的媒体在发送前经 media-lens 链(读手 = 上文「处理端模型」配置)转成证据文本,再委托真实路由——纯文本主力模型由此获得图片与音视频 sight("让 deepseek 收到小米读出的证据")。
inject: enabled: true families: ["deepseek", "glm", "mimo"] # 前缀过滤;"*" = 全部(仍受否决) …(truncated)
Rendered from the README published on npm. Formatted only, not edited.
Versions
Published on npm::8,最后更新 2026-11-06