图片¶
图片输入是对参考契约的扩展。官方 Jev 接口只接受文本(文档明确
No image, audio, or video input),Pydantic AI 客户端遇到非文本 part 直接抛
UserError: Files are not supported by this model;本项目替换文本路径,并额外支持带截图
或相机帧的证据打分。
按托管服务写的客户端仍然可用——它们只是会忽略这个字段。
图片如何挂载¶
图片固定挂在第一条 user message 的最前面,使视觉编码结果和证据 prefill 在同一次请求 的所有问题之间复用。网关会构造这样的 OpenAI content part:
并把文本 part 追加在它们之后。没有图片时,content 保持为普通字符串。
支持的形式¶
| 形式 | 示例 | 说明 |
|---|---|---|
| data URL | data:image/jpeg;base64,... |
原样转发 |
| 远程 URL | https://example.com/shot.png |
需 multimodal.allow_remote_urls: true |
| 裸 base64 | iVBORw0KGgo... |
按 magic bytes 嗅探媒体类型 |
可嗅探的格式:jpeg、png、gif、bmp、tiff、webp。base64 内部的空白(换行折行的 data URL) 会在解码前被去掉。
其他情况一律以 INVALID_REQUEST 拒绝:非图片媒体类型、base64 解不开、格式无法识别、
图片张数超限、单图超限。
限额¶
限额来自 multimodal 配置块,不是写死的常量。
| 键 | 默认值 | 含义 |
|---|---|---|
enabled |
true |
为 false 时任何 images 都报 400 |
max_images |
4 |
单次请求的图片数 |
max_image_bytes |
5242880(5 MiB) |
单图,base64 解码后计算 |
allow_remote_urls |
false |
开启后才接受 https:// 引用 |
allowed_mime_prefixes |
["image/"] |
接受的媒体类型 |
multimodal:
enabled: true
max_images: 4
max_image_bytes: 5242880
allow_remote_urls: false
allowed_mime_prefixes: ["image/"]
远程 URL 之所以默认关闭
打开 allow_remote_urls: true 后,网关会去取调用方给出的任意 URL。这是一个服务端
请求伪造面:调用方可以让你的网关访问 http://169.254.169.254/,或内网里任何东西。
只有两端都归你所有时才开启。
后端看不见的时候¶
后端没有视觉能力时,请求会以 BACKEND_CAPABILITY_UNSUPPORTED 快速失败。把
backend.supports_images 设为 false 可以彻底关闭该路径,稳定地拿到这个错误,而不用
等到请求中途才发现。
对 llama.cpp 来说,视觉模型除了权重还需要投影权重:
llama-server -m /models/model.gguf --mmproj /models/mmproj.gguf -c 8192 -np 4 --jinja
curl http://127.0.0.1:8080/props | grep -i vision
# "vision": true
GET /props → modalities.vision 必须为 true。若不是,说明模型没带投影权重加载,所有
图片请求都会失败。
布局对图片有影响¶
用 request.prompt_layout: split 时,承载图片的证据消息在一次请求的所有问题之间逐字节
相同,视觉编码只跑一次。用 fused 则每个问题都会重发整份负载。如果带图片且问题较多又
在意延迟,就用 split。
测试¶
python scripts/make_test_png.py # 一张极小的合法 PNG,不需要 Pillow
python scripts/smoke_test.py --url http://127.0.0.1:8000 --image shot.png
冒烟脚本会把文件转成 data URL 发送,并断言答案结构仍然正确。见脚本。