أهلاً بك مجدداً في رحلتنا لبناء وكلاء الذكاء الاصطناعي!
في الدرس السابق، قمنا بتزويد وكيلنا بـ "عيون" ليرى بها الإنترنت، وذلك من خلال بناء أداة بحث تستخدم واجهة برمجية خارجية. أصبح وكيلنا الآن قادراً على الوصول إلى أحدث المعلومات، لكنه لا يزال يفتقر إلى طريقة للاحتفاظ بهذه المعلومات أو التفاعل مع بيئته المباشرة.
في هذا الدرس، سنمنحه "يداً" و"ذاكرة بسيطة". هدفنا اليوم هو تحقيق مخرج التعلم التالي: بناء أداة لقراءة وكتابة محتوى ملف نصي محلي. هذا يعني أننا سنجعل الوكيل قادراً على حفظ أفكاره، أو نتائج عمله، أو أي نص يريده في ملفات على جهاز الكمبيوتر، وقراءتها لاحقاً. هذه خطوة جوهرية نحو بناء وكلاء أكثر استقلالية وفائدة.
1. لماذا يحتاج الوكيل إلى التعامل مع الملفات؟
تخيل أنك طلبت من الوكيل كتابة قصيدة. بعد أن ينتهي، إذا أُغلق البرنامج، ستضيع القصيدة إلى الأبد. التعامل مع الملفات يحل هذه المشكلة، فهو يسمح للوكيل بـ:
- حفظ العمل (Persistence): حفظ المخرجات بشكل دائم، مثل كتابة تقرير، أو ملخص، أو كود برمجي في ملف.
- العمل كدفتر ملاحظات (Scratchpad): استخدام ملف كمسودة لتدوين الأفكار المعقدة أو الخطوات أثناء حل مشكلة ما.
- قراءة البيانات المحلية: تحليل محتوى ملف موجود مسبقاً على جهازك، مثل قراءة قائمة مهام أو بيانات نصية.

2. أساسيات التعامل مع الملفات في بايثون
قبل بناء أدواتنا، نحتاج إلى فهم الطريقة التي تتعامل بها بايثون مع الملفات. أفضل مكان للبدء هو مصدر باللغة العربية يشرح هذه الأساسيات بوضوح.
الفصل الثامن: التعامل مع الملفات (File Handling)
يقدم هذا الفصل من "كتاب البرمجة من..." شرحاً ممتازاً ومبسطاً لمفاهيم التعامل مع الملفات باللغة العربية. سنركز على الأجزاء التي تشرح كيفية فتح الملفات، والأوضاع المختلفة (قراءة، كتابة، إضافة)، والطريقة المثلى للتعامل معها.
الرجاء قراءة الأقسام التالية بالترتيب للتعرف على المفاهيم الأساسية: "1. بوابة التعامل مع الملفات: دالة open()": لفهم الوظيفة الأساسية لفتح الملفات وأوضاع الفتح الرئيسية ('r', 'w', 'a'). "2. الطريقة المثلى للتعامل مع الملفات: with open()": لفهم لماذا نستخدم جملة with وكيف تضمن إغلاق الملف تلقائياً. "3. كتابة البيانات في الملفات": للاطلاع على كيفية استخدام دالة .write(). "4. قراءة البيانات من الملفات": للتعرف على الطرق المختلفة للقراءة مثل .read() وحلقة for. "5. الإضافة إلى نهاية ملف ('a')": لفهم كيف نضيف محتوى دون مسح القديم.
ملخص أهم النقاط
بعد قراءة المادة، دعنا نلخص أهم المفاهيم التي سنستخدمها:
- الدالة
open(): هي بوابتنا للملفات. تحتاج إلى وسيطين أساسيين: مسار الملف (اسمه وموقعه) والوضع (mode). - المسار (File Path): هو عنوان الملف. يمكن أن يكون بسيطاً مثل
"notes.txt"إذا كان الملف في نفس مجلد البرنامج (مسار نسبي)، أو كاملاً مثل"C:\\Users\\YourName\\Documents\\notes.txt"(مسار مطلق). - أوضاع الفتح (Modes):
'r'(Read): للقراءة فقط. يسبب خطأ إذا لم يكن الملف موجوداً.'w'(Write): للكتابة. ⚠️ تحذير: يمسح محتوى الملف بالكامل إذا كان موجوداً، وينشئه إذا لم يكن.'a'(Append): للإضافة. يكتب في نهاية الملف دون مسح المحتوى الحالي، وينشئه إذا لم يكن موجوداً.
- جملة
with: هي الطريقة القياسية والآمنة. تضمن أن الملف سيُغلق تلقائياً بعد الانتهاء من العمل عليه، حتى لو حدث خطأ.with open("my_file.txt", "w", encoding="utf-8") as f: # 'f' هو المتغير الذي نستخدمه للتفاعل مع الملف f.write("مرحباً بالعالم!") # هنا، تم إغلاق الملف تلقائياً - الترميز
encoding="utf-8": هذا الجزء ضروري جداً عند التعامل مع نصوص باللغة العربية لضمان عرضها وحفظها بشكل صحيح.
اختبر فهمك!
لديك ملف اسمه log.txt يحتوي على سجلات هامة. تريد إضافة سطر جديد في نهاية هذا الملف لتسجيل حدث جديد. أي وضع فتح يجب أن تستخدم: 'r'، 'w'، أم 'a'؟ ولماذا؟
اظهر الإجابة
يجب استخدام الوضع 'a' (Append).
- الوضع
'r'سيسمح بالقراءة فقط ولن تتمكن من الكتابة. - الوضع
'w'سيقوم بمسح كل السجلات الهامة الموجودة في الملف قبل كتابة السطر الجديد، وهذا كارثي. - الوضع
'a'هو الخيار الصحيح لأنه يفتح الملف ويضع مؤشر الكتابة في نهايته، مما يسمح لك بإضافة معلومات جديدة مع الحفاظ على المحتوى الحالي.
3. بناء أدوات التعامل مع الملفات
الآن، لنحول هذه المفاهيم إلى دوال بايثون يمكن لوكيلنا استخدامها كأدوات. سنقوم ببناء ثلاث أدوات أساسية: واحدة للقراءة، وواحدة للكتابة (مع المسح)، وواحدة للإضافة.
الأداة الأولى: قراءة ملف read_file
هذه الدالة ستقرأ محتوى ملف وتعيده كنص.
import os
def read_file(filepath: str) -> str:
"""
تقرأ محتوى ملف نصي من المسار المحدد وتعيده كنص.
:param filepath: المسار إلى الملف الذي سيتم قراءته.
:return: محتوى الملف كنص، أو رسالة خطأ إذا لم يتم العثور على الملف.
"""
try:
with open(filepath, 'r', encoding='utf-8') as f:
content = f.read()
return content
except FileNotFoundError:
return f"خطأ: الملف '{filepath}' غير موجود."
except Exception as e:
return f"حدث خطأ غير متوقع أثناء قراءة الملف: {e}"
# مثال للاستخدام
# print(read_file("example.txt"))
الأداة الثانية: الكتابة في ملف write_to_file
هذه الدالة ستكتب نصاً جديداً في ملف، وستقوم بمسح أي محتوى قديم.
def write_to_file(filepath: str, content: str) -> str:
"""
تكتب نصاً في ملف محدد. إذا كان الملف موجوداً، سيتم الكتابة فوقه.
:param filepath: المسار إلى الملف الذي سيتم الكتابة فيه.
:param content: النص الذي سيتم كتابته في الملف.
:return: رسالة تأكيد بنجاح العملية أو رسالة خطأ.
"""
try:
# نستخدم os.path.dirname للتأكد من وجود المجلد
directory = os.path.dirname(filepath)
if directory:
os.makedirs(directory, exist_ok=True)
with open(filepath, 'w', encoding='utf-8') as f:
f.write(content)
return f"تمت الكتابة بنجاح في الملف '{filepath}'."
except Exception as e:
return f"حدث خطأ غير متوقع أثناء الكتابة في الملف: {e}"
# مثال للاستخدام
# write_to_file("summary.txt", "هذا هو الملخص.")
الأداة الثالثة: الإضافة إلى ملف append_to_file
هذه الدالة ستضيف نصاً إلى نهاية ملف موجود دون التأثير على محتواه الحالي.
def append_to_file(filepath: str, content: str) -> str:
"""
تضيف نصاً إلى نهاية ملف محدد دون مسح محتواه الحالي.
:param filepath: المسار إلى الملف الذي سيتم الإضافة إليه.
:param content: النص الذي سيتم إضافته.
:return: رسالة تأكيد بنجاح العملية أو رسالة خطأ.
"""
try:
with open(filepath, 'a', encoding='utf-8') as f:
f.write(content + "\n") # غالباً ما نرغب في إضافة سطر جديد بعد الإضافة
return f"تمت الإضافة بنجاح إلى الملف '{filepath}'."
except Exception as e:
return f"حدث خطأ غير متوقع أثناء الإضافة إلى الملف: {e}"
# مثال للاستخدام
# append_to_file("log.txt", "تم تسجيل الدخول الساعة 5 مساءً.")
4. وصف الأدوات للوكيل
تماماً كما فعلنا مع أداة البحث في الدرس السابق، نحتاج الآن إلى "شرح" هذه الأدوات لوكيلنا (نموذج اللغة الكبير) باستخدام تنسيق JSON. هذا الوصف يخبر النموذج بما تفعله كل أداة ومتى يجب استخدامها.
وصف أداة read_file:
{
"type": "function",
"function": {
"name": "read_file",
"description": "لقراءة المحتوى الكامل لملف نصي موجود على الجهاز المحلي.",
"parameters": {
"type": "object",
"properties": {
"filepath": {
"type": "string",
"description": "المسار النسبي أو المطلق للملف المراد قراءته. مثال: 'data/report.txt'."
}
},
"required": ["filepath"]
}
}
}
وصف أداة write_to_file:
{
"type": "function",
"function": {
"name": "write_to_file",
"description": "لكتابة أو حفظ نص في ملف على الجهاز المحلي. تحذير: هذه الأداة ستمسح وتستبدل محتوى الملف بالكامل إذا كان موجوداً.",
"parameters": {
"type": "object",
"properties": {
"filepath": {
"type": "string",
"description": "المسار النسبي أو المطلق للملف المراد الكتابة فيه. مثال: 'output/summary.txt'."
},
"content": {
"type": "string",
"description": "النص أو المحتوى الذي سيتم كتابته في الملف."
}
},
"required": ["filepath", "content"]
}
}
}
وصف أداة append_to_file:
{
"type": "function",
"function": {
"name": "append_to_file",
"description": "لإضافة نص إلى نهاية ملف موجود دون حذف محتواه الحالي. مثالية لإضافة ملاحظات أو سجلات جديدة.",
"parameters": {
"type": "object",
"properties": {
"filepath": {
"type": "string",
"description": "المسار النسبي أو المطلق للملف المراد الإضافة إليه. مثال: 'logs/activity_log.txt'."
},
"content": {
"type": "string",
"description": "النص أو المحتوى الذي سيتم إضافته إلى نهاية الملف."
}
},
"required": ["filepath", "content"]
}
}
}
5. سيناريو عملي: وكيل يجمع المعلومات ويحفظها
لنتخيل أننا نطلب من وكيلنا، الذي يمتلك الآن أداة البحث على الإنترنت وأدوات الملفات، القيام بالمهمة التالية:
المستخدم: "ابحث عن فوائد رياضة الجري، ولخصها في 3 نقاط، ثم احفظ الملخص في ملف running_benefits.txt."
إليك كيف سيفكر الوكيل وينفذ المهمة باستخدام دورة "الفكر، الفعل، الملاحظة":
- الفكر: "المستخدم يريدني أن أبحث عن معلومات ثم أحفظها في ملف. الخطوة الأولى هي البحث. سأستخدم أداة
search_tavily." - الفعل: استدعاء
search_tavily(query="فوائد رياضة الجري"). - الملاحظة: (يتلقى نتائج البحث من الأداة، والتي تحتوي على معلومات حول تحسين صحة القلب، فقدان الوزن، وتقليل التوتر).
- الفكر: "لقد حصلت على المعلومات. الآن يجب أن ألخصها في 3 نقاط. بعد التلخيص، أحتاج إلى حفظها في ملف
running_benefits.txt. أداةwrite_to_fileهي الأنسب لهذا الغرض لأنني أقوم بإنشاء ملف جديد." - الفعل: استدعاء
write_to_file(filepath="running_benefits.txt", content="ملخص فوائد الجري:\n1. تحسين صحة القلب والأوعية الدموية.\n2. المساعدة في إدارة الوزن.\n3. تقليل مستويات التوتر وتحسين المزاج."). - الملاحظة: (يتلقى رسالة من الأداة: "تمت الكتابة بنجاح في الملف 'running_benefits.txt'.").
- الفكر: "لقد أكملت جميع الخطوات بنجاح. يمكنني الآن إبلاغ المستخدم."
- الإجابة النهائية للمستخدم: "لقد بحثت عن فوائد رياضة الجري ولخصتها لك. تم حفظ الملخص في ملف باسم
running_benefits.txtعلى جهازك."
هذا السيناريو يوضح كيف يمكن للوكيل أن يدمج أدوات مختلفة لإنجاز مهام متعددة الخطوات، مما يجعله أكثر قوة وفعالية.
خلاصة الدرس
في هذا الدرس، قمنا بتمكين وكيلنا من التفاعل مع بيئته المحلية، وهي مهارة لا غنى عنها.
أهم النقاط التي تعلمناها:
- أساسيات الملفات: فهمنا كيفية استخدام
with open()مع الأوضاع'r','w', و'a'للقراءة من الملفات والكتابة إليها بأمان. - بناء أدوات الملفات: قمنا بإنشاء ثلاث دوال بايثون منفصلة (
read_file,write_to_file,append_to_file) مع معالجة الأخطاء. - وصف الأدوات: تعلمنا كيف نكتب وصفاً دقيقاً لكل أداة ليتمكن نموذج اللغة من فهم وظيفتها واستخدامها بالشكل الصحيح.
- تكامل الأدوات: رأينا كيف يمكن للوكيل أن يجمع بين أداة البحث وأدوات الملفات لإنجاز مهمة معقدة.
ماذا بعد؟
لقد أصبح لدى وكيلنا الآن ذاكرة بسيطة على شكل ملفات نصية. لكن ماذا لو أردنا تخزين معلومات أكثر تنظيماً، والبحث فيها بكفاءة؟ الملفات النصية ليست الحل الأمثل لذلك. في الوحدة التالية، "الذاكرة المتقدمة واسترجاع المعلومات"، سنبدأ بفهم الحاجة إلى ذاكرة طويلة المدى أكثر قوة، وسنمهد الطريق لاستخدام قواعد البيانات والتقنيات المتقدمة مثل البحث الدلالي.