العودة إلى المدونة

استكشاف الأخطاءدليل

هل قُطعت إجابة الذكاء الاصطناعي؟ إصلاح finish_reason "length" وmax_tokens

هل تتوقف إجابة الـ API في منتصف الجملة أو تعيد JSON معطوبًا؟ افحص finish_reason، وارفع max_tokens ضمن الحد، وأضف خطوة متابعة.

بواسطة CheapTokens Teamنُشر في 5 دقيقة قراءة

غلاف بأسلوب الطرفية يعرض finish_reason length في استجابة API
في هذه الصفحة
  1. كيف تبدو الإجابة المقطوعة على الشاشة
  2. الخطوة 1: اقرأ الحقل الذي يخبرك
  3. الخطوة 2: ارفع max_tokens، لكن ليس فوق السقف
  4. الخطوة 3: JSON يتوقف في منتصفه
  5. الخطوة 4: دع النموذج يتابع
  6. كم تكلّف إجابة طويلة
  7. قبل الإطلاق: قائمة تحقق قصيرة

شغّل سكربتك، وأجاب النموذج، ثم انتهت الدالة في منتصف سطر. أو يرمي محلّل JSON خطأً على رد يبدو طبيعيًا تمامًا. في أغلب الأحيان لم يفشل النموذج: بل بلغ حد max_tokens الذي ضبطته (أو قيمة افتراضية لم ترها) فأوقفت الـ API التوليد في منتصف كلمة.

الحل السريع: اقرأ finish_reason في الاستجابة. إذا كانت "length" (واجهة متوافقة مع OpenAI) أو كانت stop_reason تساوي "max_tokens" (Anthropic Messages)، فقد قُطع الرد. ارفع max_tokens حتى حد مخرجات النموذج، أو اطلب من النموذج المتابعة. يعرض بقية هذه الصفحة الأمرين في كود يمكنك لصقه.

إذا أردت النسخة المبسطة لسبب حدوث ذلك، فابدأ بـ لماذا تتوقف إجابة الذكاء الاصطناعي في منتصف الجملة. هذه الصفحة هي نسخة تصحيح الأخطاء.

كيف تبدو الإجابة المقطوعة على الشاشة#

تختلف الأعراض حسب ما طلبته، لكن السبب واحد. جرّب مطابقة هذه الحالات بما رأيته:

طابق الأزواج

طابق العَرَض مع سببه المحتمل

  • تنتهي دالة بـ return resuإجابة الكود بلغت max_tokens في منتصف السطر
  • يفشل json.loads برسالة "Unterminated string" أو "Expecting ',' delimiter"قُطع JSON في منتصفه
  • تتوقف الترجمة بعد النصف الأول من النصحد المخرجات أصغر من الترجمة
  • الرد مكتمل والحقل يقول stopانتهى النموذج من تلقاء نفسه؛ ابحث في مكان آخر

هذا الصف الأخير مهم. إذا كانت finish_reason تساوي stop، فالحد ليس مشكلتك، ورفع max_tokens لن يغيّر شيئًا.

الخطوة 1: اقرأ الحقل الذي يخبرك#

كل استجابة تخبرك بسبب انتهاء التوليد. في استجابة متوافقة مع OpenAI يكون الحقل choices[0].finish_reason: قيمته "stop" عندما ينتهي النموذج، و"length" عندما تنفد الرموز. في Anthropic Messages API يكون الحقل stop_reason، وقيمة القطع هي "max_tokens". يوصي دليل أسباب التوقف من Anthropic (تم التحقق في 28 أكتوبر 2026) بالإصلاحين أدناه تحديدًا: ارفع max_tokens أو تابع الاستجابة.

إليك الفحص نفسه بثلاث صيغ. عنوان URL الأساسي هو https://api.cheaptokens.dev/v1 لـ OpenAI SDK وhttps://api.cheaptokens.dev (بدون /v1) لـ Anthropic SDK. قيمة max_tokens=200 صغيرة عمدًا، حتى ترى القطع بنفسك.

فحص واحد بثلاث طرق

Python (OpenAI SDK)

python
import os
from openai import OpenAI

client = OpenAI(
    base_url="https://api.cheaptokens.dev/v1",
    api_key=os.environ["CHEAPTOKENS_API_KEY"],
)

resp = client.chat.completions.create(
    model="deepseek-ai/DeepSeek-V4-Flash-0731",
    max_tokens=200,
    messages=[{"role": "user", "content": "Write a Python class for a todo list with tests."}],
)

choice = resp.choices[0]
if choice.finish_reason == "length":
    print("Cut off at the token limit")
print(choice.message.content)
print(resp.usage.completion_tokens)

Python (Anthropic SDK)

python
import os
import anthropic

client = anthropic.Anthropic(
    base_url="https://api.cheaptokens.dev",
    api_key=os.environ["CHEAPTOKENS_API_KEY"],
)

msg = client.messages.create(
    model="deepseek-ai/DeepSeek-V4-Flash-0731",
    max_tokens=200,
    messages=[{"role": "user", "content": "Write a Python class for a todo list with tests."}],
)

if msg.stop_reason == "max_tokens":
    print("Cut off at the token limit")
print(msg.content[0].text)

curl

bash
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",
    "max_tokens": 200,
    "messages": [{"role": "user", "content": "Write a Python class for a todo list with tests."}]
  }'

ابحث عن "finish_reason": "length" في JSON العائد.

عندما يعمل كتشخيص، سترى هذا الزوج: حد منخفض يعطي length، وحد واسع يعطي stop.

الطلب نفسه مع max_tokens منخفض وآخر مرتفع: يتغير finish_reason من length إلى stop
القيمتان لـ finish_reason اللتان ستلتقيان بهما غالبًا

الخطوة 2: ارفع max_tokens، لكن ليس فوق السقف#

الإصلاح الواضح هو رقم أكبر. المفاجأة: لكل نموذج سقفه الخاص على الإجابة. لا تضبط قيمة أعلى منه: الحد خاص بكل نموذج، لذا راجع الجدول أدناه أولًا. هذه هي حدود كل إجابة على CheapTokens (تم التحقق من الحقائق في 28 أكتوبر 2026):

النموذج أقصى مخرجات لكل إجابة السياق
DeepSeek-V4-Flash 32,768 رمزًا 380K
GLM-5.3-Flash 8,192 رمزًا 390K
MiniMax-M2.7 8,192 رمزًا 180K

ترجمة من 9,000 رمز تحتاج نموذجًا بحد أكبر لكل إجابة — يقبل DeepSeek-V4-Flash حتى 32,768 رمزًا لكل إجابة. للكود الطويل أو JSON الطويل أو مستند كامل، يملك deepseek-ai/DeepSeek-V4-Flash-0731 أكبر مساحة: حتى 32,768 رمزًا لكل إجابة، بسعر واحد في أي ساعة من اليوم. خذ معرّفات النماذج الدقيقة من وثائق CheapTokens وصفحة النماذج، لأن المزوّدين يسمّونها بشكل مختلف.

قاعدة عملية: اضبط max_tokens على أطول إجابة تقبلها، لا على الحد الأقصى. أنت تدفع مقابل الرموز التي يكتبها النموذج فعليًا، لذا لا يكلّف السقف المرتفع شيئًا عندما تكون الإجابات قصيرة.

الخطوة 3: JSON يتوقف في منتصفه#

رد JSON مقطوع هو أسوأ حالة، لأن النص يبدو صحيحًا حتى يصل المحلّل إلى النهاية. لنفترض أنك تستخرج حقولًا من 40 فاتورة في استدعاء واحد وتطلب مصفوفة. يكتب النموذج 31 كائنًا، ويبلغ الحد، ولا تُغلق المصفوفة أبدًا.

خطأ json.loads على مصفوفة مقطوعة عند حد الرموز
كيف تبدو مصفوفة JSON مقطوعة في محلّل

لا تحاول إصلاح النص بإضافة ]}. ستحصل على JSON صالح بسجلات ناقصة، وهذا أسوأ من خطأ. افحص finish_reason أولًا؛ إذا كانت length، فإما ارفع الحد وأعد تشغيل الاستدعاء، أو قسّم المدخلات إلى دفعات أصغر. لفرض الصيغة نفسها، راجع JSON صالح من LLM API.

الخطوة 4: دع النموذج يتابع#

أحيانًا تكون الإجابة أطول فعلًا من أي حد: فصل من كتاب، أو إعادة هيكلة كبيرة. حينها أعد ما لديك إلى النموذج واطلب منه المتابعة. تحتفظ الحلقة أدناه بالنص الذي لديها حتى الآن، وتطلب الباقي، وتتوقف بعد بضع جولات حتى لا تدور حلقة لا نهائية بسبب خطأ.

python
def complete(base_messages, model, max_tokens=4096, rounds=4):
    text = ""
    for _ in range(rounds):
        messages = list(base_messages)
        if text:
            messages += [
                {"role": "assistant", "content": text},
                {"role": "user", "content": "Continue exactly where you stopped. Do not repeat anything."},
            ]
        r = client.chat.completions.create(model=model, max_tokens=max_tokens, messages=messages)
        choice = r.choices[0]
        text += choice.message.content or ""
        if choice.finish_reason != "length":
            return text, True   # finished on its own
    return text, False          # still cut after all rounds

عاداتان تحافظان على الأمانة. أعد علم True/False واعرض على المستخدم تحذيرًا عند False، بدلًا من تقديم نصف مستند كأنه مكتمل. وتذكّر أن كل جولة تعيد إرسال النص السابق كمدخل، لذا تكلّف الإجابة المتابَعة أكثر من إجابة واحدة كبيرة. للبيانات المهيكلة، فضّل حدًا أكبر أو دفعات أصغر على المتابعة.

كم تكلّف إجابة طويلة#

لنفترض أن إجابة طويلة تستهلك 2,000 رمز إدخال و8,000 رمز إخراج. على DeepSeek-V4-Flash يكلّف هذا الطلب $0.00083 بسعر اليوم، وإجمالًا، مع احتساب الإدخال والإخراج معًا، فهو أرخص من OpenRouter. غيّر الأرقام إلى حملك الخاص:

حاسبة التكلفة

يوم من 50 إجابة طويلة

رموز الإدخال لكل طلب: 2,000 · رموز الإخراج لكل طلب: 8,000 · الطلبات يوميًا: 50

النموذجيوميًاشهريًا
GLM-5.3-Flash$0.018$0.54
DeepSeek-V4-Flash$0.0415$1.25
MiniMax-M2.7$0.065$1.95

الخلاصة: جانب الإخراج هو المهيمن في هذا السيناريو، لذا فإن الهامش في max_tokens تأمين رخيص ضد إعادة التشغيل. الإجابة المقطوعة التي تضطر إلى إعادة توليدها تُدفع مرتين. خلفية عن جانبي الفاتورة في رموز الإدخال مقابل الإخراج، وقائمة النماذج الكاملة مع الأسعار على صفحة الأسعار.

قبل الإطلاق: قائمة تحقق قصيرة#

قائمة التحقق

قائمة تحقق للقطع في كودك