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.

Di halaman ini
Untuk mendapatkan JSON dari model chat, lakukan tiga hal: kirim response_, 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:/ dan id model deepseek- (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:
{"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
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
}'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:
1250adalah angka,"2026-10-14"adalah string dalam format tanggal tetap. - Sebutkan apa yang harus dilakukan saat field hilang (
nulldi 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.contentmenjadi""atauNone, danjson.loads("")memunculkanExpecting value: line 1 column 1 (char 0). - Terpotong.
max_tokenshabis di tengah objek.finish_reasonadalahlength, 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.

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:
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?
Ketuk item di kiri, lalu pasangannya
- 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:
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.

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 | Input $/1Jt | Output $/1Jt | Per permintaan | 1.000 permintaan | OpenRouter, 1.000 permintaan |
|---|---|---|---|---|---|
| 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 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-) 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.



