Bloga dön

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.

Yazan: CheapTokens TeamYayınlanma tarihi: 5 dk okuma

response_format json_object ile bir sohbet tamamlama isteğini gösteren bir terminal
Bu sayfada
  1. İş: bir e-posta girer, bir satır çıkar
  2. Örnek neden bayraktan daha önemli
  3. Güvenli ayrıştırma: boş, kesilmiş veya bozuk
  4. Sözdizimini değil, şekli kontrol edin
  5. Bir e-posta yığınının maliyeti
  6. Hızlı kontrol

Bir sohbet modelinden JSON almak için üç şey yapın: response_format={"type": "json_object"} 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://api.cheaptokens.dev/v1 temel URL'sini ve deepseek-ai/DeepSeek-V4-Flash-0731 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:

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

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
  }'

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: 1250 bir 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 "" veya None olması ve json.loads("")'in Expecting value: line 1 column 1 (char 0) yükseltmesi gibi görünür.
  • Kesilmiş. max_tokens nesnenin ortasında tükendi. finish_reason length'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.
Boş bir yanıtta json.loads JSONDecodeError yükseltir
Python'da boş bir JSON modu yanıtı nasıl görünür

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:

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

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?

  • 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:

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

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.

E-postadan kontrol edilmiş kayda giden yol: örnekli istem, json_object, ayrıştırma, alan kontrolü
Beş adımda tüm yol

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 İstek başına 1.000 istek OpenRouter, 1.000 istek
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

İ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-org/GLM-5.3-Flash) 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.