Grok 图像理解接入
把图片地址塞进 content,模型却像没看见——这是图像理解接入最常见的失败。原因是 content 的形态变了:纯文本请求里它是字符串,带图片时必须变成对象数组,每一项声明自己是图片还是文本。写成字符串,请求可能照常返回,但模型只看到了那句问题。
这一节把「怎么组织图文请求」讲成可校验的结构。
一、问题不是「模型能不能看图」,而是「请求怎么声明图」
官方给的对比很直接:纯文本时 content 是一段字符串;带图片时,content 变成对象列表,图片用 {"type": "input_image", "image_url": ...},文字用 {"type": "input_text", "text": ...}。
两个细节决定成败:
image_url可以是公网 URL,也可以是 base64 的 data URL(data:image/jpeg;base64,...)。- 图文顺序任意,文字在前、图片在后都可以。
还有一条容易被忽略的官方提示:发送图片时,建议不要在服务端保存请求/响应历史,否则请求可能失败。如果你的实现默认把每轮对话存到服务端,需要为带图请求单独处理。
二、最小可运行示例:图文请求构造与校验
下面的脚本不联网、不需要真实密钥。它构造一个图文 content,并检查三件最容易错的事:有没有图片项、有没有文本项、图片地址形态对不对。
"""图文 content 构造与校验(mock)。"""
from urllib.parse import urlparse
DATA_MIME_OK = ("data:image/jpeg", "data:image/jpg", "data:image/png")
def build_content(image_url: str, question: str, detail: str = "high") -> list[dict]:
return [
{"type": "input_image", "image_url": image_url, "detail": detail},
{"type": "input_text", "text": question},
]
def check_content(content: list[dict]) -> list[str]:
errors: list[str] = []
types = [c.get("type") for c in content]
if "input_image" not in types:
errors.append("缺少 input_image")
if "input_text" not in types:
errors.append("缺少 input_text")
for c in content:
if c.get("type") != "input_image":
continue
url = c.get("image_url", "")
if url.startswith("data:image/"):
if not url.startswith(DATA_MIME_OK):
errors.append("data URL 的 MIME 不在 jpeg/png 内")
continue
if not url.startswith("http"):
errors.append("image_url 既不是公网 URL 也不是 data URL")
continue
# 只看 path,避免把 ?token=... 的签名 URL 误判成非图片
if not urlparse(url).path.lower().endswith((".jpg", ".jpeg", ".png")):
errors.append("图片格式不在 jpg/jpeg/png 内")
return errors
def offline_check() -> None:
cases = [
("合法", build_content("https://example.com/chart.png", "这张图说明了什么?")),
("带查询参数", build_content("https://cdn.example.com/a.png?token=abc", "看图")),
("缺文本", [{"type": "input_image", "image_url": "https://example.com/a.png"}]),
("格式不对", build_content("https://example.com/report.gif", "看图")),
("data URL 用 svg", build_content("data:image/svg+xml;base64,PHN2Zz4=", "看图")),
]
for label, content in cases:
errs = check_content(content)
print(f"{label}:" + ("通过" if not errs else ";".join(errs)))
if __name__ == "__main__":
offline_check()
真正上线时,把 build_content() 的返回直接作为 input[0].content;check_content() 放在发请求之前,挡掉「地址写成了 PDF」「忘了放文本」这类结构性错误。
三、输入限制与三个易错点
| 项 | 官方口径 |
|---|---|
| 单图大小 | 最大 20MiB |
| 图片数量 | 无限制 |
| 支持格式 | jpg / jpeg / png |
| 图文顺序 | 任意 |
三件要强调的事:
- 只有三种格式。
webp、gif都不在列表里,先转码再传;转码本身也可能改变画质,涉及识别精度时要留意。 - 20MiB 是单图上限。多图没有数量限制,但总量会推高 token 消耗;批量识图前先估一次成本。
detail影响精细度。官方示例用detail: "high";对表格、小字、图表这类任务,低精度可能直接丢信息。
四、预期输出与异常分支
运行校验器,预期输出:
合法:通过
带查询参数:通过
缺文本:缺少 input_text
格式不对:图片格式不在 jpg/jpeg/png 内
data URL 用 svg:data URL 的 MIME 不在 jpeg/png 内
换成真实请求时,若出现异常,按下面顺序定位:
| 现象 | 优先怀疑 | 第一步动作 |
|---|---|---|
| 模型答得像没看图 | content 还是字符串 |
改成对象数组并确认有 input_image |
| 请求直接失败 | 服务端存了历史 | 关闭带图请求的历史存储 |
| 图片读不出来 | 地址不可公网访问 | 换公网 URL 或转 base64 |
| 小字识别不准 | detail 太低 |
提高到 high 并控制图片尺寸 |
最后一行尤其重要:图像理解的失败经常不是「模型不行」,而是传给它的图已经被压缩到看不清。
五、判据卡
| 判断项 | 该这么做 | 不该这么做 |
|---|---|---|
| content 形态 | 对象数组,声明 image/text | 直接塞字符串 |
| 图片地址 | 公网 URL 或 data URL | 传本地文件路径 |
| 格式 | jpg / jpeg / png | 直接传 webp / gif |
| 历史存储 | 带图请求单独关闭 | 沿用默认存储 |
| 精度 | 图表小字用 high | 一律用最低精度 |
这张卡的重点是请求结构先对,再谈识别效果。五条里有三条在说:模型看到的东西,取决于你声明了什么。
六、来源与核验
- 官方 Image Understanding:
https://docs.x.ai/developers/model-capabilities/images/understanding(核验日期 2026-11-04)。content对象数组结构、input_image/input_text、公网 URL 与 data URL、detail参数、20MiB / jpg-png / 无数量上限 / 任意顺序,以及「不建议在服务端存储带图请求历史」的提示,均以该页为准。 - 官方模型页:
https://docs.x.ai/developers/models(核验日期 2026-11-04)。支持图像输入的模型以该页为准。 - 本节不承诺任何识别准确率;具体效果取决于图片质量、任务类型与模型版本。
七、动手试
- 用同一张含小字的截图,分别用
detail: "high"与默认值各问一次,比较识别结果差异。 - 给你的请求加一道自检:当
content是字符串却带了图片时直接报错,而不是发出去等模型「无视」。 - 设计一条带图请求的存储策略:说明哪些字段可以落库、哪些必须丢弃,以及为什么。