
把image_url写成一个普通字符串请求直接返回 400模型明明收到了消息却完全忽略你传的图片。接过多模态 API 的人对这两个现象都不陌生。问题通常不在模型本身而在请求结构和几个容易填错的参数。这篇文章给一个可直接跑通的多模态图片输入最小脚本再把本地图片、流式输出和排错一次讲清。场景与前置条件这篇文章要解决的是用 OpenAI SDK 调用大模型时怎样把一张图片作为输入交给模型让它输出一段文本描述。这里说的不是图像生成。图像生成是模型画图给你多模态理解是模型读图也就是常说的图片输入。两者调用的端点和消息结构不同不要混着用。开工前假设你手上已经有这四样东西一个已经创建好的 API key前缀通常是sk_live_一个从模型广场详情页复制来的模型调用名并且确认它带视觉能力一个 Python 3.9 或以上的运行环境以及通过pip install openai安装的 openai 包。操作系统可以是 macOS、Linux 或 Windows环境行为没有特殊差异。图片不会以本地文件路径的形式直接传给接口你需要先把它变成可访问的 URL或者亲手转成 base64 data URI。这一点后面会展开。环境准备先装 SDK。终端里执行pipinstallopenai装完以后确认当前终端能读到环境变量SILVAMUX_API_KEY。为什么不用硬编码因为密钥一旦写进源码Git 提交记录会把它永久保留。即使后来删除文件历史里依然找得到。对带sk_live_前缀的 key 来说这个风险不低。CI 环境也更适合通过密钥注入而不是改代码。接下来是base_url。初始化 OpenAI client 时base_url必须逐字符写成https://www.silvamux.com/api/v1。这个地址带www少了www会解析到不存在的域名。很多首次接入的人就是卡在这一步因为一些示例只写短域名照抄过去就失败。把这两项准备好后面代码就不会在连接层出问题。最小可跑示例下面这个脚本可以直接跑通。先看代码importosfromopenaiimportOpenAI clientOpenAI(api_keyos.environ[SILVAMUX_API_KEY],base_urlhttps://www.silvamux.com/api/v1,)TINY_PNGdata:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAwMCAO/p9sAAAAASUVORK5CYIIresponseclient.chat.completions.create(modelglm-4.6,messages[{role:system,content:你是一个图片理解助手只输出图片中能确认的信息。,},{role:user,content:[{type:text,text:这张图片里有什么},{type:image_url,image_url:{url:TINY_PNG},},],},],temperature0.2,)print(response.choices[0].message.content)这段代码先初始化 client再调用大模型的对话补全接口。api_key不写具体密钥从环境变量读取。base_url一旦写错后面消息结构再标准也跑不通。最关键的是user消息。content不是字符串而是数组。数组里可以放多个片段每个片段用type区分。text片段负责提问image_url片段负责携带图片输入。image_url片段的值又是一个对象里面才是url。很多人第一次写多模态请求时把这一层对象漏掉接口会回一个 400。别急看到 400 先去检查消息结构。model填glm-4.6。这个调用名来自模型广场详情页不要根据模型家族名自己拼版本。自己拼一个glm-4.6-vision或类似名字通常会得到 404。temperature设 0.2 是为了让描述更稳设 0 也可以。TINY_PNG是一张 1x1 的极小 PNG用 data URI 表示目的是验证整条链路。真的要做图片分析时把它替换成真实图片 URL或者使用下一节转好的本地图片 data URI。逐步扩展本地图片转 base64线上 URL 适合测试真实项目里经常要传本地文件。下面这个函数能把本地图片转成 data URIimportbase64importmimetypesdefimage_to_data_uri(path:str)-str:mimemimetypes.guess_type(path)[0]orimage/pngwithopen(path,rb)asf:encodedbase64.b64encode(f.read()).decode(ascii)returnfdata:{mime};base64,{encoded}调用时把它放在原本TINY_PNG出现的位置image_url{url:image_to_data_uri(./cat.png)}mimetypes.guess_type会根据扩展名猜 MIME 类型。猜不到时就回退到image/png。MIME 类型不要乱填。文件明明是 JPEG你却声明成image/png服务端可能按 PNG 去解码最终得到损坏的像素。有时不会立刻报错但识别结果会明显跑偏。流式输出与末尾的 usage 块如果图片识别结果很长可以使用流式输出边读边显示。代码这样改messages[{role:system,content:你是一个图片理解助手。},{role:user,content:[{type:text,text:这张图片里有什么},{type:image_url,image_url:{url:TINY_PNG}},]},]streamclient.chat.completions.create(modelglm-4.6,messagesmessages,streamTrue,)forchunkinstream:ifnotchunk.choices:continuedeltachunk.choices[0].deltaifdeltaanddelta.content:print(delta.content,end,flushTrue)流式传输时每个chunk只带一小段文本。末尾可能有一个choices为空但带有 usage 的 chunk。这个设计是为了把用量信息补在流的最后不是异常。代码里必须先判断chunk.choices是否为空否则直接访问chunk.choices[0]会触发索引错误。也不要只靠data: [DONE]判断结束因为有些客户端对最后一块的处理时机不一致。排错401密钥没有生效报错文本通常长这样openai.AuthenticationError: Error code: 401 - {error: {message: invalid api key, type: gateway_error, code: AUTH_ERROR}}直接原因是当前进程读不到环境变量或者 key 本身复制不完整。解决办法分两步先确认创建时的 key 是不是以sk_live_开头再确认当前终端里执行过 export。如果在 IDE 里运行记得让 IDE 重启一次进程否则环境变量不会生效。401 不应该靠重试解决重试只是重复失败。400image_url 类型不对这类错误常见的形态是状态码 400error.code为BAD_REQUEST。八成原因是content用了字符串或者image_url的值直接填成了 URL 字符串。多模态请求里content必须是数组image_url必须是对象。两个条件少一个服务端就会拒绝。修改方法就是回到最小示例把消息结构原样复制只替换图片的url值。404模型调用名是拼出来的如果你看到 404 或类似model not found的返回多半是把调用名写成了自己习惯的版本号。比如glm-4.6-vision。这类名字也许看起来合理但平台不会自动做模糊匹配。调用名必须从模型广场的模型详情页直接复制。不要把上游厂商的名字照搬过来两个命名体系不一定一致。400这个模型不支持图片输入还有一种 400 会出现在模型调用名正确、但模型本身没有视觉能力的时候。接口会告诉你消息里不能带image_url。解决办法是把model换成一个明确标注支持图片输入的调用名。这个信息在模型详情页里能看到。不要猜猜一次就浪费一次失败请求。常见问题问图片输入和图像生成有什么区别答图片输入是把图片交给模型去理解返回的是文本图像生成是模型根据文本生成图片。两者的消息结构和调用端点不同这篇文章只谈前者。把图片输入的消息结构发给图像生成端点通常也会收到参数错误。问为什么不能直接传本地文件路径答OpenAI 兼容的image_url字段只接受远程 URL 或 data URI。直接写./cat.png接口并不知道它是一个本地路径也不会帮你去读文件系统。要传本地图片就得先转成 base64 data URI或者先把文件传到某个可访问的 HTTP 地址。问流式返回里为什么有一段 choices 为空答那是服务端在流的末尾补充的用量信息。它保持 SSE 流的结构但不再携带文本增量。客户端要兼容这个块不能因为choices为空就当成错误。判断流程应该以流是否结束为准不要依赖某一个特殊块。以上示例在千木SilvaMux的 OpenAI 兼容端点上验证模型调用名为 glm-4.6。模型调用名和当前可用模型以开发者文档为准。