文档目录

图片

在 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,并把一张寻狗启事上的照片直接放进了一道问题里:

json
{
  "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 字段

  • assets map<string, Asset>:请求中的图片,以引用字符串为键。每个引用都必须出现在 state、instructions 或 criteria 中的某处。

    • type "image":资源种类。可选。如果填写,必须为 "image"。

    • part string:在 multipart 请求中,承载该图片的文件 part 的名字。参见上传图片。

    • data_base64 string:经过 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,并围绕它提出两个问题:

python
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)
typescript
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 的名字:

bash
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 示例发送一张照片,并围绕它提出两个问题:

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 一样。模型会把它和上下文状态其余部分一起评估,请求里的每个问题都会对照它来判断。上下文状态可以混合图像与文本,也可以容纳多张图像,像其他状态数据一样加标签或嵌套。下面这段上下文状态保存了一段相机帧历史,从早到晚排列:

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 中的图像属于该问题的一部分,只随该问题一起发送。把引用放在需要图像的那句话里:

json
{
  "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 中收到的值,图片就是模型用来比对的对象:

json
{
  "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 库预处理一张照片:

python
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 是一个开发者文档平台。