API وSDKدليل
احصل على JSON صالح من واجهة برمجة تطبيقات LLM: response_format وإعادة المحاولات
طلب Python قابل للنسخ يعيد JSON قابلًا للتحليل، وغلاف json.loads يتعامل مع الإجابات الفارغة أو المقطوعة، وقائمة تحقق لأسباب تعطل JSON.

في هذه الصفحة
للحصول على JSON من نموذج محادثة، افعل ثلاثة أشياء: مرّر response_، وضع كلمة "json" ومثالاً على البنية في المطالبة، واضبط max_tokens عاليًا بما يكفي للكائن بأكمله. ثم لا تثق بالإجابة بشكل أعمى: تحقق من أنها ليست فارغة، وتحقق من finish_reason، وعندها فقط استدعِ json.loads.
هذه هي النسخة المختصرة. بقية هذا الدليل عبارة عن سكريبت كامل لمهمة شائعة جدًا: استخراج الحقول من بريد إلكتروني. تستخدم الأمثلة عنوان URL الأساسي https:/ ومعرّف النموذج deepseek- (docs).
المهمة: بريد إلكتروني يدخل، صف يخرج#
لنفترض أن سكريبتك يقرأ رسائل الدعم الفني وعليه تحويل كل واحدة إلى سجل: المرسل، التاريخ، المبلغ، وما يريده المرسل. خذ هذه الرسالة:
مرحبًا، أنا آنا بيتروفا من شركة Lumen Ltd. في 14 أكتوبر دفعت الفاتورة رقم 3381 بمبلغ 1,250 يورو، لكن الإيصال لا يزال يظهرها كغير مدفوعة. يرجى إصلاح ذلك قبل يوم الجمعة.
تريد هذا في المقابل، ولا شيء آخر:
{"sender": "Anna Petrova", "date": "2026-10-14", "amount": 1250, "currency": "EUR", "action": "fix receipt status"}قد تحصل من مطالبة بسيطة "استخرج الحقول" على جملة ودية، أو سياج كود، أو كليهما. يتعثر json.loads عند أول علامة backtick. هذه هي المشكلة التي يحلها response_format ومطالبة جيدة معًا.
نفس الطلب في Python و 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
}'يوضح مرجع DeepSeek API لمخرجات JSON (تم التحقق في 27 أكتوبر 2026؛ الصفحة لا تحتوي على تاريخ نشر) القواعد التي يتبعها هذا الطلب: response_format مضبوط على json_object، وكلمة "json" في مكان ما في مطالبة النظام أو المستخدم، ومثال على البنية المستهدفة. بدون المثال، على النموذج تخمين أسماء الحقول الخاصة بك، وسيخمن بشكل مختلف في البريد الإلكتروني التالي.
لماذا المثال أهم من العلامة#
response_format يطلب من API إرجاع كائن JSON. لا يخبر النموذج أي كائن. فكر في الأمر كأنك تقول لزميل "أرسل لي جدول بيانات" دون تحديد الأعمدة. ستحصل على جدول بيانات، لكن ليس جدولك.
المثال في المطالبة هو صف رأس العمود. عادتان تجعلانه يعمل بشكل أفضل:
- أظهر الشكل الحقيقي، بما في ذلك الأنواع:
1250رقم،"2026-10-14"سلسلة نصية بتنسيق تاريخ ثابت. - قل ما يجب فعله عند فقدان حقل (
nullأعلاه). وإلا يختلق النموذج قيمة لملء الفراغ.
تعامل مع response_format كتلميح قوي، وليس ضمانًا. لا تزال النماذج أحيانًا تعيد سلسلة فارغة، أو تغلف الكائن في نص. ولهذا السبب يوجد الجزء التالي.
التحليل الآمن: فارغ، مقطوع، أو مجرد مكسور#
تظهر ثلاث حالات فشل في الممارسة، وكل واحدة تترك أثرًا مختلفًا:
- محتوى فارغ. تقول وثائق DeepSeek أن وضع JSON قد يعيد محتوى فارغًا أحيانًا وتقترح إعادة صياغة المطالبة. من جانبك يبدو الأمر وكأن
message.contentهو""أوNone، وjson.loads("")يرفعExpecting value: line 1 column 1 (char 0). - مقطوع. نفد
max_tokensفي منتصف الكائن.finish_reasonهوlength، وينتهي النص في منتصف سلسلة نصية. المزيد عن هذا في لماذا تتوقف إجابة الذكاء الاصطناعي في منتصف الجملة. - ليس JSON. جملة قبل الكائن، أو سياج حوله.

الإصلاح للحالتين الأولى والثالثة هو إعادة المحاولة بمطالبة أكثر حزمًا. بالنسبة للثانية، إعادة المحاولة بنفس الحد لا تغير شيئًا، لذا يرفع الغلاف ويخبرك برفع 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")إعادة محاولة واحدة كافية لسكريبت. إذا فشلت مرتين، سجّل معرّف البريد الإلكتروني وانتقل؛ نادرًا ما يساعد طلب ثالث مطابق.
طابق الأزواج
أي عرض يشير إلى أي سبب؟
اضغط على عنصر من اليسار، ثم على زوجه
- finish_reason هو "length"max_tokens صغير جدًا للكائن
- المحتوى سلسلة فارغةوضع JSON لم يعيد شيئًا؛ أعد صياغة المطالبة وأعد المحاولة
- JSONDecodeError على حرف هو backtickغلف النموذج JSON في سياج كود
- أسماء الحقول تختلف بين رسائل البريد الإلكترونيالمطالبة لا تحتوي على مثال للبنية
تحقق من الشكل، وليس فقط الصياغة#
json.loads يثبت فقط أن النص JSON صالح. لا يثبت أنه يحتوي على الحقول التي طلبتها. أضف فحصًا رخيصًا مباشرة بعد التحليل:
REQUIRED = {"sender", "date", "amount", "currency", "action"}
data = ask_json(email_text)
missing = REQUIRED - data.keys()
if missing:
raise ValueError(f"missing fields: {sorted(missing)}")من هنا يذهب القاموس مباشرة إلى صف قاعدة بيانات أو جدول بيانات. إذا كنت بحاجة إلى أن يستدعي النموذج دوالك الخاصة بدلاً من ملء سجل ثابت، فهذه أداة مختلفة: راجع استدعاء الدوال في Python.

كم تكلف دفعة من رسائل البريد الإلكتروني#
الاستخراج القصير هو النوع الرخيص من الطلبات: بضع مئات من الرموز داخلة، وبضع عشرات خارجة. افترض 1,000 بريد إلكتروني يوميًا بحوالي 400 رمز إدخال (المطالبة، المثال والبريد الإلكتروني) وحوالي 80 رمز إخراج (JSON). على DeepSeek-V4-Flash هذا $0.014 يوميًا، ويظهر الجدول نفس الحمل على النماذج الأخرى:
| النموذج | الإدخال $/1M | الإخراج $/1M | لكل طلب | 1,000 طلبات | OpenRouter، 1,000 طلبات |
|---|---|---|---|---|---|
| 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 رمز إدخال و80 رمز إخراج لكل طلب. الأسعار لكل مليون رمز حالية وتتغير مع تعريفاتنا؛ يعرض عمود OpenRouter أسعارًا مرجعية لنفس حجم العمل.
في المجموع، مع حساب الإدخال والإخراج معًا، يخرج DeepSeek-V4-Flash أرخص من سعر OpenRouter المرجعي لهذا الحمل. كما يسمح بما يصل إلى 32,768 رمز إخراج لكل إجابة، لذا لن تُقطع قائمة طويلة من البنود كما قد يحدث مع حد صغير، والسعر هو نفسه في أي ساعة. GLM-5.3-Flash (zai-) خيار آخر على نفس عنوان API: حتى 8,192 رمز إخراج، وهو أكثر من كافٍ لسجل واحد لكل بريد إلكتروني. الأسعار الحالية على صفحة الأسعار؛ صفحة نموذج DeepSeek تحتوي على التفاصيل للنموذج المستخدم هنا.
فحص سريع#
فحص سريع
JSON الخاص بك مقطوع في منتصف سلسلة نصية. ما الذي تنظر إليه أولاً؟
إظهار الإجابة
الإجابة: finish_reason و max_tokens
كائن مقطوع ينتهي مع finish_reason مضبوطًا على length. تم الوصول إلى الحد، لذا ارفع max_tokens؛ تغيير درجة الحرارة أو العنوان لا يساعد.
إذا لم تكن قد أرسلت طلبًا بعد، ابدأ بـ طلب API الأول الخاص بك، ثم عد واستبدل الكود أعلاه.



