API ve SDKKılavuz
Bir LLM API'sinden geçerli JSON alın: response_format ve yeniden denemeler
Ayrıştırılabilir JSON döndüren kopyalanabilir bir Python isteği, boş veya kesilmiş yanıtları işleyen bir json.loads sarmalayıcısı ve JSON'un neden bozulduğuna dair bir kontrol listesi.

Bu sayfada
Bir sohbet modelinden JSON almak için üç şey yapın: response_ iletin, isteme "json" kelimesini ve yapının bir örneğini koyun ve tüm nesne için max_tokens yeterince yüksek ayarlayın. Sonra yanıta asla körü körüne güvenmeyin: boş olmadığını kontrol edin, finish_reason kontrol edin ve ancak o zaman json.loads çağırın.
Kısa versiyon bu. Bu kılavuzun geri kalanı çok yaygın bir iş için tam bir betik: bir e-postadan alanları çıkarmak. Örnekler https:/ temel URL'sini ve deepseek- model kimliğini kullanır (belgeler).
İş: bir e-posta girer, bir satır çıkar#
Betiğinizin destek e-postalarını okuduğunu ve her birini bir kayda dönüştürmesi gerektiğini varsayalım: gönderen, tarih, tutar ve gönderenin ne istediği. Şunu alın:
Merhaba, ben Lumen Ltd.'den Anna Petrova. 14 Ekim'de 3381 numaralı fatura için 1.250 EUR ödedim, ancak makbuz hâlâ ödenmemiş olarak görünüyor. Lütfen Cuma'dan önce düzeltin.
Bunun geri gelmesini istiyorsunuz, başka bir şey değil:
{"sender": "Anna Petrova", "date": "2026-10-14", "amount": 1250, "currency": "EUR", "action": "fix receipt status"}Basit bir "alanları çıkar" istemi size dostane bir cümle, bir kod bloğu veya her ikisini de verebilir. json.loads ilk ters tikte boğulur. Bu, response_format ve iyi bir istemin birlikte çözdüğü sorundur.
Python ve curl'de aynı istek
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
}'JSON Çıktısı için DeepSeek API referansı (27 Ekim 2026'da kontrol edildi; sayfada yayın tarihi yok) bu isteğin uyduğu kuralları belirtir: response_format json_object olarak ayarlanır, sistem veya kullanıcı isteminde bir yerde "json" kelimesi bulunur ve hedef yapının bir örneği verilir. Örnek olmadan model alan adlarınızı tahmin etmek zorunda kalır ve bir sonraki e-postada farklı tahmin eder.
Örnek neden bayraktan daha önemli#
response_format API'den bir JSON nesnesi döndürmesini ister. Modele hangi nesneyi söylemez. Bunu bir meslektaşınıza "bana bir elektronik tablo gönder" demek gibi düşünün, hangi sütunlar olduğunu söylemeden. Bir elektronik tablo alırsınız, sadece sizin değil.
İstemdeki örnek sütun başlığı satırıdır. İki alışkanlık daha iyi çalışmasını sağlar:
- Türler dahil gerçek şekli gösterin:
1250bir sayıdır,"2026-10-14"sabit bir tarih biçiminde bir dizedir. - Bir alan eksik olduğunda ne yapılacağını söyleyin (yukarıdaki
null). Aksi takdirde model boşluğu doldurmak için bir değer uydurur.
response_format'i bir garanti değil, güçlü bir ipucu olarak görün. Modeller bazen yine de boş bir dize döndürür veya nesneyi düz metin içine sarar. Bu yüzden sonraki bölüm var.
Güvenli ayrıştırma: boş, kesilmiş veya bozuk#
Pratikte üç hata ortaya çıkar ve her biri farklı bir iz bırakır:
- Boş içerik. DeepSeek belgeleri JSON modunun bazen boş içerik döndürebileceğini ve istemi yeniden ifade etmeyi önerdiğini söyler. Sizin tarafınızda bu,
message.content'nin""veyaNoneolması vejson.loads("")'inExpecting value: line 1 column 1 (char 0)yükseltmesi gibi görünür. - Kesilmiş.
max_tokensnesnenin ortasında tükendi.finish_reasonlength'dir ve metin dize ortasında biter. Bu konuda daha fazlası bir AI yanıtı neden cümle ortasında durur yazısında. - JSON değil. Nesneden önce bir cümle veya etrafında bir kod bloğu.

Birinci ve üçüncü için çözüm, daha katı bir istemle yeniden denemektir. İkincisi için, aynı sınırla yeniden denemek hiçbir şeyi değiştirmez, bu yüzden sarmalayıcı yükseltir ve size max_tokens'yi yükseltmenizi söyler:
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")Bir betik için bir yeniden deneme yeterlidir. İki kez başarısız olursa, e-posta kimliğini günlüğe kaydedin ve devam edin; üçüncü aynı istek nadiren yardımcı olur.
Eşleştir
Hangi belirti hangi nedene işaret eder?
Soldaki bir öğeye, ardından eşine dokun
- finish_reason "length"max_tokens nesne için çok küçük
- content boş bir dizeJSON modu hiçbir şey döndürmedi; istemi yeniden ifade edin ve yeniden deneyin
- Ters tik olan bir karakterde JSONDecodeErrormodel JSON'u bir kod bloğuna sardı
- Alan adları e-postalar arasında farklıistemde yapının örneği yok
Sözdizimini değil, şekli kontrol edin#
json.loads yalnızca metnin geçerli JSON olduğunu kanıtlar. İstediğiniz alanlara sahip olduğunu kanıtlamaz. Ayrıştırmadan hemen sonra ucuz bir kontrol ekleyin:
REQUIRED = {"sender", "date", "amount", "currency", "action"}
data = ask_json(email_text)
missing = REQUIRED - data.keys()
if missing:
raise ValueError(f"missing fields: {sorted(missing)}")Buradan sözlük doğrudan bir veritabanı satırına veya elektronik tabloya gider. Modelin sabit bir kaydı doldurmak yerine kendi işlevlerinizi çağırmasını istiyorsanız, bu farklı bir araçtır: Python'da işlev çağırma yazısına bakın.

Bir e-posta yığınının maliyeti#
Kısa bir çıkarım, ucuz istek türüdür: birkaç yüz token girer, birkaç düzine çıkar. Günde 1.000 e-posta, yaklaşık 400 giriş token'ı (istem, örnek ve e-posta) ve yaklaşık 80 çıkış token'ı (JSON) varsayalım. DeepSeek-V4-Flash'te bu günde $0.014 eder ve tablo diğer modellerde aynı yükü gösterir:
| Model | Giriş $/1M | Çıkış $/1M | İstek başına | 1.000 istek | OpenRouter, 1.000 istek |
|---|---|---|---|---|---|
| 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 |
İstek başına 400 giriş ve 80 çıkış token'ı. 1M token başına fiyatlar günceldir ve tarifelerimizle birlikte değişir; OpenRouter sütunu aynı iş yükü için referans fiyatları gösterir.
Toplamda, giriş ve çıkış birlikte sayıldığında, DeepSeek-V4-Flash bu yük için OpenRouter'ın referans fiyatından daha ucuz çıkar. Ayrıca yanıt başına 32.768 çıkış token'ına izin verir, böylece uzun bir satır öğesi listesi küçük bir sınırla kesileceği gibi kesilmez ve fiyat her saat aynıdır. GLM-5.3-Flash (zai-) aynı API adresinde başka bir seçenektir: 8.192 çıkış token'ına kadar, bu e-posta başına bir kayıt için fazlasıyla yeterlidir. Güncel fiyatlar fiyatlandırma sayfasında; DeepSeek model sayfası burada kullanılan modelin ayrıntılarını içerir.
Hızlı kontrol#
Hızlı kontrol
JSON'unuz bir dizenin ortasında kesildi. İlk olarak neye bakarsınız?
Cevabı göster
Cevap: finish_reason ve max_tokens
Kesilmiş bir nesne, finish_reason length olarak ayarlanmış şekilde biter. Sınıra ulaşıldı, bu yüzden max_tokens'ı yükseltin; sıcaklığı veya adresi değiştirmek yardımcı olmaz.
Henüz bir istek göndermediyseniz, ilk API isteğiniz ile başlayın, sonra geri dönün ve yukarıdaki kodu yerine koyun.



