API และ SDKคู่มือ
รับ JSON ที่ถูกต้องจาก LLM API: response_format และ retries
คำขอ Python ที่คัดลอกได้ซึ่งส่งคืน JSON ที่แยกวิเคราะห์ได้, wrapper json.loads ที่จัดการคำตอบว่างเปล่าหรือถูกตัด, และเช็กลิสต์ว่าทำไม JSON ถึงพัง

ในหน้านี้
ในการดึง JSON ออกจากโมเดลแชท ให้ทำสามอย่าง: ส่ง response_, ใส่คำว่า "json" และตัวอย่างโครงสร้างในพรอมป์ต์, และตั้ง max_tokens ให้สูงพอสำหรับทั้งออบเจ็กต์ จากนั้นอย่าเชื่อคำตอบแบบไม่ตรวจสอบ: ตรวจว่าไม่ว่างเปล่า, ตรวจ finish_reason, แล้วจึงเรียก json.loads
นั่นคือเวอร์ชันสั้น ส่วนที่เหลือของคู่มือนี้คือสคริปต์ที่สมบูรณ์สำหรับงานที่พบบ่อยมาก: การดึงฟิลด์ออกจากอีเมล ตัวอย่างใช้ base URL https:/ และ model id deepseek- (docs)
งาน: อีเมลเข้า แถวออก#
สมมติว่าสคริปต์ของคุณอ่านอีเมลสนับสนุนและต้องแปลงแต่ละฉบับเป็นเรกคอร์ด: ผู้ส่ง, วันที่, จำนวนเงิน, และสิ่งที่ผู้ส่งต้องการ ลองดูฉบับนี้:
สวัสดี ฉันคือ Anna Petrova จาก Lumen Ltd. เมื่อวันที่ 14 ตุลาคม ฉันจ่ายใบแจ้งหนี้ #3381 เป็นจำนวน 1,250 EUR แต่ใบเสร็จยังแสดงว่าไม่ชำระ กรุณาแก้ไขก่อนวันศุกร์
คุณต้องการสิ่งนี้กลับมา และไม่มีอย่างอื่น:
{"sender": "Anna Petrova", "date": "2026-10-14", "amount": 1250, "currency": "EUR", "action": "fix receipt status"}พรอมป์ต์ "extract the fields" ธรรมดาอาจให้ประโยคที่เป็นมิตร, code fence, หรือทั้งสองอย่าง json.loads ของคุณพังที่ backtick ตัวแรก นั่นคือปัญหาที่ response_format และพรอมป์ต์ที่ดีแก้ร่วมกัน
คำขอเดียวกันใน Python และ curl
Python
import json
import os
from openai import OpenAI
client = OpenAI(
base_url="https://api.cheaptokens.dev/v1",
api_key=os.environ["CHEAPTOKENS_API_KEY"],
)
MODEL = "deepseek-ai/DeepSeek-V4-Flash-0731"
SYSTEM = """Extract fields from the email and answer with json only.
Example of the json format:
{"sender": "Jane Roe", "date": "2026-01-31", "amount": 99.5, "currency": "USD", "action": "short phrase"}
Use null for a field that is not in the email."""
resp = client.chat.completions.create(
model=MODEL,
messages=[
{"role": "system", "content": SYSTEM},
{"role": "user", "content": email_text},
],
response_format={"type": "json_object"},
max_tokens=1000,
temperature=0,
)
print(resp.choices[0].message.content)curl
curl https://api.cheaptokens.dev/v1/chat/completions \
-H "Authorization: Bearer $CHEAPTOKENS_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "deepseek-ai/DeepSeek-V4-Flash-0731",
"messages": [
{"role": "system", "content": "Extract fields from the email and answer with json only. Example: {\"sender\": \"Jane Roe\", \"amount\": 99.5}"},
{"role": "user", "content": "Hi, this is Anna Petrova from Lumen Ltd. I paid invoice #3381 for 1,250 EUR..."}
],
"response_format": {"type": "json_object"},
"max_tokens": 1000
}'เอกสารอ้างอิง DeepSeek API สำหรับ JSON Output (ตรวจสอบเมื่อ 27 ตุลาคม 2026; หน้าเว็บไม่มีวันที่เผยแพร่) ระบุกฎที่คำขอนี้ปฏิบัติตาม: response_format ตั้งเป็น json_object, คำว่า "json" ที่ไหนสักแห่งใน system หรือ user prompt, และตัวอย่างโครงสร้างเป้าหมาย หากไม่มีตัวอย่าง โมเดลต้องเดาชื่อฟิลด์ของคุณ และมันจะเดาแตกต่างกันในอีเมลฉบับถัดไป
ทำไมตัวอย่างจึงสำคัญกว่าธง#
response_format ขอให้ API ส่งคืนออบเจ็กต์ JSON มันไม่ได้บอกโมเดลว่าออบเจ็กต์ใด คิดว่ามันเหมือนการบอกเพื่อนร่วมงานว่า "ส่งสเปรดชีตมาให้ฉัน" โดยไม่บอกว่าคอลัมน์ไหน คุณได้สเปรดชีต แต่ไม่ใช่ของคุณ
ตัวอย่างในพรอมป์ต์คือแถวหัวคอลัมน์ สองนิสัยทำให้มันทำงานได้ดีขึ้น:
- แสดงรูปร่างจริง รวมถึงชนิด:
1250เป็นตัวเลข,"2026-10-14"เป็นสตริงในรูปแบบวันที่คงที่ - บอกว่าต้องทำอย่างไรเมื่อฟิลด์หายไป (
nullข้างบน) มิฉะนั้นโมเดลจะประดิษฐ์ค่าเพื่อเติมช่องว่าง
ถือว่า response_format เป็นคำใบ้ที่แข็งแกร่ง ไม่ใช่การรับประกัน โมเดลบางครั้งยังส่งคืนสตริงว่าง หรือห่อออบเจ็กต์ด้วยข้อความ ซึ่งเป็นเหตุผลที่ส่วนถัดไปมีอยู่
แยกวิเคราะห์อย่างปลอดภัย: ว่างเปล่า, ถูกตัด, หรือแค่พัง#
ความล้มเหลวสามแบบปรากฏในทางปฏิบัติ และแต่ละแบบทิ้งร่องรอยต่างกัน:
- เนื้อหาว่างเปล่า เอกสาร DeepSeek กล่าวว่า JSON mode อาจส่งคืนเนื้อหาว่างเปล่าเป็นครั้งคราวและแนะนำให้เขียนพรอมป์ต์ใหม่ ในฝั่งของคุณมันดูเหมือน
message.contentเป็น""หรือNone, และjson.loads("")โยนExpecting value: line 1 column 1 (char 0) - ถูกตัด
max_tokensหมดกลางออบเจ็กต์finish_reasonคือlength, และข้อความจบกลางสตริง เพิ่มเติมใน why an AI answer stops mid-sentence - ไม่ใช่ JSON ประโยคก่อนออบเจ็กต์ หรือ fence รอบมัน

วิธีแก้สำหรับแบบแรกและแบบที่สามคือ retry ด้วยพรอมป์ต์ที่หนักแน่นกว่า สำหรับแบบที่สอง retry ด้วยขีดจำกัดเดิมไม่เปลี่ยนอะไร ดังนั้น wrapper จึงโยนและบอกให้คุณเพิ่ม max_tokens:
def ask_json(email_text: str, retries: int = 1) -> dict:
user = email_text
for attempt in range(retries + 1):
resp = client.chat.completions.create(
model=MODEL,
messages=[
{"role": "system", "content": SYSTEM},
{"role": "user", "content": user},
],
response_format={"type": "json_object"},
max_tokens=1000,
temperature=0,
)
choice = resp.choices[0]
if choice.finish_reason == "length":
raise ValueError("JSON cut off by max_tokens: raise the limit")
text = (choice.message.content or "").strip()
if text:
try:
return json.loads(text)
except json.JSONDecodeError:
pass
# empty or not JSON: ask again, more firmly
user = email_text + "\n\nReply with one json object only, in the format of the example."
raise ValueError("no valid JSON after retry")retry หนึ่งครั้งเพียงพอสำหรับสคริปต์ หากล้มเหลวสองครั้ง ให้บันทึก email id แล้วไปต่อ คำขอที่เหมือนกันครั้งที่สามแทบไม่ช่วย
จับคู่
อาการใดชี้ไปที่สาเหตุใด?
แตะรายการทางซ้าย แล้วแตะคู่ของมัน
- finish_reason เป็น "length"max_tokens เล็กเกินไปสำหรับออบเจ็กต์
- content เป็นสตริงว่างJSON mode ส่งคืนไม่มีอะไร; เขียนพรอมป์ต์ใหม่และ retry
- JSONDecodeError ที่อักขระที่เป็น backtickโมเดลห่อ JSON ด้วย code fence
- ชื่อฟิลด์แตกต่างกันระหว่างอีเมลพรอมป์ต์ไม่มีตัวอย่างโครงสร้าง
ตรวจรูปร่าง ไม่ใช่แค่ไวยากรณ์#
json.loads พิสูจน์เพียงว่าข้อความเป็น JSON ที่ถูกต้อง มันไม่ได้พิสูจน์ว่ามีฟิลด์ที่คุณขอ เพิ่มการตรวจราคาถูกทันทีหลังการแยกวิเคราะห์:
REQUIRED = {"sender", "date", "amount", "currency", "action"}
data = ask_json(email_text)
missing = REQUIRED - data.keys()
if missing:
raise ValueError(f"missing fields: {sorted(missing)}")จากตรงนี้ dict ไปตรงเข้าสู่แถวฐานข้อมูลหรือสเปรดชีต หากคุณต้องการให้โมเดลเรียกฟังก์ชันของคุณเองแทนการเติมเรกคอร์ดคงที่ นั่นเป็นเครื่องมืออื่น: ดู function calling in Python

อีเมลเป็นชุดมีค่าใช้จ่ายเท่าไร#
การดึงข้อมูลสั้น ๆ เป็นคำขอประเภทถูก: เข้าหลายร้อยโทเคน ออกไม่กี่สิบ สมมติ 1,000 อีเมลต่อวันที่ประมาณ 400 input tokens (พรอมป์ต์, ตัวอย่าง และอีเมล) และประมาณ 80 output tokens (JSON) บน DeepSeek-V4-Flash นั่นคือ $0.014 ต่อวัน และตารางแสดงโหลดเดียวกันบนโมเดลอื่น:
| โมเดล | Input $/1M | Output $/1M | ต่อคำขอ | 1,000 คำขอ | OpenRouter, 1,000 คำขอ |
|---|---|---|---|---|---|
| GLM-5.3-Flash | $0.02 | $0.04 | $0.000011 | $0.0112 | $0.0226 |
| DeepSeek-V4-Flash | $0.015 | $0.10 | $0.000014 | $0.014 | $0.0315 |
| MiniMax-M2.7 | $0.05 | $0.15 | $0.000032 | $0.032 | $0.216 |
400 โทเค็นอินพุตและ 80 โทเค็นเอาต์พุตต่อคำขอ ราคาต่อ 1M โทเค็นเป็นราคาปัจจุบันและเปลี่ยนแปลงไปพร้อมกับอัตราค่าบริการของเรา คอลัมน์ OpenRouter แสดงราคาอ้างอิงสำหรับปริมาณงานเดียวกัน
โดยรวม เมื่อนับ input และ output รวมกัน DeepSeek-V4-Flash ออกมา cheaper กว่าราคาอ้างอิงของ OpenRouter สำหรับโหลดนี้ มันยังอนุญาตสูงสุด 32,768 output tokens ต่อคำตอบ ดังนั้นรายการ line items ยาว ๆ จะไม่ถูกตัดเหมือนกับขีดจำกัดเล็ก และราคาเท่ากันทุกชั่วโมง GLM-5.3-Flash (zai-) เป็นอีกทางเลือกที่ API address เดียวกัน: สูงสุด 8,192 output tokens ซึ่งเพียงพอสำหรับหนึ่งเรกคอร์ดต่ออีเมล ราคาปัจจุบันอยู่ที่ pricing page; DeepSeek model page มีรายละเอียดสำหรับโมเดลที่ใช้ที่นี่
ตรวจสอบเร็ว#
เช็กด่วน
JSON ของคุณถูกตัดกลางสตริง คุณดูอะไรก่อน?
แสดงคำตอบ
คำตอบ: finish_reason และ max_tokens
ออบเจ็กต์ที่ถูกตัดจะจบด้วย finish_reason ตั้งเป็น length ขีดจำกัดถูกชน ดังนั้นเพิ่ม max_tokens; การเปลี่ยน temperature หรือ address ไม่ช่วย
หากคุณยังไม่ได้ส่งคำขอ เริ่มด้วย your first API request แล้วกลับมาและสลับโค้ดข้างบน



