กลับไปที่บล็อก

API และ SDKคู่มือ

รับ JSON ที่ถูกต้องจาก LLM API: response_format และ retries

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

โดย CheapTokens Teamเผยแพร่เมื่อ ใช้เวลาอ่าน 6 นาที

เทอร์มินัลแสดงคำขอ chat completion ด้วย response_format json_object
ในหน้านี้
  1. งาน: อีเมลเข้า แถวออก
  2. ทำไมตัวอย่างจึงสำคัญกว่าธง
  3. แยกวิเคราะห์อย่างปลอดภัย: ว่างเปล่า, ถูกตัด, หรือแค่พัง
  4. ตรวจรูปร่าง ไม่ใช่แค่ไวยากรณ์
  5. อีเมลเป็นชุดมีค่าใช้จ่ายเท่าไร
  6. ตรวจสอบเร็ว

ในการดึง JSON ออกจากโมเดลแชท ให้ทำสามอย่าง: ส่ง response_format={"type": "json_object"}, ใส่คำว่า "json" และตัวอย่างโครงสร้างในพรอมป์ต์, และตั้ง max_tokens ให้สูงพอสำหรับทั้งออบเจ็กต์ จากนั้นอย่าเชื่อคำตอบแบบไม่ตรวจสอบ: ตรวจว่าไม่ว่างเปล่า, ตรวจ finish_reason, แล้วจึงเรียก json.loads

นั่นคือเวอร์ชันสั้น ส่วนที่เหลือของคู่มือนี้คือสคริปต์ที่สมบูรณ์สำหรับงานที่พบบ่อยมาก: การดึงฟิลด์ออกจากอีเมล ตัวอย่างใช้ base URL https://api.cheaptokens.dev/v1 และ model id deepseek-ai/DeepSeek-V4-Flash-0731 (docs)

งาน: อีเมลเข้า แถวออก#

สมมติว่าสคริปต์ของคุณอ่านอีเมลสนับสนุนและต้องแปลงแต่ละฉบับเป็นเรกคอร์ด: ผู้ส่ง, วันที่, จำนวนเงิน, และสิ่งที่ผู้ส่งต้องการ ลองดูฉบับนี้:

สวัสดี ฉันคือ Anna Petrova จาก Lumen Ltd. เมื่อวันที่ 14 ตุลาคม ฉันจ่ายใบแจ้งหนี้ #3381 เป็นจำนวน 1,250 EUR แต่ใบเสร็จยังแสดงว่าไม่ชำระ กรุณาแก้ไขก่อนวันศุกร์

คุณต้องการสิ่งนี้กลับมา และไม่มีอย่างอื่น:

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

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

bash
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 รอบมัน
json.loads on an empty answer raises JSONDecodeError
What an empty JSON-mode answer looks like in Python

วิธีแก้สำหรับแบบแรกและแบบที่สามคือ retry ด้วยพรอมป์ต์ที่หนักแน่นกว่า สำหรับแบบที่สอง retry ด้วยขีดจำกัดเดิมไม่เปลี่ยนอะไร ดังนั้น wrapper จึงโยนและบอกให้คุณเพิ่ม max_tokens:

python
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 ที่ถูกต้อง มันไม่ได้พิสูจน์ว่ามีฟิลด์ที่คุณขอ เพิ่มการตรวจราคาถูกทันทีหลังการแยกวิเคราะห์:

python
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

The path from email to checked record: prompt with example, json_object, parse, field check
The whole path in five steps

อีเมลเป็นชุดมีค่าใช้จ่ายเท่าไร#

การดึงข้อมูลสั้น ๆ เป็นคำขอประเภทถูก: เข้าหลายร้อยโทเคน ออกไม่กี่สิบ สมมติ 1,000 อีเมลต่อวันที่ประมาณ 400 input tokens (พรอมป์ต์, ตัวอย่าง และอีเมล) และประมาณ 80 output tokens (JSON) บน DeepSeek-V4-Flash นั่นคือ $0.014 ต่อวัน และตารางแสดงโหลดเดียวกันบนโมเดลอื่น:

โมเดล ต่อคำขอ 1,000 คำขอ OpenRouter, 1,000 คำขอ
GLM-5.3-Flash $0.000011 $0.0112 $0.0226
DeepSeek-V4-Flash $0.000014 $0.014 $0.0315
MiniMax-M2.7 $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-org/GLM-5.3-Flash) เป็นอีกทางเลือกที่ 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 แล้วกลับมาและสลับโค้ดข้างบน