跪拜 Guibai
← Back to the summary

Give DeepSeek Eyes: A Zero-Dependency Skill for Screenshots in Claude Code, Codex, and Pi

Using DeepSeek in Claude Code / Codex / Pi — How to Make It See Images?

DeepSeek writes code beautifully, and connecting it to Claude Code, Codex, and Pi is becoming increasingly common. But there's one thing you'll repeatedly stumble over: the official API is text-only. You throw in a screenshot of an error, a UI draft, or a photo of interface documentation, and the model either says it can't see it outright or fabricates something based on the filename.

I later built a very small skill: glm-vision. The idea isn't complicated — leave the image recognition to Zhipu GLM, while reasoning and coding remain with DeepSeek. The repository is here: voidman2017/glm-vision

This post explains the problem and the approach clearly: why the official API can't see images, how the skill's architecture is broken down, how to install it, and where its boundaries lie.

The Problem Isn't the Agent, It's the Model Interface

Claude, GPT, and Gemini can natively consume image blocks. In the managed API documentation for DeepSeek V4 Flash / Pro, the input type is text. No matter how smart the coding agent is, if a request carries an image, the upstream will reject it, or the image will be discarded before it reaches the model.

This leads to a very disjointed experience:

Another path in the community is a vision proxy: intercept requests locally, convert images to text, and then forward them to DeepSeek. That path can achieve "paste to view," but it requires changing base_url, keeping a port open persistently, and may stack on top of a Claude reverse proxy you're already running. I wanted something lighter first:

So that left only a skill + one command.

What It Actually Does

When the agent sees an image path, it shouldn't "look" itself, but instead run:

python3 scripts/see.py shot.png -q "What interface is this? Read out the error and key buttons."

see.py encodes the local image into data:image/...;base64,..., hits Zhipu's OpenAI-compatible interface /chat/completions, and hands the description from stdout back to DeepSeek. DeepSeek then decides how to modify the code and how to reply to you.

The whole chain can be drawn as:

flowchart TD
    A[&#34;You @ an image&#34;] --> B[&#34;SKILL.md constrains agent<br/>run see.py first, forbid pixel hallucination&#34;]
    B --> C[see.py]
    C --> C1[&#34;Read ~/.config/glm-vision/env&#34;]
    C1 --> C2[&#34;Retry same model<br/>429 / 1305 / 5xx&#34;]
    C2 --> C3[&#34;Then switch to next model in queue&#34;]
    C3 --> D[GLM outputs text description]
    D --> E[&#34;DeepSeek continues coding / explaining error&#34;]

There are two roles, don't mix them:

flowchart LR
    subgraph eye[Eye]
        G[Zhipu GLM-4.6V-Flash etc.]
    end
    subgraph brain[Brain]
        D[DeepSeek]
    end
    G -->|Text description| D
    D -->|Modify code / explain error| U[You]
Role Who Plays It What It Does
Eye Zhipu GLM-4.6V-Flash etc. Read images, OCR, describe what's on the interface
Brain DeepSeek Reason based on text description, modify code, provide solutions

This is not native multimodality. What DeepSeek receives is "notes taken by someone else after looking," not visual tokens. The upside is the main model doesn't need to change; the cost is that details missed in the description cannot be recovered later. So -q must include the current task, not a vague "describe this image."

The Architecture Has Just Three Parts

The repository is small, deliberately kept as three separated parts.

flowchart TB
    subgraph skill[For the agent]
        S[SKILL.md]
    end
    subgraph cli[Actually sends requests]
        P[scripts/see.py]
    end
    subgraph cfg[Not in git]
        E[&#34;~/.config/glm-vision/env&#34;]
    end
    S -->|Constraint: run script first, forbid hallucination| P
    E -->|API Key / Model queue / Retries| P
    P -->|image_url + description| G[Zhipu /chat/completions]

The sequence for a complete call:

sequenceDiagram
    actor User as You
    participant Agent as DeepSeek Agent
    participant Skill as SKILL.md
    participant See as see.py
    participant GLM as Zhipu GLM

    User->>Agent: @error.png What is this error?
    Agent->>Skill: Match image recognition scenario
    Skill-->>Agent: Must run see.py first
    Agent->>See: see.py error.png -q user's original words
    See->>See: Read env, convert to data URL
    loop Same model retry
        See->>GLM: chat/completions + image_url
        alt 429 / 1305 / 5xx and retries remain
            GLM-->>See: Rate limited
        else Success
            GLM-->>See: Text description
        end
    end
    alt Still fails and queue not empty
        See->>GLM: Switch to next model and retry
        GLM-->>See: Text description
    end
    See-->>Agent: stdout description
    Agent-->>User: Explain and modify code based on description

1. SKILL.md: The manual for the agent

It doesn't handle HTTP. It only specifies:

Human-readable installation instructions go in README.md, not inside the skill package, to avoid stuffing setup steps into the context every time an image is recognized.

2. scripts/see.py: The CLI that does the real work

Only uses Python standard library: urllib, base64, argparse. No need for pip install openai.

It does several specific things:

Default queue:

flowchart LR
    A[glm-4.6v-flash] --> B[glm-4.1v-thinking-flash] --> C[glm-4v-flash]

glm-4.6v-flash is officially free and supports local base64, so it's placed first in the queue. The older glm-4v-flash has a history of "not supporting base64" and can only consume public URLs, so it's placed last; if the error encountered is a format error rather than rate limiting, the script will not continue to fall back, to avoid hitting all models with the same bad request.

The retry strategy is also straightforward:

flowchart TD
    S[&#34;Send request to current model&#34;] --> R{Success?}
    R -->|Yes| OK[Output description]
    R -->|No| T{Retryable?<br/>429 / 1305 / 5xx}
    T -->|Yes and retries remain| W[&#34;Wait 2s / 4s / 8s&#34;] --> S
    T -->|Yes but retries exhausted| N{Next model available?}
    T -->|No format/auth etc.| X[Stop fallback and report error]
    N -->|Yes| M[Switch to next model] --> S
    N -->|No| X

Each model gets at most 1 + VISION_RETRIES attempts; waits double by VISION_RETRY_DELAY. Retryable: HTTP 429 / 500 / 502 / 503 / 504, and Zhipu 1302, 1305.

Free Flash models easily hit 429 during peak hours. If you switch models after a single failure, the primary model is barely used; if you stubbornly stick to one model, the whole conversation round gets stuck. So it's "politely ask twice more first, then switch."

3. ~/.config/glm-vision/env: Keep the key separate

The key does not go into git. The agent and the script read the same config:

VISION_API_KEY=your_zhipu_key
VISION_BASE_URL=https://open.bigmodel.cn/api/paas/v4
VISION_MODELS=glm-4.6v-flash,glm-4.1v-thinking-flash,glm-4v-flash
VISION_RETRIES=2
VISION_RETRY_DELAY=2
VISION_LANG=zh

Lookup order:

flowchart TD
    A{&#34;$VISION_ENV_FILE set and file exists?&#34;} -->|Yes| U[Use that file]
    A -->|No| B{&#34;~/.config/glm-vision/env exists?&#34;}
    B -->|Yes| U2[Use this env]
    B -->|No| C{&#34;~/.config/agent-vision-toolkit/env exists?&#34;}
    C -->|Yes| U3[Compatible with toolkit env]
    C -->|No| E[Missing VISION_API_KEY]

Environment variables in the process take priority over the file.

How to Use

Best recommendation: In the AI era, naturally use magic to defeat magic. Directly provide the repository address to the AI and let it help execute the installation. Of course, manual installation is also possible.

Installation

Clone first, then link to the agent you're using (no need to install for all four):

git clone https://github.com/voidman2017/glm-vision.git
cd glm-vision

ln -sfn "$(pwd)" ~/.claude/skills/glm-vision
ln -sfn "$(pwd)" ~/.codex/skills/glm-vision
ln -sfn "$(pwd)" ~/.pi/agent/skills/glm-vision

Corresponding directories:

Agent Skills Directory
Claude Code ~/.claude/skills/glm-vision
Codex ~/.codex/skills/glm-vision
Pi ~/.pi/agent/skills/glm-vision
OpenCode ~/.config/opencode/skills/glm-vision

Windows can use directory junctions. If you don't want symlinks, use cp -R. Restart the agent after installation; many CLIs only scan skills at startup.

Then configure the Key:

mkdir -p ~/.config/glm-vision
cp assets/env.example ~/.config/glm-vision/env
chmod 600 ~/.config/glm-vision/env
# Fill in VISION_API_KEY; for domestic use, VISION_LANG=zh is recommended

After real-name verification on the Zhipu open platform, you can call the Flash vision model without needing to top up first.

Verify in the Terminal First

python3 scripts/see.py --help
python3 scripts/see.py ~/Desktop/error.png -q "Read the full error text and the bottom buttons"

On success, stderr will show something like info: using model glm-4.6v-flash; stdout is the description for DeepSeek to read.

Common parameters:

# OCR, transcribe text in reading order
python3 scripts/see.py dialog.png --ocr

# Compare two images
python3 scripts/see.py before.png after.png -q "What are the differences in layout and copy?"

# Specify a queue this time, or increase retries
python3 scripts/see.py shot.png --model glm-4.1v-thinking-flash
python3 scripts/see.py shot.png --retries 3 --retry-delay 2

How to Use Inside the Agent

Switch to DeepSeek, then:

@error.png What does this error mean? Where is the relevant code?

When the skill takes effect, the model will first run see.py, then answer based on the description. If you just paste an image into the input box without any path, the skill cannot help — the pixels never landed on disk. Save first, then @.

How to Choose vs. a "Vision Proxy"

glm-vision Local Vision Proxy (e.g., agent-vision-toolkit)
Paste to view No, requires a path Can do
Need to change DeepSeek's base_url No Usually yes, may also layer a local port
Installation footprint skill + one env file Proxy process, auto-start on boot, coordinate with existing reverse proxies
Suitable for Daily @ screenshots, OCR, UI comparison Seamless pasting across three ends, built-in view_image

For my daily use, the skill is enough. People who already have a Codex / Claude reverse proxy and rely heavily on pasting are better suited to the proxy. Both can share the same VISION_* configuration.

A Few Pitfalls Encountered

Free models get rate limited. Zhipu 1305 "current access volume too high" is very common. So the script defaults to trying the same model 3 times, then switching to glm-4.1v-thinking-flash. When all three Flash models are congested, add the paid glm-4.6v to the end of VISION_MODELS as a fallback.

Old Flash models may not accept base64. Images in coding agents are almost always local files, without public URLs. The model at the front of the queue must support data URLs.

This is lossy compression. A line of small text that GLM misses, DeepSeek cannot "take another look" at, unless you have the script run again with a more specific -q. Passing in the user's original words is far more useful than "please describe in detail."

A skill is not magic. It changes the agent's behavioral constraints, not the model's input modality. No path, no image.

Summary

DeepSeek does not currently provide an official image recognition API. Rather than waiting for an interface, it's better to separate "seeing" and "thinking": GLM looks at images, DeepSeek writes code. glm-vision packages this into an installable skill, a single command with no third-party dependencies, plus retries and fallback, so that DeepSeek inside Claude Code / Codex / Pi can at least take a screenshot seriously.

Repository: https://github.com/voidman2017/glm-vision
MIT, welcome stars, issues, and PRs.

Comments

Top 2 from juejin.cn, machine-translated. The original thread is authoritative.

浪浪山_兔子精

I've learned a lot, keep it up [fighting]

取经是重中之重

Not bad, thumbs up for you 👍