File size: 12,993 Bytes
fe5e111
 
3b1892c
fe5e111
 
d9871ec
 
 
727e2ab
fe5e111
727e2ab
 
fe5e111
 
aa9fcbf
fe5e111
 
 
3b1892c
b336ec9
d9871ec
727e2ab
b336ec9
3b1892c
 
 
 
d9871ec
b336ec9
 
 
d9871ec
b336ec9
 
 
 
727e2ab
d9871ec
3b1892c
d9871ec
 
 
727e2ab
b336ec9
fe5e111
b336ec9
 
 
 
 
 
 
 
 
eacd013
d9871ec
b336ec9
 
 
 
 
 
 
d9871ec
fe5e111
b336ec9
d9871ec
 
 
fe5e111
d9871ec
 
fe5e111
d9871ec
 
 
b336ec9
eacd013
d9871ec
 
fe5e111
d9871ec
b336ec9
fe5e111
 
 
3d1d3f8
fe5e111
 
3b1892c
fe5e111
3b1892c
d9871ec
3b1892c
 
b336ec9
3b1892c
 
d9871ec
3b1892c
fe5e111
3b1892c
d9871ec
3b1892c
 
 
 
 
b336ec9
727e2ab
 
fe5e111
727e2ab
698205a
fe5e111
 
 
 
 
08f0a85
fe5e111
a019a91
 
 
727e2ab
 
e6e795e
727e2ab
7d066db
c73f556
7d066db
 
 
 
e6e795e
ae21b56
727e2ab
 
 
546a8db
 
 
 
 
727e2ab
fd639bb
7d066db
 
fd639bb
 
 
d9871ec
7d066db
fd639bb
 
 
 
d9871ec
3b1892c
d9871ec
3d1d3f8
 
 
fe5e111
3d1d3f8
fe5e111
fd639bb
3d1d3f8
fd639bb
3d1d3f8
fd639bb
 
 
3d1d3f8
fd639bb
 
d9871ec
 
 
 
fd639bb
fe5e111
 
fd639bb
 
d9871ec
b336ec9
d9871ec
fe5e111
 
 
 
 
 
 
 
 
 
 
727e2ab
 
fd639bb
727e2ab
d9871ec
727e2ab
 
b336ec9
fe5e111
 
fd639bb
b336ec9
727e2ab
 
 
 
fd639bb
727e2ab
eacd013
 
 
3d1d3f8
b336ec9
 
 
 
727e2ab
a0fd9f4
 
 
727e2ab
a0fd9f4
 
 
8d55e0b
d9871ec
b336ec9
 
 
d9871ec
b336ec9
 
d9871ec
a0fd9f4
d9871ec
 
 
 
 
 
fe5e111
d9871ec
 
 
 
eacd013
d9871ec
 
 
 
b336ec9
a0fd9f4
d9871ec
727e2ab
b336ec9
727e2ab
a0fd9f4
727e2ab
d9871ec
727e2ab
b336ec9
727e2ab
a0fd9f4
b336ec9
d9871ec
b336ec9
a0fd9f4
 
b336ec9
727e2ab
 
 
d9871ec
a0fd9f4
3b1892c
b336ec9
3b1892c
727e2ab
 
fe5e111
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
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)