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

الذكاء الاصطناعي للمبتدئيندليل

حدّد سقف تكلفة دفعة LLM في Python: سجل وميزانية صارمة

سجّل رموز وتكلفة كل استدعاء API في ملف JSONL، وأوقف الدفعة عند ميزانية، وقدّر الفاتورة قبل تشغيلها. كود Python مضمّن.

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

طرفية تعرض تشغيل دفعة يتوقف عند بلوغ الميزانية
في هذه الصفحة
  1. ما يفعله تشغيل Clocktower ويمكنك نسخه
  2. السجل: ملف واحد، سطر واحد لكل استدعاء
  3. حلقة الدفعة: تخطَّ ما أُنجز، توقّف عند الحد الأقصى
  4. قدّر الفاتورة قبل أن تضغط تشغيل
  5. عندما تكون الفاتورة أكبر من المتوقع: اقرأ السجل
  6. قبل أن تُطلق

يتكرر سكربتك على 200 مُدخل، وتُغلق غطاء الحاسوب المحمول، وفي الصباح تكتشف كم كلّفتك الليلة. عادتان صغيرتان تُصلحان ذلك. بعد كل استدعاء API، أضِف سطرًا واحدًا يحتوي على الرموز والتكلفة إلى ملف سجل. قبل كل استدعاء، قارن الإجمالي الجاري بميزانية وتوقّف إذا بلغتها. هذا أقل من 50 سطرًا من Python، وتجربة تشغيل من 5 مُدخلات تُخبرك بالفاتورة قبل أن تبدأ التشغيل الفعلي.

ما يفعله تشغيل Clocktower ويمكنك نسخه#

شغّل Dwarkesh Patel، مضيف Dwarkesh Podcast، 11 نموذجًا ضد بعضها في Blood on the Clocktower، 386 لعبة وفقًا لملف README، ونشر الكود والسجلات والتقييمات (github.com/dwarkeshsp/clocktower، تم التحقق في 8 نوفمبر 2026). تفصيلان من المستودع يستحقان السرقة. يحتفظ كل مجلد لعبة بسجل لكل استدعاء يحتوي على الرموز والتكلفة، ويذهب الإنفاق إلى games/ledger.jsonl. راية --budget تحدّ من إجمالي الإنفاق؛ القيمة الافتراضية 1,000 دولار. يضيف موقع المعيار حارسًا ثالثًا: يحصل كل نموذج على السقف نفسه البالغ 8,000 رمز لكل دور (clocktowerbench.com).

إذن: سقف على كل استدعاء، وسجل لكل استدعاء، وحد أقصى للإجمالي. تحتاج البطولة إلى الثلاثة، وكذلك يحتاج تشغيلك الليلي، لكن أصغر.

فكّر في السجل كلفافة الإيصالات في صندوق متجر. كل عملية بيع تطبع سطرًا؛ ويمكن للمدير جمع اللفافة في أي لحظة ويعرف متى نفدت نقدية اليوم.

السجل: ملف واحد، سطر واحد لكل استدعاء#

إليك الأمر كله. يستخدم حزمة openai موجّهة إلى CheapTokens (https://api.cheaptokens.dev/v1، راجع المستندات للمفتاح ومعرّفات النماذج).

python
import json
import os
from pathlib import Path

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'
PRICE_IN, PRICE_OUT = 1.00, 2.00  # USD per 1M tokens: example, substitute current ones


class BudgetExceeded(Exception):
    pass


class Ledger:
    def __init__(self, path, budget_usd):
        self.path = Path(path)
        self.budget = budget_usd
        self.rows = []
        if self.path.exists():
            with self.path.open() as f:
                self.rows = [json.loads(line) for line in f if line.strip()]

    @property
    def spent(self):
        return sum(r['cost_usd'] for r in self.rows)

    def done(self):
        return {r['unit'] for r in self.rows}

    def check(self, unit):
        if self.spent >= self.budget:
            raise BudgetExceeded(
                f'stopped before {unit}: spent {self.spent:.4f} of {self.budget} USD'
            )

    def add(self, unit, usage, finish_reason):
        cost = (usage.prompt_tokens * PRICE_IN
                + usage.completion_tokens * PRICE_OUT) / 1_000_000
        row = {
            'unit': unit,
            'model': MODEL,
            'prompt_tokens': usage.prompt_tokens,
            'completion_tokens': usage.completion_tokens,
            'finish_reason': finish_reason,
            'cost_usd': round(cost, 6),
        }
        self.rows.append(row)
        with self.path.open('a') as f:
            f.write(json.dumps(row) + '\n')


def ask(ledger, unit, prompt, max_tokens=400):
    ledger.check(unit)
    resp = client.chat.completions.create(
        model=MODEL,
        messages=[{'role': 'user', 'content': prompt}],
        max_tokens=max_tokens,
    )
    ledger.add(unit, resp.usage, resp.choices[0].finish_reason)
    return resp.choices[0].message.content

يمر كل استدعاء عبر الخطوات الأربع نفسها:

أربع خطوات حول كل استدعاء API: تحقّق من الميزانية، استدعِ، اقرأ الاستخدام، أضِف إلى السجل
الدورة وراء كل استدعاء في الغلاف

ثلاثة خيارات هناك متعمّدة.

  • كائن JSON واحد لكل سطر (JSONL)، يُفتح في وضع الإضافة. إذا تعطّل السكربت عند الاستدعاء 130، فإن الاستدعاءات من 1 إلى 129 موجودة بالفعل على القرص.
  • الإجمالي يأتي من الملف، لا من متغيّر. أعد تشغيل السكربت ويظل الحد الأقصى ساريًا، لأن السجل يتذكّر ما أنفقه التشغيل السابق.
  • يُسجَّل finish_reason بجانب الرموز. صف يقول length يعني أن الإجابة بلغت max_tokens وقُطعت. ستحتاج معرفة ذلك لاحقًا.

ثابتا PRICE_ عنصران نائبان. خُذ الأرقام الحالية من جدول الأسعار. بالنسبة إلى DeepSeek-V4-Flash وقت قراءتك لهذا، هذا $0.015 لكل مليون رمز إدخال و$0.10 لكل مليون رمز إخراج. غيّر MODEL إلى zai-org/GLM-5.3-Flash أو MiniMaxAI/MiniMax-M2.7 والثابتين، ويعمل السجل نفسه معهما.

حلقة الدفعة: تخطَّ ما أُنجز، توقّف عند الحد الأقصى#

الحلقة قصيرة. لنفترض أن مُدخلاتك في قاموس مفتاحه معرّف وحدة، اسم قطعة عمل واحدة، مثل doc-001:

python
prompts = {
    'doc-001': 'Summarize this ticket in one sentence: ...',
    'doc-002': 'Summarize this ticket in one sentence: ...',
    # ... your 200 prompts
}

ledger = Ledger('ledger.jsonl', budget_usd=0.50)
already = ledger.done()

for unit, prompt in prompts.items():
    if unit in already:
        continue
    try:
        answer = ask(ledger, unit, prompt)
    except BudgetExceeded as e:
        print(e)
        break
    Path(f'out-{unit}.txt').write_text(answer)

شغّله، اضغط Ctrl+C في المنتصف، شغّله مرة أخرى: تُتخطّى الوحدات المنجزة، فيعمل السجل كملف تقدّمك أيضًا. دع الميزانية تنفد وستحصل على سطر واحد بدلًا من مفاجأة، شيء مثل stopped before doc-142: spent 0.5003 of 0.5 USD.

لاحظ كلمة قبل. يحدث التحقق قبل كل استدعاء، لذا يمكن للاستدعاء الأخير أن يدفع الإجمالي قليلًا فوق الميزانية. مقدار التجاوز محدود: لا يمكن لاستدعاء واحد أن يكلّف أكثر من أطول مُدخل لديك زائد max_tokens. بالنسبة إلى مُدخل من 1,500 رمز وmax_tokens=400، هذا على الأكثر $0.000063. لهذا لكل استدعاء في الغلاف max_tokens: يحوّل خطرًا مفتوحًا إلى رقم يمكنك التخطيط حوله.

صفوف من ملف سجل مع رموز لكل استدعاء وتوقف ميزانية نهائي
صفوف سجل مثال ورسالة التوقف (قيم توضيحية)

قدّر الفاتورة قبل أن تضغط تشغيل#

الآن النصف الآخر: معرفة التكلفة قبل الإطلاق. شغّل أول خمسة مُدخلات عبر ask() بميزانية صغيرة واقرأ السجل. لنفترض أن الصفوف الخمسة متوسطها 1,500 رمز إدخال و400 رمز إخراج. هذه أرقامك للدفعة كلها.

بالنسبة إلى 200 مُدخل، هذا 200 × 1,500 = 300,000 رمز إدخال و200 × 400 = 80,000 رمز إخراج. إليك هذا الحمل على الأسعار الحية:

النموذج لكل طلب 200 طلبات OpenRouter، 200 طلبات
GLM-5.3-Flash $0.000046 $0.0092 $0.0167
DeepSeek-V4-Flash $0.000063 $0.0125 $0.0322
MiniMax-M2.7 $0.00014 $0.027 $0.324

1,500 رمز إدخال و400 رمز إخراج لكل طلب. الأسعار لكل مليون رمز حالية وتتغير مع تعريفاتنا؛ يعرض عمود OpenRouter أسعارًا مرجعية لنفس حجم العمل.

يحتوي الجدول على عمود OpenRouter أيضًا. مع احتساب الإدخال والإخراج معًا، يخرج DeepSeek-V4-Flash أرخص من OpenRouter لهذا الحمل، وهذا المجموع هو ما تدفعه الدفعة فعليًا.

تختلف المُدخلات في الطول، لذا خمس عينات تخمين لا وعد. استخدم الأطول من الخمس عندما تريد جانبًا أعلى آمنًا، ثم دع سقف الميزانية يلتقط الباقي. تريد مقارنة نموذجين على المُدخلات الـ200 نفسها؟ أعطِ كل نموذج ملف سجله الخاص، مثل Ledger(f'ledger-{MODEL.split("/")[-1]}.jsonl', 0.50)، ويظهر الإجماليان جنبًا إلى جنب بعد ذلك.

جرّب دفعتك هنا: اضبط الرموز لكل طلب من عينتك وعدد الطلبات التي تخطط لها.

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

دفعتك: رموز من عينتك المكوّنة من 5 مُدخلات

رموز الإدخال لكل طلب: 1,500 · رموز الإخراج لكل طلب: 400 · الطلبات يوميًا: 200

النموذجيوميًاشهريًا
GLM-5.3-Flash$0.0092$0.276
DeepSeek-V4-Flash$0.0125$0.375
MiniMax-M2.7$0.027$0.81

لمزيد من المعلومات حول عدّ الرموز قبل الإرسال، راجع عدّ الرموز باستخدام tiktoken.

عندما تكون الفاتورة أكبر من المتوقع: اقرأ السجل#

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

python
rows = [json.loads(line) for line in open('ledger.jsonl')]
for r in sorted(rows, key=lambda r: r['cost_usd'], reverse=True)[:5]:
    print(r['unit'], r['prompt_tokens'], r['completion_tokens'], r['finish_reason'])

بالنسبة إلى وكيل، سمِّ الوحدة على اسم الخطوة (step-17) حتى ترى شكل التشغيل. تظهر أنماط قليلة مرارًا وتكرارًا؛ طابِق كل واحد مع سببه المعتاد:

طابق الأزواج

طابِق كل نمط سجل مع سببه المعتاد

  • ينمو prompt_tokens قليلًا في كل صفتعيد الحلقة إرسال التاريخ كله في كل خطوة
  • completion_tokens يساوي max_tokens وfinish_reason هو lengthتُقطع الإجابات عند سقفك، وتدفع مقابل النص المقطوع
  • تظهر الوحدة نفسها مرات كثيرة على التواليحلقة إعادة محاولة، أو وكيل يكرر خطوة واحدة
  • صف واحد يكلّف أكثر بكثير من كل الصفوف الأخرىإدخال واحد مفرط الحجم، مثل ملف كامل ملصق في المُدخل

كل نمط مرئي في السجل قبل الفاتورة بوقت طويل. الأول مُغطّى في لماذا يعيد الوكيل قراءة تاريخه.

إذا أردت أيضًا الإنفاق لكل مشروع وتنبيهات خارج السكربت، هذا الدليل إلى تنبيهات الميزانية لكل مشروع يغطي ذلك الجانب. السجل هنا هو الجزء الذي يعيش في كودك ويمكنه أن يقول توقّف.

قبل أن تُطلق#

قائمة التحقق

قبل التشغيل الفعلي الأول

حجم السياق وحد الإجابة في DeepSeek-V4-Flash، مفيدان عند اختيار max_tokens، موجودان في صفحته: DeepSeek-V4-Flash على CheapTokens.