دمج نظام RAG الذي بنيناه سابقاً داخل واجهة Streamlit وعرضه للمستخدمين
دمج نظام RAG الذي بنيناه سابقاً داخل واجهة Streamlit وعرضه للمستخدمين
بعد أن أنجزنا المراحل الأساسية لبناء نظام RAG من استخراج النصوص، وتقطيعها، وتحويلها إلى Embeddings، ثم تخزينها داخل Vector DB، تأتي الخطوة الهندسية الأهم: تحويل هذا السكربت الخلفي إلى تطبيق يمكن للمستخدم النهائي التفاعل معه مباشرة. هنا يظهر دور Streamlit كواجهة سريعة لبناء طبقة عرض تربط المستخدم بمنظومة الاسترجاع والتوليد دون الحاجة إلى بناء frontend كامل باستخدام أطر معقدة.
الهدف هنا ليس مجرد إظهار مربع إدخال وسرد جواب، بل بناء مسار بيانات واضح يبدأ من سؤال المستخدم، ثم يمر إلى Retriever، ثم إلى النموذج اللغوي LLM، ثم يعود إلى الواجهة مع إمكانية عرض المصادر، وإدارة الجلسة، وتحسين تجربة الاستخدام. هذه الطبقة ضرورية إذا كنت تنوي تقديم مساعد معرفي داخلي، أو محرك سؤال وجواب فوق وثائق شركة، أو حتى مشروعاً تعليمياً يتيح “الدردشة” مع ملفات PDF.
المعمارية العامة لتوصيل RAG مع Streamlit
عند دمج النظامين، فنحن عملياً نبني معمارية من ثلاث طبقات:
- طبقة الواجهة: تستقبل السؤال، تعرض الرسائل، وتُظهر النتيجة والمراجع.
- طبقة المنطق التطبيقي: تدير تحميل قاعدة المعرفة، استدعاء
Retriever، وتجهيزprompt. - طبقة المعرفة: تتكون من قاعدة متجهية مثل
ChromaDBأو أي مخزن مشابه يحتوي على المقاطع النصية وبياناتها الوصفية.
تدفق البيانات يمر غالباً بهذا الترتيب:
- المستخدم يكتب السؤال داخل واجهة المحادثة.
- الواجهة ترسل النص إلى سلسلة
LangChainأو أي منطق مخصص. - يتم تحويل السؤال ضمنياً إلى تمثيل دلالي ومقارنته بالمخزون المتجهي.
- يسترجع النظام المقاطع الأعلى صلة.
- يتم بناء
promptيتضمن السؤال والسياق المسترجع. - يعيد
LLMإجابة منضبطة مبنية على تلك المقاطع. - تعرض
Streamlitالجواب مع المصادر داخل الواجهة.
تجهيز ملف التطبيق وربط المكونات
إذا كنت قد أنهيت مسبقاً إعداد المكتبات كما شرحنا في إعداد بيئة العمل الذكية: تثبيت مكتبات Python الأساسية للتعامل مع الذكاء الاصطناعي، فغالباً ستبدأ بملف مثل app.py. في هذا الملف سنحمّل قاعدة المتجهات، وننشئ كائن الاسترجاع، ثم نبني دالة مسؤولة عن الرد على أسئلة المستخدم.
import os
import streamlit as st
from langchain_openai import ChatOpenAI, OpenAIEmbeddings
from langchain_chroma import Chroma
from langchain.prompts import ChatPromptTemplate
from langchain.chains import RetrievalQA
st.set_page_config(page_title="RAG Chat App", page_icon="📚", layout="wide")
@st.cache_resource
def load_vectorstore():
embeddings = OpenAIEmbeddings(api_key=os.getenv("OPENAI_API_KEY"))
vectorstore = Chroma(
persist_directory="chroma_db",
embedding_function=embeddings
)
return vectorstore
@st.cache_resource
def load_qa_chain():
vectorstore = load_vectorstore()
retriever = vectorstore.as_retriever(search_kwargs={"k": 4})
llm = ChatOpenAI(
model="gpt-4o-mini",
temperature=0,
api_key=os.getenv("OPENAI_API_KEY")
)
prompt = ChatPromptTemplate.from_template(
"""
You are a helpful RAG assistant.
Answer the user question based only on the retrieved context.
If the answer is not found in the context, say clearly that the information is unavailable.
Context:
{context}
Question:
{question}
"""
)
qa_chain = RetrievalQA.from_chain_type(
llm=llm,
retriever=retriever,
return_source_documents=True,
chain_type_kwargs={"prompt": prompt}
)
return qa_chain
لاحظ هنا استخدام @st.cache_resource لتقليل إعادة تحميل الموارد الثقيلة في كل تفاعل. هذه نقطة هندسية مهمة جداً، لأن تحميل vector store أو إنشاء اتصالات متكررة يرفع زمن الاستجابة ويُضعف تجربة الاستخدام.
بناء واجهة المحادثة للمستخدم النهائي
لإنشاء تجربة قريبة من تطبيقات الدردشة الحديثة، يمكنك الاستفادة من مكونات بناء واجهة شات احترافية تشبه ChatGPT باستخدام Streamlit. الأهم هنا هو حفظ الرسائل في session_state حتى لا تختفي مع كل إعادة تنفيذ للسكربت.
st.title("الدردشة مع قاعدة المعرفة")
st.write("اسأل أي سؤال متعلق بالمستندات المفهرسة داخل النظام.")
if "messages" not in st.session_state:
st.session_state.messages = []
for message in st.session_state.messages:
with st.chat_message(message["role"]):
st.markdown(message["content"])
user_question = st.chat_input("اكتب سؤالك هنا...")
if user_question:
st.session_state.messages.append({"role": "user", "content": user_question})
with st.chat_message("user"):
st.markdown(user_question)
qa_chain = load_qa_chain()
with st.chat_message("assistant"):
with st.spinner("جاري البحث في المستندات وتوليد الإجابة..."):
result = qa_chain.invoke({"query": user_question})
answer = result["result"]
source_docs = result["source_documents"]
st.markdown(answer)
if source_docs:
with st.expander("عرض المصادر"):
for i, doc in enumerate(source_docs, start=1):
source_name = doc.metadata.get("source", "Unknown Source")
st.markdown(f"**المصدر {i}:** {source_name}")
st.markdown(doc.page_content[:400] + "...")
st.session_state.messages.append({"role": "assistant", "content": answer})
هذا التصميم يحقق ثلاثة عناصر حاسمة:
- فصل واضح بين رسائل المستخدم والمساعد.
- ربط مباشر بين السؤال ونتيجة الاسترجاع.
- عرض المصادر لرفع الثقة وتقليل الانطباع بأن النظام “يخترع” الإجابات.
تصميم Prompt مناسب داخل تطبيق RAG
رغم أن الاسترجاع يقلل الهلوسة، إلا أن جودة المخرجات تعتمد أيضاً على هندسة التعليمات. يمكنك الاستفادة من مبادئ هندسة الأوامر داخل الكود: قوالب النصوص المتغيرة لبناء تعليمات أكثر صرامة. المثال التالي مناسب عندما تريد إجابات دقيقة ومقتضبة مع الإشارة إلى غياب المعلومة عند الحاجة:
أنت مساعد يعتمد على نظام
RAG. أجب فقط بالاعتماد على السياق المسترجع. إذا لم تجد الإجابة بشكل صريح، فاذكر أن المعلومات غير متوفرة في المستندات الحالية. لا تضف افتراضات، ولا تُنشئ حقائق غير موجودة في السياق.
هذا النوع من prompts مهم خصوصاً في التطبيقات المؤسسية والتعليمية والبحثية، لأن الخطأ هنا ليس مجرد رد غير دقيق، بل قد يتحول إلى قرار خاطئ من المستخدم.
تحسين الأداء وتجربة الاستخدام
1) تقليل زمن الاستجابة
أبطأ جزء في التطبيق غالباً ليس واجهة Streamlit نفسها، بل عملية الاسترجاع ثم استدعاء API. لذلك احرص على:
- تقليل قيمة
kفي الاسترجاع إلى الحد المناسب. - استخدام التخزين المؤقت للموارد.
- اختيار نموذج خفيف نسبياً عند الحاجة.
- ضبط المعلمات مثل
temperatureكما شرحنا في فهم وإعداد معاملاتTemperatureوTop-Kبرمجياً.
2) عرض المراجع بشفافية
من أفضل ممارسات الثقة أن تُظهر للمستخدم الأجزاء التي اعتمد عليها النظام في الإجابة. هذا مفيد في أنظمة الدعم الداخلي، والبحث الأكاديمي، وتطبيقات الامتثال. كما أنه يحسّن قابلية التدقيق ويقلل الشكاوى حول “إجابات الذكاء الاصطناعي غير الموثوقة”.
3) دعم البث التدريجي للإجابة
إذا أردت تجربة أكثر سلاسة، يمكنك لاحقاً توصيل التطبيق مع البث المباشر للردود بحيث تظهر الكلمات تدريجياً بدلاً من الانتظار حتى اكتمال الجواب. هذا لا يغير المنطق الداخلي لـ RAG، لكنه يحسن الإحساس بسرعة النظام.
أخطاء شائعة عند دمج RAG داخل الواجهة
- تحميل قاعدة المعرفة في كل رسالة: يؤدي إلى بطء كبير واستهلاك غير ضروري للموارد.
- إخفاء المصادر: يجعل المستخدم لا يفهم على أي أساس تم توليد الجواب.
- استخدام
promptعام جداً: فيسمح للنموذج بالإجابة من معرفته العامة بدلاً من المستندات. - نسيان إدارة مفاتيح
APIعبر المتغيرات البيئية: وهو خطأ أمني شائع، خصوصاً عند رفع المشروع إلى مستودع عام. راجع الاتصال الأول: جلب مفاتيحAPI.
الخلاصة الهندسية
دمج نظام RAG داخل واجهة Streamlit ليس خطوة تجميلية، بل انتقال من نموذج برمجي تجريبي إلى منتج يمكن استخدامه فعلياً. القيمة الحقيقية تظهر عندما تبني واجهة تجعل الاسترجاع، وتوليد الإجابة، وعرض المصادر، وإدارة الجلسة، تعمل كوحدة واحدة متماسكة. بهذا الأسلوب يتحول مشروع الدردشة مع كتاب PDF أو قاعدة معرفية داخلية إلى تطبيق عملي جاهز للعرض، والاختبار، ثم التطوير لاحقاً بإضافة الذاكرة، أو المصادقة، أو النشر السحابي، أو تحسينات الاسترجاع مثل MMR.
1 comment