Spaces:
Sleeping
Sleeping
| import faiss | |
| import json | |
| import gradio as gr | |
| import os | |
| from typing import List, Dict, Any | |
| from langchain_huggingface import HuggingFaceEmbeddings | |
| from langchain_openai import ChatOpenAI | |
| from langchain_core.runnables import RunnablePassthrough | |
| from langchain_community.vectorstores import FAISS | |
| from langchain_core.documents import Document | |
| from langchain.chains import RetrievalQA, LLMChain | |
| from langchain.prompts import PromptTemplate | |
| from langchain_core.messages import AIMessage ,HumanMessage | |
| from langchain_openai import ChatOpenAI | |
| from datetime import datetime | |
| # Tracking Answer : | |
| LOG_FILE = "chat_log.txt" | |
| TRACKING_FILE = "source_tracking.json" | |
| ANSWERS_FILE = "answers_tracking.txt" | |
| def init_files(): | |
| if not os.path.exists(LOG_FILE): | |
| with open(LOG_FILE, 'w', encoding='utf-8') as f: | |
| f.write("سجل محادثات التأمين الصحي\n") | |
| f.write("="*50 + "\n\n") | |
| if not os.path.exists(TRACKING_FILE): | |
| with open(TRACKING_FILE, 'w', encoding='utf-8') as f: | |
| json.dump([], f) | |
| if not os.path.exists(ANSWERS_FILE): | |
| with open(ANSWERS_FILE, 'w', encoding='utf-8') as f: | |
| f.write("سجل الإجابات والمصادر\n") | |
| f.write("="*50 + "\n\n") | |
| def write_to_log(*messages): | |
| with open(LOG_FILE, 'a', encoding='utf-8') as f: | |
| timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S') | |
| for message in messages: | |
| f.write(f"{timestamp} - {message}\n") | |
| def track_sources(question, answer, sources): | |
| # 1. JSON | |
| with open(TRACKING_FILE, 'r+', encoding='utf-8') as f: | |
| data = json.load(f) | |
| entry = { | |
| "timestamp": datetime.now().strftime('%Y-%m-%d %H:%M:%S'), | |
| "question": question, | |
| "answer": answer, | |
| "sources": [ | |
| { | |
| "file": doc.metadata['source'], | |
| "section": doc.metadata.get('section', 'غير محدد'), | |
| "content": doc.page_content | |
| } | |
| for doc in sources | |
| ] | |
| } | |
| data.append(entry) | |
| f.seek(0) | |
| json.dump(data, f, ensure_ascii=False, indent=2) | |
| # 2. ANSWERS_FILE | |
| with open(ANSWERS_FILE, 'a', encoding='utf-8') as f: | |
| f.write(f"\n{'='*100}\n") | |
| f.write(f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}]\n") | |
| f.write(f"السؤال الأصلي: {question}\n\n") | |
| f.write("الإجابة الكاملة:\n") | |
| f.write(f"{answer}\n\n") | |
| f.write(f"المصادر المستخدمة ({len(sources)} مصدر):\n") | |
| for i, doc in enumerate(sources, 1): | |
| f.write(f"\nالمصدر #{i}:\n") | |
| f.write(f"- الملف: {os.path.basename(doc.metadata['source'])}\n") | |
| f.write(f"- العنوان: {doc.metadata.get('section', 'غير محدد')}\n") | |
| f.write(f"- المحتوى الكامل:\n{doc.page_content}\n") | |
| f.write("-"*80 + "\n") | |
| f.write(f"{'='*100}\n") | |
| # load Embeddings | |
| # load Embeddings | |
| def load_embeddings() -> FAISS: | |
| """ FAISS files """ | |
| required_files = ["index.faiss", "index.pkl", "source_files.txt"] | |
| missing_files = [file for file in required_files if not os.path.exists(file)] | |
| if missing_files: | |
| error_msg = f"الملفات التالية غير موجودة: {', '.join(missing_files)}" | |
| write_to_log(error_msg) | |
| raise ValueError(error_msg) | |
| try: | |
| embedding_model = HuggingFaceEmbeddings(model_name="intfloat/multilingual-e5-large" ) | |
| vectorstore = FAISS.load_local( | |
| folder_path=".", | |
| embeddings=embedding_model, | |
| allow_dangerous_deserialization=True | |
| ) | |
| return vectorstore | |
| except Exception as e: | |
| raise RuntimeError(f"فشل في تحميل ملفات FAISS: {str(e)}") | |
| def setup_chains(vectorstore: FAISS): | |
| """chains """ | |
| llm = ChatOpenAI( | |
| model="llama3-70b-8192", # | |
| base_url="https://api.groq.com/openai/v1", | |
| #model="meta-llama/llama-3-70b-instruct", | |
| #base_url="https://openrouter.ai/api/v1", | |
| #api_key="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", | |
| api_key = "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", | |
| temperature=0.4, | |
| max_tokens=1500, | |
| request_timeout=60 | |
| ) | |
| rephrase_prompt = PromptTemplate.from_template(""" | |
| قم بتحويل العبارة التالية من العامية المصرية إلى اللغة العربية الفصحى مع الالتزام بالتالي: | |
| الكلمات التاليه لا تغيرها (زوجه-اجنبي- المولود - الرعايه - المؤمن - المغترب- المستفدين - العائل - خطاب-الإسعافية -الموافقة ). | |
| 1. إذا كانت العبارة بالفصحى بالفعل، اتركها كما هي دون تغيير | |
| 2. لا تقم بإضافة أي كلمات أو تعليقات إضافية | |
| 3. حافظ على نفس المعنى بدقة | |
| 4. غير فقط الكلمات العامية إلى فصحى مع الحفاظ على الكلمات الفصيحة كما هي | |
| 5.حول كلمه (ورق - الورق ) الي الاوراق لتكون جمع | |
| السؤال: "{question}" | |
| السؤال بالفصحى: | |
| """) | |
| rephrase_chain = ( | |
| {"question": RunnablePassthrough()} | |
| | rephrase_prompt | |
| | llm | |
| ) | |
| qa_prompt = PromptTemplate.from_template( | |
| """ | |
| أجب على السؤال التالي بناءً على المعلومات الموجودة في النصوص المقدمة لك مع اتباع : | |
| اشتراطات الإجابة: | |
| 1. التزم باللغة العربية فقط الاجابه بالعربية فقط | |
| 2. لا تذكر المستندات الا اذا كان السوال يسال عن ذكر سواء مستندات او اوراق او وثائق اي معني منهم | |
| 3. اذا كان السوال به اكتر من جزء فتجيب عن كل جزء فالسوال ولا تترك شي | |
| 4. الإجابة يجب أن تكون كاملة دون حذف شي من النص | |
| 5. اذا كان المطلوب اوراق طفل مولود فلا تذكر بطاقه الرقم القومي للمولود بدلا منها اذكر شهاده الميلاد | |
| 6. التامين الصحي شامل هذه المناطق فقط (بورسعيد، الإسماعيلية، السويس، جنوب سيناء، الأقصر، وأسوان) | |
| 7. لا تذكر أرقام خطوات أو إجراءات | |
| 8. لا تشير إلى مصدر المعلومة ولكن تم اخذ كلام من قسم اخر اذا كان في حالة معينه بسببها اذكر هذا النص ويجب ذكر الحالة | |
| مثال عند السوال عن تسجيل الاسرة | |
| يظهر من ضمن المستندات | |
| إفادة حديثة مختومة من مصلحة السجون موجهة إلى التأمين الصحي الشامل تفيد بأن الزوج مسجون مع تحديد مدة الحبس | |
| : ولكن يجب ان تذكر انه في حاله الزوج مسجون | |
| إفادة حديثة مختومة من مصلحة السجون موجهة إلى التأمين الصحي الشامل تفيد بأن الزوج مسجون مع تحديد مدة الحبس | |
| وهكذا في اي شي اخر | |
| 9. لا تضيف أي معلومات خارجية | |
| 10. عند السؤال عن المستندات المطلوبة، يجب ذكر جميع أنواع المستندات (التسجيل، الدخل ) ولكن يكون خاص بالمحافظات التاليه (بورسعيد، الإسماعيلية، السويس، جنوب سيناء، الأقصر، وأسوان ) لو ذكر اي محافظة اخري يرجي الرد بالمستندات ولكن التامين الصحي الشامل غير متوفر حاليا في هذه المحافظة ونعمل علي ضم هذه المحافظة قريبا | |
| 11. لا تكرر الكلام في نفس الرد | |
| 12.لا تذكر مستندات التسجيل والدخل الا عند السوال عنهم | |
| 1 مثال | |
| السوال : الاوراق المطلوبه لتسجيل طفل مولود | |
| :الاجابه | |
| (مستندات التسجيل ) | |
| *********************************************************** | |
| -يتم استلام صور مع ضرورة الاطلاع على الاصل | |
| -صورة بطاقة الرقم القومي )سارية – وجهين( | |
| -صورة شهادة ميالد مميكنة للطفل | |
| -أصل بطاقة التأمين الصحي الشامل الاسرة | |
| -أصل قيد عائلي مميكن إن تطلب الامر | |
| مستندات الدخل وفًقا لمتطلبات اإلدارة المالية: | |
| *********************************************************** | |
| -طبعة التأمينات اإلجتماعية للمستفيد إذا كان مؤمن عليه أو غير مؤمن عليه أو بالمعاش | |
| -طبعة مدد تأمينية | |
| - مفردات المرتب للمستفيد بالقطاع الحكومي والقطاع الخاص | |
| -إقرار ضريبي للمستفيد في حالة العمل الحر | |
| -إفادة من الضرائب تفيد بعدم فتح نشاط | |
| -قرار للمستفيد من لجنة غيرالقادرين باإلعفاء من سداد االشتراكات | |
| - إفادة من وزارة التضامن اإلجتماعي بأن المستفيد من مستحقي أحد معاشات وزارة التضامن الاجتماعي | |
| -بطاقة تكافل وكرامة سارية | |
| السؤال: {question} | |
| النصوص: {context} | |
| الاجابة: | |
| """) | |
| qa_chain = RetrievalQA.from_chain_type( | |
| llm=llm, | |
| retriever=vectorstore.as_retriever( | |
| search_type = "mmr", | |
| search_kwargs={'k': 10, 'fetch_k': 50, 'lambda_mult': 0.7 ,'score_threshold': 0.7 } | |
| ), | |
| return_source_documents=True, | |
| chain_type_kwargs={"prompt": qa_prompt} | |
| ) | |
| return rephrase_chain, qa_chain | |
| def create_gradio_interface(rephrase_chain, qa_chain): | |
| with gr.Blocks(title="المساعد الذكي للتأمين الصحي") as demo: | |
| gr.Markdown("## 🏥 المساعد الذكي للتأمين الصحي") | |
| gr.Markdown("اسأل عن أي معلومات في وثائق وسياسة التأمين") | |
| chatbot = gr.Chatbot(label="المحادثة", height=500) | |
| question_box = gr.Textbox(label="اكتب سؤالك هنا", placeholder="مثال: ما هي مستندات التسجيل؟") | |
| with gr.Row(): | |
| submit_btn = gr.Button("إرسال", variant="primary") | |
| clear_btn = gr.Button("مسح المحادثة") | |
| chat_history = gr.State([]) | |
| def process_and_display(question, history): | |
| rewritten = rephrase_chain.invoke({"question": question}) | |
| fusha_question = rewritten.content.strip() | |
| result = qa_chain.invoke(fusha_question) | |
| answer = result["result"] | |
| # يمكنك الاحتفاظ بالسجل إذا كنت بحاجة إليه للتتبع الداخلي | |
| write_to_log( | |
| f"السؤال: {question}", | |
| f"السؤال المحول: {fusha_question}", | |
| f"الإجابة: {answer}", | |
| "المصادر المستخدمة:" | |
| ) | |
| for i, doc in enumerate(result["source_documents"], 1): | |
| write_to_log( | |
| f"المصدر #{i}:", | |
| f"الملف: {os.path.basename(doc.metadata['source'])}", | |
| f"القسم: {doc.metadata.get('section', 'غير محدد')}", | |
| f"المحتوى: {doc.page_content[:300]}..." if len(doc.page_content) > 300 else f"المحتوى: {doc.page_content}", | |
| "-"*50 | |
| ) | |
| history.append((question, answer)) | |
| return "", history | |
| submit_btn.click( | |
| fn=process_and_display, | |
| inputs=[question_box, chat_history], | |
| outputs=[question_box, chatbot] | |
| ) | |
| question_box.submit( | |
| fn=process_and_display, | |
| inputs=[question_box, chat_history], | |
| outputs=[question_box, chatbot] | |
| ) | |
| clear_btn.click( | |
| fn=lambda: [], | |
| outputs=[chatbot], | |
| inputs=[] | |
| ) | |
| return demo | |
| if __name__ == "__main__": | |
| init_files() | |
| vectorstore = load_embeddings() | |
| rephrase_chain, qa_chain = setup_chains(vectorstore) | |
| demo = create_gradio_interface(rephrase_chain, qa_chain) | |
| demo.launch(share=False) | |