الذاكرة في الذكاء الاصطناعي: لماذا ينسى الـ API المحادثات السابقة وكيف نعالج ذلك؟


الذاكرة في الذكاء الاصطناعي: لماذا ينسى الـ API المحادثات السابقة وكيف نعالج ذلك؟

مرحباً أيها المبرمجون والمحاضرون التقنيون! اليوم، سنتعمق في أحد التحديات الأساسية عند بناء تطبيقات الذكاء الاصطناعي التخاطبية: مشكلة "النسيان" التي تواجهها نماذج اللغة الكبيرة (LLMs) عند التفاعل عبر الـ API. سنتعلم لماذا يحدث ذلك وكيف يمكننا بناء ذاكرة فعالة لها باستخدام مكتبة LangChain القوية في بايثون.

الخطوة 1: فهم المشكلة - الـ API بلا ذاكرة

تخيل أنك تتحدث مع شخص ينسى كل شيء قلته له بمجرد أن تنتهي جملتك! هذا هو بالضبط ما يحدث مع العديد من واجهات برمجة تطبيقات نماذج اللغة الكبيرة (LLMs) بشكل افتراضي. كل استدعاء للـ API هو استدعاء "بلا حالة" (stateless)، مما يعني أنه لا يحتفظ بأي معلومات عن الاستدعاءات السابقة. إذا سألت "ما هي عاصمة فرنسا؟" ثم سألت "وما هي لغتها الرسمية؟"، فإن النموذج لن يعرف أن "لغتها" تشير إلى فرنسا ما لم تذكر فرنسا مرة أخرى بشكل صريح في الطلب الثاني.

ملاحظة تقنية: السبب وراء كون الـ APIs بلا حالة هو الكفاءة وقابلية التوسع. فكل طلب يمكن معالجته بشكل مستقل، مما يسهل توزيع الأحمال ويقلل من تعقيد إدارة الجلسات.

لنرى مثالاً بسيطاً يوضح هذه المشكلة:

from langchain_openai import ChatOpenAI
from langchain_core.messages import HumanMessage, AIMessage

# تهيئة نموذج اللغة الكبير
# تأكد من تعيين مفتاح API الخاص بك كمتغير بيئة (OPENAI_API_KEY)
llm = ChatOpenAI(temperature=0.7)

print("--- المحادثة الأولى ---")
# السؤال الأول
response1 = llm.invoke([
    HumanMessage(content="مرحباً، ما هي عاصمة فرنسا؟")
])
print(f"النموذج: {response1.content}")

print("\n--- المحادثة الثانية (تنسى السياق) ---")
# السؤال الثاني - النموذج لا يتذكر "فرنسا" لأننا لم نمرر السياق السابق
response2 = llm.invoke([
    HumanMessage(content="وما هي لغتها الرسمية؟")
])
print(f"النموذج: {response2.content}")

في هذا المثال، حتى لو أجاب النموذج بشكل صحيح على السؤال الثاني، فإنه لم يفعل ذلك لأنه "تذكر" المحادثة السابقة بشكل صريح، بل لأنه حصل على سؤال جديد تماماً. لتشغيل هذا الكود، تأكد من تثبيت مكتبة langchain-openai وتهيئة مفتاح API الخاص بك.

الخطوة 2: بناء سلسلة محادثة بذاكرة (ConversationChain with ConversationBufferMemory)

لحل مشكلة النسيان، نحتاج إلى "ذاكرة" تخزن المحادثات السابقة وتمررها مع كل طلب جديد إلى النموذج. توفر LangChain أدوات قوية لذلك، وأبسطها هو ConversationBufferMemory الذي يخزن المحادثة بالكامل، وConversationChain الذي يربط النموذج بالذاكرة.

from langchain_openai import ChatOpenAI
from langchain.chains import ConversationChain
from langchain.memory import ConversationBufferMemory
from langchain_core.prompts import PromptTemplate

# تهيئة نموذج اللغة الكبير
llm = ChatOpenAI(temperature=0.7)

# تهيئة الذاكرة
# ConversationBufferMemory تخزن المحادثة كاملة في قائمة الرسائل
memory = ConversationBufferMemory()

# بناء قالب موجه لـ ConversationChain
# يجب أن يتضمن هذا القالب متغيراً لـ 'history' ومتغيراً لـ 'input'
prompt_template = PromptTemplate(
    input_variables=["history", "input"],
    template="""أنت مساعد ذكاء اصطناعي ودود ومفيد.
تاريخ المحادثة الحالي:
{history}
سؤال المستخدم الجديد: {input}
إجابتك:"""
)

# بناء سلسلة المحادثة
# نمرر النموذج والذاكرة وقالب الموجه للسلسلة
conversation = ConversationChain(
    llm=llm,
    memory=memory,
    prompt=prompt_template,
    verbose=True # لتتبع ما يحدث داخل السلسلة وعرض تاريخ المحادثة المرسل للنموذج
)

print("تم تهيئة سلسلة المحادثة بذاكرة.")

ملاحظة تقنية: ConversationBufferMemory هي أبسط أنواع الذاكرة. تقوم بتخزين كل رسالة (المدخلات والمخرجات) في قائمة. هذا فعال للمحادثات القصيرة، ولكن للمحادثات الطويلة جداً، يمكن أن يؤدي إلى تجاوز حد الرموز المميزة (token limit) وزيادة التكاليف. توجد أنواع ذاكرة أخرى مثل ConversationBufferWindowMemory (تحتفظ بآخر N رسالة) وConversationSummaryMemory (تلخص المحادثة القديمة).

الخطوة 3: التفاعل مع الذاكرة وتجربة تذكر السياق

الآن بعد أن قمنا بتهيئة السلسلة بذاكرة، يمكننا التفاعل معها ورؤية كيف تتذكر السياق السابق. في كل مرة نستخدم conversation.invoke()، ستقوم السلسلة تلقائياً بتحميل تاريخ المحادثة من الذاكرة، ودمجه مع سؤال المستخدم الجديد ضمن قالب الموجه (prompt template)، وإرساله إلى النموذج، ثم حفظ إجابة النموذج في الذاكرة.

# المحادثة الأولى
print("\n--- المحادثة الأولى مع الذاكرة ---")
response1 = conversation.invoke({"input": "مرحباً، ما هي عاصمة فرنسا؟"})
print(f"النموذج: {response1['response']}")

# المحادثة الثانية - النموذج سيتذكر السياق بفضل الذاكرة
print("\n--- المحادثة الثانية مع الذاكرة (النموذج يتذكر!) ---")
response2 = conversation.invoke({"input": "وما هي لغتها الرسمية؟"})
print(f"النموذج: {response2['response']}")

# المحادثة الثالثة - استمرار السياق
print("\n--- المحادثة الثالثة مع الذاكرة ---")
response3 = conversation.invoke({"input": "هل لي أن أزورها في الصيف؟"})
print(f"النموذج: {response3['response']}")

# لعرض محتوى الذاكرة مباشرة (اختياري)
print("\n--- محتوى الذاكرة الحالي ---")
print(conversation.memory.load_memory_variables({}))

ستلاحظ الآن أن النموذج يجيب على الأسئلة الثانية والثالثة مع الأخذ في الاعتبار أننا نتحدث عن فرنسا، وذلك بفضل الذاكرة التي وفرناها له. مخرجات verbose=True ستوضح كيف يتم تمرير تاريخ المحادثة إلى النموذج في كل طلب.

الكود النهائي الكامل

هذا هو السكربت الكامل الذي يجمع كل الخطوات المذكورة أعلاه. تأكد من تثبيت مكتبات LangChain و OpenAI: pip install langchain langchain-openai. وتأكد من تعيين مفتاح API الخاص بك كمتغير بيئة باسم OPENAI_API_KEY.

from langchain_openai import ChatOpenAI
from langchain.chains import ConversationChain
from langchain.memory import ConversationBufferMemory
from langchain_core.prompts import PromptTemplate
from langchain_core.messages import HumanMessage, AIMessage
import os # لاستخدام متغيرات البيئة

# --- الجزء الأول: المشكلة - API بلا ذاكرة ---
print("--- تجربة API بلا ذاكرة ---")
llm_stateless = ChatOpenAI(temperature=0.7)

# السؤال الأول
response1_stateless = llm_stateless.invoke([
    HumanMessage(content="مرحباً، ما هي عاصمة فرنسا؟")
])
print(f"النموذج (بلا ذاكرة): {response1_stateless.content}")

# السؤال الثاني - النموذج لا يتذكر "فرنسا" لأننا لم نمرر السياق السابق
response2_stateless = llm_stateless.invoke([
    HumanMessage(content="وما هي لغتها الرسمية؟")
])
print(f"النموذج (بلا ذاكرة): {response2_stateless.content}")
print("-" * 30)

# --- الجزء الثاني والثالث: بناء سلسلة محادثة بذاكرة والتفاعل معها ---
print("--- بناء وتجربة سلسلة محادثة بذاكرة ---")

# تهيئة نموذج اللغة الكبير (يمكن إعادة استخدام نفس النموذج)
llm_stateful = ChatOpenAI(temperature=0.7)

# تهيئة الذاكرة
memory = ConversationBufferMemory()

# بناء قالب موجه لـ ConversationChain
prompt_template = PromptTemplate(
    input_variables=["history", "input"],
    template="""أنت مساعد ذكاء اصطناعي ودود ومفيد.
تاريخ المحادثة الحالي:
{history}
سؤال المستخدم الجديد: {input}
إجابتك:"""
)

# بناء سلسلة المحادثة
conversation = ConversationChain(
    llm=llm_stateful,
    memory=memory,
    prompt=prompt_template,
    verbose=True # لتتبع ما يحدث داخل السلسلة وعرض تاريخ المحادثة المرسل للنموذج
)

print("تم تهيئة سلسلة المحادثة بذاكرة بنجاح.")

# المحادثة الأولى
print("\n--- المحادثة الأولى مع الذاكرة ---")
response1_stateful = conversation.invoke({"input": "مرحباً، ما هي عاصمة فرنسا؟"})
print(f"النموذج (مع ذاكرة): {response1_stateful['response']}")

# المحادثة الثانية - النموذج سيتذكر السياق بفضل الذاكرة
print("\n--- المحادثة الثانية مع الذاكرة (النموذج يتذكر!) ---")
response2_stateful = conversation.invoke({"input": "وما هي لغتها الرسمية؟"})
print(f"النموذج (مع ذاكرة): {response2_stateful['response']}")

# المحادثة الثالثة - استمرار السياق
print("\n--- المحادثة الثالثة مع الذاكرة ---")
response3_stateful = conversation.invoke({"input": "هل لي أن أزورها في الصيف؟"})
print(f"النموذج (مع ذاكرة): {response3_stateful['response']}")

# عرض محتوى الذاكرة مباشرة
print("\n--- محتوى الذاكرة الحالي (بعد المحادثات) ---")
print(conversation.memory.load_memory_variables({}))

النتيجة المتوقعة

عند تشغيل السكربت، ستلاحظ الفرق الواضح بين التفاعل بلا ذاكرة ومع الذاكرة. في الجزء الأول، سترى أن النموذج يجيب على كل سؤال بشكل مستقل، حتى لو كانت الإجابة صحيحة، فإنه لم يحصل على سياق المحادثة السابقة بشكل صريح. بينما في الجزء الثاني، سترى النموذج يجيب بشكل متسلسل ومنطقي، حيث يتذكر أن المحادثة تدور حول فرنسا، ويقدم إجابات صحيحة ومترابطة بناءً على السياق المخزن في الذاكرة. ستظهر مخرجات verbose=True تفاصيل تمرير المحادثة للنموذج (تاريخ المحادثة)، وستطبع محتويات الذاكرة في النهاية.

--- تجربة API بلا ذاكرة ---
النموذج (بلا ذاكرة): أهلاً بك! عاصمة فرنسا هي باريس.
النموذج (بلا ذاكرة): اللغة الرسمية لفرنسا هي الفرنسية.
------------------------------
--- بناء وتجربة سلسلة محادثة بذاكرة ---
تم تهيئة سلسلة المحادثة بذاكرة بنجاح.

--- المحادثة الأولى مع الذاكرة ---
> Entering new ConversationChain chain...
Prompt after formatting:
أنت مساعد ذكاء اصطناعي ودود ومفيد.
تاريخ المحادثة الحالي:

سؤال المستخدم الجديد: مرحباً، ما هي عاصمة فرنسا؟
إجابتك:
> Finished chain.
النموذج (مع ذاكرة): أهلاً بك! عاصمة فرنسا هي باريس.

--- المحادثة الثانية مع الذاكرة (النموذج يتذكر!) ---
> Entering new ConversationChain chain...
Prompt after formatting:
أنت مساعد ذكاء اصطناعي ودود ومفيد.
تاريخ المحادثة الحالي:
Human: مرحباً، ما هي عاصمة فرنسا؟
AI: أهلاً بك! عاصمة فرنسا هي باريس.
سؤال المستخدم الجديد: وما هي لغتها الرسمية؟
إجابتك:
> Finished chain.
النموذج (مع ذاكرة): اللغة الرسمية لفرنسا هي الفرنسية.

--- المحادثة الثالثة مع الذاكرة ---
> Entering new ConversationChain chain...
Prompt after formatting:
أنت مساعد ذكاء اصطناعي ودود ومفيد.
تاريخ المحادثة الحالي:
Human: مرحباً، ما هي عاصمة فرنسا؟
AI: أهلاً بك! عاصمة فرنسا هي باريس.
Human: وما هي لغتها الرسمية؟
AI: اللغة الرسمية لفرنسا هي الفرنسية.
سؤال المستخدم الجديد: هل لي أن أزورها في الصيف؟
إجابتك:
> Finished chain.
النموذج (مع ذاكرة): بالتأكيد! باريس رائعة في الصيف، يمكنك الاستمتاع بالعديد من الأنشطة والمعالم.

--- محتوى الذاكرة الحالي (بعد المحادثات) ---
{'history': 'Human: مرحباً، ما هي عاصمة فرنسا؟\nAI: أهلاً بك! عاصمة فرنسا هي باريس.\nHuman: وما هي لغتها الرسمية؟\nAI: اللغة الرسمية لفرنسا هي الفرنسية.\nHuman: هل لي أن أزورها في الصيف؟\nAI: بالتأكيد! باريس رائعة في الصيف، يمكنك الاستمتاع بالعديد من الأنشطة والمعالم.'}