AR ▾
احصل على مفتاح API

ذكاء اصطناعي لبرمجة بوت ديسكورد: أنشئه باستخدام واجهة برمجة تطبيقات LLM

يُجيب بوت ديسكورد للذكاء الاصطناعي في البرمجة عن أسئلة البرمجة داخل خادمك، باستخدام نموذج لغوي خلف واجهة برمجة تطبيقات. يبني هذا الدليل واحدًا في حوالي 70 سطرًا من بايثون ويغطي الأجزاء التي تتعطل عادةً.

تم التحديث

شخصية بوت دردشة مجردة محاطة بفقاعات كلام متوهجة، توضح بوت ديسكورد مدعومًا بواجهة برمجة تطبيقات LLM

نقاط رئيسية

  • يحتاج البوت إلى discord.py، وحزمة openai، ورمز البوت ومفتاح واجهة برمجة تطبيقات LLM.
  • فعّل نية محتوى الرسائل في بوابة مطوري ديسكورد وإلا سيرى البوت رسائل فارغة.
  • احتفظ بسجل قصير لكل قناة وقلّصه، لأن النموذج لا يتذكر شيئًا بين الاستدعاءات.
  • جزّئ الردود عند 2000 حرف وحدد المكالمات المتزامنة لتبقى تحت حد التزامن البالغ 8 طلبات.

ما سيفعله البوت

الهدف هو إنشاء ذكاء اصطناعي لكتابة الأكواد في Discord يستجيب عند الإشارة إليه. يكتب المستخدم "@helper لماذا تُرجع قائمة الفهم None؟"، يرسل البوت الرسالة إلى النموذج، وتعود إجابة النموذج كرد في نفس القناة. يتذكر البوت أيضًا المحادثات الأخيرة في كل قناة، مما يتيح طرح أسئلة متابعة.

يتكون التصميم من ثلاثة أجزاء. تتعامل مكتبة discord.py مع الاتصال بـ Discord. تتحدث مكتبة OpenAI Python SDK مع واجهة برمجة تطبيقات LLM، وهي تعمل هنا لأن الواجهة متوافقة مع OpenAI. يحتوي قاموس صغير على سجل المحادثات. لا تحتاج إلى قاعدة بيانات للبدء.

ستحتاج إلى Python 3.9 أو أحدث، وحساب Discord قادر على إنشاء تطبيق، ومفتاح API من صفحة مفتاح API. يمكن للحسابات الجديدة المطالبة برصيد تجريبي مجاني بقيمة $0.50 بنقرة واحدة، وهو ما يكفي لبناء واختبار هذا الروبوت.

الخطوة 1: إنشاء تطبيق Discord

  1. افتح بوابة مطوري Discord وأنشئ تطبيقًا جديدًا.
  2. في قسم البوت، أنشئ مستخدم البوت وانسخ الرمز الخاص به. عامله ككلمة مرور.
  3. في نفس الصفحة، فعّل نية محتوى الرسائل ضمن نوايا البوابة المميزة. بدونها، يتلقى البوت رسائل بنص فارغ، وستقضي ساعة تتساءل لماذا لا يجيب أبدًا.
  4. في OAuth2، أنشئ رابط دعوة بنطاق bot والصلاحيات لقراءة الرسائل وإرسالها. افتح الرابط وأضف البوت إلى خادم اختبار.

قم بتثبيت المكتبات وأضف متغيرين للبيئة.

pip install discord.py openai

export DISCORD_TOKEN="your-discord-bot-token"
export LLM_API_KEY="your-llm-api-key"

# quick check that the LLM key works, using model uncensored
curl -s https://api.codingllmapi.com/v1/chat/completions \
  -H "Authorization: Bearer $LLM_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"model":"uncensored","messages":[{"role":"user","content":"ping"}],"max_tokens":5}'

الخطوة 2: كود البوت

احفظ هذا باسم bot.py. يجيب عند الإشارة إليه، ويحتفظ باثنتي عشرة رسالة من السجل لكل قناة، ويعرض مؤشر الكتابة أثناء الانتظار، ويقسم الإجابات الطويلة.

import asyncio
import os

import discord
from openai import AsyncOpenAI, RateLimitError, APIError

llm = AsyncOpenAI(
    base_url="https://api.codingllmapi.com/v1",
    api_key=os.environ["LLM_API_KEY"],
)

SYSTEM = (
    "You are a helpful programming assistant in a Discord server. "
    "Answer briefly. Put code in fenced blocks. Say so when you are unsure."
)
MAX_TURNS = 12
history = {}                     # channel id -> list of messages
slots = asyncio.Semaphore(6)     # stay under the 8 concurrent request cap

intents = discord.Intents.default()
intents.message_content = True
bot = discord.Client(intents=intents)


def split_reply(text, limit=1900):
    parts = []
    while len(text) > limit:
        cut = text.rfind("\n", 0, limit)
        if cut < limit // 2:
            cut = limit
        parts.append(text[:cut])
        text = text[cut:].lstrip("\n")
    parts.append(text)
    return parts


@bot.event
async def on_message(message):
    if message.author.bot or bot.user not in message.mentions:
        return

    question = message.clean_content.replace(f"@{bot.user.display_name}", "").strip()
    if not question:
        await message.reply("Ask me a coding question after the mention.")
        return

    log = history.setdefault(message.channel.id, [])
    log.append({"role": "user", "content": question})
    del log[:-MAX_TURNS]

    async with message.channel.typing():
        try:
            async with slots:
                resp = await llm.chat.completions.create(
                    model="uncensored",
                    messages=[{"role": "system", "content": SYSTEM}] + log,
                    temperature=0.4,
                    max_tokens=900,
                )
        except RateLimitError:
            await message.reply("The model is busy. Try again in a few seconds.")
            log.pop()
            return
        except APIError:
            await message.reply("The model call failed. Try again shortly.")
            log.pop()
            return

    answer = resp.choices[0].message.content or "(empty reply)"
    log.append({"role": "assistant", "content": answer})
    for part in split_reply(answer):
        await message.reply(part)


bot.run(os.environ["DISCORD_TOKEN"])

شغله باستخدام python bot.py، ثم أشر إلى البوت في خادم الاختبار الخاص بك. يجب أن تصل الإجابة الأولى خلال بضع ثوانٍ.

سبب كتابة الكود بهذه الطريقة

يتم تقصير السجل عمدًا

النموذج لا يملك ذاكرة، لذا ترسل كل مكالمة المحادثات الأخيرة. بدون del log[:-MAX_TURNS] ستنمو القائمة إلى ما لا نهاية. في النهاية ستصل إلى نافذة السياق التي تحتوي على 100,000 رمز، وسترتفع تكاليفك مع كل رسالة. اثنا عشر إدخالًا كافية لمحادثة حول خطأ واحد. لخادم يحتوي على سلاسل طويلة، قم بزيادتها أو تلخيص المحادثات الأقدم.

حد Discord البالغ 2,000 حرف

يرفض Discord الرسائل الأطول من 2,000 حرف، وتصل إجابات الكود إلى هذا الحد بسرعة. يقوم المساعد split_reply بالقطع عند سطر جديد قريب من الحد. لا يعرف عن علامات الكود (code fences)، لذا يمكن قطع الكتلة في المنتصف. إذا كان ذلك يزعجك، أضف منطقًا يغلق العلامة في نهاية جزء ويعيد فتحها في بداية الجزء التالي.

مُعدِّل للتزامن

المفتاح الكامل يسمح بـ 8 طلبات متزامنة، والمفتاح التجريبي يسمح بـ 2. يمكن للخادم المزدحم أن ينتج عدة أسئلة في وقت واحد. يقوم السيمافور (semaphore) بترتيب المكالمات الإضافية داخل عملية التنفيذ الخاصة بك بدلاً من إرسالها واستلام رمز 429 مع رمز الخطأ concurrency. إذا كنت لا تزال تستخدم المفتاح التجريبي، غيّر Semaphore(6) إلى Semaphore(2).

لا تُحسب الأخطاء

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

تحسين جودة الإجابات

بمجرد أن تعمل الحلقة الأساسية، فإن بعض التغييرات الصغيرة تحسّنها كثيرًا.

  • بث الإجابة. مرر stream=True وعدّل رسالة الروبوت كل ثانية تقريبًا بالنص المتاح حتى الآن. لا تعدّل على كل رمز (token)، لأن Discord يفرض حدود معدل خاصة به على تعديلات الرسائل. يحتفظ الطلب المتدفق بمكان التزامن الخاص به حتى ينتهي، لذا أبقِ السيمافور (semaphore) حول البث كاملًا.
  • استخدم الأوامر المائلة (slash commands). يتجنب الأمر مثل /ask تحليل الإشارة (Mention) ولا يحتاج إلى نية محتوى الرسالة على الإطلاق، مما يسهل أيضاً التحقق من صحة البوت لاحقاً إذا نما ليتجاوز عدداً صغيراً من الخوادم.
  • سجل لكل مستخدم. رتب السجل حسب القناة والمستخدم إذا طلب عدة أشخاص أسئلة غير ذات صلة في قناة واحدة.
  • ضبط موجّه النظام. حدد لغات مجتمعك، اطلب إجابات قصيرة، وأخبر النموذج بطرح سؤال توضيحي عندما تكون المشكلة غامضة. تغييرات الموجّه مجانية وغالباً ما تتفوق على تغييرات النموذج.
  • خفض درجة الحرارة. بالنسبة للكود، تعطي القيم بين 0.2 و 0.4 نتائج أكثر استقراراً من القيمة الافتراضية.

استكشاف الأخطاء وإصلاحها

عندما يبقى البوت الجديد صامتاً، فإن السبب يكون في الغالب أحد البنود أدناه. تحقق منها بهذا الترتيب.

  1. البوت متصل لكنه لا يرد أبداً. نية محتوى الرسالة (Message Content) غير مفعّلة في بوابة المطورين، أو أن intents.message_content = True مفقود من الكود. يجب تعيين كلاهما.
  2. يرد في بعض القنوات فقط. يفتقر البوت إلى إذن القراءة أو إرسال الرسائل في القنوات الأخرى. تحقق من تجاوزات أذونات القناة، وليس فقط دور الخادم على مستوى الخادم.
  3. كل رد هو رسالة "مشغول". على الأرجح أنت تضرب حدّ المعدل 429. اطبع جسم الاستثناء مرة واحدة. إذا كان الرمز rate_limited، فقد تجاوزت الحد الأقصى لكل دقيقة وهو 300 طلب على مفتاح كامل أو 60 على مفتاح تجريبي. إذا كان الرمز concurrency، قلل حجم السيمافور (semaphore).
  4. يظهر خطأ مصادقة عند أول استدعاء. مفتاح LLM مفقود من بيئة عملية تشغيل البوت. المفتاح الذي تم تصديره في علامة تبويب طرفية واحدة غير مرئي لخدمة تبدأ في مكان آخر.
  5. الإجابة مقطوعة. يوقف max_tokens=900 الموجود في العينة الردود الطويلة. ارفعها، أو أزلها، لأن max_tokens اختياري وقد يستخدم الإكمال باقي نافذة السياق.
  6. الروبوت يجيب على رسائله الخاصة. يحدث ذلك في الحلقة إذا حذفت فحص message.author.bot. احتفظ به.

لأغراض التصحيح، قم بتسجيل قيمة X-Request-Id لكل استدعاء. مع SDK الخاص بـ OpenAI يمكنك قراءته من الاستجابة الخام عن طريق استدعاء llm.chat.completions.with_raw_response.create(...) ثم قراءة .headers. إذا تواصلت مع الدعم بشأن فشل معين، فإن هذا المعرّف يسمح لهم بإيجاد الطلب الدقيق.

تكلفة التشغيل

التسعير هو $0.15 لكل مليون رمز مدخل و $0.50 لكل مليون رمز مخرج، بدون اشتراك. خذ مثالاً شائعاً: موجّه النظام بالإضافة إلى اثني عشر إدخالاً في السجل يشكل حوالي 2,500 رمز مدخل، والإجابة حوالي 400 رمز.

الإدخال: 2,500 × $0.15 / 1,000,000 = $0.000375. المخرجات: 400 × $0.50 / 1,000,000 = $0.0002. تكلفة الإجابة الواحدة حوالي $0.000575. مجتمع يطرح 500 سؤال في اليوم سينفق 500 × $0.000575 = $0.2875، أي أقل قليلاً من ثلاثين سنتًا. ستختلف أرقامك مع نسخ الكود الأطول، لذا سجل resp.usage لمدة أسبوع واقرأ المتوسطات الحقيقية. يعرض صفحة التسعير خيارات شحن الرصيد: أي مبلغ صحيح من $20 إلى $500، مع زيادة بنسبة 5% من $50، و10% من $100، و15% من $200، بالدفع بالبطاقة أو العملات المشفرة.

للتحكم في سوء الاستخدام، أضف فترة انتظار لكل مستخدم وحداً لطول السؤال. هذان الفحصان يحميان رصيدك بشكل أفضل من أي شيء آخر يمكنك إضافته.

ملاحظات الاستضافة والسلامة

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

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

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

أسئلة وأجوبة

هل أحتاج إلى نية محتوى الرسالة؟

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

هل يمكن للبوت بث الردود؟

نعم. اضبط stream=True وعدّل رسالة Discord على فترات هادئة، مثل مرة واحدة في الثانية، لتبقى ضمن حدود Discord الخاصة.

ماذا يحدث إذا طلب الكثير من الناس في وقت واحد؟

بعد تجاوز 8 طلبات متزامنة على مفتاح كامل، تُعيد الـ API رمز HTTP 429 مع الرمز concurrency. يقوم السيمافور (semaphore) في الكود الخاص بك بترتيب الأسئلة الإضافية في طابور.

هل سيتذكر البوت المحادثات بعد إعادة التشغيل؟

ليس مع القاموس المستخدم هنا. احفظ السجل في SQLite أو Redis إذا كنت تحتاج إلى أن يبقى بعد إعادة التشغيل.

هل مفتاح API آمن في بوت Discord؟

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

مفتاحك على بُعد استمارة واحدة

أنشئ حساباً، وانسخ المفتاح، ثم غيّر URL الأساسي. هذه هي الإعدادات الكاملة.

احصل على مفتاح API