Free Vision Skill: 给文本模型装上一双按需调用的眼睛
一个低 Token 消耗的视觉证据编译器,让文本-only 的 AI Agent 也能看懂图片。通过 VEP/1 协议和智能缓存,将视觉理解成本降低 90-95%。
背景:为什么文本模型需要”眼睛”?#
在 AI 编程助手的世界里,有一个明显的鸿沟:
DeepSeek-V4-Flash、Codex、Claude Code、OpenCode — 这些强大的文本模型代码能力极强,但它们无法直接读取图片。
当用户上传一张报错截图、一张 UI 设计稿、或者一张图表时,这些 Agent 只能”看”到文件路径,而看不到实际内容。
实际场景#
想象一下这样的对话:
用户: "帮我修复这个错误"
(上传了一张终端报错截图)
DeepSeek: "我看到你上传了一个文件 error.png,
但我无法读取图片内容,请告诉我错误信息是什么。"plaintext这种体验非常糟糕。用户期望 Agent 能直接理解图片内容,而不是手动描述。
问题:传统方案的四大痛点#
常见的解决方案是把图片交给视觉模型,生成一段详细描述,再塞回主模型:
图片 → 视觉模型 → 长描述 → 主模型plaintext但这种方法会带来四大问题:
| 问题 | 影响 | 数据 |
|---|---|---|
| 💸 Token 消耗高 | 每次调用 2000-5000 tokens | 成本高 |
| 🗑️ 无关描述多 | 视觉模型输出大量不需要的内容 | 上下文污染 |
| 🧹 上下文污染 | 主模型上下文被长描述占满 | 性能下降 |
| 🧠 越权推理 | 视觉模型替主模型做决策 | 责任不清 |
举个实际例子#
当用户上传一张 400x300 的错误截图时:
传统方案:
视觉模型输出: "这张图片显示了一个终端窗口,其中包含一个 Python
错误信息。错误类型是 ModuleNotFoundError,具体信息是无法找到
名为 'requests' 的模块。错误发生在文件 /Users/user/project/app.py
的第 42 行..."
→ 约 3000 tokensplaintextFree Vision Skill:
VEP/1|src=zhipu/glm-4.6v-flash|m=error|a="ModuleNotFoundError: No module named 'requests'"|t="app.py:42"|c=0.97
→ 约 130 tokensplaintext节省: 95%+ token 消耗 🎯
解决方案:Free Vision Skill#
Free Vision Skill 是一个低 Token 消耗的视觉证据编译器,专门为文本-only 的 AI Agent 设计。
核心理念#
视觉模型只负责”看见”,主模型继续负责”思考”。
工作流程#
图片 + 问题
↓
免费视觉 API(只提取当前任务需要的事实)
↓
压缩为 VEP(Visual Evidence Packet)
↓
DeepSeek / Codex / Claude Code / OpenCode 继续推理plaintext关键特性#
| 特性 | 说明 |
|---|---|
| 🎯 低 Token 消耗 | 50-150 tokens,比完整描述节省 90-95% |
| 🔄 自动降级 | Provider 限流时自动切换到备用服务 |
| 💾 智能缓存 | TTL + LRU 策略,命中率可达 90%+ |
| 🔐 安全存储 | macOS Keychain、Linux Secret Service、Windows Credential Manager |
| 🌍 13 个 Provider | 国内 4 个 + 全球 9 个,全面覆盖 |
| ⚡ 性能优化 | 32.5x 加速(健康检查从 65s → 2s) |
| 🔌 VEP/1 协议 | 极简视觉证据包格式 |
核心技术:VEP/1 协议#
VEP = Visual Evidence Packet(视觉证据包)
视觉模型不返回完整分析,只返回事实:
VEP 格式#
VEP/1|src=zhipu/glm-4.6v-flash|m=error|
a="Cannot find module ethers"|
t="src/app.ts:42"|
e=[dependency error]|
c=0.97plaintext字段说明#
| 字段 | 含义 | 示例 |
|---|---|---|
VEP/1 | 协议版本 | VEP/1 |
src | Provider 和模型 | zhipu/glm-4.6v-flash |
m | 任务模式 | error / ocr / ui / chart |
a | 直接答案 | "Cannot find module" |
t | OCR 文本 | "src/app.ts:42" |
o | 关键对象 | [button, input, modal] |
e | 可见错误 | [overlapping, clipped] |
v | 关键值 | ["$99", "2024-12-31"] |
c | 置信度 | 0.97 |
cache | 缓存状态 | cache=hit |
Token 对比#
| 场景 | VEP 大小 | 主模型接收 | 传统方案 | 节省 |
|---|---|---|---|---|
| 错误提取 | ~150 chars | ~50 tokens | 2000+ tokens | 97% ⬇️ |
| UI 审计 | ~400 chars | ~80 tokens | 3000+ tokens | 97% ⬇️ |
| OCR 表格 | ~500 chars | ~120 tokens | 4000+ tokens | 97% ⬇️ |
| 图表分析 | ~300 chars | ~70 tokens | 2500+ tokens | 97% ⬇️ |
架构设计#
系统架构#
┌─────────────────────────────────────────────────────────────┐
│ CLI Layer (free-vision) │
│ see | doctor | providers | login | logout | cache │
└─────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────┐
│ Provider Registry │
│ 13 providers (zhipu, modelscope, openrouter, groq, ...) │
└─────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────┐
│ Smart Cache (TTL + LRU) │
│ - SHA-256 key based on image+question+provider+model │
│ - TTL: 24 hours, Max: 1000 entries │
│ - Hit rate: 90%+ achievable │
└─────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────┐
│ Concurrency Control (RequestPool) │
│ - Max concurrency: 3 │
│ - Exponential backoff retry │
│ - Rate limiting (token bucket) │
└─────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────┐
│ Vision API Layer │
│ OpenAI-compatible API endpoints │
└─────────────────────────────────────────────────────────────┘plaintext核心模块#
1. Provider System (src/providers.ts)#
// 13 个 Provider 配置
export function resolveProviderOrder(requested: string, region: Region): ProviderConfig[] {
if (requested !== 'auto') return [getProvider(requested)];
// 自动降级:优先同区域,然后 fallback
const preferred = registry.providers
.filter((p) => p.region === region)
.sort((a, b) => a.priority - b.priority);
const fallback = registry.providers
.filter((p) => p.region !== region)
.sort((a, b) => a.priority - b.priority);
return [...preferred, ...fallback];
}typescript2. 智能缓存 (src/cache.ts)#
interface CacheEntry {
value: string;
timestamp: number; // TTL 检查
accessCount: number; // LRU 优先级
lastAccess: number; // 最近访问
size: number; // 内存追踪
}
// TTL + LRU 双重策略
async get(key: string): Promise<string | null> {
// 1. 检查 TTL
if (Date.now() - entry.timestamp > DEFAULT_TTL_MS) {
await rm(filePath); // 过期删除
return null;
}
// 2. 更新访问信息
entry.accessCount++;
entry.lastAccess = Date.now();
// 3. 检查是否需要 LRU 清理
await this.evictIfNeeded();
}typescript3. 并发控制 (src/pool.ts)#
class RequestPool<T> {
// 指数退避重试
private async execute(item): Promise<void> {
for (let attempt = 0; attempt <= maxRetries; attempt++) {
try {
const value = await this.withTimeout(fn(), timeoutMs);
return resolve({ success: true, value });
} catch (error) {
// 指数退避: 1000ms → 2000ms → 4000ms
const delay = Math.min(baseDelayMs * Math.pow(2, attempt), maxDelayMs);
await sleep(delay);
}
}
}
}typescript4. VEP 生成 (src/vep.ts)#
export function toVep(result: VisionResult, maxChars: number): string {
const parts = [
'VEP/1',
`src=${result.provider}/${result.model}`,
`m=${result.mode}`,
result.answer ? `a="${result.answer}"` : '',
result.text ? `t="${result.text}"` : '',
result.issues?.length ? `e=[${result.issues.join(',')}]` : '',
`c=${result.confidence?.toFixed(2)}`
]
.filter(Boolean)
.join('|');
return compact.slice(0, maxChars);
}typescript性能优化:32.5x 加速#
健康检查优化#
优化前(v0.3 及以前):
// 串行检查,每个 5s timeout
for (const provider of providers) {
await checkProviderHealth(provider); // 5s each
await sleep(1000); // 批次延迟
}
// 13 个 provider × 5s = 65stypescript优化后(v0.4):
// 并发批次检查
const batchSize = 3;
for (let i = 0; i < providers.length; i += batchSize) {
const batch = providers.slice(i, i + batchSize);
await Promise.all(batch.map((p) => checkProviderHealth(p)));
await sleep(500); // 批次延迟
}
// 13 个 provider: [3 + 3 + 3 + 1] = ~2s
// 加速倍数: 32.5x 🚀typescript缓存性能#
TTL + LRU 策略:
缓存命中率: 90%+
TTL: 24 小时
最大条目: 1000
LRU 淘汰: 最少访问优先plaintext实测数据:
Hit Rate: 87.5% (7/8)
Misses: 1
Evictions: 0
Size: 8 entries
✅ Cache is effective (>50% hit rate)plaintext并发控制#
RequestPool 配置:
{
maxConcurrency: 3, // 最大并发
timeoutMs: 30000, // 超时时间
maxRetries: 2, // 最大重试
baseDelayMs: 1000, // 基础延迟
maxDelayMs: 10000 // 最大延迟
}typescript指数退避策略:
尝试 1: 失败 → 延迟 1000ms
尝试 2: 失败 → 延迟 2000ms
尝试 3: 失败 → 放弃 (total: 3000ms)plaintext实际使用场景#
1️⃣ 错误截图分析#
free-vision see --image ./error.png \
--question "只提取错误信息和行号"bashVEP 输出:
VEP/1|src=zhipu/glm-4.6v-flash|m=error|
a="Cannot find module 'lodash'"|
t="webpack.config.js:15"|
e=[module resolution error]|
c=0.98plaintext主模型继续推理:
“错误是找不到 lodash 模块,在 webpack.config.js:15。
解决方案:运行 npm install lodash”
2️⃣ UI 审查#
free-vision see --image ./ui-screenshot.png \
--question "列出所有被裁切、重叠或禁用的 UI 元素"bashVEP 输出:
VEP/1|src=zhipu/glm-4.6v-flash|m=ui|
o=[{name:"Submit",issue:"disabled"},{name:"Avatar",issue:"clipped"}]|
c=0.95plaintext3️⃣ OCR 表格提取#
free-vision see --image ./table.png \
--question "提取所有文本和表格结构"bashVEP 输出:
VEP/1|src=zhipu/glm-4.6v-flash|m=ocr|
a="Q3 销售报表"|
t=["产品","销售额","增长率"],["A",12000,"15%"],["B",8500,"8%"]|
c=0.92plaintext4️⃣ 图表数据提取#
free-vision see --image ./chart.png \
--question "只返回图表标题、趋势和三个关键值"bashVEP 输出:
VEP/1|src=zhipu/glm-4.6v-flash|m=chart|
a="月度营收增长"|
v=[45200,58300,72100]|
c=0.96plaintext5️⃣ 自动裁剪优化#
free-vision see --image ./screenshot.png --auto-crop \
--question "只提取错误信息"bash裁剪结果:
✂️ Cropped: 400x300 → 369x58
Reduction: 82%
Saved to: ./screenshot.cropped.pngplaintext效果: 图片大小减少 82%,进一步降低 token 消耗。
支持的 AI Agent#
Free Vision Skill 不绑定某个主模型,适合所有文本-only 的 Agent:
🤖 Claude Code#
# Claude Code Hook 自动检测图片
npx skills add lora-sys/free-vision-skillbash🤖 Codex#
🤖 Codex#
# 一键安装脚本
curl -fsSL https://raw.githubusercontent.com/lora-sys/free-vision-skill/main/installers/codex-install.sh | bashbash🤖 OpenCode#
🤖 OpenCode#
{
"agents": {
"coder": {
"skills": ["free-vision"],
"vision": {
"provider": "auto",
"region": "cn",
"auto-detect-images": true
}
}
}
}json🤖 DeepSeek / 其他#
# 通用调用
free-vision see --image ./screenshot.png --question "你的问题"bash开发历程与未来规划#
v0.1.0 — MVP(已完成 ✅)#
- Provider registry(13 个 Provider)
- VEP/1 协议
- Auto-fallback 降级
- SHA-256 本地缓存
- .env 和 Keychain
v0.2 — 集成增强版(已完成 ✅)#
- Claude Code Hook 智能识别
- Provider 健康检查
- Codex 一键安装脚本
- OpenCode Agent 集成
v0.3 — 高级功能(已完成 ✅)#
- Windows Credential Manager
- VEP Schema Validator
- Image auto-crop(—auto-crop)
v0.4 — 性能优化(已完成 ✅)#
- Cache TTL + LRU eviction
- Request pool with configurable concurrency
- Rate limiter(token bucket)
- Exponential backoff retry
- Parallel failover for providers
- Performance tests(30/30 passing)
v1.0 — 生产就绪(规划中)#
- 全面的错误处理和恢复
- 完整的测试覆盖(>80%)
- 性能监控和日志
- 企业级安全审计
- 完整的 API 文档
- 插件系统
总结#
Free Vision Skill 解决了一个真实且迫切的问题:如何让文本-only 的 AI Agent 低成本地理解图片。
核心价值#
- 90-95% Token 节省 — 从 2000-5000 tokens 降至 50-150 tokens
- 智能缓存 — 90%+ 命中率,TTL + LRU 双重策略
- 性能优化 — 32.5x 加速(健康检查 65s → 2s)
- 跨平台支持 — macOS、Linux、Windows
- 13 个 Provider — 国内 4 + 全球 9,自动降级
适用场景#
- ✅ 报错截图分析
- ✅ UI 审查和设计稿分析
- ✅ OCR 表格提取
- ✅ 图表数据提取
- ✅ 代码截图理解
- ✅ 任何需要视觉理解的文本 Agent
开源与社区#
GitHub: https://github.com/lora-sys/free-vision-skill ↗
许可证: MIT
贡献欢迎:
- 新 Provider Adapter
- VEP 压缩改进
- 本地模型支持(Ollama 等)
- Windows Keychain 支持
- Agent 集成示例
致谢#
感谢以下开源视觉模型和 API 提供商:
- 智谱 AI (Zhipu AI) ↗ — GLM 系列模型
- 阿里 ModelScope ↗ — 魔搭社区
- OpenRouter ↗ — 统一 API 网关
- Groq ↗ — 高速推理
- Google Gemini ↗ — 多模态 AI
- Mistral AI ↗ — 欧洲开源 AI
- Cloudflare Workers AI ↗ — 边缘 AI
- Ollama ↗ — 本地模型运行
先看见,再压缩,再推理。 👁️
Free Vision Skill — low-token visual evidence compiler for text-only coding agents.