لا تُبني AI Agent بمجرد استدعاء API. اكتشف كيف تصمم نظاماً قادراً على اتخاذ القرارات، إدارة الذاكرة، والتعامل مع الأخطاء دون تدخل بشري — بخطوات عملية وكود حقيقي.
في آخر مشروع لي مع فريق في شركة ناشئة في مجال الرعاية الصحية، واجهنا مشكلة حقيقية: كيف نجعل نظاماً قادراً على تحليل تقارير الأشعة دون تدخل بشري، واتخاذ قرارات مثل إرسال تنبيهات للأطباء أو طلب فحوصات إضافية؟ الحل لم يكن مجرد استدعاء نموذج لغة كبير (LLM) بل بناء ما يُسمى AI Agent — نظام مستقل يتخذ القرارات بنفسه، يتعلم من الأخطاء، ويتعامل مع تدفقات البيانات المعقدة. المشكلة؟ معظم المقالات تتحدث عن AI Agents كما لو كانت سحراً، بينما الحقيقة هي أنها مجرد برمجة ذكية تجمع بين عدة تقنيات. في هذا المقال، سأريك كيف تبني واحداً من الصفر، مع كل التفاصيل التي لا تجدها في الدروس السطحية.
الفرق بين AI Agent ونموذج لغة تقليدي هو كالفرق بين سائق سيارة ذاتي القيادة وسائق يستخدم خرائط جوجل. النموذج اللغوي يستطيع الإجابة على أسئلة محددة، لكن AI Agent يملك خطة، ذاكرة، وقدرة على التفاعل مع العالم الخارجي. مثلاً، عندما طلبنا من النموذج تحليل صورة أشعة، كانت الإجابة: "هذه صورة أشعة صدر تظهر علامات محتملة لالتهاب رئوي". لكن عندما طلبنا نفس الطلب من AI Agent، كانت النتيجة مختلفة تماماً: "تم تحليل الصورة رقم #12345، تم اكتشاف علامات التهاب رئوي باحتمالية 87%. تم إرسال تنبيه للدكتور أحمد محمد، وتم جدولة فحص متابعة للمريض بعد 48 ساعة. تم حفظ النتيجة في قاعدة البيانات مع رمز التحذير #PNEUMO-2024". الفرق هنا ليس في الذكاء فقط، بل في الاستقلالية.
الخطأ الشائع هو الاعتقاد أن أي سكربت يستدعي LLM هو AI Agent. الحقيقة أن الاستقلالية تتطلب أربع مكونات أساسية: الإدراك (Perception)، الذاكرة (Memory)، اتخاذ القرار (Decision Making)، والتنفيذ (Action). بدون هذه المكونات، أنت تبني مجرد واجهة ذكية لنموذج لغة. مثلاً، عندما يتفاعل المستخدم مع الواجهة، يجب على النظام ليس فقط فهم الطلب بل أيضاً تذكر السياق السابق، اتخاذ قرار بشأن الخطوة التالية، وتنفيذها دون انتظار تعليمات إضافية. هذا هو الفرق بين "أعطني تقرير الطقس" و"إذا كان الطقس ممطراً غداً، أرسل لي تنبيهاً في الساعة 6 صباحاً مع اقتراح ملابس مناسبة".
في مشروعنا الصحي، كان الإدراك يعتمد على تحليل الصور الطبية باستخدام نماذج متخصصة مثل ResNet50، بينما كانت الذاكرة تعتمد على قاعدة بيانات متكاملة تحتوي على تاريخ المرضى. اتخاذ القرار كان يعتمد على سلسلة من القواعد المنطقية المدعومة بنموذج لغة، والتنفيذ كان يشمل إرسال رسائل نصية، تحديث قواعد البيانات، وجدولة مهام مستقبلية. المشكلة التي واجهناها؟ عندما حاولنا بناء النظام باستخدام مكونات منفصلة، وجدنا أن الـ Event Loop في Node.js كان يتجمد عند التعامل مع المهام المتزامنة، خاصة عند إرسال عشرات الطلبات في نفس الوقت. الحل كان استخدام نظام قائم على الـ Actor Model مثل Erlang أو مكتبة مثل Akka في Java، لكننا اخترنا في النهاية استخدام Python مع مكتبة asyncio لتجنب التعقيد الزائد.
# مثال مبسط لكيفية بناء حلقة الإدراك والقرار والتنفيذ في AI Agent
import asyncio
from typing import Dict, List, Optional
class AIAgent:
def __init__(self):
self.memory: Dict[str, str] = {} # ذاكرة قصيرة المدى
self.long_term_memory: List[Dict] = [] # ذاكرة طويلة المدى
self.tools = {
"send_sms": self._send_sms,
"query_database": self._query_database,
"schedule_task": self._schedule_task
}
async def perceive(self, input_data: Dict) -> Dict:
"""تحليل المدخلات وتحديث الذاكرة"""
# هنا يمكن استخدام نموذج لغة أو نموذج رؤية لتحليل البيانات
analysis = {"status": "analyzed", "details": input_data}
self.memory.update(analysis)
return analysis
async def decide(self) -> Optional[str]:
"""اتخاذ قرار بناءً على الذاكرة والمعلومات المتاحة"""
if "urgent" in self.memory.get("details", {}).get("status", ""):
return "send_sms"
elif "follow_up" in self.memory.get("details", {}):
return "schedule_task"
return None
async def act(self, action: str) -> bool:
"""تنفيذ الإجراء باستخدام الأدوات المتاحة"""
if action in self.tools:
return await self.tools[action]()
return False
async def _send_sms(self) -> bool:
print("إرسال رسالة نصية: تنبيه طبي عاجل!")
return True
async def _query_database(self) -> bool:
print("استعلام قاعدة البيانات...")
return True
async def _schedule_task(self) -> bool:
print("جدولة مهمة مستقبلية...")
return True
async def run(self, input_data: Dict):
"""الحلقة الرئيسية للإدراك والقرار والتنفيذ"""
await self.perceive(input_data)
action = await self.decide()
if action:
await self.act(action)
# تحديث الذاكرة بعد التنفيذ
self.long_term_memory.append({
"input": input_data,
"action": action,
"timestamp": asyncio.get_event_loop().time()
})
# مثال على الاستخدام
async def main():
agent = AIAgent()
await agent.run({
"patient_id": "12345",
"status": "urgent",
"details": {"condition": "pneumonia", "probability": 0.87}
})
asyncio.run(main())الذاكرة هي المكون الأكثر تجاهلاً في بناء AI Agents، ومع ذلك فهي السبب الرئيسي لفشل معظم الأنظمة في الإنتاج. المشكلة ليست في تخزين البيانات، بل في كيفية استرجاعها واستخدامها لاتخاذ القرارات. مثلاً، في مشروعنا الصحي، كان لدينا قاعدة بيانات تحتوي على ملايين السجلات، لكن عندما حاولنا استخدام الذاكرة القصيرة المدى (Short-Term Memory) فقط، وجدنا أن النظام ينسى السياق بعد بضع تفاعلات. الحل؟ استخدام نظام ذاكرة متعدد الطبقات يشبه ذاكرة الإنسان: ذاكرة قصيرة المدى للبيانات الفورية، وذاكرة طويلة المدى للبيانات التاريخية، وذاكرة دلالية (Semantic Memory) للمفاهيم العامة.
في التطبيق العملي، استخدمنا قاعدة بيانات Vector Database مثل Pinecone لتخزين الذكريات الطويلة المدى كمجموعات من المتجهات (Embeddings). هذا يسمح للنظام باسترجاع المعلومات بناءً على التشابه الدلالي وليس فقط المفاتيح التقليدية. مثلاً، عندما يسأل المستخدم عن "التهاب رئوي"، يمكن للنظام استرجاع جميع الحالات المشابهة حتى لو لم تستخدم نفس الكلمات بالضبط. المشكلة التي واجهناها؟ تكاليف التخزين والاستعلام كانت مرتفعة جداً عند التعامل مع ملايين المتجهات. الحل كان استخدام تقنيات مثل Product Quantization لتقليل حجم المتجهات مع الحفاظ على الدقة. أيضاً، وجدنا أن استخدام ذاكرة مؤقتة (Cache) للبيانات المستخدمة بشكل متكرر يقلل من زمن الاستجابة بنسبة 60%.
# مثال على استخدام Vector Database مع ذاكرة متعددة الطبقات
from sentence_transformers import SentenceTransformer
import numpy as np
import pinecone
class MemorySystem:
def __init__(self):
self.short_term_memory = []
self.model = SentenceTransformer('all-MiniLM-L6-v2')
pinecone.init(api_key="YOUR_API_KEY", envir"us-west1-gcp")
self.index = pinecone.Index("ai-agent-memory")
def add_to_short_term(self, data: str):
"""إضافة بيانات إلى الذاكرة القصيرة"""
self.short_term_memory.append(data)
if len(self.short_term_memory) > 10: # حد أقصى للذاكرة القصيرة
self._transfer_to_long_term(self.short_term_memory.pop(0))
def _transfer_to_long_term(self, data: str):
"""نقل البيانات إلى الذاكرة الطويلة"""
embedding = self.model.encode(data)
self.index.upsert([(data, embedding.tolist())])
def retrieve_similar(self, query: str, top_k: int = 3) -> List[str]:
"""استرجاع البيانات المشابهة من الذاكرة الطويلة"""
query_embedding = self.model.encode(query)
results = self.index.query(
vector=query_embedding.tolist(),
top_k=top_k,
include_metadata=True
)
return [match["id"] for match in results["matches"]]
# مثال على الاستخدام
memory = MemorySystem()
memory.add_to_short_term("المريض رقم 12345 لديه أعراض التهاب رئوي")
memory.add_to_short_term("تم إرسال تنبيه للدكتور أحمد")
print(memory.retrieve_similar("التهاب في الرئتين")) # ['المريض رقم 12345 لديه أعراض التهاب رئوي']معظم المطورين يعتقدون أن اتخاذ القرار في AI Agents يعتمد فقط على نماذج اللغة. الحقيقة أن الاعتماد الكلي على LLM في اتخاذ القرارات هو وصفة للفشل. لماذا؟ لأن نماذج اللغة ليست مصممة لاتخاذ قرارات منطقية معقدة، بل لتوليد نص متماسك. مثلاً، عندما طلبنا من نموذج لغة اتخاذ قرار بشأن إرسال تنبيه طبي، كانت الإجابة أحياناً متناقضة: "هذه الحالة ليست عاجلة، لكن يفضل إرسال تنبيه للدكتور". الحل؟ استخدام نظام هجين يجمع بين القواعد الصارمة (Rule-Based) والتعلم الآلي. في مشروعنا، استخدمنا شجرة قرار (Decision Tree) للتعامل مع الحالات الواضحة، بينما تركنا لنموذج اللغة التعامل مع الحالات الغامضة فقط.
المشكلة الأخرى هي التعامل مع عدم اليقين (Uncertainty). مثلاً، عندما تكون احتمالية الإصابة بمرض ما 60%، هل يجب إرسال تنبيه؟ الحل كان استخدام نظرية القرار (Decision Theory) لحساب القيمة المتوقعة لكل قرار. مثلاً، إذا كانت تكلفة التنبيه الخاطئ منخفضة مقارنة بتكلفة عدم إرسال تنبيه لحالة حقيقية، فإن القرار الأمثل هو إرسال التنبيه. أيضاً، استخدمنا تقنيات مثل Monte Carlo Tree Search لاستكشاف سيناريوهات متعددة قبل اتخاذ القرار النهائي. المشكلة التي واجهناها؟ زمن الاستجابة زاد بشكل كبير عند استخدام هذه التقنيات. الحل كان استخدام تقنيات مثل Pruning لتقليل عدد السيناريوهات المدروسة، وتخزين النتائج في ذاكرة مؤقتة لتجنب إعادة الحساب.
# مثال على نظام اتخاذ قرار هجين باستخدام شجرة القرار ونظرية القرار
import numpy as np
from sklearn.tree import DecisionTreeClassifier
class DecisionMaker:
def __init__(self):
# شجرة قرار للتعامل مع الحالات الواضحة
self.decisi DecisionTreeClassifier()
self.decision_tree.fit(
X=np.array([[0.9, 1], [0.1, 0], [0.7, 1], [0.3, 0]]), # [probability, is_urgent]
y=np.array([1, 0, 1, 0]) # 1: send alert, 0: no alert
)
# قيم التكلفة للقرارات
self.cost_matrix = {
"false_positive": 1, # تكلفة التنبيه الخاطئ
"false_negative": 10, # تكلفة عدم إرسال تنبيه لحالة حقيقية
"true_positive": 0,
"true_negative": 0
}
def make_decision(self, probability: float, is_urgent: bool) -> bool:
"""اتخاذ قرار بناءً على الاحتمالية والحالة العاجلة"""
# استخدام شجرة القرار للحالات الواضحة
tree_decision = self.decision_tree.predict([[probability, int(is_urgent)]])[0]
if tree_decision == 1 or tree_decision == 0:
return bool(tree_decision)
# استخدام نظرية القرار للحالات الغامضة
expected_value_alert = (
probability * self.cost_matrix["true_positive"] +
(1 - probability) * self.cost_matrix["false_positive"]
)
expected_value_no_alert = (
probability * self.cost_matrix["false_negative"] +
(1 - probability) * self.cost_matrix["true_negative"]
)
return expected_value_alert < expected_value_no_alert
# مثال على الاستخدام
decision_maker = DecisionMaker()
print(decision_maker.make_decision(0.6, False)) # قد يرجع True أو False بناءً على الحساباتالتنفيذ هو المرحلة التي تفشل فيها معظم AI Agents. لماذا؟ لأن العالم الحقيقي مليء بالمشاكل التي لا تتوقعها: اتصالات إنترنت غير مستقرة، واجهات برمجة تطبيقات (APIs) بطيئة، وأنظمة خارجية تتعطل. في مشروعنا الصحي، واجهنا مشكلة حقيقية عندما توقف نظام إرسال الرسائل النصية عن العمل أثناء الليل، مما تسبب في توقف كامل للنظام. الحل؟ تصميم نظام التنفيذ ليكون مرناً ومقاوماً للأخطاء. استخدمنا نمط التصميم Circuit Breaker لمنع النظام من محاولة الاتصال بخدمة معطلة بشكل متكرر، ونمط Retry مع Backoff الأسي لإعادة المحاولة بعد فترات زمنية متزايدة.
المشكلة الأخرى هي التعامل مع المهام الطويلة الأمد (Long-Running Tasks). مثلاً، تحليل صورة أشعة قد يستغرق عدة دقائق، ولا يمكنك جعل المستخدم ينتظر كل هذا الوقت. الحل كان استخدام نظام قائم على الأحداث (Event-Driven Architecture) مع قوائم انتظار (Queues) مثل RabbitMQ أو Kafka. عندما يتلقى النظام طلب تحليل صورة، يضيف المهمة إلى قائمة انتظار، ويرسل رداً فورياً للمستخدم بأن الطلب قيد المعالجة. عندما تنتهي المهمة، يرسل النظام تنبيهاً للمستخدم بالنتيجة. المشكلة التي واجهناها؟ عندما زاد عدد المهام في قائمة الانتظار، بدأ النظام يعاني من تأخيرات كبيرة. الحل كان استخدام تقنيات مثل Auto-Scaling لزيادة عدد العمال عند الحاجة، وتقسيم المهام الكبيرة إلى مهام أصغر باستخدام نمط MapReduce.
# مثال على تنفيذ مرن باستخدام نمط Circuit Breaker وقوائم الانتظار
import time
import random
from typing import Callable, Any
from functools import wraps
class CircuitBreaker:
def __init__(self, max_failures: int = 3, reset_timeout: int = 60):
self.max_failures = max_failures
self.reset_timeout = reset_timeout
self.failures = 0
self.last_failure_time = 0
self.state = "CLOSED"
def __call__(self, func: Callable) -> Callable:
@wraps(func)
def wrapper(*args, **kwargs) -> Any:
if self.state == "OPEN":
if time.time() - self.last_failure_time > self.reset_timeout:
self.state = "HALF-OPEN"
else:
raise Exception("Circuit is OPEN")
try:
result = func(*args, **kwargs)
if self.state == "HALF-OPEN":
self.state = "CLOSED"
self.failures = 0
return result
except Exception as e:
self.failures += 1
self.last_failure_time = time.time()
if self.failures >= self.max_failures:
self.state = "OPEN"
raise e
return wrapper
# مثال على استخدام Circuit Breaker مع وظيفة إرسال رسالة نصية
@CircuitBreaker(max_failures=2, reset_timeout=30)
def send_sms(phone_number: str, message: str) -> bool:
# محاكاة فشل عشوائي
if random.random() < 0.3: # 30% فرصة للفشل
raise Exception("فشل في إرسال الرسالة")
print(f"تم إرسال الرسالة إلى {phone_number}: {message}")
return True
# مثال على استخدام قائمة انتظار مع Celery (مكتبة لمعالجة المهام غير المتزامنة)
from celery import Celery
app = Celery('tasks', broker='pyamqp://guest@localhost//')
@app.task(bind=True, max_retries=3)
def analyze_medical_image(self, image_path: str):
try:
# محاكاة معالجة الصورة
time.sleep(5)
if random.random() < 0.2: # 20% فرصة للفشل
raise Exception("فشل في تحليل الصورة")
return {"result": "التهاب رئوي", "probability": 0.85}
except Exception as e:
self.retry(exc=e, countdown=60) # إعادة المحاولة بعد 60 ثانية
# مثال على الاستخدام
try:
send_sms("+123456789", "تنبيه طبي عاجل!")
except Exception as e:
print(f"فشل في إرسال الرسالة: {e}")
result = analyze_medical_image.delay("/path/to/image.jpg")
print("تم إرسال المهمة للتحليل، المعرف:", result.id)إذا كنت تفكر في بناء AI Agent مستقل، فهذه هي النصيحة الوحيدة التي تحتاجها: ابدأ صغيراً، لكن صمّم للنمو. معظم المطورين يقعون في فخ محاولة بناء نظام مثالي من البداية، وينسون أن الأنظمة الحقيقية تتطور مع الوقت. ابدأ بنظام بسيط يقوم بمهمة واحدة بشكل جيد، ثم أضف المكونات الأخرى تدريجياً. مثلاً، في مشروعنا الصحي، بدأنا بنظام يرسل تنبيهات فقط، ثم أضفنا الذاكرة، ثم اتخاذ القرار، وأخيراً التنفيذ المتكامل. أيضاً، لا تعتمد فقط على نماذج اللغة في اتخاذ القرارات — استخدم قواعد منطقية عندما يكون الأمر واضحاً، واترك للنموذج التعامل مع الحالات الغامضة فقط. وأخيراً، تذكر أن الاستقلالية لا تعني عدم وجود أخطاء، بل تعني القدرة على التعامل مع الأخطاء دون تدخل بشري. صمّم نظامك ليكون مرناً، وقادراً على التعلم من الأخطاء، ومقاوماً للفشل.
الخطوة التالية؟ ابدأ ببناء نموذج أولي بسيط باستخدام الكود الذي قدمته في هذا المقال. جرب إضافته إلى مشروع حقيقي، وراقب كيف يتفاعل مع البيانات الحقيقية. ستكتشف بسرعة أن بناء AI Agent ليس عن الذكاء الاصطناعي فقط، بل عن هندسة البرمجيات الجيدة.