多模态大模型 API 实战:图片理解能力对比与接入指南

16 阅读8分钟

多模态能力已经成为大模型的标配。从 OCR 识图到图表分析,从代码截图到复杂场景描述,国产多模态模型的能力在过去一年里突飞猛进。本文梳理主流国产多模态模型的 API 接入方式,用真实测试场景对比各自的输出质量,并给出选型建议。


主流国产多模态模型一览

模型提供方特点
Qwen-VL-Max阿里云(通义)中文理解强,支持高分辨率,文档理解能力突出
DeepSeek-VL2DeepSeek开源模型,推理能力强,细节描述准确
GLM-4V智谱 AI多轮对话体验好,中文场景优化
MiniMax-VLMiniMax长上下文支持,适合多图分析

这四个模型均支持 Chat Completions 兼容格式,content 字段可以传数组,混合 textimage_url 两种类型。


Vision API 消息格式

基础结构

Vision 请求与普通文本请求的区别在于 content 从字符串变成了数组:

{
  "model": "qwen-vl-max",
  "messages": [
    {
      "role": "user",
      "content": [
        {
          "type": "image_url",
          "image_url": {
            "url": "https://example.com/image.png"
          }
        },
        {
          "type": "text",
          "text": "这张图片里写了什么?"
        }
      ]
    }
  ]
}

图片传入方式:URL vs Base64

方式一:URL 链接(适合公开可访问的图片)

content = [
    {
        "type": "image_url",
        "image_url": {"url": "https://example.com/chart.png"}
    },
    {"type": "text", "text": "分析这张折线图的趋势"},
]

方式二:Base64 编码(适合本地文件、私有图片)

import base64

def image_to_base64(file_path: str, mime_type: str = "image/png") -> str:
    with open(file_path, "rb") as f:
        data = base64.b64encode(f.read()).decode("utf-8")
    return f"data:{mime_type};base64,{data}"

content = [
    {
        "type": "image_url",
        "image_url": {"url": image_to_base64("screenshot.png")}
    },
    {"type": "text", "text": "读取图中的文字内容"},
]

Base64 方式无需图片公网可达,适合处理用户上传文件;URL 方式更省带宽,但图片必须公开可访问。


完整代码示例(Qwen-VL)

安装与配置

pip install openai python-dotenv
# .env
QWEN_API_KEY=sk-xxxxxxxxxxxxxxxxxxxxxxxx

单图理解

import os
import base64
from openai import OpenAI
from dotenv import load_dotenv

load_dotenv()

client = OpenAI(
    api_key=os.getenv("QWEN_API_KEY"),
    base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",
)

def analyze_image_url(image_url: str, question: str) -> str:
    """通过 URL 传入图片"""
    response = client.chat.completions.create(
        model="qwen-vl-max",
        messages=[
            {
                "role": "user",
                "content": [
                    {"type": "image_url", "image_url": {"url": image_url}},
                    {"type": "text", "text": question},
                ],
            }
        ],
        max_tokens=1024,
    )
    return response.choices[0].message.content

def analyze_image_file(file_path: str, question: str) -> str:
    """通过 Base64 传入本地图片"""
    with open(file_path, "rb") as f:
        b64 = base64.b64encode(f.read()).decode()

    # 根据扩展名判断 MIME 类型
    ext = file_path.rsplit(".", 1)[-1].lower()
    mime = {"jpg": "image/jpeg", "jpeg": "image/jpeg",
            "png": "image/png", "webp": "image/webp"}.get(ext, "image/png")
    data_url = f"data:{mime};base64,{b64}"

    response = client.chat.completions.create(
        model="qwen-vl-max",
        messages=[
            {
                "role": "user",
                "content": [
                    {"type": "image_url", "image_url": {"url": data_url}},
                    {"type": "text", "text": question},
                ],
            }
        ],
    )
    return response.choices[0].message.content

多图输入

def compare_images(image_urls: list[str], question: str) -> str:
    """多图对比分析"""
    content = []
    for url in image_urls:
        content.append({"type": "image_url", "image_url": {"url": url}})
    content.append({"type": "text", "text": question})

    response = client.chat.completions.create(
        model="qwen-vl-max",
        messages=[{"role": "user", "content": content}],
    )
    return response.choices[0].message.content

# 对比两张产品设计稿的差异
result = compare_images(
    ["https://cdn.example.com/v1.png", "https://cdn.example.com/v2.png"],
    "对比这两张设计稿,列出所有视觉差异",
)

多轮视觉对话

def visual_conversation():
    """保持图片上下文的多轮对话"""
    history = []

    # 第一轮:发送图片并提问
    history.append({
        "role": "user",
        "content": [
            {"type": "image_url", "image_url": {"url": "https://cdn.example.com/code.png"}},
            {"type": "text", "text": "这段代码有什么问题?"},
        ],
    })

    resp1 = client.chat.completions.create(
        model="qwen-vl-max", messages=history
    )
    assistant_reply = resp1.choices[0].message.content
    history.append({"role": "assistant", "content": assistant_reply})
    print("AI:", assistant_reply)

    # 第二轮:追问,无需再次发图片
    history.append({
        "role": "user",
        "content": [{"type": "text", "text": "如何修复这个问题?给出修复后的代码"}],
    })
    resp2 = client.chat.completions.create(
        model="qwen-vl-max", messages=history
    )
    print("AI:", resp2.choices[0].message.content)

四大测试场景实测对比

测试方法:用同一张图片,向四个模型发送相同 Prompt,评分维度:准确率(40%)+ 细节完整度(30%)+ 中文表达(30%)。

场景一:OCR 文字识别

测试图片:一张含表格和手写注释的合同扫描件

模型印刷体识别手写识别表格结构综合得分
Qwen-VL-Max★★★★★★★★★☆★★★★★4.7
DeepSeek-VL2★★★★★★★★★☆★★★★☆4.5
GLM-4V★★★★☆★★★☆☆★★★★☆4.0
MiniMax-VL★★★★☆★★★☆☆★★★☆☆3.7

结论:OCR 场景 Qwen-VL-Max 表现最好,文档理解是其核心优势,尤其是表格结构还原非常准确。

场景二:图表数据理解

测试图片:一张含多条折线的销售趋势图(含坐标轴、图例)

Prompt:"请分析这张折线图,指出哪个产品销量增长最快,以及在哪个季度出现了明显下滑"

Qwen-VL-Max 输出(节选):

"从图表来看,产品 B(蓝线)在 Q1-Q3 增速最快,同比增长约 47%。而产品 A 在 Q2 到 Q3 区间出现明显下滑,跌幅约 23%,这与图中该时段折线的陡降趋势吻合。"

DeepSeek-VL2 输出(节选):

"蓝色折线对应产品 B 增长最为显著。产品 A 红线在第二季度末至第三季度初有明显下行,降幅较大。"

Qwen 给出了量化数据,DeepSeek 描述较为定性。两者都识别出了正确的趋势,但 Qwen 在数值推断上更大胆(当然准确性依赖图表清晰度)。

场景三:代码截图分析

测试图片:一段 Python 代码截图,含一个典型的 KeyError 异常

模型Bug 定位修复建议代码可读性说明
Qwen-VL-Max准确完整
DeepSeek-VL2准确完整有,更详细
GLM-4V准确基础
MiniMax-VL准确基础

代码场景 DeepSeek-VL2 解释最详细,习惯性给出 "为什么会这样" 的推理链,适合学习场景。

场景四:场景描述

测试图片:一张繁华商业街的街景照片

Prompt:"详细描述这张图片中的场景,包括人物、建筑、文字标识等细节"

模型中文标识识别人物数量估算氛围描述
Qwen-VL-Max★★★★★★★★★☆★★★★☆
DeepSeek-VL2★★★★☆★★★★★★★★★☆
GLM-4V★★★★☆★★★☆☆★★★★★
MiniMax-VL★★★☆☆★★★☆☆★★★★☆

中文标识(店铺名、广告牌)识别 Qwen 最准;GLM-4V 的氛围描述最有文采。


图片 Token 消耗对比

Vision 请求的 Token 消耗与图片分辨率直接相关。以下是 Qwen-VL-Max 的参考数据:

图片尺寸估算 Image Tokens
512×512~340 tokens
1024×768~680 tokens
1920×1080~1500 tokens
高清文档(A4 扫描)~2000-3000 tokens

节省 Token 的建议

  • OCR 场景:保持原始分辨率,压缩会降低文字清晰度,反而影响识别率
  • 图表分析:1024px 宽度通常足够,无需传 4K 图
  • 场景描述:可以适当降低分辨率,768px 以上即可满足

实际应用选型建议

按场景选模型

应用场景推荐模型原因
企业文档 OCRQwen-VL-Max表格还原、中文印刷体识别最佳
数据图表分析Qwen-VL-Max数值推断能力强
代码审查/DebugDeepSeek-VL2推理链详细,解释到位
内容审核/场景描述GLM-4V中文叙述自然,多轮对话体验好
批量图片处理MiniMax-VL长上下文,适合一次处理多图

接入架构建议

用户上传图片
    ↓
压缩/格式转换(统一为 JPEG/PNG,控制分辨率)
    ↓
转 Base64 或上传到 CDN 获取 URL
    ↓
调用 Vision API
    ↓
解析返回内容 → 业务逻辑

生产环境中,图片最好先上传到自有 CDN 再传 URL,避免 Base64 膨胀请求体(Base64 比原文件大约 33%),同时也方便日志追踪。

值得一提的是,不同多模态模型在图片字段的细节上存在一些差异(比如 image_url 的嵌套结构、Base64 的 MIME 前缀要求等)。笔者在开发 TheRouter 时专门对多模态请求做了格式统一处理,上游无论是哪个模型,应用层只需按标准格式发送一次即可。

错误处理注意事项

Vision 请求有几个特有的错误场景:

from openai import BadRequestError

try:
    result = analyze_image_url(url, question)
except BadRequestError as e:
    if "image" in str(e).lower():
        # 图片格式不支持、分辨率过高、内容违规等
        print(f"图片处理失败: {e}")
    else:
        raise

常见错误原因:

  • 图片 URL 不可访问(防盗链、临时链接过期)
  • 图片格式不支持(建议统一转 JPEG/PNG)
  • 单图分辨率过高(部分模型有上限,通常 8K×8K 以内安全)
  • 图片内容触发安全审核(色情、暴力等)

小结

多模态 API 的接入本身并不复杂,关键在于理解 content 数组的构造方式,以及 Base64 和 URL 两种图片传入方式的适用场景。

选型上没有万能答案:

  • 文档/OCR 类首选 Qwen-VL-Max
  • 代码分析类首选 DeepSeek-VL2
  • 内容生成/描述类GLM-4V 中文体验更流畅

建议在正式接入前,用你自己的真实业务图片做一轮测试,不同图片质量和场景差异很大,实测结果比任何 Benchmark 都可靠。


本文代码均在 Python 3.10+ 下测试通过。所有模型 API 均需在对应平台申请账号并获取 API Key。


作者:TheRouter 开发者,专注 AI 模型路由网关。项目主页:therouter.ai