أهلاً بك مجدداً! في درسنا السابق، وضعنا الأساس لذاكرة وكيلنا طويلة المدى بتعلم كيفية إعداد قاعدة بيانات SQLite والتفاعل معها باستخدام الأوامر الأساسية في بايثون. لقد رأينا كيف ننشئ جدولاً، ونضيف إليه بيانات (INSERT)، ونسترجعها (SELECT).
اليوم، سنرتقي بهذه المعرفة خطوة إلى الأمام لتحقيق هدفنا التالي: تصميم دالة لحفظ المعلومات المهمة (مثل ملخصات المحادثات) في قاعدة البيانات. سننتقل من كتابة كود لمرة واحدة إلى بناء مكون برمجي منظم وقابل لإعادة الاستخدام، وهو ما يمثل ممارسة أساسية في عالم البرمجة وبناء الأنظمة الذكية.
1. لماذا نغلف منطق قاعدة البيانات في دالة؟
في الدرس السابق، كتبنا خطوات الاتصال بقاعدة البيانات، تنفيذ الأوامر، ثم إغلاق الاتصال مباشرة في الملف البرمجي. تخيل أن وكيلنا يحتاج إلى حفظ معلومة جديدة عشرات المرات خلال عمله. هل سنكرر كتابة نفس الكود في كل مرة؟ بالطبع لا.
وهنا يأتي دور الدوال (Functions). عندما نضع كود حفظ البيانات داخل دالة، نحصل على ثلاث فوائد رئيسية:
- إعادة الاستخدام (Reusability): بدلاً من تكرار الكود، نستدعي الدالة ببساطة كلما أردنا حفظ معلومة جديدة.
- التجريد (Abstraction): نخفي التفاصيل المعقدة. الكود الرئيسي للوكيل لن يهتم بكيفية عمل
sqlite3، بل سيقوم فقط باستدعاء دالة مثلsave_memory("...some info..."). - سهولة الصيانة (Maintainability): إذا قررنا في المستقبل تغيير طريقة التخزين (مثلاً، إضافة حقل جديد في الجدول)، سنحتاج فقط لتعديل الدالة في مكان واحد بدلاً من البحث عن كل الأماكن التي تم فيها تكرار الكود.
2. تصميم دالة save_memory
هدفنا هو إنشاء دالة بايثون تأخذ معلومة نصية وتقوم بحفظها بشكل دائم في جدول memories الذي أنشأناه في الدرس السابق. لنتذكر هيكل الجدول الذي اتفقنا عليه:
id: رقم صحيح فريد (يتم إنشاؤه تلقائياً).content: المحتوى النصي للذاكرة.type: نوع الذاكرة (مثل "تفضيل المستخدم" أو "ملخص محادثة").timestamp: وقت الحفظ (يتم تسجيله تلقائياً).
إذاً، الدالة التي سنصممها ستحتاج إلى مُدخلين: محتوى الذاكرة ونوعها.
def save_memory(content: str, memory_type: str):
# ... هنا سنكتب منطق الاتصال والحفظ ...
pass
الآن، لنملأ هذه الدالة بالخطوات التي تعلمناها، مع إضافة معالجة للأخطاء لضمان قوة الكود.
import sqlite3
def save_memory(content: str, memory_type: str):
"""
تقوم هذه الدالة بالاتصال بقاعدة البيانات وحفظ ذاكرة جديدة
في جدول 'memories'.
Args:
content (str): المحتوى النصي للذاكرة.
memory_type (str): نوع الذاكرة (مثل 'تفضيل' أو 'ملخص').
"""
conn = None # نهيئ المتغير ليكون متاحاً في كل أجزاء الدالة
try:
# 1. الاتصال بقاعدة البيانات
conn = sqlite3.connect('agent_memory.db')
# 2. إنشاء كائن المؤشر (Cursor)
cursor = conn.cursor()
# 3. تجهيز أمر SQL للإضافة (استخدام ؟؟ للحماية)
insert_query = "INSERT INTO memories (content, type) VALUES (?, ?);"
# 4. تنفيذ الأمر مع تمرير البيانات
cursor.execute(insert_query, (content, memory_type))
# 5. تأكيد حفظ التغييرات في قاعدة البيانات
conn.commit()
print(f"✅ تم حفظ الذاكرة بنجاح: '{content}'")
except sqlite3.Error as e:
print(f"❌ حدث خطأ أثناء الاتصال أو الحفظ: {e}")
finally:
# 6. إغلاق الاتصال في كل الأحوال (سواء نجحت العملية أو فشلت)
if conn:
conn.close()
تحليل خطوات الدالة
دعنا نراجع الخطوات داخل الدالة وكيف تتوافق مع ما تعلمناه.
- الخطوات 1-5 هي جوهر عملية الكتابة في قاعدة البيانات. هذه الخطوات مشروحة بالتفصيل في الموارد التي سنستعرضها.
- استخدام
try...except...finally: هذه إضافة مهمة.- كتلة
tryتحتوي على الكود الذي قد يفشل (مثل الاتصال أو الكتابة). - كتلة
exceptتلتقط أي خطأ من نوعsqlite3.Errorوتطبع رسالة واضحة بدلاً من توقف البرنامج. - كتلة
finallyتضمن تنفيذ الكود الموجود بداخلها دائماً، وهنا نستخدمها للتأكد من إغلاق الاتصال بقاعدة البيانات (conn.close()) حتى لو حدث خطأ.
- كتلة
لتعميق فهمك لعملية الكتابة في قاعدة البيانات، أرجو منك قراءة المقال التالي الذي يشرح هذه الخطوات بالتفصيل باللغة العربية.
كيفية استخدام نظام إدارة قواعد البيانات SQLite مع لغة البرمجة ...
يقدم هذا المقال من 'وادي التقنيات' شرحاً واضحاً ومباشراً لكيفية الكتابة إلى قاعدة بيانات SQLite باستخدام بايثون. سيعزز هذا قراءتك للكود الذي كتبناه.
اقرأ القسم بعنوان 'كيفية الكتابة إلى قاعدة البيانات في SQLite'. ركز على فهم دور كل من connect, cursor, execute, و commit.
ولمشاهدة تطبيق عملي لهذه الخطوات، الفيديو التالي مفيد جداً، خاصة في توضيح أهمية استخدام العناصر النائبة (?) ودور connection.commit().
Python SQLite Data Entry Form with Tkinter tutorial for beginners - Python GUI project
يشرح هذا المقطع من قناة Code First with Hala كيفية بناء جملة INSERT في بايثون، وتمرير البيانات إليها بشكل آمن، وأهمية commit() لحفظ البيانات.
شاهد من الدقيقة 8:19 إلى 12:11. لاحظ كيف يتم استخدام علامات الاستفهام (?) كعناصر نائبة للقيم، وكيف يتم تمرير البيانات الفعلية في tuple عند استدعاء cursor.execute().
3. استخدام الدالة في سياق الوكيل
الآن بعد أن أصبح لدينا دالة save_memory, يمكننا استخدامها بسهولة في أي مكان في برنامج الوكيل.
لنفترض أن وكيلنا أنهى محادثة مع المستخدم، وقام بتلخيصها باستخدام نموذج لغوي كبير (LLM). الآن، يريد حفظ هذا الملخص كذاكرة طويلة المدى.
# هذا هو الكود الرئيسي للبرنامج
if __name__ == "__main__":
# لنفترض أن هذا الملخص تم إنشاؤه بواسطة الوكيل
conversation_summary = "المستخدم استفسر عن كيفية عمل الذاكرة طويلة المدى وأبدى اهتماماً كبيراً بها."
# ببساطة نستدعي الدالة لحفظ الملخص
save_memory(content=conversation_summary, memory_type="ملخص محادثة")
# يمكننا حفظ نوع آخر من المعلومات
user_preference = "المستخدم يفضل الأمثلة البرمجية الكاملة."
save_memory(content=user_preference, memory_type="تفضيل المستخدم")
عند تشغيل هذا الكود (بعد التأكد من وجود جدول memories الذي أنشأناه في الدرس السابق)، سترى رسائل النجاح مطبوعة، وستكون هذه المعلومات قد حُفظت بشكل دائم في ملف agent_memory.db.
اختبر فهمك!
في بعض الأحيان، يكون من المفيد معرفة المعرّف (id) الخاص بالمعلومة التي تم حفظها للتو. المقال الذي قرأته (LINK) يذكر خاصية تسمى cursor.lastrowid التي تسمح لنا بالحصول على id آخر صف تم إدراجه.
المطلوب: قم بتعديل دالة save_memory بحيث تقوم بإرجاع (return) قيمة id للذاكرة الجديدة بعد حفظها بنجاح.
اظهر الإجابة
import sqlite3
def save_memory(content: str, memory_type: str) -> int | None:
"""
تقوم هذه الدالة بحفظ ذاكرة جديدة وترجع الـ ID الخاص بها.
ترجع None في حال حدوث خطأ.
"""
conn = None
try:
conn = sqlite3.connect('agent_memory.db')
cursor = conn.cursor()
insert_query = "INSERT INTO memories (content, type) VALUES (?, ?);"
cursor.execute(insert_query, (content, memory_type))
# نحصل على الـ ID قبل عمل commit
last_id = cursor.lastrowid
conn.commit()
print(f"✅ تم حفظ الذاكرة بنجاح (ID: {last_id})")
return last_id # إرجاع الـ ID
except sqlite3.Error as e:
print(f"❌ حدث خطأ أثناء الاتصال أو الحفظ: {e}")
return None # إرجاع None في حالة الفشل
finally:
if conn:
conn.close()
# --- مثال على الاستخدام ---
if __name__ == "__main__":
new_memory_id = save_memory("التحقق من حالة الطلب رقم #12345", "مهمة")
if new_memory_id:
print(f"تم إنشاء الذاكرة الجديدة بالمعرف رقم: {new_memory_id}")
هذا التعديل البسيط يجعل الدالة أكثر قوة، حيث يمكننا الآن تتبع الذكريات المحددة باستخدام معرفاتها الفريدة.
خلاصة الدرس
في هذا الدرس، حققنا نقلة نوعية في كيفية تعاملنا مع قاعدة البيانات.
أهم النقاط التي تعلمناها:
- فوائد الدوال: تعلمنا أهمية تغليف الكود المتكرر في دوال لتحقيق إعادة الاستخدام، التجريد، وسهولة الصيانة.
- تصميم دالة الحفظ: قمنا بتصميم وبناء دالة
save_memoryتأخذ البيانات المطلوبة وتتولى جميع تفاصيل التفاعل مع قاعدة البيانات، بما في ذلك التعامل مع الأخطاء. - الكود القوي: أضفنا معالجة الأخطاء باستخدام
try...except...finallyلضمان أن برنامجنا لا يتوقف بشكل غير متوقع وأن الاتصال بقاعدة البيانات يُغلق دائمًا.
لقد أصبح لدى وكيلنا الآن قدرة حقيقية على "التذكر" بشكل منظم وموثوق.
ماذا بعد؟
بعد أن تعلمنا كيف نضع المعلومات في الذاكرة، فإن الخطوة المنطقية التالية هي تعلم كيفية استخراجها عند الحاجة. في الدرس القادم، سنكمل النصف الآخر من المعادلة من خلال تصميم دالة لاسترجاع المعلومات من قاعدة البيانات. سنبني دالة retrieve_memory التي ستسمح لوكيلنا بالبحث في ذكرياته للعثور على المعلومات الأكثر صلة بمهمته الحالية.