ब्लॉग पर वापस जाएं

समस्या निवारणगाइड

LLM जवाब पर json.JSONDecodeError: Python में JSON निकालें

मॉडल जवाब पर json.loads विफल होता है? कारण खोजने के लिए exc.pos और finish_reason पढ़ें, फिर raw_decode से JSON निकालें और एक बार फिर कोशिश करें।

CheapTokens Team द्वाराप्रकाशित 11 मिनट का पाठ

पोज़िशन 0 पर JSONDecodeError के बगल में एक फ़ेंस किया JSON जवाब
इस पृष्ठ पर
  1. जवाब json.loads को तोड़ने के तीन तरीके
  2. कटे हुए जवाब: पार्स करने से पहले finish_reason जाँचें
  3. टेक्स्ट से JSON निकालें
  4. सख्त प्रॉम्प्ट के साथ एक बार फिर कोशिश करें
  5. कौन सा लक्षण, कौन सा इलाज
  6. क्या API मान्य JSON को बाध्य कर सकती है?

आपकी स्क्रिप्ट मॉडल से JSON माँगती है, json.loads(reply) कॉल करती है और json.JSONDecodeError के साथ मर जाती है। आमतौर पर मॉडल ने जवाब दिया ही था: उसने JSON को ```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 में लपेट दिया, और अगर कुछ भी बचाया न जा सके तो एक बार फिर से कोशिश करें।

जवाब json.loads को तोड़ने के तीन तरीके#

Python के json दस्तावेज़ (Python 3.15 docs, 2 नवंबर 2026 को जाँचा गया) कहते हैं कि JSONDecodeError, ValueError का सबक्लास है जिसमें msg, doc, pos, lineno और colno एट्रिब्यूट्स हैं। यहाँ जो मायने रखता है वह है pos: स्ट्रिंग में वह इंडेक्स जहाँ पार्सिंग विफल हुई।

मान लें कोई स्क्रिप्ट ग्राहक ईमेल से फ़ील्ड निकालती है और {"name": ..., "age": ...} माँगती है। ये तीन स्ट्रिंग्स वे जवाब हैं जो इसे तोड़ते हैं। इन्हें Python शेल में पेस्ट करें:

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

पहले दो pos 0 पर विफल होते हैं: पार्सर ने सबसे पहले कैरेक्टर को देखा, एक बैकटिक या एक S, और हार मान ली। तीसरा स्ट्रिंग के अंत में विफल होता है (pos बराबर है len(reply)): जो कुछ भी उसने पढ़ा वह ठीक था, फिर टेक्स्ट खत्म हो गया। यही अंतर आपका निदान है। पोज़िशन 0 का मतलब है कि JSON शायद वहाँ है, बस लपेटा हुआ है। अंत में पोज़िशन का मतलब है कि जवाब जल्दी रुक गया।

विफल जवाब के लिए निर्णय पथ: finish_reason जाँचें, raw_decode से निकालें, एक बार फिर कोशिश करें
इस लेख में इस्तेमाल किए गए जाँचों का क्रम

कटे हुए जवाब: पार्स करने से पहले finish_reason जाँचें#

बीच में रुका हुआ जवाब पार्सिंग की समस्या नहीं, लंबाई की समस्या है। API आपको बताती है: चॉइस में finish_reason है, और "length" का मतलब है कि जवाब max_tokens से कट गया (पूरी कहानी AI answer cut off? Fix finish_reason "length" and max_tokens में है)। किसी भी एक्सट्रैक्शन से पहले इसे देखें।

अगर आप वह कदम छोड़ते हैं तो एक गंदा जाल है। पहला पार्स करने योग्य { खोजने वाला स्कैनर आधे-अधूरे बाहरी ऑब्जेक्ट से एक पूरा भीतरी ऑब्जेक्ट उठा सकता है, और आपकी स्क्रिप्ट गलत डेटा के साथ आगे बढ़ जाती है और कोई एरर नहीं आता। इसे आज़माएँ:

स्वयं आज़माएँआज़माएँ: इस कटे जवाब के लिए "पहला ऑब्जेक्ट खोजो" फ़ंक्शन क्या लौटाता है?उत्तर दिखाएँ

'{"user": {"name": "Anna"}, "age": 31' लें। बाहरी ऑब्जेक्ट कभी बंद नहीं होता, लेकिन भीतरी {"name": "Anna"} पूरा है, इसलिए नीचे दिया एक्सट्रैक्टर {'name': 'Anna'} लौटाता है। कोई अपवाद नहीं, गलत परिणाम। इसीलिए finish_reason जाँच एक्सट्रैक्शन से पहले आती है।

असली कट-ऑफ का इलाज है ज़्यादा जगह: max_tokens बढ़ाएँ। DeepSeek-V4-Flash प्रति जवाब अधिकतम 32,768 आउटपुट टोकन की अनुमति देता है, GLM-5.3-Flash अधिकतम 8,192, इसलिए एक लंबी JSON सूची के पास पहले पर ज़्यादा जगह है। मॉडलों का विवरण the CheapTokens models page पर है।

टेक्स्ट से JSON निकालें#

फ़ेंस और बकवास के लिए, स्टैंडर्ड लाइब्रेरी में पहले से औज़ार है। उसी Python पेज के अनुसार, JSONDecoder.raw_decode(s) एक ऐसी स्ट्रिंग से JSON डॉक्यूमेंट डिकोड करता है जो उससे शुरू होती है और ऑब्जेक्ट के साथ वह इंडेक्स लौटाता है जहाँ डॉक्यूमेंट खत्म हुआ। दस्तावेज़ कहते हैं कि इसे अंत में अतिरिक्त डेटा वाली स्ट्रिंग पर इस्तेमाल किया जा सकता है। इसे डॉक्यूमेंट के पहले कैरेक्टर से शुरू होने की ज़रूरत होती है, इसलिए पहले आगे का टेक्स्ट काटें: एक { खोजें, वहाँ से डिकोड करने की कोशिश करें, और विफल होने पर अगले { पर जाएँ।

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

यह फ़ेंस को संभालता है (आगे का ```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] "see note [1]" जैसे वाक्य में मान्य JSON है और जीत जाएगा।

दो चीज़ें यह जानबूझकर नहीं करता। यह कभी टेक्स्ट संपादित नहीं करता (बैकटिक हटाने वाला कोई regex नहीं, गायब ब्रैकेट का कोई अनुमान नहीं), इसलिए आप एक टूटे जवाब को मान्य लेकिन गलत डेटा में नहीं बदल सकते। और जब खोजने को कुछ न हो तो यह ज़ोर से विफल होता है, जो नीचे दिए रीट्राई को चाहिए।

पहले: फ़ेंस किए जवाब पर json.loads। बाद में: extract_json ऑब्जेक्ट लौटाता है
extract_json से पहले और बाद में वही फ़ेंस किया जवाब

सख्त प्रॉम्प्ट के साथ एक बार फिर कोशिश करें#

अगर जवाब कट गया या उसमें कोई ऑब्जेक्ट ही नहीं है, तो फिर से पूछें। एक बार, अनंत लूप में नहीं। कट-ऑफ पर, दूसरी कोशिश को ज़्यादा जगह दें; वरना सीधे-सीधे बताएँ कि आप क्या चाहते हैं। यह CheapTokens बेस URL (https://api.cheaptokens.dev/v1) के खिलाफ OpenAI Python SDK और दस्तावेज़ों से पूरी मॉडल id इस्तेमाल करता है; कुंजी एक एनवायरनमेंट वेरिएबल में रहती है।

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

क्रम ही मुद्दा है: पहले finish_reason, दूसरा एक्सट्रैक्शन, और उसके बाद ही रीट्राई। दूसरी कोशिश के बाद एरर उठाएँ ताकि कोई बैच जॉब उस आइटम को लॉग करके आगे बढ़ सके।

रीट्राई की लागत का अनुमान लगाना आसान है। मान लें 1,200 इनपुट टोकन और 300 आउटपुट टोकन का अनुरोध (एक अनुमान, अपने नंबर इस्तेमाल करें): DeepSeek-V4-Flash पर $0.000048 या GLM-5.3-Flash पर $0.000036। अगर 1,000 में से 30 अनुरोधों को दूसरी कोशिश चाहिए, तो अतिरिक्त बिल 30 × वह अनुरोध है, DeepSeek-V4-Flash पर $0.0014। कीमतें लाइव हैं, वे मौजूदा मूल्य सूची से आती हैं।

क्रम में लगाएँ

मरम्मत के कदमों को क्रम में रखें

  1. finish_reason जाँचें: अगर यह length कहता है तो यहीं रुकें
  2. पहला { खोजें और उससे पहले का टेक्स्ट काटें
  3. raw_decode से डिकोड करें और आगे जो है उसे अनदेखा करें
  4. अगर कुछ न मिले, तो सख्त प्रॉम्प्ट के साथ एक बार फिर कोशिश करें

कौन सा लक्षण, कौन सा इलाज#

ऊपर के कदम आपके दिमाग में तब तक फिट रहते हैं जब तक अगली अजीब एरर न आ जाए। यह एक लुकअप जैसा ही है: स्क्रीन पर जो दिखे उसे खोजें और इलाज पढ़ें।

अपना मामला चुनें

जो दिखे उसे चुनें

json.loads pos 0 पर विफल होता है

जवाब एक फ़ेंस या एक वाक्य से शुरू होता है। इसे extract_json से गुज़ारें; रीट्राई की ज़रूरत नहीं।

pos स्ट्रिंग के अंत में है

जवाब जल्दी रुक गया। finish_reason जाँचें: अगर यह length है, तो max_tokens बढ़ाएँ और फिर से पूछें।

एरर बीच में कहीं है

मॉडल ने खुद टूटी JSON लिखी (गायब कॉमा, ट्रेलिंग कॉमा, बिना एस्केप किया कोट)। इसे पैच न करें: सख्त प्रॉम्प्ट के साथ एक बार फिर कोशिश करें और फिर विफल होने पर जवाब लॉग करें।

json.loads काम करता है लेकिन एक कुंजी गायब है

पार्सिंग ठीक है, सामग्री नहीं। प्रॉम्प्ट में सटीक कुंजियाँ सूचीबद्ध करें और पार्सिंग के बाद कोड में उन्हें जाँचें।

क्या API मान्य JSON को बाध्य कर सकती है?#

कभी-कभी। OpenAI की structured outputs गाइड दो मोड दस्तावेज़ित करती है: JSON मोड (type: json_object), जो मान्य JSON लौटाता है लेकिन स्कीमा का पालन नहीं करता, और Structured Outputs (type: json_schema, strict: true), जो करता है; OpenAI जहाँ संभव हो दूसरे की सलाह देता है (स्रोत: developers.openai.com, 2 नवंबर 2026 को जाँचा गया)। ये OpenAI के अपने API विकल्प हैं। किसी दूसरे एंडपॉइंट के साथ response_format पर भरोसा करने से पहले, CheapTokens docs जाँचें; ऊपर का एक्सट्रैक्शन और रीट्राई सादे चैट कम्प्लीशन के साथ काम करते हैं और किसी विशेष पैरामीटर की ज़रूरत नहीं। रिक्वेस्ट-साइड विकल्पों पर अधिक जानकारी Get valid JSON from an LLM API: response_format and retries में है।

पार्सर शिप करने से पहले, चार चीज़ें जाँचें: टेक्स्ट पार्स होने से पहले finish_reason पढ़ा जाता है, केवल एक रीट्राई है (और दूसरी विफलता आइटम id के साथ लॉग होती है), कोड पार्सिंग के बाद अपेक्षित कुंजियों के मौजूद होने की जाँच करता है, और max_tokens आपकी अपेक्षित सबसे लंबी JSON के लिए जगह छोड़ता है।

अगली बार जब json.loads फेंका जाए, तो पहले exc.pos और finish_reason प्रिंट करें। ये दो नंबर आमतौर पर आपको बता देते हैं कि क्या हुआ, जवाब की एक पंक्ति पढ़ने से पहले।