文档目录
图片
在 System One 请求中发送图片:放在 state 中、问题的 instructions 里,或者作为评判该问题的 criteria。
非官方中文翻译 · 内容以 英文原文 ↗ 为准 · 同步于 2026-10-15
图片可以放在 System One 请求中任何可以放文本的位置:state、问题的 instructions,以及它的 criteria。每种题型都支持图片,而 Box 这种题型可以返回某个东西在图片里的位置。
<img className="block dark:hidden mx-auto" style={{ maxWidth: 480 }} src="https://mintcdn.com/ts-docs/OmOS-OoUA_8VxwSR/images/images/anatomy-light.webp?fit=max&auto=format&n=OmOS-OoUA_8VxwSR&q=85&s=7d5e99842cb8a482309f65a6d4aa195e" alt="请求体中有三处高亮引用,每处都指向它代表的图片:door.jpg 是 state 中 camera 字段的值;badge.jpg 出现在某道 Noul(是非题)问题的 instructions 里;thumb.jpg 是某道 Choice(选择题)问题 criteria 中 thumbs_up 选项的值。图片本身则列在请求的 assets 中。" width="960" height="1048" data-path="images/images/anatomy-light.webp" />
<img className="hidden dark:block mx-auto" style={{ maxWidth: 480 }} src="https://mintcdn.com/ts-docs/OmOS-OoUA_8VxwSR/images/images/anatomy-dark.webp?fit=max&auto=format&n=OmOS-OoUA_8VxwSR&q=85&s=13804f2b47a23dc560f322bc2e5df981" alt="请求体中有三处高亮引用,每处都指向它代表的图片:door.jpg 是 state 中 camera 字段的值;badge.jpg 出现在某道 Noul(是非题)问题的 instructions 里;thumb.jpg 是某道 Choice(选择题)问题 criteria 中 thumbs_up 选项的值。图片本身则列在请求的 assets 中。" width="960" height="1048" data-path="images/images/anatomy-dark.webp" />
开启图片
图片和 Box 问题默认关闭。要使用它们,需要在每个包含图片或 Box 问题的请求中带上请求头 TypeSafe-Danger-Zone-Images: true。上传图片 一节的示例展示了具体写法。本页其他示例只展示请求体部分。
在请求中发送图片
每张图片只在请求的顶层 assets map 中列出一次。它在该 map 中的 key 是一个引用字符串。把这个引用字符串写在图片所属的位置——可以是一个完整的字符串值,也可以嵌在更长的字符串里——模型就会在该位置收到这张图片。
下面这个请求把一张找到的狗的照片作为 state,并把一张寻狗启事上的照片直接放进了一道问题里:
{
"model": "jev-latest",
"state": {
"photo": "a3f1c2e8-found"
},
"assets": {
"a3f1c2e8-found": {"type": "image", "format": "jpg", "data_base64": "/9j/4AAQSkZJRg..."},
"77b0d4a1-poster": {"type": "image", "format": "jpg", "data_base64": "/9j/4AAQSkZJRg..."}
},
"questions": {
"same_dog": {
"type": "noul",
"instructions": "Is the dog in `photo` the same dog as in this picture: 77b0d4a1-poster?"
},
"has_collar": {
"type": "noul",
"instructions": "Is the dog in `photo` wearing a collar?"
},
"coat": {
"type": "choice",
"instructions": "What is the coat of the dog in `photo` like?",
"criteria": {
"short": "Short and smooth.",
"long": "Long or feathered.",
"curly": "Curly or wiry."
}
}
}
}问题可以通过反引号包裹的名字或路径来引用其中的图片,方式与引用其他字段相同(参见引用特定字段)。只有一道问题会用的图片,直接放在那道问题的 instructions 或 criteria 里即可,就像上面那张启事照片那样。
Jev 1.13 和 Jev 1.14 都支持图片。Box 问题需要 Jev 1.14。
Asset 字段
assetsmap<string, Asset>:请求中的图片,以引用字符串为键。每个引用都必须出现在state、instructions或criteria中的某处。type"image":资源种类。可选。如果填写,必须为"image"。partstring:在 multipart 请求中,承载该图片的文件 part 的名字。参见上传图片。data_base64string:经过 base64 编码的图片文件。除非设置了part,否则此项必填。使用标准 base64 字母表,并附带=填充,不含换行或其他空白字符,且不带data:前缀。format"png" | "jpg" | "jpeg":图片的格式,小写。与data_base64同时给出时必填;与part同时给出时可选。"jpeg"是"jpg"的另一种写法。
每张图片只用一种方式发送,要么用 part,要么用 data_base64。如果一个 asset 同时含有两者,API 会使用 part。
引用规则
引用以纯文本形式在请求的字符串中匹配,因此引用既可以作为完整的值,也可以出现在句子的中间。像 cat 这种短引用,也可能意外命中 category 这种文本。请使用不会在你的文本中意外出现的引用。每次请求、每张图片都使用一个新生成的 UUID 是最稳妥的做法。
- 每个 asset 都必须被引用。 一个 asset 在请求中没有任何位置引用它,请求会返回
400。 - 完整值优先匹配。 当一个字符串恰好等于某个引用时,整段就被替换为该图片。在更长的字符串里,引用从左到右依次替换,且匹配互不重叠。
- 每次出现都会再加一份图片。 文件只上传一次,但引用每出现一次,就会把该图片再添加到模型阅读的内容中一份,并且每一份都计入展开后的请求上限。
- 对象 key 不会被替换。 Choice(选择题)的选项名、问题的 ID,以及任何其他对象 key,都保持为文本。要给 Choice 的某个选项配上图片,请把引用放在该选项的描述里,见 Criteria。
- 答案中重复出现的是引用而不是图片。 在答案会回显你 criteria 的位置(例如 Score(打分题)的
legend),重复出现的是引用字符串本身,而不是图片。 - 保持 key 较短。 每个引用最多 1,024 字节 UTF-8,整个请求中所有引用加起来最多 65,536 字节。这些限制按字节数计算,而不是字符数。引用也不能为空或仅含空白字符。
Python 和 JavaScript SDK 会自动帮你生成引用。参见上传图片。
上传图片
使用最新的 Python 或 JavaScript SDK,把一个 image 对象放在任何可以放文本的位置即可。SDK 会自动为它生成引用并上传文件。下面的示例把一张门铃照片作为 state,并围绕它提出两个问题:
from pathlib import Path
from typesafe_sdk import Choice, Image, Noul, TypeSafeClient
with TypeSafeClient() as client:
result = client.system_one(
state={"door": Image(Path("door-camera.jpg"))},
questions={
"person_present": Noul(instructions="Is there a person in `door`?"),
"carrying": Choice(
instructions="What is the person in `door` carrying?",
criteria={
"package": "A box or parcel.",
"bag": "A bag of groceries or shopping.",
"nothing": "Empty hands.",
},
),
},
extra_headers={"TypeSafe-Danger-Zone-Images": "true"},
)
print(result.answers)import { openAsBlob } from "node:fs";
import { choice, image, noul, TypeSafeClient } from "@typesafe-ai/sdk";
const client = new TypeSafeClient();
const door = image(await openAsBlob("door-camera.jpg"));
const { answers } = await client.systemOne(
{
state: { door },
questions: {
person_present: noul("Is there a person in `door`?"),
carrying: choice("What is the person in `door` carrying?", {
package: "A box or parcel.",
bag: "A bag of groceries or shopping.",
nothing: "Empty hands.",
}),
},
},
{ headers: { "TypeSafe-Danger-Zone-Images": "true" } },
);
console.log(answers);要把图片嵌进一句话,或者想自己指定引用,请参见 SDK 指南。
直接通过 HTTP
如果没有使用 SDK,可以把每张图片作为 multipart/form-data 请求中的一个 file part 发送。Multipart 是更推荐的做法:文件按原样上传;用 base64 会让每张图片大约膨胀 1/3,而膨胀后的体积也会计入 32 MiB 的请求体上限。只有在你无法发送 multipart 时,才把图片 base64 编码后放入 JSON。
Multipart(推荐)
把 JSON body 作为第一个 part,名为 request,content type 为 application/json。每张图片作为单独的文件 part 发送,并在 assets 中把该图片的 part 设为该文件 part 的名字:
curl https://api.typesafe.ai/v1/systemone \
-H "Authorization: Bearer $TYPESAFE_API_KEY" \
-H "TypeSafe-Danger-Zone-Images: true" \
-F 'request={
"model": "jev-latest",
"state": {"door": "a3f1c2e8-door"},
"assets": {"a3f1c2e8-door": {"type": "image", "part": "door-photo"}},
"questions": {
"person_present": {"type": "noul", "instructions": "Is there a person in `door`?"}
}
};type=application/json' \
-F 'door-photo=@door-camera.jpg;type=image/jpeg'multipart 请求的规则:
- 第一个 part 名为
request,用来承载 JSON body。 - 各 part 名称必须唯一且非空。
request已预留给 JSON body。 - 每个图片 part 的 content type 为
image/png、image/jpeg、image/jpg或application/octet-stream,也可以不设 content type。其他任何 content type 都会返回400。 - 每个文件 part 恰好属于一个 asset。没有 asset 引用的 part、两个 asset 引用了同一个 part,或者 asset 引用了缺失的 part,都会返回
400。
要在多个位置使用同一张图片,就在每个位置都写上它的引用。
Base64 嵌入 JSON
按照 Asset 字段 中的规则,把文件 base64 编码后放入 data_base64,并设置 format。Python 的 base64.b64encode 会产生正确的编码。下面的 Python 示例发送一张照片,并围绕它提出两个问题:
import base64
import os
import uuid
import requests
def image_asset(path: str) -> tuple[str, dict]:
"""Return a new reference and the asset entry for one image file."""
fmt = "png" if path.lower().endswith(".png") else "jpg"
with open(path, "rb") as f:
data = base64.b64encode(f.read()).decode("ascii")
return str(uuid.uuid4()), {"type": "image", "format": fmt, "data_base64": data}
photo_key, photo = image_asset("door-camera.jpg")
response = requests.post(
"https://api.typesafe.ai/v1/systemone",
headers={
"Authorization": f"Bearer {os.environ['TYPESAFE_API_KEY']}",
"TypeSafe-Danger-Zone-Images": "true",
},
json={
"model": "jev-latest",
"state": {"door": photo_key},
"assets": {photo_key: photo},
"questions": {
"person_present": {
"type": "noul",
"instructions": "Is there a person in `door`?",
},
"carrying": {
"type": "choice",
"instructions": "What is the person in `door` carrying?",
"criteria": {
"package": "A box or parcel.",
"bag": "A bag of groceries or shopping.",
"nothing": "Empty hands.",
},
},
},
},
)
print(response.json()["answers"])图像放置
上下文状态(state)
state 中的图像是上下文状态的一部分,和其中的文本或 JSON 一样。模型会把它和上下文状态其余部分一起评估,请求里的每个问题都会对照它来判断。上下文状态可以混合图像与文本,也可以容纳多张图像,像其他状态数据一样加标签或嵌套。下面这段上下文状态保存了一段相机帧历史,从早到晚排列:
{
"state": {
"frames": [
{"seconds_ago": 2, "frame": "5c0e9a12-f1"},
{"seconds_ago": 1, "frame": "1d7be0c4-f2"},
{"seconds_ago": 0, "frame": "e48a6f30-f3"}
]
}
}一个问题可以问 "frames 中的门在这些帧之间是否打开过?",也可以通过路径引用某一帧,例如 `frames[2].frame`。
指令(instructions)
问题 instructions 中的图像属于该问题的一部分,只随该问题一起发送。把引用放在需要图像的那句话里:
{
"state": {"door": "e48a6f30-door"},
"questions": {
"is_badge_holder": {
"type": "noul",
"instructions": "Is the person in `door` the same person as in this badge photo: 9a21c7d3-badge?"
}
}
}instructions 中的图像会随每个引用它的问题各发送一次。当多个问题共用同一张图像时,把它放进 state 一次,再用路径从每个问题中引用。这样消耗的 token 更少,占用的 大小限制 也更少。
判定标准(criteria)
选择题(Choice)选项、打分题(Score)档位,以及是非题(Noul)的 true 和 false 定义都可以包含图像。下面这个 Choice 通过示例图片对手势进行分类。选项名就是代码在 choice 中收到的值,图片就是模型用来比对的对象:
{
"state": {"camera": "e48a6f30-cam"},
"questions": {
"gesture": {
"type": "choice",
"instructions": "Which gesture is the hand in `camera` making?",
"criteria": {
"thumbs_up": "0b6c51aa-thumb",
"open_palm": {
"description": "A hand held up with the palm facing the camera.",
"examples": ["4fd2e7b9-palm1", "c83a0e15-palm2"]
},
"fist": "a7e94d02-fist",
"none": "No hand, or a hand that matches none of the examples."
}
}
}
}一个选项可以只是一张图片(thumbs_up),可以是一段描述加上一张或多张示例(open_palm),也可以只有文字(none)。description 和 examples 这两个键名是为本示例所取,并非必需字段:criteria 接受任何 结构。
Score 和 Noul 的 criteria 以相同方式使用图像:
- Score 档位 可以各自包含一张示例图片。一个用于判断手张开程度的 Score 可以用握拳、半开的手、完全张开的手作为三个档位的示例。
- Noul criteria 可以用图片定义
true和false:{"true": {"description": "Correctly assembled", "examples": ["<reference>"]}, "false": "Parts missing or in the wrong place."}。
结构化数据
state、instructions 和 criteria 都接受嵌套的 JSON 结构,引用在任意层级都生效。一个问题可以引用数组中的某一张图片,例如 `lineup[2]`,也可以引用带标签对象里的某一张,例如 `faces.happy`。一个判定标准可以包含多张示例图片组成的数组。
设计模式
| 设计模式 | 工作方式 | 图像放在哪里 |
|---|---|---|
| 与已知图片比对 | 询问上下文状态展示的是否与某张已知图片相同的某个人、产品或地点。 | 已知图片放 instructions,待比对的照片放 state |
| 用示例定义选项 | 为每个 Choice 选项提供一张或几张示例图片。 | criteria |
| 找出变化 | 发送上一帧或一段简短的帧历史,询问运动、新出现的事物或差异。 | state |
| 需要时看得更仔细 | 先就图像的小拷贝提问。答案需要更多细节时,再发送一张更大的拷贝(最大到大小限制),或一张全分辨率的裁剪图。 | state |
| 在大图中搜索 | 把图像切成瓦片,用一个 Choice 问题把这些瓦片作为它的选项。再对选中的瓦片重复此过程,直到目标占满瓦片的大部分。 | criteria |
| 定位目标 | 用一个 Box 问题来询问目标的边界。 | state,可选地在 instructions 放一张目标的图片 |
大小限制
| 限制项 | 取值 |
|---|---|
| 格式 | PNG 或 JPEG,仅限静态图像。动画 PNG 会被拒绝。 |
| 文件大小 | 每个图像文件 16 MiB。base64 时按解码后的大小计算。 |
| 宽和高 | 各在 256 到 1568 像素之间。 |
| 总像素(宽 × 高) | 78,400 到 2,458,624,介于 280 × 280 与 1568 × 1568 正方形的面积之间。256 × 256 的图像过小。 |
| 请求体 | 32 MiB,包含 JSON、base64 文本以及 multipart 的分隔部分。HTTP 头不计入。压缩 不会提高该上限。 |
| 请求中的所有图像 | 32 MiB,由 API 处理后的每张不同图像共享。处理会重新编码每张图像,处理后可能比上传的文件更大,因此小文件组成的请求也可能超出上限。 |
| 展开后的请求 | 32 MiB,用于文本、问题、结构化数据以及每处引用被替换为图像后的图像。每多一次引用,图像就会再计入一次:一张处理后 10 MiB 的图像被引用四次就会超出限制。 |
所有限制都包含两端。超出范围的图像会被直接拒绝,绝不会缩放到合适尺寸。请求中的任何一张图像被拒绝,整个请求就会失败:不会出现部分答案。状态码见 错误。
单次请求能容纳多少张图像取决于这些限制以及 上下文长度。图像没有单独的 限流:包含图像的请求与任何其他请求一样计入同样的限流。
要在请求限制之内,可以少发几张图像、发更小的图像、把多个问题共用的图像放进 state 一次而不是放进每个问题的 instructions,或者把任务拆分到多个请求中。
图像预处理
每张图像应以 PNG 或 JPEG 发送,旋转到正向,并调整到 限制 范围内再发送。发送前对每张图像做如下预处理:
- 发送前先调整大小。 把图像缩放到最长边不超过 1568 像素。一张典型的 1200 万像素手机照片会变成 1568 × 1176。小于最小值的图像要放大或加边。
- 把像素旋转到正向。 API 会移除所有元数据(EXIF、色彩配置、注释),且不会应用 EXIF 方向。在你的看图软件里看着是正向的照片,到达模型时可能是侧着的,所以要先旋转像素本身。
- 发送静态图像。 动画或视频,把想要的帧导出为 PNG 或 JPEG。其他格式(如 GIF、WebP、HEIC)请转换为 PNG 或 JPEG。
- 优先使用标准 JPEG。 标准的 8 位 baseline JPEG 是最稳妥的 JPEG 格式。把 progressive JPEG 重新保存为 baseline。
- 重新导出被拒绝的文件。 如果 API 拒绝了一个在你电脑上能正常打开的文件,用标准的图像编辑器或库打开后再次导出为 PNG 或 JPEG。
下面这个 Python 示例使用 Pillow 库预处理一张照片:
from PIL import Image, ImageOps
with Image.open("photo.jpg") as original:
img = ImageOps.exif_transpose(original) # 把像素旋转到正向
img.thumbnail((1568, 1568)) # 最长边不超过 1568,保持相同宽高比
# thumbnail 只缩小:小于最小值的图像需要单独放大或加边
img.convert("RGB").save("photo-ready.jpg", "JPEG", quality=90) # baseline JPEG图像 token
每张图像都计入输入 token,按与文本相同的 输入价格 计费。图像没有单独的收费。响应中的 usage.input_tokens 字段包含请求中所有图像的 token。图像 token 也计入请求的 上下文长度。
关于如何估算图像 token 用量,我们很快会提供指引。
错误
| 状态码 | 原因 |
|---|---|
400 |
未开启图像功能:请求中包含图像或 Box 问题,但缺少 TypeSafe-Danger-Zone-Images: true 请求头。参见 开启图像功能。 |
400 |
图像或资源被拒绝。文件不是 PNG 或 JPEG,已损坏或不完整,或是动画;图像的宽、高或总像素小于最小值;资源没有被任何地方引用;base64 或 format 不合法;引用为空;或 multipart 请求违反了某条 multipart 规则。 |
400 |
max_tokens_exceeded:图像与文本合在一起超过了请求的 上下文长度。请少发或缩小图像,或把图像从 instructions 移到 state。 |
413 |
请求超出大小限制:图像文件、图像的宽 / 高 / 总像素、请求体、所有图像合计、展开后的请求,或引用长度。参见 大小限制。 |
如果一次请求同时存在多个问题,错误只报告其中之一。请对照 大小限制 和 图像预处理 检查请求。
本文档由 Mintlify 构建并托管,Mintlify 是一个开发者文档平台。