MiniCPM-V 4.7 接进自己的脚本后图片被当纯文本 / 先检查消息结构怎么写

文章导读
图片被当成纯文本,通常不是模型没能力看图,而是脚本把 prompt 拼成了一个字符串,图片路径只是字符串里的一段字符,没有作为独立的图片项进入请求。判断方向很直接:先看送进模型的对象是 str 还是 list,再看 list 里是否有一个与文本并列的图片元素。这两步能在改提示词之前排除大部分“看不到图”的情况。
📋 目录
  1. 一 先确认脚本里的输入是消息列表还是纯字符串
  2. 二 按所用框架的多模态格式组装图片与文本
  3. 三 在送入模型前打印输入结构
  4. 四 用一张已知内容的小图做端到端验证
  5. 五 把可用的组装方式封装成函数
A A

图片被当成纯文本,通常不是模型没能力看图,而是脚本把 prompt 拼成了一个字符串,图片路径只是字符串里的一段字符,没有作为独立的图片项进入请求。判断方向很直接:先看送进模型的对象是 str 还是 list,再看 list 里是否有一个与文本并列的图片元素。这两步能在改提示词之前排除大部分“看不到图”的情况。

接入多模态模型时,先确认输入是消息列表而不是拼接字符串,再按框架约定把图片作为独立内容项与文本并列放进同一轮 user 消息。改完后打印一次消息结构,用一张自己生成的、内容已知的小图做端到端问答,回答能复述图中具体信息才算真正读到图。字段名各框架不同,以所用版本的文档和实际打印结果为准,不要凭记忆硬写。

先确认脚本里的输入是消息列表还是纯字符串

最常见的写法是 prompt = "看图回答问题:" + image_path,然后整个字符串丢给 chat 接口。对纯文本模型这样没问题,对多模态模型就只是把路径当成了几个 token,模型自然按文字回答。下面两种写法并列摆出来,对比看差异的位置。

# 方式 A:纯字符串 —— 图片信息只是一段文字,模型看不到像素
prompt = '这张图里有什么? /data/pic/cat.png'
out = client.chat(prompt)

# 方式 B:消息列表 —— 图片作为独立内容项
messages = [
    {
        'role': 'user',
        'content': [
            {'type': 'text', 'text': '这张图里有什么?'},
            {'type': 'image', 'image': '/data/pic/cat.png'},  # 图片字段放在这里
        ],
    }
]
out = client.chat(messages)

需要放图片字段的位置就是 content 列表里那个和 text 并列的元素;只把它写进 text 的字符串里,等于没传。也有框架支持顶层单独传一个 images 参数配合字符串 prompt,这同样可行,但前提是该版本确实支持这个参数,否则参数会被忽略而脚本不报错。先确认当前调用的函数签名里有没有图片入口,再决定用哪种写法。

按所用框架的多模态格式组装图片与文本

消息结构的通用骨架一般是角色加内容项列表,文本和图片各占一项。字段名差异很大:有的叫 image,有的叫 image_url,有的用 type: input_image,图片值可以是本地路径、URL、base64 或已经读好的张量。以所用框架文档为准,先用最小结构跑通再补其他参数。

MiniCPM-V 4.7 接进自己的脚本后图片被当纯文本 / 先检查消息结构怎么写
# 通用骨架,字段名按实际框架替换
messages = [
    {
        'role': 'system',            # 可选,部分框架用 system 字段或单独参数
        'content': [{'type': 'text', 'text': '你是一个看图回答问题的助手。'}],
    },
    {
        'role': 'user',
        'content': [
            {'type': 'text',  'text': '用户提问放在这里'},
            {'type': 'image', 'image': '图片路径或 URL 或 base64'},
        ],
    },
]

替换项集中在三处:role 的取值、内容项的 type 名称、图片字段名。有的框架要求图片项排在文本项之前,有的要求图片走单独参数,这些只能靠文档加打印确认。路径、URL、base64 三者不要混着传,同一次调用里保持同一种形式,能减少解码失败导致的静默降级。

在送入模型前打印输入结构

组装完之后不要直接调用,先打印一次,确认图片确实被读进来了。

import os, pathlib

def dump_messages(messages):
    for i, m in enumerate(messages):
        print(f'[{i}] role={m.get("role")}')
        content = m.get('content')
        if isinstance(content, str):
            print('    !! content 是字符串,图片不会被读取')
            continue
        for j, item in enumerate(content):
            t = item.get('type')
            val = item.get('image') or item.get('image_url') or item.get('text')
            print(f'    item{j} type={t} py_type={type(val).__name__}')
            if isinstance(val, str) and os.path.sep in val:
                p = pathlib.Path(val)
                print('      存在:', p.exists(), '大小:', p.stat().st_size if p.exists() else 0)
            elif isinstance(val, (bytes, bytearray)):
                print('      字节长度:', len(val))

dump_messages(messages)

需要核对的字段:content 是不是列表、每个 item 的 type 是否正确、图片项的值是不是非空的路径或字节、路径指向的文件是否真实存在且大小不为零。如果打印出来 content 是字符串,或者图片项的 py_type 是 str 而内容只是文件名,基本就能确认图片没进模型。

用一张已知内容的小图做端到端验证

验证图建议自己生成,不要用网图。做法:白底小图,画两个简单图形,比如一个红色圆形和一个蓝色三角形,再在图里写上一个自己编的短字符串,例如 ZQ-48。提问写成“图里写了什么字?一共有几个图形,分别是什么颜色?”

MiniCPM-V 4.7 接进自己的脚本后图片被当纯文本 / 先检查消息结构怎么写

判定标准可以分三档:能准确说出 ZQ-48 和图形颜色,说明图片确实进入输入并被解码;只回答“这是一张图片”“看起来像图表”,说明图片没进去或解码失败;文字对了但图形颜色说错,先怀疑图片被过度缩放或裁剪,换更大的图再试一次。同时可以跑一次不带图片的同样提问做对照,两次回答风格一致,通常说明图片根本没起作用。

把可用的组装方式封装成函数

结构确认可用后,把它收进函数,避免每次调用都手写消息列表,也顺带把路径校验做掉。

def build_vlm_messages(image_paths, question, system=None):
    """image_paths: str 或 list[str];question: str;system: 可选系统提示。
    返回可直接送入模型的 messages 列表。"""
    if isinstance(image_paths, str):
        image_paths = [image_paths]
    if not image_paths:
        raise ValueError('至少需要一张图片')

    messages = []
    if system:
        messages.append({'role': 'system',
                         'content': [{'type': 'text', 'text': system}]})

    content = [{'type': 'text', 'text': question}]
    for p in image_paths:
        if not os.path.exists(p):
            raise FileNotFoundError(f'图片不存在: {p}')   # 宁可报错,不要静默当文本
        content.append({'type': 'image', 'image': p})

    messages.append({'role': 'user', 'content': content})
    return messages

参数说明:image_paths 传单张时给字符串,多张时给列表,函数内部统一按列表处理并依次追加图片项,顺序和提问里说的“第一张、第二张”保持一致。多张图还要留意所用框架对图片数量和尺寸的限制,超出后可能被丢弃或被压缩。question 只放纯文本提问,不要在里面再拼路径;system 可省略。函数里对不存在的路径直接抛错,是为了避免路径字符串被当成普通文本送进去,那种情况下模型往往还能正常回话,问题会拖到很后面才被发现。