Pag-troubleshootGabay
json.JSONDecodeError sa isang sagot ng LLM: i-extract ang JSON sa Python
Nabigo ang json.loads sa isang sagot ng modelo? Basahin ang exc.pos at finish_reason para mahanap ang sanhi, pagkatapos i-extract ang JSON gamit ang raw_decode at mag-retry nang isang beses.

Sa pahinang ito
Ang iyong script ay humihingi ng JSON sa modelo, tumatawag ng json. at namamatay sa json.. Karaniwan, sumagot naman ang modelo: binalot nito ang JSON sa isang ```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., at mag-retry nang isang beses kung walang maisasalba.
Tatlong paraan kung paano nasisira ng sagot ang json.loads#
Sinasabi ng dokumentasyon ng json ng Python (Python 3.15 docs, sinuri noong 2 Nobyembre 2026) na ang JSONDecodeError ay subclass ng ValueError na may mga attribute na msg, doc, pos, lineno at colno. Ang mahalaga dito ay ang pos: ang index sa string kung saan nabigo ang pag-parse.
Sabihin nating may script na kumukuha ng mga field mula sa mga email ng customer at humihingi ng {"name": .. Ang tatlong string na ito ang mga sagot na sumisira dito. I-paste ang mga ito sa Python shell:
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))Nabigo ang unang dalawa sa pos 0: tiningnan ng parser ang pinakaunang karakter, isang backtick o isang S, at sumuko. Nabigo ang pangatlo sa dulo ng string (pos katumbas ng len(reply)): maayos ang lahat ng nabasa nito, tapos naubos ang teksto. Ang pagkakaibang iyon ang iyong diagnosis. Ang posisyon 0 ay nangangahulugang malamang naroon ang JSON, nakabalot lang. Ang posisyon sa dulo ay nangangahulugang naagang huminto ang sagot.

Mga putol na sagot: suriin ang finish_reason bago mag-parse#
Ang sagot na huminto sa gitna ay hindi problema sa pag-parse, problema ito sa haba. Sinasabi sa iyo ng API: ang choice ay may finish_reason, at ang "length" ay nangangahulugang naputol ang sagot ng max_tokens (ang buong kuwento ay nasa AI answer cut off? Fix finish_reason "length" and max_tokens). Tingnan ito muna, bago ang anumang extraction.
May nakakalokong bitag kung lalaktawan mo ang hakbang na iyon. Ang scanner na naghahanap ng unang maipaparse na { ay maaaring makakuha ng kumpletong panloob na object mula sa kalahating-tapos na panlabas na object, at magpapatuloy ang iyong script sa maling data na walang error. Subukan ito:
Subukan mo mismoSubukan: ano ang ibinabalik ng function na "hanapin ang unang object" para sa putol na sagot na ito?Ipakita ang sagot
Kunin ang '{"user": {"name": "Anna"}, "age": 31'. Hindi kailanman nagsasara ang panlabas na object, ngunit kumpleto ang panloob na {"name": "Anna"}, kaya ibinabalik ng extractor sa ibaba ang {'name': 'Anna'}. Walang exception, maling resulta. Iyon ang dahilan kung bakit ang pagsusuri ng finish_reason ay nauuna sa extraction.
Ang solusyon para sa tunay na putol ay mas maraming espasyo: taasan ang max_tokens. Ang DeepSeek-V4-Flash ay pinapayagan ang hanggang 32,768 output token bawat sagot, ang GLM-5.3-Flash hanggang 8,192, kaya ang mahabang JSON list ay may mas maraming espasyo sa una. Ang mga detalye ng mga modelo ay nasa pahina ng mga modelo ng CheapTokens.
Hilahin ang JSON mula sa teksto#
Para sa mga fence at chatter, mayroon nang tool ang standard library. Ayon sa parehong pahina ng Python, ang JSONDecoder. ay nagde-decode ng JSON document mula sa string na nagsisimula rito at ibinabalik ang object kasama ang index kung saan natapos ang document. Sinasabi ng docs na magagamit ito sa string na may extraneous data sa dulo. Kailangan nitong magsimula ang document sa unang karakter, kaya putulin muna ang nangungunang teksto: hanapin ang isang {, subukang mag-decode mula roon, at magpatuloy sa susunod na { kung mabigo iyon.
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')Hinahawakan nito ang fence (ang nangungunang ```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] sa isang pangungusap na tulad ng "see note [1]" ay valid JSON at mananalo.
Dalawang bagay ang sadyang hindi nito ginagawa. Hindi nito kailanman ine-edit ang teksto (walang regex na nagbubura ng backtick, walang paghula sa mga kulang na bracket), kaya hindi mo maaaring gawing valid pero maling data ang isang sirang sagot. At malakas itong nabibigo kapag walang mahahanap, na siyang kailangan ng retry sa ibaba.

Mag-retry nang isang beses gamit ang mas mahigpit na prompt#
Kung naputol ang sagot o wala itong object, magtanong muli. Isang beses, hindi sa walang katapusang loop. Sa putol, bigyan ng mas maraming espasyo ang ikalawang pagtatangka; kung hindi, sabihin nang malinaw ang gusto mo. Gumagamit ito ng OpenAI Python SDK laban sa base URL ng CheapTokens (https:/) at ang buong model id mula sa docs; ang key ay nasa environment variable.
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')Ang pagkakasunod-sunod ang punto: finish_reason muna, extraction pangalawa, at saka lang mag-retry. Itaas ang error pagkatapos ng ikalawang pagtatangka para ma-log ng batch job ang item na iyon at magpatuloy.
Madaling tantiyahin ang halaga ng retry. Ipagpalagay na may request na 1,200 input token at 300 output token (isang palagay, gamitin ang sarili mong numero): $0.000048 sa DeepSeek-V4-Flash o $0.000036 sa GLM-5.3-Flash. Kung 30 sa 1,000 request ang nangangailangan ng ikalawang pagtatangka, ang dagdag na bill ay 30 × ang request na iyon, $0.0014 sa DeepSeek-V4-Flash. Live ang mga presyo, galing sila sa kasalukuyang listahan ng presyo.
Ilagay sa tamang pagkakasunod-sunod
Ayusin ang mga hakbang sa pag-aayos
I-drag ang mga item o gamitin ang mga arrow
- Suriin ang finish_reason: huminto dito kung sinasabi nitong length
- Hanapin ang unang { at putulin ang teksto bago ito
- Mag-decode gamit ang raw_decode at huwag pansinin ang sumusunod
- Kung walang natagpuan, mag-retry nang isang beses gamit ang mas mahigpit na prompt
Aling sintomas, aling lunas#
Kasya sa isip mo ang mga hakbang sa itaas hanggang sa susunod na kakaibang error. Ito ay parang lookup: hanapin ang nakikita mo sa screen at basahin ang lunas.
Piliin ang iyong kaso
Piliin ang nakikita mo
Pumili ng opsyon upang makita ang sagot
Nabigo ang json.loads sa pos 0
Nagsisimula ang sagot sa fence o pangungusap. Patakbuhin ito sa extract_json; hindi kailangan ang retry.
Ang pos ay nasa dulo ng string
Naagang huminto ang sagot. Suriin ang finish_reason: kung ito ay length, taasan ang max_tokens at magtanong muli.
Ang error ay nasa gitna
Ang modelo mismo ang nagsulat ng sirang JSON (kulang na comma, trailing comma, hindi naka-escape na quote). Huwag itong i-patch: mag-retry nang isang beses gamit ang mas mahigpit na prompt at i-log ang sagot kung mabigo muli.
Gumagana ang json.loads pero may kulang na key
Maayos ang pag-parse, hindi ang nilalaman. Ilista ang eksaktong mga key sa prompt at suriin ang mga ito sa code pagkatapos ng pag-parse.
Maaari bang pilitin ng API ang valid JSON?#
Minsan. Ang structured outputs guide ng OpenAI ay nagdodokumento ng dalawang mode: JSON mode (type: json_), na nagbabalik ng valid JSON pero hindi sumusunod sa schema, at Structured Outputs (type: json_, strict: true), na sumusunod; inirerekomenda ng OpenAI ang pangalawa kung posible (source: developers.openai.com, sinuri noong 2 Nobyembre 2026). Iyon ay mga opsyon ng sariling API ng OpenAI. Bago umasa sa response_format sa ibang endpoint, tingnan ang CheapTokens docs; gumagana ang extraction at retry sa itaas sa plain chat completions at hindi nangangailangan ng espesyal na parameter. Higit pa sa mga opsyon sa request-side sa Get valid JSON from an LLM API: response_format and retries.
Bago mo i-ship ang parser, suriin ang apat na bagay: binabasa ang finish_reason bago i-parse ang teksto, may isang retry lang (at naka-log ang ikalawang pagkabigo kasama ang item id), sinusuri ng code na umiiral ang inaasahang mga key pagkatapos ng pag-parse, at nag-iiwan ng espasyo ang max_tokens para sa pinakamahabang JSON na inaasahan mo.
Sa susunod na mag-throw ang json.loads, i-print muna ang exc.pos at finish_reason. Karaniwang sinasabi ng dalawang numerong iyon kung ano ang nangyari bago ka pa magbasa ng isang linya ng sagot.



