← 返回资讯

多模态输入:图片与语音怎么传给大模型

2026-06-24

多模态大模型支持接收图片、音频等非文本输入。主流方案:图片通过 image_url content block 传给 vision 模型(如 gpt-4o);语音先用 Whisper API 转成文字再送入对话模型。两者均走 OpenAI 兼容接口,对接方式一致。

你可能是这么撞上这篇文章的:产品经理丢过来一个需求,“用户拍张截图就能自动生成工单描述”,或者”客服录音自动转文字存进工单系统”。你打开文档一看,vision 和语音转写居然不是同一套 API,一个塞 content 数组,一个是独立的 audio.transcriptions 端点,命名风格都不太一样。别慌,这篇把两条链路的坑一次说完,包括你大概率会踩的三个:base64 图片把上下文撑爆、Whisper 报 413、多轮对话里图片重复计费。

图片输入:两种传法

方式适用场景限制
公网 URL图片已有公开地址模型需能访问该 URL
base64 编码本地文件、私有图片图片越大请求体越大,建议压缩到 1 MB 以内
文件上传(Files API)复用同一图片多次需先上传获取 file_id

支持格式:PNG、JPEG、WebP、GIF(静态帧)。最大分辨率因模型而异,通常 2048×2048 以内效果最佳。

三种方式怎么选,不用纠结太久,记住这个判断顺序就行:图片本来就在你自己的对象存储或 CDN 上、且地址长期有效 → 用公网 URL,最省事,请求体也小;图片是用户临时上传、本地生成、或者压根没有公网地址(比如企业内网截图)→ 只能 base64;同一张图要在一个会话里反复问好几轮(“这张图里的表格第二行是什么” “第三列呢”)→ 上传一次拿 file_id,后面每轮都引用它,省得每次都把几百 KB 的 base64 塞进请求体。实际项目里我见过最常见的翻车,是团队图省事全用 base64,图片一大,请求体轻松破 2MB,网关超时、或者触发反向代理的 client_max_body_size 限制,报的错却是一个不相关的 502,排查了半天才发现是图片没压缩。

Python:图片 URL 传入

import os
from openai import OpenAI

client = OpenAI(
    api_key=os.environ["OPENAI_API_KEY"],
    base_url=os.environ.get("OPENAI_BASE_URL", "https://api.lidayun.com/v1"),
)

resp = client.chat.completions.create(
    model="gpt-4o",
    messages=[
        {
            "role": "user",
            "content": [
                {"type": "text", "text": "图片里有什么?请详细描述。"},
                {
                    "type": "image_url",
                    "image_url": {
                        "url": "https://example.com/sample.jpg",
                        "detail": "high",   # low / high / auto
                    },
                },
            ],
        }
    ],
    max_tokens=512,
)
print(resp.choices[0].message.content)

跑完这段代码,你应该在终端看到一段完整的自然语言描述,比如”图片中是一张办公室场景,桌上有一台笔记本电脑……”这类几十到几百字的文本,而不是空字符串或者报错。如果 resp.choices[0].message.content 是空的,先别怀疑模型能力,大概率是这三个原因之一:一是图片 URL 模型访问不到(内网地址、需要登录态的地址、防盗链拦截),二是 detail 设成 "low" 导致模型看不清细节只能给出模糊回答,三是图片本身确实信息量很少(比如纯色背景)。养成习惯:调试阶段先把 max_tokens 调大一点(比如 1024),避免回答被截断误判成”模型看不懂”。

detail 这个参数很多人第一次用会忽略,但它直接决定了这次调用花多少钱、模型看得多细。默认是 "auto",模型自己判断;如果你只是要一个粗略分类(“这张图是发票还是合同”),显式传 "low" 能省下不少 token,响应也更快;如果要做 OCR 级别的精读(提取表格里的具体数字),必须用 "high",否则模型经常会把数字看错或者干脆说”看不清”。

Python:本地图片 base64 传入

import base64, mimetypes

def image_to_data_url(path: str) -> str:
    mime, _ = mimetypes.guess_type(path)
    with open(path, "rb") as f:
        b64 = base64.b64encode(f.read()).decode()
    return f"data:{mime};base64,{b64}"

data_url = image_to_data_url("/tmp/screenshot.png")

resp = client.chat.completions.create(
    model="gpt-4o",
    messages=[
        {
            "role": "user",
            "content": [
                {"type": "text", "text": "这张截图的错误原因是什么?"},
                {"type": "image_url", "image_url": {"url": data_url}},
            ],
        }
    ],
)
print(resp.choices[0].message.content)

这里有个很多人不知道的细节:base64 编码本身会让数据体积膨胀约 33%(Base64 是把每 3 字节原始数据编成 4 字节可打印字符),一张 1.5MB 的截图编码完差不多变成 2MB 的字符串,直接塞进 JSON 请求体。如果你的服务前面挂了 Nginx,默认 client_max_body_size 通常是 1MB,超了会直接 413,而且这个 413 是你自己网关返回的,跟大模型服务商没关系——排查时先看响应状态码是谁返回的,别一上来就怀疑 API key 或者模型。实际项目里我们的做法是:上传前用 Pillow 把长边压到 1600px 以内、JPEG 质量压到 85,这样绝大多数截图能压到 300KB 以内,肉眼几乎看不出差别,模型识别效果也不受影响(除非你需要读很小的文字)。

还有一个更容易被忽略的坑:多轮对话里如果每一轮都把同一张图片的 base64 塞进 messages 历史,上下文会指数级膨胀。比如你做了个”针对这张图连续追问”的功能,第一轮请求带一张 300KB 图片折算大约 400 token(detail=high 情况下更高),如果你把完整的 messages 历史(含图片)原样带入第二轮、第三轮,图片会被重复计费 N 次,五轮对话下来 token 消耗可能翻 5 倍,账单涨到你都不敢看。正确做法是:图片只在首轮传入,模型给出的描述文字进入历史记录留存,后续追问只带纯文本历史 + 新问题,不再重复带图;如果确实需要模型”记得”图片细节,用 Files API 上传一次拿 file_id,用引用代替重复编码。

语音输入:Whisper 转写

# 先转写音频,再送入对话模型
audio_resp = client.audio.transcriptions.create(
    model="whisper-1",
    file=open("/tmp/recording.mp3", "rb"),
    language="zh",   # 指定语言可提升准确率
)
transcript = audio_resp.text
print("转写结果:", transcript)

# 再用转写结果调对话模型
chat_resp = client.chat.completions.create(
    model="gpt-4o-mini",
    messages=[{"role": "user", "content": transcript}],
)
print(chat_resp.choices[0].message.content)

支持音频格式:mp3、mp4、mpeg、mpga、m4a、wav、webm;文件大小上限通常 25 MB。这个 25MB 的坎很多人第一次做语音功能会栽在这——一段 20 分钟的会议录音用 44.1kHz 立体声录制,随随便便就是 100MB+,直接调用会收到 413 Request Entity Too Large 或者服务商返回的 file too large 错误。解决办法不是压缩音质(会掉转写准确率),而是先用 ffmpeg 把音频转成单声道、16kHz 采样率(人声转写完全够用,Whisper 内部本身就是按 16kHz 处理的),命令大概是 ffmpeg -i input.mp3 -ac 1 -ar 16000 output.mp3,同样时长的文件能压缩到原来的四分之一左右;如果还是超限,就按静音切片,用 pydubsilence.split_on_silence 在停顿处切开分段转写,再把每段文字拼起来。

transcriptions.create 还有几个参数决定了转写质量,值得展开说说:

参数作用什么时候用
language指定源语言(ISO-639-1,如 "zh""en"明确知道语言时一定要传,不传的话模型要先猜语言,偶尔会把中文方言、夹杂英文的录音猜成别的语种,导致转写完全跑偏
prompt给模型一段上下文提示,比如专业术语、人名转写行业黑话、产品名、生僻词时加上,比如 prompt="讨论内容涉及:算力、token、RAG、向量数据库",能显著减少专有名词被听写成同音字
response_formatjson(默认)/ text / srt / vtt / verbose_json做字幕用 srt/vtt 直接带时间轴;只要纯文本用 text;需要每个词的时间戳做后续对齐用 verbose_json
temperature转写的随机性,默认 0基本不用改,0 就是最稳定、最贴近实际发音的结果

实际排查中常见的两个错误:一是 401 Unauthorized,先确认 OPENAI_API_KEY 有没有读到(很多人在 .env 里配了但没 load_dotenv() 或者进程没重启),再确认 base_url 有没有指错环境;二是转写结果里中文标点混乱、简繁混杂,这通常不是你的问题,是模型本身的输出习惯,如果对格式要求严格,加一步后处理用正则统一标点即可,不用怀疑是编码问题(response.text 本身就是 UTF-8 字符串,不需要你手动 decode)。

Node.js:图片 URL 传入

import OpenAI from "openai";
import fs from "fs";
const client = new OpenAI({
  apiKey: process.env.OPENAI_API_KEY,
  baseURL: process.env.OPENAI_BASE_URL ?? "https://api.lidayun.com/v1",
});

const resp = await client.chat.completions.create({
  model: "gpt-4o",
  messages: [
    {
      role: "user",
      content: [
        { type: "text", text: "图片里有什么?" },
        { type: "image_url", image_url: { url: "https://example.com/sample.jpg" } },
      ],
    },
  ],
});
console.log(resp.choices[0].message.content);

detail 参数:控制分辨率与成本

detail 值行为token 消耗
"low"固定缩至 512×512 处理~85 tokens
"high"先缩至 2048×2048,再切 512 块按块计费,约 170+ tokens/块
"auto"根据图片尺寸自动选择推荐默认

常见问题

图片 URL 模型访问不到怎么办? 改用 base64 传入,或将图片托管到公网 CDN。内网/私有 URL 模型无法请求,会报错或返回空描述。

多张图片怎么传?content 数组里追加多个 image_url block,顺序即为模型看图顺序,最多通常支持 10-20 张,具体看模型文档。

Whisper 转写准确率不高? 指定 language 参数(如 "zh")、提供 prompt(上下文词汇)可显著提升中文准确率。


更多接入基础见大模型 API 接入完全指南接入教程专题。如需批量图片处理,请结合并发控制与速率限制;结构化提取图片信息可参考 JSON mode 使用