ysr666/dsh-vision-router
ysr666/dsh-vision-router为纯文本 Agent 提供视觉能力:内置免 Key 视觉链 + 像素级视觉工具(看图问答、定位、裁剪、像素对比、取色、OCR、矢量化、抠图、截图);粘贴图片即可用。
本地优先的结构化视觉插件:图片交给本地 OpenAI 兼容 VLM,返回 JSON 证据(摘要、逐字 OCR、版面区域、实体/关系、配色、显式不确定项),带反幻觉回退与可选粘贴/上传桥;零云端成本,图片不出本机。
Local-first vision for text-only DeepSeek Harness agents.
npm package:
dsh-vision-local· source repo:gloryxpnv/dsh-tool-vision
Give a text-only model (DeepSeek, GLM, or any chat model without image input) the ability to see — using a local vision model, with zero API cost and zero image data leaving your machine.
DeepSeek (text-only) ──▶ vision tool ──▶ local VLM (LM Studio / Ollama / any OpenAI-compatible endpoint)
◀── structured JSON evidence ──▶
summary, ocr (verbatim full-text + lines), layout regions with reading order, semantics (entities & relations), visual (colors / style), and an explicit uncertainty list. Your main model quotes specifics instead of guessing.uncertainty; OCR of an image with no text returns an empty field rather than invented words. If the VLM fails to produce valid JSON, the plugin falls back to the raw answer and marks it — never silently fabricates.vision-bridge service lets text-only routes admit pasted or uploaded images: the host hands the image to a local VLM for description before the prompt reaches the model. No read_image gate rejection, no saving to a file first. With keepThumbnail on, the message history keeps the original image thumbnail with the description text right after it; with autoDescribe off (default on), recognition becomes on-demand — the image appears instantly, and the model calls the vision tool only when it needs the content, avoiding double recognition.http://127.0.0.1:1234/v1 by default (LM Studio's default port); every knob — endpoint, model id, token budget, timeouts, image size cap, structured on/off — is a documented config field.vision tool (call it whenever an image path or question is in play) plus an optional vision-bridge service for hosts that want automatic image admission on text-only routes.# in a DSH profile directory (or via the dsh CLI):
dsh plugin --profile web add dsh-vision-local
Then add the plugin row to your profile patch (cordis.patch.yml), or rely on the bundle's own layer — the bundle ships a ready-to-use cordis.patch.yml with sane defaults.
After installing, restart the host (pnpm dsh web or your launch command) so the module is loaded.
/chat/completions endpoint (e.g. LM Studio, Ollama, vLLM, or any gateway).dsh) with the plugin loader.The model sees a vision tool. Any time an image file path or an image question appears, it calls:
vision(file_path: "/path/to/image.png", question?: "What does this show?")
Supported formats: PNG, JPEG, WebP, GIF.
In structured mode (default), answer is a normalized evidence object:
{
"summary": "one-paragraph overview",
"ocr": { "full_text": "every visible character, verbatim", "lines": [{ "text": "line" }] },
"layout": { "regions": [{ "type": "title|paragraph|list|table|chart|form|code|image|icon|link|nav|other", "reading_order": 1, "text": "..." }] },
"semantics": {
"scene": "what kind of scene",
"entities": [{ "name": "...", "type": "person|org|place|object|brand|number|date|other", "evidence": "..." }],
"relations": [{ "subject": "...", "predicate": "...", "object": "..." }]
},
"visual": { "dominant_colors": ["#ffffff"], "style": "...", "notes": ["..."] },
"uncertainty": ["anything the model could not determine"]
}
If the VLM reply cannot be parsed as JSON, answer falls back to the raw text with uncertainty noting the fallback — the tool never invents content.
ctx.provide('vision-bridge', { describeImages(content) }) — lets a text-only host route admit image parts, replacing them with 【name】<VLM summary>(already described by the local vision model; no need to look up the original file). Returns undefined on failure so the host keeps its original behavior.
keepThumbnail: true, image blocks are kept in the message history (the UI renders their thumbnails) with the description text as an adjacent text block. Note this requires a host whose text-only serializer drops image blocks (the model only receives text) — if your host rejects image content on text-only routes, leave keepThumbnail at false (the default), where image blocks are replaced by the description text alone.autoDescribe: false (default true), admission no longer calls the VLM (saves tens of seconds per paste): the image keeps its thumbnail, and only a one-line hint 【图片 <name> 已上传,可用 vision 工具读取工作区中的该文件查看内容】 is injected, so the main model reads the image via the vision tool only when it actually needs it — one deliberate recognition instead of an automatic one plus a follow-up.| Field | Default | Description |
|---|---|---|
baseURL | http://127.0.0.1:1234/v1 | OpenAI-compatible endpoint root (no trailing path) |
model | qwen3.5-9b-vlm | Vision-language model id served by the endpoint |
maxTokens | 8192 | Output token cap; reasoning VLMs burn part of it on thinking |
structured | true | Ask for fixed-shape JSON evidence and return it parsed |
keepThumbnail | false | Keep image blocks in the message history (thumbnails); requires a host whose text-only serializer drops image blocks |
autoDescribe | true | Auto-call the VLM to describe images at admission; false switches to on-demand recognition (hint + model calls vision itself) |
timeoutMs | 180000 | Per-request wall-time cap |
maxImageBytes | 52428800 (50 MB) | Maximum encoded image size accepted |
vision tool resolves the image path against the session workspace (sandboxed fs), reads the bytes./chat/completions.Reasoning-model note: VLMs that emit reasoning_content may stop mid-thought under a tight token budget, leaving content empty. The plugin prefers any non-empty field (content → reasoning_content) and the 8192-token default leaves headroom for both.
MIT