
最近在对接一个多模态识别的需求需要把图片和文字放在同一个请求里发给模型让它输出结构化结果。模型这边用的是 Qwen3.5-27B联调工具我选了 Postman——倒不是因为它有什么 AI 魔法而是它对图文请求构造、环境变量管理、批量测试和响应断言这套流程的支持确实够顺手。这篇文章就把从零开始用 Postman 配置图文 API 请求、再到响应断言验证的完整过程拆开讲一遍。适合已经拿到 API 访问权限、正准备上手联调的开发者也适合刚接触多模态 API、想快速验证模型能力的朋友。里面提到的所有步骤都可以直接照着操作相关参数和脚本我会一并给出。1. 环境准备与请求前必读1.1 本轮工具清单与版本注意事项开始配置之前先把工具链理清楚。Postman 我建议直接用最新版如果你还在用老旧的 8.x 版本功能上其实也够但新版对请求体格式校验、断言脚本提示、集合 Runner 的稳定性都要好不少。我这边的环境是 macOS 上的 Postman v10 系列Windows 上的布局基本一致只是快捷键略有差别。另外你要确认手头的模型服务端能力。Qwen3.5-27B 这种多模态模型输入侧支持文本和图片两种模态输出侧通常返回纯文本内容也会附带 token 使用量、请求 ID 这类元信息。你拿到手的 API 文档大概率有两种图片传入方式一是传公网图片 URL二是传 base64 编码后的图片内容。这两种方式我在后文都会讲到它们在 Postman 里的配置方式差异不小。还有一个容易被忽略的点接口的 Base URL 和鉴权字段。有些服务商的 endpoint 是统一的有些则按模型区分比如/compatible-mode/v1/chat/completions这种路径风格。先把接口文档完整读一遍确认三件事——endpoint 地址、Header 里的鉴权 key 名称、POST 请求体的字段结构。这三个信息不确认后面全是猜。1.2 拿到密钥后的核对清单与初检我习惯在接入任何大模型 API 时先做一次“健康检查”不直接上 Postman而是先在命令行里用最简结构验证连通性。这一步能帮你排除掉到底是网络问题还是配置问题这种最常见的内耗。比如先拿一条最简单的纯文本消息请求确认能正常返回结果后再往请求体里加图片字段。你手头需要准备的东西并不复杂一个可用的 API Key或者叫 Access Token目标模型的完整模型名确认是写qwen3.5-27b还是类似变体图片素材建议准备一张体积不大、但特征清晰的本地图签收单、产品包装、截图都可以图片 URL如果接口支持公网图片准备一个可直连的图片地址别用需要登录才能访问的内网地址。拿到 API Key 之后第一件事不是直接写 Postman而是先想清楚怎么管理这个密钥。我的经验是别把硬编码直接贴在请求 Header 里测试而是从第一步就建立环境变量。原因很简单一旦你同时测试生产环境和测试环境或者换了新 Key硬编码会让你挨个请求找一遍。不规范还容易泄密。所以打开 Postman 后先进入 Environments环境管理新建一个环境我通常起名dev-env。在这个环境里定义好两个变量变量名初始值当前值base_url你的接口域名比如https://api.example.com同上api_key你的完整 API Key同上变量建好之后所有请求里的域名和密钥都改成写{{base_url}}和{{api_key}}的引用形式。这样后期换环境、换密钥只动环境变量一个地方就够了这也是 Postman 使用中算不上秘密但极其重要的习惯。如果连环境变量都还没用起来强烈建议现在就改不然后面的批量测试和团队协作会很难受。2. Postman 请求配置全流程解析2.1 新建请求与 URL 拼接方法健康检查通过后开始在 Postman 里新建请求。点击左上角的 New 菜单或者直接用快捷键CtrlN / CmdN选择 HTTP Request。请求名我建议直接用接口语义来命名比如Qwen3.5-27B 图文识别别用test这种明天就不知道是测什么的文件名。URL 地址栏里填的是完整请求地址结构是“基础域名 接口路径”。比如我这边测试用的地址是{{base_url}}/v1/chat/completions其中{{base_url}}会自动被环境变量替换成真实域名。路径部分以你拿到的文档为准不要照抄任何教程里的路径因为不同部署方的路由设计差异很大。请求方法设置为 POST。大模型对话类接口几乎都是 POST因为你需要把大量的输入内容放在请求体里交给服务端。这一点上如果你之前习惯用 GET 传参需要调整一下观念GET 方式的 URL 长度限制根本无法承载图像 base64 数据POST 的 body 才是放下图片的地方。URL 拼好之后先把请求体暂时留空切到 Headers 页签确认默认请求头。Postman 会为 POST 请求自动加上Content-Type: application/json这点通常没问题。重点需要手动加的是鉴权信息也就是 API Key 怎么传给服务端。2.2 Headers 鉴权与 Content-Type 的规范写法鉴权 Header 常见的有两种风格一种是服务商要求用Authorization: Bearer token另一种是自定义 Header比如X-API-Key: your-key。具体用哪种以你拿到的接口文档为准不要凭感觉猜。接上面说的环境变量Headers 里的写法应该是这样Authorization: Bearer {{api_key}}注意如果接口要求的是自定义 Header则在 Key 那一列填X-API-KeyValue 列填{{api_key}}。两个变量一模一样区别只在于 Key 的名称。你可以在请求的 Headers 页签里看到这些键值对也可以在最底部的Code按钮里查看请求生成的真实 cURL 语句这是排查鉴权问题最快的方式之一。还需要检查的一个点是 Host 头。如果你用了某些云厂商网关可能还要求配置Host为特定的值。一般来说 Postman 会自动填充但如果你遇到 403 或证书类报错可以对照接口文档检查一下这个头。我在实际联调中踩过一次坑域名走了网关转发但 Host 头还是原始域名导致鉴权签名一直校验不过。这类问题在 Postman 里很难看出来因为 UI 上不会标红必须打开生成的 cURL 代码对比检查才能发现。Content-Type 通常是application/json但如果你的图片采用 base64 方式放在 JSON 里Content-Type 不需要改成multipart/form-data。这里很多新手会搞混以为传图片就得用 form-data事实上对于大模型 APIbase64 图片已经被封装成 JSON 字符串的一部分走一层 JSON 序列化就够了。如果你的服务商明确要求 multipart 混合表单那另说但绝大多数兼容 OpenAI 格式的接口都走纯 JSON。2.3 请求体组织文本与图片的两种传入方式请求体是整个配置过程的核心。我以常见的 OpenAI 兼容消息结构作为示例Qwen3.5-27B 的组织方式大体一致细节以文档为准。结构上就是messages数组里放一条用户消息用户消息的content字段不再是一段字符串而是一个数组里面的元素分别代表文本和图片。先看图片 URL 方式的请求体{ model: qwen3.5-27b, messages: [ { role: user, content: [ { type: image_url, image_url: { url: https://example.com/images/packaging.jpg } }, { type: text, text: 请描述这张产品包装图重点关注文字内容和颜色搭配。 } ] } ], max_tokens: 512, temperature: 0.3 }注意几点第一image_url是一个对象不是字符串不要写成image_url: ...这种错误格式第二文本和图片没有先后顺序的硬性限制但我通常把图片放前面、文本提示放后面方便人类阅读第三max_tokens建议设成 300 到 600 之间多模态识别场景的返回内容通常不会太长设得太大浪费延迟设太小会截断结果。再看 base64 方式的请求体。这种方式需要先把图片转成 base64 字符串然后拼到 JSON 里。Postman 本身不自带图片转 base64 的能力我一般先在本地用命令行处理base64 -w 0 packaging.jpg packaging_b64.txt生成的文本文件里那一长串字符就是 base64 编码结果。然后在 Postman 的请求体里把image_url.url字段替换成data:image/jpeg;base64, 那串字符也就是最终长这样{ type: image_url, image_url: { url: data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQ... } }这里有一个性能上的重要建议Postman 编辑大段 base64 字符串非常卡特别是几 MB 的图片编码后接近上百万字符粘贴、修改、保存都会让人想砸电脑。我的做法是准备一张尽量小的测试图长边控制在 1024 像素以内JPG 格式文件体积控制在 200KB 以下。小图不仅能避免卡顿识别速度也更快。你真正要处理大图时也不建议直接把超大 base64 塞进 Postman 做调试先用脚本验证逻辑再回到 Postman 做接口联调会更高效。无论用哪种方式请求体填完后先不要急着点 Send。我习惯先看一眼请求体的 JSON 格式是否合法Postman 编辑区会自动做语法高亮如果某个括号或逗号出了问题红色波浪线会提示你。也可以在请求体左上角的格式化按钮Beautify整理一遍。格式问题是最基础的检查项做完这些点 Send 看初步结果。3. 响应断言与数据验证实战3.1 先看懂响应结构再写断言请求发送成功后Postman 的响应区会显示返回结果。不同部署方返回的结构有差异但大体会长成这样的风格{ code: 0, message: OK, data: { text: 这是一张产品包装图主色调为蓝色盒面有白色文字..., model: qwen3.5-27b, usage: { prompt_tokens: 32, completion_tokens: 45, total_tokens: 77 } }, request_id: 8ad3c72e2f1a4b3b8e9e0a2f0d6e6c31 }也可能是直接 Gemini 风格的candidates结构或 ChatGLM 风格的自定义结构。你不需要把所有字段都记下来只需要确认三件事第一哪个字段表示“本次请求成功”的判断标志第二哪个字段放的是真正的模型输出文本第三哪个字段是请求 ID 或用例 ID方便后续排查问题。在写断言之前我强烈建议你先在响应区里手动点开返回的 JSON 树形结构找到这几个关键字段的位置。不要照着别人的断言脚本瞎改字段路径不同部署方可能把输出放在data.text、data.choices[0].message.content或者response里。路径不对就断言失败甚至断言永远失败后你还以为是模型出错了。如果你用的是 OpenAI 兼容格式输出文本通常在choices[0].message.content下面。而如果是自定义封装结构可能像上面那样包了一层data.text。所以我要再强调一次字段路径以实际返回为准这是断言环节最核心的工作没有捷径。3.2 基于 Postman 脚本的断言清单Postman 的断言写在 Responses 区域下面的 Tests 页签里本质是 JavaScript 脚本。我整理了一套在多模态图文接口项目中实际用过的核心断言组合你可以在pm.test里根据自己的字段路径微调后直接使用。第一类是检查 HTTP 状态码和基本成功标志pm.test(HTTP 状态码为 200, function () { pm.response.to.have.status(200); }); pm.test(接口返回成功标志, function () { const json pm.response.json(); pm.expect(json.code).to.eql(0); });第二类是检查返回文本的内容类型这一步特别关键能帮助你确认模型确实读到了图片而非纯文本兜底pm.test(返回结果包含关键特征词, function () { const json pm.response.json(); const text json.data.text; pm.expect(text).to.include(包装); pm.expect(text.length).to.be.above(20); }); pm.test(返回内容不包含报错信息, function () { const json pm.response.json(); pm.expect(json.data.text).to.not.include(error); });特征词检查需要你想清楚自己图片里的确定性信息。比如我测试用的包装图上面一定有“净含量”三个字那就断言返回文本包含这个字段。如果你只是随手拿一张生活照特征词无从谈起那这条断言的意义就不太大了。第三类是检查 token 使用量和请求 ID 是否正常返回pm.test(请求 ID 存在, function () { const json pm.response.json(); pm.expect(json.request_id).to.be.a(string); pm.expect(json.request_id.length).to.eql(32); }); pm.test(token 统计字段正确, function () { const json pm.response.json(); pm.expect(json.data.usage.total_tokens).to.be.above(0); });这些断言脚本在发送请求后会自动执行右侧会显示每一条是否通过。你不需要一次写完可以先运行一遍请求观察返回结构后再逐步补充断言。断言的价值在于把你的“肉眼检查”变成可重复的“自动检查”以后每次改参数重发都能快速知道结果是否异常。3.3 用数据文件批量跑断言单张图片测试通过后你就可以进入批量验证阶段了——把多张图片和多条提示词组合成数据文件让 Postman 集合 Runner 一次性跑完全部用例。这个功能对模型接口测试特别实用因为它能在几分钟内验证模型在不同图片上的表现稳定性、响应延迟和是否偶发超时。先准备好一个 CSV 或者 JSON 文件作为测试数据。JSON 格式比较灵活我的做法是准备这样的数据文件[ { image_path: https://example.com/images/packaging_blue.jpg, prompt: 描述这张包装图的配色和文字, expect_keyword: 蓝色 }, { image_path: https://example.com/images/packaging_red.jpg, prompt: 描述这张包装图的配色和文字, expect_keyword: 红色 } ]然后在 Postman 请求体里使用变量占位{ model: qwen3.5-27b, messages: [ { role: user, content: [ { type: image_url, image_url: { url: {{image_path}} } }, { type: text, text: {{prompt}} } ] } ] }断言里同样可以引用变量pm.test(识别结果包含预期特征词, function () { const json pm.response.json(); pm.expect(json.data.text).to.include(pm.variables.get(expect_keyword)); });打开集合 Runner选择这个请求和对应的 JSON 数据文件Postman 会依次发起多次请求并统计通过率。我实测下来一批十个用例跑完通常只需要几分钟比一个一个人肉验证高效太多。如果某个用例断言失败Runner 结果页会直接标红点击就能看到响应内容定位问题非常快。批量跑完之后还有一个高级技巧把响应时间也固化成断言。多模态接口的响应时间波动通常比纯文本接口大一张大图动辄几秒很正常。你可以设置一条上限断言来防止服务端异常拖垮业务pm.test(响应时间低于10秒, function () { pm.expect(pm.response.responseTime).to.be.below(10000); });这条断言在单次调试时用处不大但在批量跑集合并做性能摸底时就很有价值了。它能把一些偶发的慢请求直接标记出来方便你逐个查看是否跟图片大小有关。4. 常见问题排查与避坑记录4.1 鉴权失败与超时场景处理整个配置流程中鉴权类问题是出现频率最高的主要表现为 401 Unauthorized 和 403 Forbidden 两类情况。401 的本质是服务端不认识你的身份最常见的原因是 Header 格式写错。比如接口要求X-API-Key而你写成了Authorization或者把 Key 多打了一个空格。排查方式是打开请求的 Code 按钮查看生成的 cURL 原文把每一行 Header 和文档示例逐字对比。我碰到过一个很隐蔽的问题环境变量里的 Key 值尾部多了一个不可见的换行符Postman 在 UI 上完全看不出来但请求头里就携带了这个换行符服务端解析时直接拒掉。后来我在环境变量编辑区把 Key 重新复制了一遍才解决。这个案例告诉我环境变量的赋值最好用复制粘贴并且填入后可以在终端里执行echo $KEY感知一下值有没有多余字符。403 则通常是身份识别成功、但权限不足。可能原因包括API Key 没有开通对应模型的访问权限模型名称拼写错误导致被归为未知模型或者当前账号的并发额度已经用完。403 一般不会出现在 Header 格式层面更可能是账号权限问题需要去服务商控制台检查。超时问题同样常见。默认的 Postman 请求超时时间是 30 秒多模态识别如果传大图或者服务端排队严重很容易碰上限。你可以在请求设置Settings里把超时时间调到 60 秒或更长。但我更建议从源头优化控制图片大小把长边控制在 1024 以内文件压缩到几百 KB响应时间通常能降到 5 秒左右。如果连续出现响应时间超过 30 秒的情况优先考虑是不是图片 base64 过大导致上传链路阻塞而不是服务端算力问题。4.2 图片内容出不来或返回 400 的请求体陷阱我遇到过好多次请求发出去服务端返回 400 Bad Request响应体里报“Invalid input或malformed request”。这类问题的根源几乎都在请求体 JSON 结构上。最常见的错误是把image_url写成了字符串这是新手最容易踩的格式坑。规范的结构是嵌套对象image_url: {url: ...}注意url这个子字段名也不能改。如果你按自己的理解写成image: {src: ...}服务端不认识直接报错。第二个重要陷阱是 base64 字符串里包含了换行。如果你在命令行用base64命令生成字符串时没有加-w 0输出可能会被自动切成多行。把这些带换行的字符串直接贴进 JSONJSON 解析器会认为字符串被提前截断请求体直接非法。解决办法是生成时强制不换行或者用一个简单的 Python 脚本处理import base64 with open(image.jpg, rb) as f: data f.read() b64_str base64.b64encode(data).decode(utf-8) print(fdata:image/jpeg;base64,{b64_str})第三个坑是图片 URL 无法直连。如果你传的是公网 URL一定要确认服务端能访问到。我之前用过一个本地测试图片地址Postman 这边访问没问题但服务端的机房网络根本拿不到返回的识别结果就是空白或者报错。验证方式很简单把图片 URL 扔到浏览器无痕窗口里访问能直接打开说明可能可用打不开则一定不行。更保险的方式是直接转 base64 传。还有一类情况是模型明明收到了图片但返回的内容跟图片无关看起来像是只处理了文本。这种问题通常是消息结构里图片格式不符合要求或者模型本身不支持某个图片格式比如 WebP 换成 PNG 就好了。你可以尝试把图片转为 JPG 或 PNG 格式并用长宽比正常的图片再做一次测试。4.3 断言“永远成功”或“永远失败”的原因断言脚本写完之后还有一个让人头疼的表现无论请求结果如何断言全都通过了看起来非常完美实际上半点作用没有。这种“假成功”通常是因为脚本拿的不是真实响应体而是空对象。比如你写pm.response.json()但实际返回的内容不是合法 JSON那这个调用可能抛异常导致脚本中断而 Postman 的默认行为会把这轮测试视为失败。可有些情况下你写的是pm.test(检查code, function () { const json pm.response.json(); pm.expect(json.data.code).to.eql(0); });如果实际响应里没有data.code这个字段pm.expect(undefined).to.eql(0)会失败这倒还正常。真正的问题是另一种写法——你在响应还没返回时就对空对象做了断言比如测试脚本中使用了错误字段路径但表达式恰好不会抛错例如pm.expect(undefined).to.not.eql(null)这条断言就会“通过”但毫无意义。我建议写断言时遵循一个原则第一层断言必须先验证响应存在且 JSON 能正常解析。等你确认了响应结构再去顺着真实字段逐级展开。不要从一个教程里复制一大段断言脚本就完事了至少把字段路径用实际响应核对一遍。另外断言失败时 Postman 默认不会阻止请求流程它只是标红。如果你在集合 Runner 里批量跑测试建议在失败情况下追加一条主动日志方便事后定位pm.test(请求成功且包含输出, function () { const json pm.response.json(); if (!json || !json.data || !json.data.text) { console.log(请求ID:, json.request_id, 原始响应:, JSON.stringify(json)); } pm.expect(json.data.text).to.be.a(string); });通过这种方式就算断言失败你也能从控制台拿到原始响应和请求 ID不需要再手动重发一次请求去复现问题。最后再分享一个小经验Qwen3.5-27B 这类多模态模型的接口调试最难的部分往往不是某个请求怎么构造而是如何在反复调整图片、提示词和参数的循环中保持验证方法的稳定。Postman 的环境变量、集合 Runner 和断言脚本本质上是一套把“人工测试”变成“可复用验证流程”的机制。等你把这套流程跑熟了再回到代码里去调用这个模型接口很多坑都已经在前面踩完了联调效率会快很多。