修复 AI Gateway 图片 MIME 类型错误:用魔数检测替代扩展名猜测
修复 AI Gateway 图片 MIME 类型错误用魔数检测替代扩展名猜测问题背景在使用 Hermes Discord Gateway 发送图片时遇到了一个隐蔽的 bugHTTP 400: messages.16.content.1.image.source.base64: The image was specified using the image/webp media type, but the image appears to be a image/png imageClaude API 校验失败原因是传入的 MIME typeimage/webp与图片实际内容PNG 格式不符。根本原因分析排查路径如下Discord CDN 的行为Discord 下载入站图片附件时会根据 HTTP Response 的Content-Typeheader 决定本地缓存文件的扩展名CDN 转码问题Discord CDN 对 PNG 图片有时返回content-type: image/webpDiscord 内部会自动做格式转换导致文件被存成.webp后缀MIME 猜测逻辑缺陷Gateway 的_guess_mime()函数纯粹基于文件扩展名判断类型文件名叫.webp→ 返回image/webpClaude API 严格校验Claude 在接收 base64 图片时会校验实际字节头与声明的 MIME type 是否一致不匹配直接 400整个链路文件内容是 PNG但扩展名是 .webp → MIME 声明错误 → Claude 拒绝。修复方案魔数Magic Number检测核心思路不再依赖文件扩展名猜 MIME type改为读取文件头字节通过魔数来判断真实格式。常见图片格式的魔数格式字节头Hex说明PNG89 50 4E 47 0D 0A 1A 0A固定 8 字节签名JPEGFF D8 FFJFIF/EXIF 前缀WebP52 49 46 46 xx xx xx xx 57 45 42 50RIFF…WEBPGIF47 49 46 38GIF8修复代码def_guess_mime(path:str,data:bytes|NoneNone)-str: 优先用文件头魔数判断真实 MIME type 兜底才使用文件扩展名。 # 读取文件头字节header:bytesbifdataisnotNone:headerdata[:12]else:try:withopen(path,rb)asf:headerf.read(12)exceptOSError:pass# 魔数匹配ifheader[:8]b\x89PNG\r\n\x1a\n:returnimage/pngifheader[:3]b\xff\xd8\xff:returnimage/jpegifheader[:6]in(bGIF87a,bGIF89a):returnimage/gififheader[:4]bRIFFandheader[8:12]bWEBP:returnimage/webp# 兜底扩展名猜测extos.path.splitext(path)[1].lower()return{.png:image/png,.jpg:image/jpeg,.jpeg:image/jpeg,.gif:image/gif,.webp:image/webp,}.get(ext,application/octet-stream)关键细节魔数优先于扩展名无论文件被命名成什么扩展名字节头永远反映真实格式。传递 bytes 避免重复 IO_file_to_data_url已经读了文件内容直接把bytes传给_guess_mime不用再 open 一次def_file_to_data_url(path:str)-str:withopen(path,rb)asf:dataf.read()mime_guess_mime(path,datadata)# 传入已读字节b64base64.b64encode(data).decode()returnfdata:{mime};base64,{b64}实际效果修复后即使 Discord CDN 把 PNG 文件存成了.webp扩展名魔数检测也能正确识别出image/pngClaude API 接收正常HTTP 400 错误消失。经验总结不要用文件扩展名判断文件类型——这是安全和兼容性领域的经典教训在 AI Gateway 场景下同样成立扩展名是用户/系统可以随意命名的元数据不可信文件头字节是由生成工具写入的实际数据可靠CDN、代理、缓存系统在转码/转发过程中可能改变扩展名但不改变内容这个 bug 的出现恰好是三方联动——Discord CDN 的转码行为 Gateway 的扩展名猜测逻辑 Claude API 的严格校验三者叠加才暴露出来。单独看每一方都没错但组合在一起就出问题了。这类分布式系统中的隐蔽 bug往往需要把整个数据流串起来才能找到根因。参考PNG 文件格式规范 - WikipediaList of file signatures - Wikipediapython-magic更完整的文件类型检测库