Kembali ke blog

API & SDKPanduan

Dapatkan JSON valid dari API LLM: response_format dan retry

Permintaan Python yang dapat disalin yang mengembalikan JSON yang dapat di-parse, wrapper json.loads yang menangani jawaban kosong atau terpotong, dan daftar periksa mengapa JSON rusak.

Oleh CheapTokens TeamDiterbitkan 5 menit baca

Terminal yang menampilkan permintaan chat completion dengan response_format json_object
Di halaman ini
  1. Pekerjaannya: email masuk, satu baris keluar
  2. Mengapa contoh lebih penting daripada flag
  3. Parse dengan aman: kosong, terpotong, atau rusak
  4. Periksa bentuknya, bukan hanya sintaksnya
  5. Berapa biaya satu batch email
  6. Cek cepat

Untuk mendapatkan JSON dari model chat, lakukan tiga hal: kirim response_format={"type": "json_object"}, letakkan kata "json" dan contoh struktur di prompt, dan setel max_tokens cukup tinggi untuk seluruh objek. Lalu jangan pernah percaya jawabannya begitu saja: periksa apakah tidak kosong, periksa finish_reason, dan baru setelah itu panggil json.loads.

Itu versi singkatnya. Sisa panduan ini adalah satu skrip lengkap untuk pekerjaan yang sangat umum: mengambil field dari sebuah email. Contoh-contohnya menggunakan base URL https://api.cheaptokens.dev/v1 dan id model deepseek-ai/DeepSeek-V4-Flash-0731 (dokumentasi).

Pekerjaannya: email masuk, satu baris keluar#

Misalkan skrip Anda membaca email dukungan dan harus mengubah setiap email menjadi record: pengirim, tanggal, jumlah, dan apa yang diinginkan pengirim. Ambil contoh ini:

Hai, ini Anna Petrova dari Lumen Ltd. Pada 14 Oktober saya membayar invoice #3381 sebesar 1.250 EUR, tetapi tanda terima masih menunjukkan belum dibayar. Tolong perbaiki sebelum Jumat.

Anda ingin ini kembali, dan tidak ada yang lain:

json
{"sender": "Anna Petrova", "date": "2026-10-14", "amount": 1250, "currency": "EUR", "action": "fix receipt status"}

Prompt "ekstrak field" biasa bisa menghasilkan kalimat ramah, code fence, atau keduanya. json.loads Anda tersedak pada backtick pertama. Itulah masalah yang diselesaikan bersama oleh response_format dan prompt yang baik.

Permintaan yang sama dalam Python dan 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
  }'

Referensi API DeepSeek untuk JSON Output (diperiksa 27 Oktober 2026; halaman tersebut tidak memiliki tanggal publikasi) menyatakan aturan yang diikuti permintaan ini: response_format disetel ke json_object, kata "json" di suatu tempat di system atau user prompt, dan contoh struktur target. Tanpa contoh, model harus menebak nama field Anda, dan tebakannya akan berbeda pada email berikutnya.

Mengapa contoh lebih penting daripada flag#

response_format meminta API mengembalikan objek JSON. Itu tidak memberi tahu model objek yang mana. Anggap saja seperti memberi tahu rekan kerja "kirimkan saya spreadsheet" tanpa menyebutkan kolomnya. Anda mendapat spreadsheet, hanya saja bukan milik Anda.

Contoh di prompt adalah baris header kolom. Dua kebiasaan membuatnya bekerja lebih baik:

  • Tunjukkan bentuk aslinya, termasuk tipe: 1250 adalah angka, "2026-10-14" adalah string dalam format tanggal tetap.
  • Sebutkan apa yang harus dilakukan saat field hilang (null di atas). Jika tidak, model akan mengarang nilai untuk mengisi celah.

Perlakukan response_format sebagai petunjuk kuat, bukan jaminan. Model terkadang masih mengembalikan string kosong, atau membungkus objek dalam prosa. Itulah sebabnya bagian berikutnya ada.

Parse dengan aman: kosong, terpotong, atau rusak#

Tiga kegagalan muncul dalam praktik, dan masing-masing meninggalkan jejak berbeda:

  • Konten kosong. Dokumentasi DeepSeek menyatakan mode JSON kadang dapat mengembalikan konten kosong dan menyarankan menulis ulang prompt. Di sisi Anda, ini terlihat seperti message.content menjadi "" atau None, dan json.loads("") memunculkan Expecting value: line 1 column 1 (char 0).
  • Terpotong. max_tokens habis di tengah objek. finish_reason adalah length, dan teks berakhir di tengah string. Selengkapnya di mengapa jawaban AI berhenti di tengah kalimat.
  • Bukan JSON. Ada kalimat sebelum objek, atau fence di sekelilingnya.
json.loads pada jawaban kosong memunculkan JSONDecodeError
Seperti apa jawaban mode JSON yang kosong di Python

Perbaikan untuk kasus pertama dan ketiga adalah retry dengan prompt yang lebih tegas. Untuk kasus kedua, retry dengan limit yang sama tidak mengubah apa pun, jadi wrapper akan raise dan memberi tahu Anda untuk menaikkan 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")

Satu retry cukup untuk skrip. Jika gagal dua kali, catat id email dan lanjutkan; permintaan identik ketiga jarang membantu.

Cocokkan pasangan

Gejala mana yang menunjuk ke penyebab mana?

  • finish_reason adalah "length"max_tokens terlalu kecil untuk objek
  • content adalah string kosongmode JSON tidak mengembalikan apa pun; tulis ulang prompt dan retry
  • JSONDecodeError pada karakter backtickmodel membungkus JSON dalam code fence
  • Nama field berbeda antar emailprompt tidak memiliki contoh struktur

Periksa bentuknya, bukan hanya sintaksnya#

json.loads hanya membuktikan teksnya JSON valid. Itu tidak membuktikan field yang Anda minta ada. Tambahkan pemeriksaan murah tepat setelah parsing:

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)}")

Dari sini dict langsung masuk ke baris database atau spreadsheet. Jika Anda perlu model memanggil fungsi Anda sendiri alih-alih mengisi record tetap, itu alat yang berbeda: lihat function calling di Python.

Jalur dari email ke record yang diperiksa: prompt dengan contoh, json_object, parse, pemeriksaan field
Seluruh jalur dalam lima langkah

Berapa biaya satu batch email#

Ekstraksi singkat adalah jenis permintaan yang murah: beberapa ratus token masuk, beberapa lusin keluar. Asumsikan 1.000 email per hari dengan sekitar 400 token input (prompt, contoh, dan email) dan sekitar 80 token output (JSON). Pada DeepSeek-V4-Flash itu $0.014 per hari, dan tabel menunjukkan beban yang sama pada model lain:

Model Per permintaan 1.000 permintaan OpenRouter, 1.000 permintaan
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 token input dan 80 token output per permintaan. Harga per 1 juta token adalah harga saat ini dan berubah seiring tarif kami; kolom OpenRouter menunjukkan harga referensi untuk beban kerja yang sama.

Secara total, dengan input dan output dihitung bersama, DeepSeek-V4-Flash lebih murah daripada harga referensi OpenRouter untuk beban ini. Model ini juga mengizinkan hingga 32.768 token output per jawaban, sehingga daftar item baris yang panjang tidak akan terpotong seperti jika limitnya kecil, dan harganya sama pada jam berapa pun. GLM-5.3-Flash (zai-org/GLM-5.3-Flash) adalah opsi lain di alamat API yang sama: hingga 8.192 token output, yang cukup untuk satu record per email. Harga terkini ada di halaman harga; halaman model DeepSeek memiliki detail untuk model yang digunakan di sini.

Cek cepat#

Cek cepat

JSON Anda terpotong di tengah string. Apa yang Anda lihat pertama?

Tampilkan jawaban

Jawaban: finish_reason dan max_tokens

Objek yang terpotong berakhir dengan finish_reason disetel ke length. Limit tercapai, jadi naikkan max_tokens; mengubah temperature atau alamat tidak membantu.

Jika Anda belum mengirim permintaan, mulailah dengan permintaan API pertama Anda, lalu kembali dan tukar dengan kode di atas.