VisionRouter:让任意纯文本模型秒变多模态
VisionRouter:让任意纯文本模型秒变多模态
背景
很多强悍的大模型是纯文本的——不支持图片输入。比如 DeepSeek、部分 Claude 模型、开源推理模型等。当你在 Claude Code、OpenCode 或其他 AI 编码助手里想发一张截图、报错信息、设计稿给它看时,就直接卡住了。
你手头可能恰好有一个能看图的视觉模型(比如 Qwen-VL、GPT-4o、某个多模态开源模型),但它推理能力不如你的主力文本模型。
问题:如何让纯文本模型"看懂"图片?
方案:一个本地代理,拦截请求中的图片,先丢给视觉模型生成文字描述,再把图片替换成描述文字后转发给文本模型——纯文本模型从此也能"看图"。
而且,视觉模型和文本模型可以来自完全不同的供应商。
架构
核心思路:同一格式直通,不做 OpenAI ↔ Anthropic 跨格式转换。
| 路径 | 入口端点 | 客户端 | 上游格式 |
|---|---|---|---|
| OpenAI | POST /v1/chat/completions | OpenCode / 任意 OpenAI 兼容客户端 | OpenAI |
| Anthropic | POST /v1/messages | Claude Code | Anthropic |
视觉调用是代理内部发起的,用配置里声明的格式,与客户端入口格式无关。
配置:多 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 自动启停
✅ 开源免费
转载请注明出处。
更多推荐



所有评论(0)