系列导语 本文是【大模型API中转站】系列 篇。本系列致力于用最低的成本、最清晰的方法,帮你打通多模型 API 的任督二脉。建议先收藏,随用随查。 前面讲过 Gemini 多模态接入避坑,但很多人卡在同一个坑:"我把音频/视频 URL 传过去,模型却说看不到文件。" 这一期我们用真实抓包数据,把"传文件给 Gemini 到底该怎么写"一次性讲透——两个端点、两种正确写法、三种常见错误格式。
先给最忙的人一句话结论:别用 input_audio / video_url 这种你以为存在的格式——OpenAI 端点统一用 image_url{url} 传图/音/视频;Gemini 原生端点统一用 fileData{fileUri,mimeType}。 这两种是实测唯一能把媒体喂进 Gemini 的写法。
1. 问题:不是 Gemini 读不了文件,是格式没对
很多人从 OpenAI 的文档里抄来音频传参结构,想当然地给音/视频也套一个"看起来对称"的格式:
// ❌ 想当然的写法(不工作)
{ "type": "input_audio", "input_audio": { "url": "https://你的存储/audio.mp3", "format": "audio/mp3" } }
{ "type": "video_url", "video_url": { "url": "https://你的存储/video.mp4" } }
发过去 HTTP 200、也有回复,但回复驴唇不对马嘴——模型在瞎编。一查 usage 就露馅:
prompt_tokens_details: { text_tokens: 7, audio_tokens: 0, image_tokens: 0 }
audio_tokens=0 说明媒体根本没进模型,中转网关在解析阶段就把这两个块丢了(详见第 5 节原理)。所以"Gemini 没有读取文件能力"是个误会——是块格式没被网关识别,文件压根没送到 Gemini。
2. 核心结论:两个端点,两种正确写法
| 端点 | 调用路径 | 媒体写法 | mime |
|---|---|---|---|
| OpenAI 兼容 | POST /v1/chat/completions |
image_url{url} |
网关下载后自动探测 |
| Gemini 原生 | POST /v1beta/models/{model}:generateContent |
fileData{fileUri, mimeType} |
需手动写 |
两个端点都能用任意公网可访问的音/视频/图片 URL,但取 URL 的人不同:OpenAI 端点是中转网关把 URL 下载下来转 base64 再喂 Gemini;Gemini 原生端点是纯透传,由 Google 自己去抓取这个 URL。区别在字段名、mime 要不要手写、以及谁来下载。
实测模型用
gemini-2.5-flash(支持音视频多模态);gemini-2.5-pro、gemini-3.x同理。
3. OpenAI 端点:image_url 是万能媒体载体
OpenAI 协议里,image_url 这个块不止能传图片——中转网关会把它的 url 当通用文件源下载,而 Gemini 的 fileData 本来就吃任意媒体类型。所以图、音、视频全塞 image_url:
3.1 音频(实测有效)
from openai import OpenAI
client = OpenAI(api_key="sk-你的中转Key", base_url="https://api.4sapi.com/v1")
resp = client.chat.completions.create(
model="gemini-2.5-flash",
messages=[{"role": "user", "content": [
{"type": "text", "text": "这段音频里有什么声音?用一句话说"},
{"type": "image_url", "image_url": {"url": "https://你的存储/audio.mp3"}},
]}],
)
print(resp.choices[0].message.content)
实测返回里 audio_tokens 大于 0(不再是 0),模型也正确分析出了音频内容——音频确实进去了。
3.2 视频(实测有效)
把上面的 url 换成 .mp4 即可:
messages=[{"role": "user", "content": [
{"type": "text", "text": "这个视频里有什么?"},
{"type": "image_url", "image_url": {"url": "https://你的存储/video.mp4"}},
]}]
实测 prompt_tokens 大幅上升(视频帧 + 音轨都被采样),模型准确描述出了视频画面内容——视频被真正解析了。
4. Gemini 原生端点:用 fileData
如果你直连 Gemini 原生协议(generateContent),就没有 image_url 这个概念了,改用 Gemini 自己的 fileData:
import requests
r = requests.post(
"https://api.4sapi.com/v1beta/models/gemini-2.5-flash:generateContent",
headers={"x-goog-api-key": "sk-你的中转Key", "content-type": "application/json"},
json={"contents": [{"parts": [
{"text": "这段音频里有什么声音?一句话"},
{"fileData": {"mimeType": "audio/mp3", "fileUri": "https://你的存储/audio.mp3"}},
]}]},
)
print(r.json())
实测返回里 promptTokensDetails 含 {"modality": "AUDIO", "tokenCount": 104},模型分析了音频内容。视频同理:{"fileData": {"mimeType": "video/mp4", "fileUri": "https://你的存储/video.mp4"}}。
✅ 澄清(实测 + 源码核实):Gemini 原生
fileData.fileUri直接吃公网 http/https 媒体直链——中转网关在原生路径上是纯透传(仅对 YouTube 链接补个 mimeType,并不下载),URL 是 Google 自己去抓取并处理的。所以 Files API uri、YouTube 链接、普通公网直链三种都能用(官方文档例子只列了前两种,但实测公网直链也接受)。 对比:OpenAI 端点的image_url{url}是中转网关下载后转 inlineData 再发——两条路径"谁下载 URL"不同,但对调用方都是一行 URL 的事。
5. 踩坑速查:这些写法为什么不工作
| 写法 | 结果 | 原因 |
|---|---|---|
input_audio{url} |
❌ 被丢 | OpenAI 标准 input_audio 只收 base64 data,没有 url 字段;网关解析时 data 为空直接跳过 |
video_url{url:...}(对象) |
❌ 多数被丢 | 不少网关版本的 video_url 只认字符串 "video_url":"http://...",不认 {url} 对象 |
input_audio{data:base64} |
✅ 可用 | 这才是 OpenAI 音频的标准写法(但要自己 base64 编码,URL 更省事) |
image_url{url} |
✅ 万能 | 唯一同时接受 {url} 对象、又被当通用文件源下载的块 |
一句话避坑:图/音/视频全走 image_url{url}(OpenAI 端点)或 fileData{fileUri}(原生端点),别去碰 input_audio / video_url 这些"看起来该有"的格式。
6. 完整可跑示例(OpenAI 端点,一个函数传任意媒体)
from openai import OpenAI
client = OpenAI(api_key="sk-你的中转Key", base_url="https://api.4sapi.com/v1")
def ask_with_media(prompt: str, media_url: str, model="gemini-2.5-flash"):
resp = client.chat.completions.create(
model=model,
messages=[{"role": "user", "content": [
{"type": "text", "text": prompt},
{"type": "image_url", "image_url": {"url": media_url}}, # 图/音/视频通吃
]}],
)
print(resp.choices[0].message.content)
print("[tokens]", resp.usage.prompt_tokens_details) # 看 audio/image 是否>0 验证媒体进没进
ask_with_media("音频里有什么声音?", "https://你的存储/audio.mp3")
ask_with_media("视频讲了什么?", "https://你的存储/video.mp4")
ask_with_media("图里是什么?", "https://你的存储/image.jpg")
验证技巧:永远盯着返回 usage 里的 audio_tokens / image_tokens——大于 0 才说明媒体真的进了模型,等于 0 就是被丢了,别看模型那段一本正经的胡话。
7. 使用建议与注意事项
- URL 要公网可达:不论是网关下载(OpenAI 端点)还是 Google 抓取(原生端点),链接都得能公开访问。内网/需鉴权的资源传不进去——这种就改成 base64 内联,或先上传到可公开访问的存储。
- 格式走主流容器:系统会探测 mime 并校验是否在 Gemini 支持列表(常见
audio/mp3、audio/wav、video/mp4、image/*等);冷门格式先转成主流封装更稳。 - 视频按需截取控成本:视频按帧+音轨采样,token 消耗远高于文本(实测 5 秒视频 ~1800 tokens)。长视频建议只截关键片段(原生端点可用
videoMetadata设帧偏移)。 - 中转站帮你省掉的麻烦:多模态最烦的就是各家协议不一、文件 URL/base64 来回转、还要走 Files API 上传换 uri。走中转站一个 Key、一套统一格式(OpenAI 或原生任选),音视频 URL 直传、协议转换、计费明细全给你包好,不用自己拼上传流程,也不用为每家模型改一遍代码——这正是中转站对多模态场景最实在的价值。
8. 总结与系列导航
一句话总结:
传文件给 Gemini 不难,难在选对块格式。 OpenAI 端点万能键是
image_url{url}(网关下载转 base64),Gemini 原生端点是fileData{fileUri,mimeType}(Google 自己抓取,公网直链 / YouTube / Files API uri 都行)。别用input_audio{url}/video_url{url}这种会被静默丢弃的格式——验证就看usage里audio_tokens有没有大于 0。
通过 4SAPI ,一个 Key 就能用统一格式(OpenAI 或 Gemini 原生任选)调全系多模态——协议转换、音视频 URL 直传、计费明细、各家模型统一接口全帮你包好,不用为每家改代码、来回切 SDK。