API at SDKGabay
Kumuha ng valid JSON mula sa isang LLM API: response_format at mga retry
Isang makokopyang Python request na nagbabalik ng parseable JSON, isang json.loads wrapper na humahawak ng walang laman o putol na mga sagot, at isang checklist kung bakit nasisira ang JSON.

Sa pahinang ito
Upang makakuha ng JSON mula sa isang chat model, gawin ang tatlong bagay: ipasa ang response_, ilagay ang salitang "json" at isang halimbawa ng istruktura sa prompt, at itakda ang max_tokens nang sapat para sa buong object. Pagkatapos, huwag kailanman magtiwala nang bulag sa sagot: tiyakin na hindi ito walang laman, suriin ang finish_reason, at saka lamang tawagan ang json.loads.
Iyon ang maikling bersyon. Ang natitirang bahagi ng gabay na ito ay isang kumpletong script para sa isang karaniwang gawain: pagkuha ng mga field mula sa isang email. Gumagamit ang mga halimbawa ng base URL na https:/ at ang model id na deepseek- (docs).
Ang gawain: isang email papasok, isang row palabas#
Sabihin nating binabasa ng iyong script ang mga support email at kailangang gawing record ang bawat isa: sender, petsa, halaga, at kung ano ang gusto ng nagpadala. Kunin natin ito:
Hi, this is Anna Petrova from Lumen Ltd. On 14 October I paid invoice #3381 for 1,250 EUR, but the receipt still shows it as unpaid. Please fix it before Friday.
Gusto mong ibalik ito, at wala nang iba:
{"sender": "Anna Petrova", "date": "2026-10-14", "amount": 1250, "currency": "EUR", "action": "fix receipt status"}Ang simpleng prompt na "extract the fields" ay maaaring magbigay sa iyo ng magiliw na pangungusap, isang code fence, o pareho. Ang iyong json.loads ay sumasabog sa unang backtick. Iyan ang problemang nilulutas nang sabay ng response_format at isang mahusay na prompt.
Ang parehong kahilingan sa Python at curl
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
}'Ang DeepSeek API reference para sa JSON Output (sinuri noong 27 Oktubre 2026; walang petsa ng publikasyon ang pahina) ay nagsasaad ng mga patakarang sinusunod ng kahilingang ito: response_format nakatakda sa json_object, ang salitang "json" sa isang lugar sa system o user prompt, at isang halimbawa ng target na istruktura. Kung walang halimbawa, kailangang hulaan ng modelo ang iyong mga pangalan ng field, at mag-iiba ang hula nito sa susunod na email.
Bakit mas mahalaga ang halimbawa kaysa sa flag#
response_format ay humihiling sa API na magbalik ng isang JSON object. Hindi nito sinasabi sa modelo kung aling object. Isipin mo na parang sinabi mo sa isang katrabaho na "padalhan mo ako ng spreadsheet" nang hindi sinasabi kung anong mga column. Makakakuha ka ng spreadsheet, hindi lang iyong sa iyo.
Ang halimbawa sa prompt ay ang row ng column header. Dalawang ugali ang nagpapagana nito nang mas mahusay:
- Ipakita ang tunay na hugis, kasama ang mga uri: ang
1250ay isang numero, ang"2026-10-14"ay isang string sa isang takdang format ng petsa. - Sabihin kung ano ang gagawin kapag nawawala ang isang field (
nullsa itaas). Kung hindi, gagawa ang modelo ng halaga para punan ang puwang.
Ituring ang response_format bilang isang malakas na pahiwatig, hindi isang garantiya. Minsan nagbabalik pa rin ang mga modelo ng walang laman na string, o binabalot ang object sa prosa. Kaya naman may susunod na bahagi.
Ligtas na pag-parse: walang laman, putol, o sira lang#
Tatlong pagkabigo ang lumilitaw sa praktika, at bawat isa ay nag-iiwan ng ibang bakas:
- Walang laman na content. Sinasabi ng DeepSeek docs na ang JSON mode ay maaaring paminsan-minsan ay magbalik ng walang laman na content at nagmumungkahi na baguhin ang prompt. Sa iyong panig, mukhang ang
message.contentay""oNone, at angjson.loads("")ay nagtataas ngExpecting value: line 1 column 1 (char 0). - Putol. Naubos ang
max_tokenssa gitna ng object. Angfinish_reasonaylength, at ang teksto ay nagtatapos sa gitna ng string. Higit pa tungkol dito sa bakit humihinto ang sagot ng AI sa gitna ng pangungusap. - Hindi JSON. Isang pangungusap bago ang object, o isang fence sa paligid nito.

Ang lunas para sa una at pangatlo ay isang retry na may mas matibay na prompt. Para sa pangalawa, ang retry na may parehong limit ay walang binabago, kaya ang wrapper ay nagtataas at nagsasabi sa iyo na itaas ang max_tokens:
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")Sapat na ang isang retry para sa isang script. Kung mabigo ito nang dalawang beses, i-log ang email id at magpatuloy; bihirang makatulong ang pangatlong magkaparehong kahilingan.
Itugma ang mga pares
Aling sintomas ang tumutukoy sa aling sanhi?
I-tap ang isang item sa kaliwa, pagkatapos ang pares nito
- finish_reason ay "length"masyadong maliit ang max_tokens para sa object
- ang content ay walang laman na stringwalang ibinalik ang JSON mode; baguhin ang prompt at subukan muli
- JSONDecodeError sa isang karakter na backtickbinabalot ng modelo ang JSON sa isang code fence
- Nagkakaiba ang mga pangalan ng field sa pagitan ng mga emailwalang halimbawa ng istruktura ang prompt
Suriin ang hugis, hindi lang ang syntax#
json.loads ay nagpapatunay lamang na ang teksto ay wastong JSON. Hindi nito pinapatunayan na mayroon itong mga field na hiniling mo. Magdagdag ng murang pagsusuri kaagad pagkatapos ng pag-parse:
REQUIRED = {"sender", "date", "amount", "currency", "action"}
data = ask_json(email_text)
missing = REQUIRED - data.keys()
if missing:
raise ValueError(f"missing fields: {sorted(missing)}")Mula dito, ang dict ay dumiretso sa isang database row o spreadsheet. Kung kailangan mong tawagan ng modelo ang iyong sariling mga function sa halip na punan ang isang fixed record, ibang tool iyon: tingnan ang function calling sa Python.

Magkano ang isang batch ng mga email#
Ang maikling extraction ay ang murang uri ng kahilingan: ilang daang token papasok, ilang dosena palabas. Ipagpalagay na 1,000 email bawat araw sa humigit-kumulang 400 input token (prompt, halimbawa at email) at humigit-kumulang 80 output token (ang JSON). Sa DeepSeek-V4-Flash, iyon ay $0.014 bawat araw, at ipinapakita ng talahanayan ang parehong load sa iba pang mga modelo:
| Modelo | Input $/1M | Output $/1M | Bawat kahilingan | 1,000 mga kahilingan | OpenRouter, 1,000 mga kahilingan |
|---|---|---|---|---|---|
| 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 |
400 input at 80 output na token bawat kahilingan. Ang mga presyo kada 1M token ay kasalukuyan at nagbabago kasabay ng aming mga taripa; ang column ng OpenRouter ay nagpapakita ng reference na presyo para sa parehong workload.
Sa kabuuan, kasama ang input at output na binibilang nang magkasama, ang DeepSeek-V4-Flash ay lumalabas na mas mura kaysa sa reference na presyo ng OpenRouter para sa load na ito. Pinapayagan din nito ang hanggang 32,768 output token bawat sagot, kaya ang mahabang listahan ng mga line item ay hindi mapuputol tulad ng mangyayari sa maliit na limit, at pareho ang presyo sa anumang oras. Ang GLM-5.3-Flash (zai-) ay isa pang opsyon sa parehong API address: hanggang 8,192 output token, na sapat na para sa isang record bawat email. Ang mga kasalukuyang presyo ay nasa pricing page; ang DeepSeek model page ay may mga detalye para sa modelong ginamit dito.
Mabilis na pagsusuri#
Mabilisang pagsusuri
Ang iyong JSON ay naputol sa gitna ng isang string. Ano ang una mong titingnan?
Ipakita ang sagot
Sagot: finish_reason at max_tokens
Ang putol na object ay nagtatapos sa finish_reason na nakatakda sa length. Naabot ang limit, kaya itaas ang max_tokens; hindi nakakatulong ang pagbabago ng temperatura o address.
Kung hindi ka pa nakakapagpadala ng kahilingan, magsimula sa iyong unang API request, pagkatapos ay bumalik at ipalit ang code sa itaas.



