الخطوة 2 في الوكلاء: الأدوات واستدعاء الدوال

ابنِ أول أداة لك — آلة حاسبة يستطيع الذكاء الاصطناعي استخدامها.

ما ستتعلمه

  • تعريف مخططات الأدوات لـ OpenAI
  • التعامل مع tool_calls في استجابة API
  • تنفيذ الدوال بناءً على قرارات الذكاء الاصطناعي
  • حلقة استدعاء الأدوات

الخطوة 1: تعريف أداة الآلة الحاسبة

أنشئ agentic/agent.py:

import json
from openai import OpenAI
import os

client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))


def calculate(expression: str) -> str:
    """Evaluate a math expression safely."""
    try:
        # تقييم آمن — اسمح بالعمليات الرياضية الأساسية فقط
        allowed = set("0123456789+-*/.() ")
        if not all(c in allowed for c in expression):
            return "Error: Invalid characters in expression"
        result = eval(expression)
        return str(result)
    except Exception as e:
        return f"Error: {str(e)}"


tools = [
    {
        "type": "function",
        "function": {
            "name": "calculate",
            "description": "Evaluate a mathematical expression",
            "parameters": {
                "type": "object",
                "properties": {
                    "expression": {
                        "type": "string",
                        "description": "Math expression like '2 + 2' or '3 * (4 + 5)'"
                    }
                },
                "required": ["expression"]
            }
        }
    }
]

الخطوة 2: حلقة الوكيل

def run_agent(user_message: str) -> str:
    messages = [
        {"role": "system", "content": "You are a helpful assistant with access to a calculator."},
        {"role": "user", "content": user_message},
    ]

    response = client.chat.completions.create(
        model="gpt-4o-mini",
        messages=messages,
        tools=tools,
        tool_choice="auto",
    )

    message = response.choices[0].message

    # إذا أراد الذكاء الاصطناعي استدعاء أداة
    if message.tool_calls:
        messages.append(message)

        for tool_call in message.tool_calls:
            if tool_call.function.name == "calculate":
                args = json.loads(tool_call.function.arguments)
                result = calculate(args["expression"])
                messages.append({
                    "role": "tool",
                    "tool_call_id": tool_call.id,
                    "content": result,
                })

        # احصل على الاستجابة النهائية بعد استدعاء الأداة
        final = client.chat.completions.create(
            model="gpt-4o-mini",
            messages=messages,
            tools=tools,
        )
        return final.choices[0].message.content

    # استجابة نصية عادية (لا حاجة إلى أداة)
    return message.content

الخطوة 3: جرّبها

if __name__ == "__main__":
    test_queries = [
        "What is 234 * 567?",
        "Calculate (15 + 27) * 3",
        "Hello, how are you?",
        "What's the square root of 144? (hint: use 144 ** 0.5)",
    ]

    for query in test_queries:
        print(f"\nUser: {query}")
        result = run_agent(query)
        print(f"AI: {result}")

شغّلها:

cd aiproject
source backend/venv/bin/activate
python advanced/agentic/agent.py

الخطوة 4: افحص أداتك واختبرها

افحص الوكيل

cd aiproject
ruff check advanced/agentic/

يكشف هذا الفحص عن مشكلات مثل: الاستيرادات غير المستخدمة، والمتغيرات غير المعرّفة، ومشكلات التسمية.

اختبار دالة الآلة الحاسبة

أنشئ advanced/agentic/tests/test_tools.py:

import sys
import os
sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))

from agent import calculate


def test_calculate_adds_numbers():
    assert calculate("2 + 2") == "4"


def test_calculate_multiplies():
    assert calculate("3 * 4") == "12"


def test_calculate_rejects_invalid_chars():
    result = calculate("import os")
    assert result.startswith("Error")

شغّلها:

cd aiproject
python -m pytest advanced/agentic/tests/ -v

المخرجات المتوقعة:

test_tools.py::test_calculate_adds_numbers PASSED
test_tools.py::test_calculate_multiplies PASSED
test_tools.py::test_calculate_rejects_invalid_chars PASSED

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

الخطوة 5: فهم استدعاء الأداة

عندما تسأل «ما ناتج 234 * 567؟»:

  1. يستقبل الذكاء الاصطناعي الرسالة
  2. يقرر: «لا أستطيع حساب هذا بدقة، لكن لديّ أداة calculate»
  3. يعيد استجابة tool_calls:
{
  "tool_calls": [{
    "id": "call_abc123",
    "function": {
      "name": "calculate",
      "arguments": "{\"expression\": \"234 * 567\"}"
    }
  }]
}
  1. يستدعي كودك calculate("234 * 567") فيعيد "132678"
  2. تعيد النتيجة إلى الذكاء الاصطناعي باستخدام role: "tool"
  3. يرد الذكاء الاصطناعي: "234 × 567 = 132,678"

مسار استدعاء الأداة

المستخدم: "What's 234 * 567?"

1. POST /v1/chat/completions (messages=[user_question], tools=[calculate])
   ← استجابة: tool_calls → calculate(234 * 567)

2. تنفيذ calculate("234 * 567") → "132678"

3. POST /v1/chat/completions (messages=[user_question, ai_tool_call, tool_result])
   ← استجابة: "234 × 567 = 132,678"

ملاحظة حول السلامة

تعتمد دالة calculate على eval()، وهو أمر خطير إذا أرسل المستخدم كودًا ضارًا. لذلك أضفنا قائمة أحرف مسموح بها تحجب كل شيء عدا الأرقام والعمليات الرياضية. أما في الإنتاج، فاستخدم محلّلًا رياضيًا مناسبًا مثل numexpr.