Bloga dön

Sorun gidermeKılavuz

Bir LLM yanıtında json.JSONDecodeError: JSON'u Python'da çıkarın

json.loads bir model yanıtında başarısız mı oluyor? Nedeni bulmak için exc.pos ve finish_reason'ı okuyun, ardından JSON'u raw_decode ile çıkarın ve bir kez yeniden deneyin.

Yazan: CheapTokens TeamYayınlanma tarihi: 5 dk okuma

0 konumunda bir JSONDecodeError'ın yanında çitle çevrili bir JSON yanıtı
Bu sayfada
  1. Bir yanıtın json.loads'u bozmasının üç yolu
  2. Kesilmiş yanıtlar: ayrıştırmadan önce finish_reason'ı kontrol edin
  3. JSON'u metinden çıkarın
  4. Daha katı bir istemle bir kez yeniden deneyin
  5. Hangi belirti, hangi düzeltme
  6. API geçerli JSON'u zorlayabilir mi?

Betiğiniz modelden JSON ister, json.loads(reply) çağırır ve json.JSONDecodeError ile ölür. Genellikle model yanıt vermiştir: JSON'u bir ```json fence, put a line of chat in front of it, or ran into max_tokens halfway through. Look at exc.pos and finish_reason to tell which one it is, pull the object out with JSONDecoder.raw_decode ile çıkarın ve kurtarılabilecek bir şey yoksa bir kez yeniden deneyin.

Bir yanıtın json.loads'u bozmasının üç yolu#

Python'un json belgeleri (Python 3.15 belgeleri, 2 Kasım 2026'da kontrol edildi) JSONDecodeError'nin ValueError'nin bir alt sınıfı olduğunu ve msg, doc, pos, lineno ve colno özniteliklerine sahip olduğunu söyler. Burada önemli olan pos: ayrıştırmanın başarısız olduğu dizedeki dizin.

Bir betiğin müşteri e-postalarından alanlar çıkardığını ve {"name": ..., "age": ...} istediğini varsayalım. Bu üç dize onu bozan yanıtlardır. Bunları bir Python kabuğuna yapıştırın:

python
import json

fenced = '```json\n{"name": "Anna", "age": 31}\n```'
chatty = 'Sure! Here is the JSON:\n{"name": "Anna", "age": 31}'
cut = '{"name": "Anna", "age": 31'

for reply in (fenced, chatty, cut):
    try:
        json.loads(reply)
    except json.JSONDecodeError as exc:
        print(exc.pos, len(reply))

İlk ikisi pos 0'da başarısız olur: ayrıştırıcı en ilk karaktere, bir ters tik veya bir S'ye baktı ve pes etti. Üçüncüsü dizenin sonunda başarısız olur (pos, len(reply)'ye eşittir): okuduğu her şey sorunsuzdu, sonra metin tükendi. Bu fark sizin teşhisinizdir. Konum 0, JSON'un muhtemelen orada olduğu, sadece sarıldığı anlamına gelir. Konumun sonda olması, yanıtın erken durduğu anlamına gelir.

Başarısız bir yanıt için karar yolu: finish_reason'ı kontrol et, raw_decode ile çıkar, bir kez yeniden dene
Bu makalede kullanılan kontrol sırası

Kesilmiş yanıtlar: ayrıştırmadan önce finish_reason'ı kontrol edin#

Ortada duran bir yanıt ayrıştırma sorunu değil, uzunluk sorunudur. API size söyler: seçimde finish_reason vardır ve "length", yanıtın max_tokens tarafından kesildiği anlamına gelir (tam hikaye AI yanıtı kesildi mi? finish_reason "length" ve max_tokens'ı düzeltin içinde). Herhangi bir çıkarmadan önce ona bakın.

Bu adımı atlarsanız kötü bir tuzak var. İlk ayrıştırılabilir {'yi arayan bir tarayıcı, yarım kalmış bir dış nesneden tam bir iç nesne alabilir ve betiğiniz yanlış veriyle ve hatasız devam eder. Deneyin:

Kendin deneDeneyin: "ilk nesneyi bul" işlevi bu kesik yanıt için ne döndürür?Cevabı göster

'{"user": {"name": "Anna"}, "age": 31' alın. Dış nesne asla kapanmaz, ancak iç {"name": "Anna"} tamdır, bu nedenle aşağıdaki çıkarıcı {'name': 'Anna'} döndürür. İstisna yok, yanlış sonuç. Bu yüzden finish_reason kontrolü çıkarmadan önce gelir.

Gerçek bir kesilme için çözüm daha fazla alandır: max_tokens'yi artırın. DeepSeek-V4-Flash yanıt başına 32.768 çıktı token'ına, GLM-5.3-Flash 8.192'ye kadar izin verir, bu nedenle uzun bir JSON listesi ilkinde daha fazla alana sahiptir. Modellerin ayrıntıları CheapTokens modeller sayfasında.

JSON'u metinden çıkarın#

Çitler ve gevezelik için standart kütüphanede zaten araç var. Aynı Python sayfasına göre, JSONDecoder.raw_decode(s) bir JSON belgesini onunla başlayan bir dizeden çözer ve nesneyi artı belgenin bittiği dizini döndürür. Belgeler, sonda gereksiz veri bulunan bir dizede kullanılabileceğini söyler. Belgenin ilk karakterde başlaması gerekir, bu nedenle önce baştaki metni kesin: bir { arayın, oradan çözmeyi deneyin ve başarısız olursa bir sonraki {'ye geçin.

python
import json

def extract_json(text):
    """Return the first JSON object found in text, or raise ValueError."""
    decoder = json.JSONDecoder()
    for i, ch in enumerate(text):
        if ch == '{':
            try:
                obj, _end = decoder.raw_decode(text[i:])
                return obj
            except json.JSONDecodeError:
                continue
    raise ValueError('no JSON object in the reply')

Çiti işler (baştaki ```json is skipped because it is not a {), the sentence before the object, and text after the closing brace. Expecting a list? Look for '[' instead. Don't look for both: a stray [1] "bkz. not [1]" gibi bir cümlede geçerli JSON'dur ve kazanır.

Kasıtlı olarak yapmadığı iki şey var. Metni asla düzenlemez (ters tikleri silen regex yok, eksik parantezleri tahmin etme yok), böylece bozuk bir yanıtı geçerli ama yanlış veriye dönüştüremezsiniz. Ve bulunacak bir şey olmadığında yüksek sesle başarısız olur, aşağıdaki yeniden denemenin ihtiyacı olan da budur.

Önce: çitle çevrili bir yanıtta json.loads. Sonra: extract_json nesneyi döndürür
extract_json öncesi ve sonrası aynı çitle çevrili yanıt

Daha katı bir istemle bir kez yeniden deneyin#

Yanıt kesildiyse veya hiç nesne içermiyorsa, tekrar sorun. Bir kez, sonsuz döngüde değil. Kesilmede ikinci denemeye daha fazla alan verin; aksi takdirde ne istediğinizi açıkça söyleyin. Bu, CheapTokens temel URL'sine (https://api.cheaptokens.dev/v1) karşı OpenAI Python SDK'sını ve belgelerdeki tam model kimliğini kullanır; anahtar bir ortam değişkeninde bulunur.

python
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'
STRICT = '\n\nReply with one JSON object only. No text before or after it, no code fence.'

def ask_json(prompt, max_tokens=800):
    content = prompt
    for attempt in (1, 2):
        resp = client.chat.completions.create(
            model=MODEL,
            messages=[{'role': 'user', 'content': content}],
            max_tokens=max_tokens,
        )
        choice = resp.choices[0]
        if choice.finish_reason == 'length':
            max_tokens *= 2          # cut off: more room next time
        else:
            try:
                return extract_json(choice.message.content or '')
            except ValueError:
                pass                 # no object: try the stricter prompt
        content = prompt + STRICT
    raise ValueError('no valid JSON after one retry')

Sıra önemlidir: önce finish_reason, ikinci olarak çıkarma ve ancak ondan sonra yeniden deneme. İkinci denemeden sonra hatayı yükseltin, böylece bir toplu iş o öğeyi günlüğe kaydedip devam edebilir.

Yeniden denemenin maliyeti kolayca tahmin edilir. 1.200 giriş token'ı ve 300 çıktı token'ı içeren bir istek varsayalım (bir varsayım, kendi sayılarınızı kullanın): DeepSeek-V4-Flash'te $0.000048 veya GLM-5.3-Flash'te $0.000036. 1.000 istekten 30'u ikinci denemeye ihtiyaç duyarsa, ekstra fatura 30 × o istek, DeepSeek-V4-Flash'te $0.0014. Fiyatlar canlıdır, güncel fiyat listesinden gelir.

Sıraya koy

Onarım adımlarını sıraya koyun

  1. finish_reason'ı kontrol edin: length diyorsa burada durun
  2. İlk { işaretini bulun ve metni ondan önce kesin
  3. raw_decode ile çözün ve sonrasını yok sayın
  4. Hiçbir şey bulunamadıysa, daha katı istemle bir kez yeniden deneyin

Hangi belirti, hangi düzeltme#

Yukarıdaki adımlar, bir sonraki tuhaf hata ortaya çıkana kadar kafanıza sığar. Bu, bir arama tablosuyla aynı şeydir: ekranda gördüğünüzü bulun ve düzeltmeyi okuyun.

Durumunu seç

Gördüğünüzü seçin

json.loads pos 0'da başarısız olur

Yanıt bir çitle veya bir cümleyle başlar. extract_json üzerinden geçirin; yeniden deneme gerekmez.

pos dizenin sonunda

Yanıt erken durdu. finish_reason kontrol edin: length ise, max_tokens artırın ve tekrar sorun.

Hata ortada bir yerde

Model bozuk JSON'u kendisi yazdı (eksik virgül, sondaki virgül, kaçışsız tırnak). Yamalamayın: daha katı istemle bir kez yeniden deneyin ve tekrar başarısız olursa yanıtı günlüğe kaydedin.

json.loads çalışır ama bir anahtar eksik

Ayrıştırma sorunsuz, içerik değil. İstemde tam anahtarları listeleyin ve ayrıştırmadan sonra kodda kontrol edin.

API geçerli JSON'u zorlayabilir mi?#

Bazen. OpenAI'nin yapılandırılmış çıktılar kılavuzu iki mod belgeler: JSON modu (type: json_object), geçerli JSON döndürür ancak bir şemayı izlemez ve Yapılandırılmış Çıktılar (type: json_schema, strict: true), izler; OpenAI mümkün olduğunda ikincisini önerir (kaynak: developers.openai.com, 2 Kasım 2026'da kontrol edildi). Bunlar OpenAI'nin kendi API seçenekleridir. response_format'ye başka bir uç noktayla güvenmeden önce CheapTokens belgelerini kontrol edin; yukarıdaki çıkarma ve yeniden deneme düz sohbet tamamlamalarıyla çalışır ve özel bir parametre gerektirmez. İstek tarafındaki seçenekler hakkında daha fazlası Bir LLM API'sinden geçerli JSON alın: response_format ve yeniden denemeler içinde.

Ayrıştırıcıyı göndermeden önce dört şeyi kontrol edin: metin ayrıştırılmadan önce finish_reason okunur, yalnızca bir yeniden deneme vardır (ve ikinci başarısızlık öğe kimliğiyle günlüğe kaydedilir), kod ayrıştırmadan sonra beklenen anahtarların var olduğunu kontrol eder ve max_tokens beklediğiniz en uzun JSON için yer bırakır.

Bir dahaki sefere json.loads fırlattığında, önce exc.pos ve finish_reason yazdırın. Bu iki sayı genellikle yanıtın bir satırını okumadan önce ne olduğunu size söyler.