VisionRouter:让任意纯文本模型秒变多模态

背景

很多强悍的大模型是纯文本的——不支持图片输入。比如 DeepSeek、部分 Claude 模型、开源推理模型等。当你在 Claude Code、OpenCode 或其他 AI 编码助手里想发一张截图、报错信息、设计稿给它看时,就直接卡住了。

你手头可能恰好有一个能看图的视觉模型(比如 Qwen-VL、GPT-4o、某个多模态开源模型),但它推理能力不如你的主力文本模型。

问题:如何让纯文本模型"看懂"图片?

方案:一个本地代理,拦截请求中的图片,先丢给视觉模型生成文字描述,再把图片替换成描述文字后转发给文本模型——纯文本模型从此也能"看图"

而且,视觉模型和文本模型可以来自完全不同的供应商


架构

消息+图片

检测到图片

生成文字描述

替换图片为文字

返回结果

AI 助手

本地代理
localhost:3456

视觉模型
可来自不同供应商

文本模型
你的主力模型

核心思路:同一格式直通,不做 OpenAI ↔ Anthropic 跨格式转换。

路径入口端点客户端上游格式
OpenAIPOST /v1/chat/completionsOpenCode / 任意 OpenAI 兼容客户端OpenAI
AnthropicPOST /v1/messagesClaude CodeAnthropic

视觉调用是代理内部发起的,用配置里声明的格式,与客户端入口格式无关。


配置:多 Profile

所有供应商信息集中在 config.json。一个配置可以定义多个 profile,每个 profile 就是一对(文本模型 + 可选的视觉模型)。客户端通过模型名 = profile 名来选择。

{
  "port": 3456,
  "defaultProfile": "deepseek",
  "profiles": {
    "deepseek": {
      "text": {
        "format": "openai",
        "host": "api.deepseek.com",
        "basePath": "/v1",
        "apiKey": "sk-xxx",
        "model": "deepseek-chat"
      },
      "vision": {
        "format": "openai",
        "host": "dashscope.aliyuncs.com",
        "basePath": "/compatible-mode/v1",
        "apiKey": "sk-yyy",
        "model": "qwen-vl-max"
      }
    }
  }
}
  • text:主力文本模型,请求最终转发到这里
  • vision:可选,用于描述图片的视觉模型,可以和 text 不同供应商、不同 key、不同格式
  • 省略 vision:该 profile 不处理图片,请求原样透传
  • 推理深度:模型名后加 -low / -medium / -high 后缀,代理自动剥掉并透传 reasoning_effort

安装

前置条件

  • Node.js v18+

交互式安装(Windows PowerShell)

git clone https://github.com/JoJohanse/VisionRouter.git
cd VisionRouter
.\setup.ps1

脚本会引导你逐个创建 profile(填写 text/vision 的 host、basePath、key、model、format),自动写入 config.json 并复制代理文件。

手动配置(任意平台)

cp proxy/config.example.json proxy/config.json
# 编辑 config.json,填入你的供应商信息
node proxy/server.js

技术实现

配置驱动,零硬编码

// 启动时加载配置,校验 schema
const CONFIG = loadConfig();  // 从 config.json 或 --config 参数读取

// 通用 HTTPS 请求,host/format/key 全部由配置决定
function httpsRequest(host, basePath, endpoint, bodyJson, apiKey, format) {
  const headers = { 'Content-Type': 'application/json' };
  if (format === 'anthropic') {
    headers['x-api-key'] = apiKey;
    headers['anthropic-version'] = '2023-06-01';
  } else {
    headers['Authorization'] = `Bearer ${apiKey}`;
  }
  // ...
}

Profile 解析

// 客户端发的 model 名 = profile 名
// 找不到则回退 defaultProfile,支持 -low/-medium/-high 后缀
function resolveProfile(requestedModel) {
  let variant = null;
  let stripped = requestedModel.replace(/-(low|medium|high)$/, ...);
  stripped = stripped.replace(/-auto-version$/, '');
  
  if (CONFIG.profiles[stripped]) return { profile: CONFIG.profiles[stripped], variant };
  if (CONFIG.defaultProfile)   return { profile: CONFIG.profiles[defaultProfile], variant };
  return { profile: null };
}

图片处理管线

// 检测 OpenAI 格式图片
function openaiHasImages(content) {
  return Array.isArray(content) && content.some(p => p.type === 'image_url');
}

// 用 profile.vision 描述图片(供应商由配置决定,不再硬编码)
async function openaiDescribeImages(textParts, imageUrls, visionEp) {
  const visionContent = [
    { type: 'text', text: `Context: ${textParts.join('\n')}\n\nDescribe the image(s)...` },
    ...imageUrls.map(url => ({ type: 'image_url', image_url: { url } }))
  ];
  
  const result = await httpsRequest(
    visionEp.host, visionEp.basePath, '/chat/completions',
    { model: visionEp.model, messages: [{ role: 'user', content: visionContent }], max_tokens: 4096 },
    visionEp.apiKey, visionEp.format
  );
  
  return JSON.parse(result.body).choices?.[0]?.message?.content || '';
}

// 替换图片为文字描述
async function openaiProcessMessage(msg, visionEp) {
  if (!openaiHasImages(msg.content)) return msg;
  if (!visionEp) return msg;  // 无视觉配置则原样透传
  // ... 提取图片 → 调用 describeImages → 替换为 [Image: 描述]
}

关键变化:之前 host、model、apiKey 全部硬编码为小米的值。现在每一个都从 visionEp / textEp 读取,供应商完全由配置决定。


实际使用

OpenAI 兼容客户端

Base URL: http://127.0.0.1:3456/v1
Model:    <profile-name>      # 如 deepseek
API Key:  任意非空值

Claude Code

$env:ANTHROPIC_BASE_URL = "http://127.0.0.1:3456"
$env:ANTHROPIC_AUTH_TOKEN = "anything"
claude

MCP 自动启停(推荐)

在任意 MCP 客户端注册代理即可随客户端自动启停:

{
  "mcpServers": {
    "vision-router": {
      "type": "stdio",
      "command": "node",
      "args": ["path/to/proxy/mcp-launcher.js"]
    }
  }
}

故障排除

# 检查代理状态 + profile 列表
curl http://127.0.0.1:3456/health

# 检查端口占用
netstat -ano | findstr :3456

# 手动启动看日志
node proxy/server.js

图片未处理?

  • 确认 profile 配置了 vision 块(无 vision 的 profile 会原样透传)
  • 看日志里 [vision/...] 行是否有上游报错

模型未识别?

  • 客户端 model 名必须是 profile 名
  • 找不到时回退到 defaultProfile
  • GET /health 会列出所有可用 profile

格式不匹配?

  • 代理是同格式直通:OpenAI 入口对应 OpenAI 上游,Anthropic 入口对应 Anthropic 上游
  • text.format 必须与客户端入口格式一致

项目结构

VisionRouter/
├── setup.ps1                  # 交互式配置生成器
├── package.json
├── README.md / README.en.md
├── CLAUDE.md
└── proxy/
    ├── server.js              # 代理服务器(OpenAI / Anthropic 双路径)
    ├── mcp-launcher.js        # 通用 MCP 生命周期管理
    ├── start.ps1              # 手动 start/stop/status
    ├── config.example.json    # 配置模板
    └── config.json            # 实际配置(含密钥,gitignored)

链接

  • GitHub: https://github.com/JoJohanse/VisionRouter (如果帮到您,麻烦给个 star ⭐)

总结

VisionRouter 通过本地代理,让任意纯文本模型获得视觉能力。视觉模型和文本模型可以来自不同供应商、不同 API 格式——你只需要一个配置文件。

✅ 零依赖,纯 Node.js stdlib
✅ 多 Profile,混合供应商
✅ 同时支持 OpenAI 和 Anthropic 格式
✅ 交互式安装,开箱即用
✅ MCP 自动启停
✅ 开源免费


转载请注明出处。

Logo

欢迎加入DeepSeek 技术社区。在这里,你可以找到志同道合的朋友,共同探索AI技术的奥秘。

更多推荐