Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 39

"اختيار الأداة: التنفيذ بناءً على قرار النموذج"

Recommended resources

أهلاً بك في هذا الدرس الجديد!

في الدرس السابق، قمنا ببناء جسر مهم بين عالم البرمجة وعالم الذكاء الاصطناعي. تعلمنا كيف نصف "أداة" برمجية (دالة بايثون) باستخدام تنسيق JSON، بحيث يتمكن النموذج اللغوي الكبير (LLM) من فهم وظيفتها ومتطلباتها. لقد جهزنا "دليل المستخدم" لأداتنا count_words.

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


1. دورة التفاعل: محادثة من خطوتين مع النموذج

قبل كتابة الكود، من الضروري أن نفهم أن عملية "استدعاء الدوال" (Function Calling) ليست مجرد طلب واحد واستجابة واحدة. إنها محادثة قصيرة من خطوتين بين تطبيقنا والنموذج اللغوي:

  1. الخطوة الأولى (قرار الاستدعاء):

    • نحن نرسل: طلب المستخدم (مثلاً، "كم عدد الكلمات في هذه الجملة؟") + قائمة أوصاف الأدوات المتاحة (وصف أداة count_words التي أنشأناها).
    • النموذج يستجيب: لا يعطي إجابة نهائية، بل يرسل "قراراً" باستخدام أداة معينة، محدداً اسمها والمدخلات التي يجب تمريرها لها.
  2. الخطوة الثانية (الحصول على الإجابة النهائية):

    • نحن نرسل: نتيجة تنفيذ الأداة التي طلبها النموذج في الخطوة السابقة (مثلاً، الرقم 6).
    • النموذج يستجيب: يستخدم النتيجة التي أرسلناها لصياغة إجابة نهائية باللغة الطبيعية (مثلاً، "يوجد 6 كلمات في الجملة.").

الرسم التوضيحي التالي يصف هذه الدورة بدقة. ركز على كيفية تبادل الرسائل بين "التطبيق" (الكود الذي سنكتبه) و"نموذج Gemini" (النموذج اللغوي).

تدفق استدعاء الدوال بين التطبيق ونموذج Gemini
هذا الرسم البياني يوضح دورة استدعاء الدوال: 1) نرسل الموجه (Prompt) مع وصف الدالة. 2) النموذج يقرر استدعاء الدالة ويرسل اسمها والوسيطات (arguments). 3) تطبيقنا ينفذ الدالة. 4) نرسل نتيجة التنفيذ مرة أخرى للنموذج. 5) النموذج يستخدم النتيجة ليقدم إجابة نهائية.

مهمتنا اليوم هي برمجة الخطوات 2، 3، و 4 في هذا الرسم.



2. الخطوة العملية الأولى: طلب قرار النموذج

لنبدأ بكتابة كود بايثون الذي ينفذ الخطوة الأولى من المحادثة. سنستخدم مكتبة openai للتواصل مع النموذج. يفترض هذا الكود أنك قمت بالفعل بإعداد مفتاح API الخاص بك كمتغير بيئة (Environment Variable) كما تعلمنا في الوحدة الثانية.

أولاً، لنتذكر دالتنا ووصفها من الدرس السابق:

import openai
import json

# لنفترض أن مفتاح OpenAI API موجود كمتغير بيئة
# client = openai.OpenAI()

# 1. تعريف الأداة (الدالة الفعلية في بايثون)
def count_words(text):
    """تقوم هذه الدالة بحساب عدد الكلمات في نص معين."""
    words = text.split()
    return len(words)

# 2. وصف الأداة بصيغة JSON
tools = [
    {
        "type": "function",
        "function": {
            "name": "count_words",
            "description": "تقوم بحساب عدد الكلمات في نص معين.",
            "parameters": {
                "type": "object",
                "properties": {
                    "text": {
                        "type": "string",
                        "description": "النص الذي سيتم حساب عدد الكلمات فيه.",
                    }
                },
                "required": ["text"],
            },
        }
    }
]

الآن، سنقوم بإرسال طلب المستخدم مع وصف الأداة إلى النموذج. لاحظ المعاملين الجديدين في استدعاء الـ API: tools و tool_choice.

# 3. إرسال طلب المستخدم مع الأدوات المتاحة
user_prompt = "كم عدد الكلمات في جملة 'أهلاً بك في عالم الذكاء الاصطناعي'؟"

messages = [{"role": "user", "content": user_prompt}]

# هذا هو الاستدعاء الرئيسي
# response = client.chat.completions.create(
#     model="gpt-4-turbo", # أو أي موديل يدعم استدعاء الأدوات
#     messages=messages,
#     tools=tools,
#     tool_choice="auto",  # 'auto' تعني أن النموذج حر في اختيار أداة أو عدم اختيارها
# )

# response_message = response.choices[0].message
# print(response_message)

عند تشغيل هذا الكود (بعد إزالة التعليقات ووضع الكود في ملف واحد)، لن تحصل على إجابة مباشرة. بدلاً من ذلك، ستحصل على استجابة خاصة من النموذج تبدو كالتالي:

ChatCompletionMessage(
  content=None,
  role='assistant',
  function_call=None,
  tool_calls=[
    ToolCall(
      id='call_abc123',
      function=Function(
        arguments='{"text":"أهلاً بك في عالم الذكاء الاصطناعي"}',
        name='count_words'
      ),
      type='function'
    )
  ]
)

هذه هي "الخطوة الأولى" من محادثتنا. النموذج يقول لنا: "لقد قررت استخدام الأداة count_words مع النص المحدد". الآن، حان دورنا لتنفيذ هذا القرار.

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

OpenAI Function Calling - Full Beginner Tutorial

يشرح هذا المقطع من قناة Dave Ebbelaar كيف يمكن تزويد النموذج بوصف لعدة أدوات، وكيف تسمح له قيمة 'auto' باختيار الأنسب منها بناءً على طلب المستخدم.

شاهد من الدقيقة 17:00 إلى 19:07. ركز على كيفية بناء قائمة تحتوي على أوصاف لثلاث دوال مختلفة، وكيف أن تحديد function_call إلى auto يمنح النموذج حرية الاختيار.



3. الخطوة العملية الثانية: تحليل الاستجابة وتنفيذ الأداة

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

سنقوم بالخطوات التالية في الكود:

  1. التحقق مما إذا كان النموذج قد قرر استخدام أداة (tool_calls).
  2. استخلاص اسم الأداة (name) والوسيطات (arguments).
  3. الوسيطات تأتي كنص بصيغة JSON، لذا يجب تحويلها إلى قاموس (dictionary) بايثون باستخدام json.loads().
  4. استخدام جملة if للتحقق من اسم الأداة واستدعاء الدالة الفعلية المطابقة لها في بايثون.
  5. تمرير الوسيطات إلى الدالة باستخدام ** لتمرير القاموس كوسيطات.

لنكمل الكود السابق:

# response_message هو المتغير الذي يحتوي على استجابة النموذج من الخطوة السابقة

tool_calls = response_message.tool_calls

if tool_calls:
    # 4. تحليل قرار النموذج
    available_functions = {
        "count_words": count_words,
    }
    
    # قد يقرر النموذج استدعاء عدة أدوات، لذا سنمر عليها في حلقة
    for tool_call in tool_calls:
        function_name = tool_call.function.name
        function_to_call = available_functions[function_name]
        
        # الوسيطات تأتي كنص، لذا نحولها إلى قاموس
        function_args = json.loads(tool_call.function.arguments)
        
        # 5. تنفيذ الدالة الفعلية وتخزين النتيجة
        print(f"النموذج قرر استدعاء: {function_name}")
        print(f"مع الوسيطات: {function_args}")
        
        function_response = function_to_call(
            text=function_args.get("text")
        )
        
        print(f"نتيجة التنفيذ: {function_response}")
        # في الخطوة التالية، سنرسل هذه النتيجة مرة أخرى إلى النموذج

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

OpenAI Function Calling - Full Beginner Tutorial

يوضح هذا الفيديو من قناة Dave Ebbelaar بالتفصيل كيفية تحليل استجابة النموذج في بايثون، تحويل الوسيطات من نص إلى قاموس، ومن ثم استدعاء الدالة الحقيقية.

شاهد أولاً من الدقيقة 09:14 إلى 10:40 لترى شكل استجابة النموذج التي تحتوي على استدعاء الدالة. ثم شاهد من الدقيقة 12:05 إلى 15:03 لترى الكود الذي يحلل هذه الاستجابة، ويستخدم json.loads، ويستدعي الدالة في النهاية. ركز على كيفية الانتقال من نص غير منظم (طلب المستخدم) إلى قاموس بايثون منظم.

How does function calling with tools really work?

هذا الفيديو من قناة Matt Williams يعزز نفس المفهوم، موضحاً أن هذه الآلية تعمل مع نماذج مختلفة. يركز على منطق 'التبديل' (switch statement)، والذي سنطبقه باستخدام if/elif في بايثون لاختيار الدالة الصحيحة.

شاهد من الدقيقة 06:21 إلى 07:53. لاحظ كيف يستخدم دالة execute_function مع جملة switch (ما يعادل if/elif/else في بايثون) لتوجيه الطلب إلى الدالة الصحيحة بناءً على الاسم الذي يقرره النموذج.

الصورة التالية تلخص ما يقوم به الكود الذي كتبناه. لاحظ كيف "يفكر" الوكيل، ويقرر "الإجراء" (استدعاء دالة analyze_sentiment)، ثم يحصل على "استجابة الإجراء" (النتيجة 0.6). هذا هو بالضبط ما فعلناه مع دالة count_words.

AI Agent Sentiment Analysis Workflow in VS Code
هذه الصورة توضح دورة عمل الوكيل: التفكير (Thought)، ثم تحديد الإجراء (Action) الذي يتضمن استدعاء دالة، ثم الحصول على استجابة الإجراء (Action_Response) لاستخدامها في الخطوة التالية.
اختبر فهمك!

لنفترض أنك أضفت دالة get_character_count التي أنشأتها في تمرين الدرس السابق. كيف ستعدل الكود الخاص بتنفيذ الدالة (الجزء الذي يحتوي على جملة if أو قاموس available_functions) ليدعم هذه الأداة الجديدة؟

اظهر الإجابة

الطريقة الأولى (باستخدام القاموس available_functions):
ستحتاج أولاً إلى تعريف الدالة get_character_count في كودك. ثم، ستوسع القاموس ليشملها:

def get_character_count(text):
    """تحسب عدد الحروف في النص."""
    return len(text)

available_functions = {
    "count_words": count_words,
    "get_character_count": get_character_count, # الإضافة هنا
}

بهذه الطريقة، عندما يقرر النموذج استدعاء get_character_count، سيجدها الكود تلقائياً في القاموس وينفذها. هذه الطريقة أكثر تنظيماً وقابلية للتوسع.

الطريقة الثانية (باستخدام if/elif):

function_name = tool_call.function.name
function_args = json.loads(tool_call.function.arguments)

if function_name == "count_words":
    function_response = count_words(text=function_args.get("text"))
elif function_name == "get_character_count": # الإضافة هنا
    function_response = get_character_count(text=function_args.get("text"))


خلاصة الدرس

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

أهم النقاط:

  • استدعاء الأدوات هو عملية من خطوتين: طلب القرار أولاً، ثم إرسال نتيجة التنفيذ للحصول على الإجابة النهائية.
  • نستخدم المعامل tools في استدعاء الـ API لتزويد النموذج بقائمة الأدوات المتاحة، و tool_choice="auto" لمنحه حرية الاختيار.
  • استجابة النموذج التي تطلب استدعاء أداة تحتوي على كائن tool_calls يتضمن اسم الدالة ووسيطاتها.
  • نستخدم json.loads() لتحويل نص الوسيطات إلى قاموس بايثون.
  • نستخدم قاموساً أو جمل if/elif لربط اسم الدالة النصي بالدالة الفعلية في كود بايثون وتنفيذها.

ماذا بعد؟

لقد أصبح وكيلنا الآن قادراً على "الفعل" (Act) باستخدام الأدوات. لكن كيف يمكننا أن نجعل قراراته أكثر ذكاءً وموثوقية؟

في الدرس القادم، سنتعمق في كيفية تحسين عملية "التفكير" (Thinking) لدى الوكيل. سندرس استراتيجية 'سلسلة الأفكار' (Chain-of-Thought)، وهي تقنية تجعل النموذج "يفكر بصوت عالٍ" ويضع خطة قبل أن يقرر أي أداة سيستخدمها. هذا سيجعل وكيلنا أكثر منهجية وقدرة على حل المشاكل المعقدة.