文章目錄

Jev 1.13 與 1.14 開放影像輸入後,決策模型也能判斷圖片的內容。我們只要把照片縮小到官方規定的尺寸範圍內並以 multipart 上傳,問一題「是否為貓咪的照片?」就能取得這張照片是貓咪照片的機率,再由程式依門檻回答「是」或「否」。
本文用 10 張自己拍的相機照片示範整個流程,其中 6 張有貓咪,另外 4 張沒有。範例數據來自 2026-10-18 以 jev-1.14.0 與 jev-1.13.0 實際呼叫 API 的結果,每張照片只送出一次請求,數據用來說明流程而不代表準確率。題型與門檻的基本用法已寫在先前的 Jev API 串接筆記,本文只補上影像輸入需要的部分。
影像輸入的請求結構
根據 2026-10-18 的官方 Images 文件,影像輸入仍是實驗功能,請求格式、限制與準確度都可能隨版本改變,官方目前只建議用於原型與實驗。包含圖片的請求比文字請求多了三個項目:
| 項目 | 內容 |
|---|---|
| Header | 每個包含圖片的請求都要加上 TypeSafe-Danger-Zone-Images: true,沒有加上時會回傳 400 |
| Assets | 請求最上層的 assets 以 Reference 為鍵列出每張圖片,multipart 請求以 part 指定圖片所在的 Part 名稱 |
| Reference | 寫在 state、instructions 或 criteria 中的字串,模型會在該位置讀到對應的圖片,官方建議每次請求都替每張圖片產生新的 UUID 以免和一般文字意外相符 |
圖片可以用 base64 編碼後放進 JSON,也可以用 multipart/form-data 直接上傳原始檔案。官方建議優先使用 multipart,因為 base64 會讓每張圖片變大約三分之一,增加的大小也會計入每個請求 32 MiB 的上限。
multipart 請求的第一個 Part 固定命名為 request 並放入 JSON Body,圖片則放在另一個 Part。本例的圖片放在名為 photo 的 Part 且 Content-Type 為 image/jpeg,state 的值就是這張圖片的 Reference。request 的內容如下:
1{
2 "model": "jev-1.14.0",
3 "state": "3f6c2a90-8d4e-4b1a-9c7e-2e5d1b0a7f43",
4 "assets": {
5 "3f6c2a90-8d4e-4b1a-9c7e-2e5d1b0a7f43": { "type": "image", "part": "photo" }
6 },
7 "questions": {
8 "cat": { "type": "noul", "instructions": "是否為貓咪的照片?" }
9 }
10}
以 DSC05249 這張貓咪照片送出後,回應的格式和文字請求相同,圖片佔用的 Token 也一併計入 usage.input_tokens:
1{
2 "model": "jev-1.14.0",
3 "answers": { "cat": { "type": "noul", "noul": 0.96 } },
4 "usage": { "input_tokens": 4564, "output_tokens": 20 }
5}
圖片前處理
API 只接受 PNG 或 JPEG 靜態圖片且不會自動縮放,尺寸不符規格的圖片會讓整個請求失敗。官方錯誤表寫的是回傳 413,實測送出 1569x800 的圖片則回傳 400 與訊息 Invalid image dimensions.,程式要把這兩種狀態碼都當成圖片不符規格。官方的限制與本文照片的處理方式如下:
| 限制 | 官方規格 | 本文照片的處理 |
|---|---|---|
| 格式 | PNG 或 JPEG 靜態圖片 | 相機原始檔 ARW 先轉成 JPEG |
| 寬度與高度 | 每邊 280 到 1568 像素 | 原圖為 6024x4024,縮小到長邊 1568 後為 1568x1047,直式照片為 1047x1568 |
| 總像素 | 78,400 到 2,458,624 | 1568x1047 約 164 萬像素 |
| 單張檔案 | 16 MiB | 縮小後的 JPEG 約 240 到 430 KB |
API 會移除 EXIF 等 Metadata 且不會套用其中的方向資訊,直拍的照片如果只靠 EXIF 標記方向,送到模型時就會是橫躺的。因此前處理要先依 EXIF 轉正像素,再等比例縮小到長邊 1568 像素並輸出成 JPEG。等比例縮小不會處理過小的圖片,任一邊小於 280 像素的圖片(例如長寬比很極端的全景圖)要另外放大或加上 Padding。
辨識結果
測試用的 10 張照片如下:

本例判斷的是「是否為貓咪的照片?」,noul 是照片為貓咪照片的機率,程式在 noul 大於或等於 0.5 時回答「是」,其餘回答「否」。下表的 1.14 與 1.13 兩欄是各版本回傳的 noul:
| 照片 | 內容 | 正確答案 | 1.14 | 1.13 |
|---|---|---|---|---|
| DSC05249 | 三花色系的貓趴在床上 | 是 | 0.96 | 0.99 |
| DSC05257 | 橘白貓側躺在床邊 | 是 | 0.95 | 0.99 |
| DSC05393 | 藍天下的枯木 | 否 | 0.01 | 0.05 |
| DSC05491 | 海岸邊的石頭裝置藝術 | 否 | 0.01 | 0.00 |
| DSC07460 | 橘白貓的臉部特寫 | 是 | 0.96 | 0.99 |
| DSC07466 | 只拍到橘貓的背部 | 是 | 0.78 | 0.64 |
| DSC07469 | 虎斑白貓趴在地上 | 是 | 0.96 | 0.99 |
| DSC07480 | 虎斑白貓的側臉 | 是 | 0.96 | 0.99 |
| DSC07701 | 手機與轉接卡 | 否 | 0.02 | 0.00 |
| DSC07829 | 木製點字模型 | 否 | 0.02 | 0.00 |
兩個版本的回答都和正確答案一致。拍到貓咪臉部的 5 張照片機率都在 0.95 以上,沒有貓咪的 4 張都在 0.05 以下,只拍到背部的 DSC07466 則是 0.78 與 0.64。如果改用 Jev API 串接筆記 提到的官方範例門檻,也就是 0.8 以上算「是」、0.2 以下算「否」,DSC07466 在兩個版本都會交給人工確認。以 1.14 把同一批照片再送一次時,每張照片的機率差異都在 0.02 以內。
官方 Models 頁 表示英文的準確度最好,本例的中文題目在這 10 張照片上都判斷正確。每張照片的請求約 4,565 Tokens,依同一頁每百萬 Tokens 0.042 美元的輸入價格計算約 0.0002 美元,一個請求放入多張圖片時也要確認總 Token 數不超過 Context 長度上限。
後端呼叫流程
範例以 Python 撰寫並使用 Pillow 處理圖片,換成其他語言時流程相同。API 金鑰、模型版本與端點的設定方式和 Jev API 串接筆記 相同。
我們可建立一個函式 prepare_image 來處理圖片,它輸入圖片路徑並輸出轉正且縮小後的 JPEG 內容:
1def prepare_image(path):
2 with Image.open(path) as original:
3 image = ImageOps.exif_transpose(original) # 依 EXIF 轉正像素
4 image.thumbnail((1568, 1568)) # 等比例縮小,長邊不超過 1568
5 buffer = io.BytesIO()
6 image.convert("RGB").save(buffer, "JPEG", quality=90) # 品質為範例值
7 return buffer.getvalue()
再建立一個函式 ask_is_cat 來送出請求,它替圖片產生新的 UUID 作為 Reference,再把前面的 request 內容與 prepare_image 輸出的圖片組成 multipart 請求送出:
1def ask_is_cat(client, path):
2 ref = str(uuid.uuid4())
3 request = {
4 "model": MODEL,
5 "state": ref,
6 "assets": {ref: {"type": "image", "part": "photo"}},
7 "questions": {"cat": {"type": "noul", "instructions": "是否為貓咪的照片?"}},
8 }
9 files = {
10 "request": (None, json.dumps(request, ensure_ascii=False), "application/json"),
11 "photo": (path.name, prepare_image(path), "image/jpeg"),
12 }
13 response = client.post(API_URL, files=files)
14 response.raise_for_status()
15 return response.json()
遇到 429 或 529 時的重試可以沿用前文 ask_jev 的寫法。後端服務啟動時建立共用的 HTTP Client,在 Authorization Header 之外再加上影像輸入的 Header。下例的 photos 是照片路徑的清單,每張照片各呼叫一次 ask_is_cat,再依 noul 回答「是」或「否」:
1with httpx.Client(
2 headers={"Authorization": f"Bearer {API_KEY}", "TypeSafe-Danger-Zone-Images": "true"},
3 timeout=httpx.Timeout(60, connect=10), # 範例值
4) as client:
5 for path in photos:
6 noul = ask_is_cat(client, path)["answers"]["cat"]["noul"]
7 print(path.stem, "是" if noul >= 0.5 else "否")
總結
用 Jev 辨識圖片類型只需要一題 Noul,比文字請求多做的是圖片前處理與請求格式:
- 送出前先依 EXIF 轉正像素,再等比例縮小到長邊 1568 像素並輸出 JPEG。
- 以 multipart 上傳圖片,在
assets用 UUID 作為 Reference 連到圖片的 Part,並在每個請求加上TypeSafe-Danger-Zone-Images: trueHeader。 - 程式依
noul的門檻回答「是」或「否」,只拍到局部的照片機率會偏低,需要更謹慎時可以把中間區段交給人工確認。