Spaces:
Sleeping
Sleeping
Update pages/2_Chatbot_AR.py
Browse files- pages/2_Chatbot_AR.py +296 -296
pages/2_Chatbot_AR.py
CHANGED
@@ -1,296 +1,296 @@
|
|
1 |
-
import streamlit as st
|
2 |
-
import pandas as pd
|
3 |
-
import os
|
4 |
-
from pathlib import Path
|
5 |
-
import base64
|
6 |
-
|
7 |
-
# LangChain & Hugging Face
|
8 |
-
from langchain.embeddings import HuggingFaceEmbeddings
|
9 |
-
from langchain.vectorstores import Chroma
|
10 |
-
from langchain.schema import Document
|
11 |
-
from langchain.prompts import PromptTemplate
|
12 |
-
from langchain.llms import HuggingFaceHub
|
13 |
-
from langchain.chains import LLMChain
|
14 |
-
|
15 |
-
import pysqlite3
|
16 |
-
import sys
|
17 |
-
sys.modules["sqlite3"] = pysqlite3
|
18 |
-
|
19 |
-
#####################
|
20 |
-
# 1. HELPER FUNCTIONS
|
21 |
-
#####################
|
22 |
-
|
23 |
-
def get_base64_of_bin_file(bin_file_path: str) -> str:
|
24 |
-
file_bytes = Path(bin_file_path).read_bytes()
|
25 |
-
return base64.b64encode(file_bytes).decode()
|
26 |
-
|
27 |
-
def find_parent_ar(data, r, col):
|
28 |
-
"""
|
29 |
-
Trouve la question parente pour une ligne et colonne donnée dans le DataFrame (version AR).
|
30 |
-
"""
|
31 |
-
i = r - 1
|
32 |
-
parent = None
|
33 |
-
while i >= 0 and pd.isna(parent):
|
34 |
-
parent = data.iloc[i, col]
|
35 |
-
i -= 1
|
36 |
-
return parent
|
37 |
-
|
38 |
-
def create_contextual_ar(df, category, strat_id=0):
|
39 |
-
"""
|
40 |
-
Crée un DataFrame avec questions-réponses contextuelles (version AR).
|
41 |
-
"""
|
42 |
-
rows = []
|
43 |
-
columns_qna = list(df.columns)
|
44 |
-
|
45 |
-
for r, row in df.iterrows():
|
46 |
-
for level, col in enumerate(df.columns):
|
47 |
-
question = row[col]
|
48 |
-
if pd.isna(question):
|
49 |
-
continue
|
50 |
-
|
51 |
-
# Si la question est un "leaf node"
|
52 |
-
if level == 4 or pd.isna(row[columns_qna[level + 1]]):
|
53 |
-
# Gérer des sous-questions multiples
|
54 |
-
if "\n*Si" in question or "\n *" in question or "\n*" in question:
|
55 |
-
questions = question.replace("\n*Si", "\n*").replace("\n *", "\n*").split("\n*")
|
56 |
-
for subquestion in questions:
|
57 |
-
if len(subquestion.strip()) == 0:
|
58 |
-
continue
|
59 |
-
|
60 |
-
context = []
|
61 |
-
for i in range(level - 1, -1, -1):
|
62 |
-
parent = df.iloc[r, i]
|
63 |
-
if pd.isna(parent):
|
64 |
-
parent = find_parent_ar(df, r, i)
|
65 |
-
if pd.notna(parent):
|
66 |
-
context = [parent] + context
|
67 |
-
|
68 |
-
rows.append({
|
69 |
-
"id": strat_id + len(rows) + 1,
|
70 |
-
"question": " > ".join(context),
|
71 |
-
"answer": subquestion.strip(),
|
72 |
-
"category": category,
|
73 |
-
})
|
74 |
-
else:
|
75 |
-
context = []
|
76 |
-
for i in range(level - 1, -1, -1):
|
77 |
-
parent = df.iloc[r, i]
|
78 |
-
if pd.isna(parent):
|
79 |
-
parent = find_parent_ar(df, r, i)
|
80 |
-
if pd.notna(parent):
|
81 |
-
context = [parent] + context
|
82 |
-
|
83 |
-
rows.append({
|
84 |
-
"id": strat_id + len(rows) + 1,
|
85 |
-
"question": " > ".join(context),
|
86 |
-
"answer": question.strip(),
|
87 |
-
"category": category,
|
88 |
-
})
|
89 |
-
|
90 |
-
return pd.DataFrame(rows)
|
91 |
-
|
92 |
-
def load_excel_and_create_vectorstore_ar(excel_path: str, persist_dir: str = "./chroma_db_ar"):
|
93 |
-
"""
|
94 |
-
Charge les données depuis plusieurs feuilles Excel (version AR),
|
95 |
-
construit & stocke un Chroma VectorStore.
|
96 |
-
"""
|
97 |
-
# 1. Charger les feuilles Excel
|
98 |
-
qna_tree_ar0 = pd.read_excel(excel_path, sheet_name="Prépayé (AR)", skiprows=1).iloc[:, :5]
|
99 |
-
qna_tree_ar1 = pd.read_excel(excel_path, sheet_name="Postpayé (AR)", skiprows=1).iloc[:, :5]
|
100 |
-
qna_tree_ar2 = pd.read_excel(excel_path, sheet_name="Wifi (AR)", skiprows=1).iloc[:, :5]
|
101 |
-
|
102 |
-
# 2. Construire le contexte
|
103 |
-
context_ar0 = create_contextual_ar(qna_tree_ar0, "دفع مسبق", strat_id = 0)
|
104 |
-
context_ar1 = create_contextual_ar(qna_tree_ar1, "دفع لاحق", strat_id = len(context_ar0))
|
105 |
-
context_ar2 = create_contextual_ar(qna_tree_ar2, "واي فاي", strat_id = len(context_ar0) + len(context_ar1))
|
106 |
-
|
107 |
-
# 3. Concaténer les DataFrame
|
108 |
-
context_ar = pd.concat([context_ar0, context_ar1, context_ar2], axis=0)
|
109 |
-
|
110 |
-
# 4. Créer une colonne "context"
|
111 |
-
context_ar["context"] = context_ar.apply(
|
112 |
-
lambda row: f"{row['question']} > {row['answer']}",
|
113 |
-
axis=1
|
114 |
-
)
|
115 |
-
|
116 |
-
# 5. Convertir chaque ligne en Document
|
117 |
-
documents_ar = [
|
118 |
-
Document(
|
119 |
-
page_content=row["context"],
|
120 |
-
metadata={"id": row["id"], "category": row["category"]}
|
121 |
-
)
|
122 |
-
for _, row in context_ar.iterrows()
|
123 |
-
]
|
124 |
-
|
125 |
-
# 6. Créer & persister le vecteur
|
126 |
-
embedding_model_ar = HuggingFaceEmbeddings(model_name="all-MiniLM-L6-v2")
|
127 |
-
vectorstore_ar = Chroma.from_documents(documents_ar, embedding_model_ar, persist_directory=persist_dir)
|
128 |
-
vectorstore_ar.persist()
|
129 |
-
|
130 |
-
return vectorstore_ar
|
131 |
-
|
132 |
-
def load_existing_vectorstore_ar(persist_dir: str = "./chroma_db_ar"):
|
133 |
-
"""
|
134 |
-
Charge un VectorStore Chroma déjà stocké (version AR).
|
135 |
-
"""
|
136 |
-
embedding_model_ar = HuggingFaceEmbeddings(model_name="all-MiniLM-L6-v2")
|
137 |
-
vectorstore_ar = Chroma(
|
138 |
-
persist_directory=persist_dir,
|
139 |
-
embedding_function=embedding_model_ar
|
140 |
-
)
|
141 |
-
return vectorstore_ar
|
142 |
-
|
143 |
-
def retrieve_context_ar(retriever_ar, query, top_k=5):
|
144 |
-
"""
|
145 |
-
Récupère les top_k résultats pour la question (version AR).
|
146 |
-
"""
|
147 |
-
results_ar = retriever_ar.get_relevant_documents(query)
|
148 |
-
context_ar_list = []
|
149 |
-
for _, result in enumerate(results_ar[:top_k], start=1):
|
150 |
-
context_ar_list.append(result.page_content)
|
151 |
-
return context_ar_list
|
152 |
-
|
153 |
-
|
154 |
-
#########################
|
155 |
-
# 2. PROMPT & LLM (AR) #
|
156 |
-
#########################
|
157 |
-
|
158 |
-
prompt_template_ar = PromptTemplate(
|
159 |
-
input_variables=["context", "query"],
|
160 |
-
template=(
|
161 |
-
"""[SYSTEM]
|
162 |
-
أنت مساعد لخدمة عملاء INWI، محترف وخبير ومتعاون. تتقن التعامل مع استفسارات ومشاكل العملاء.
|
163 |
-
استند فقط إلى المعلومات المتوفرة في السياقات التالية دون اختراع معلومات غير موجودة:
|
164 |
-
- استخدم تحية مهذبة وودّية، على سبيل المثال: "مرحباً، أنا المساعد الذكي من إنوي. كيف يمكنني خدمتك اليوم؟"
|
165 |
-
- تعرّف على احتياج العميل واطلب التوضيح إذا لزم الأمر بالاعتماد على المعلومات المتوفرة فقط.
|
166 |
-
- إن لم يكن السؤال ضمن سياق إنوي، أخبر العميل بلطف أنك غير قادر على الإجابة خارج سياق إنوي.
|
167 |
-
- إذا لم تجد إجابة واضحة في السياق، يمكنك إبلاغ العميل بعدم توفر المعلومات واقتراح الاتصال بخدمة العملاء على الرقم 120.
|
168 |
-
- احرص على أن تكون ردودك موجزة وفعالة. وتجنّب اختلاق أي تفاصيل غير موجودة في السياق.
|
169 |
-
- أخبر العميل بأنه يمكنه التواصل معك مجدداً لمزيد من المساعدة.
|
170 |
-
- لا تتحدث عن المنافسين الذين يقدمون نفس خدمات إنوي.
|
171 |
-
- امتنع تماماً عن أي إهانة أو رد على إهانة.
|
172 |
-
- لا تطلب أي معلومات شخصية أو هوية العميل.
|
173 |
-
- وجّه العميل إلى كتالوج موقع إنوي إذا كان سؤاله يتعلق بعروض من الكتالوج.
|
174 |
-
- قدّم حلولاً قياسية للمشكلات التقنية مع عرض الخيارات المتاحة.
|
175 |
-
- قبل إرسال الجواب، تجنب أي تنسيق مثل "[Action] [نص]" واحتفظ فقط بالمعلومات المفيدة.
|
176 |
-
- لا تتحدث عن المواضيع التالية إطلاقاً: [
|
177 |
-
"السياسة", "الانتخابات", "الأحزاب", "الحكومة", "القوانين", "الإصلاحات",
|
178 |
-
"الدين", "العقائد", "الممارسات الدينية", "علم اللاهوت",
|
179 |
-
"الأخلاق", "الجدل", "الفلسفة", "المعايير", "التمييز",
|
180 |
-
"المنافسة", "مقارنة إنوي مع شركات أخرى",
|
181 |
-
"الأمن", "الاحتيال", "الصحة", "الأدوية", "التشخيص الطبي",
|
182 |
-
"التمويل", "الاستثمار", "البورصة", "العملات الرقمية", "البنوك", "التأمين",
|
183 |
-
"العنف", "الكراهية", "المحتوى الفاضح", "الجنس",
|
184 |
-
"المخالفات القانونية", "الوثائق المزورة", "البث غير الشرعي"
|
185 |
-
]
|
186 |
-
إنوي (INWI) هي شركة اتصالات مغربية تقدم خدمات الهاتف المحمول والإنترنت وحلول الاتصالات للأفراد والشركات.
|
187 |
-
تتميز بالتزامها بتوفير خدمات عالية الجودة ومبتكرة، والمساهمة في التطور الرقمي في المغرب.
|
188 |
-
العملاء هم أولويتنا، وهدفنا مساعدتهم وحل مشاكلهم.
|
189 |
-
دورك هو تقديم خدمة عملاء احترافية وفعالة بدون اختراع معلومات من خارج السياق.
|
190 |
-
|
191 |
-
[السياق]
|
192 |
-
{context}
|
193 |
-
|
194 |
-
[سؤال العميل]
|
195 |
-
{query}
|
196 |
-
|
197 |
-
[الإجابة]"""
|
198 |
-
)
|
199 |
-
)
|
200 |
-
|
201 |
-
# Configuration du LLM HuggingFace (AR)
|
202 |
-
os.environ["
|
203 |
-
llm_ar = HuggingFaceHub(
|
204 |
-
repo_id="MBZUAI-Paris/Atlas-Chat-9B",
|
205 |
-
model_kwargs={
|
206 |
-
"temperature": 0.5,
|
207 |
-
"max_length": 500
|
208 |
-
}
|
209 |
-
)
|
210 |
-
|
211 |
-
# Chaîne AR
|
212 |
-
llm_chain_ar = LLMChain(llm=llm_ar, prompt=prompt_template_ar)
|
213 |
-
|
214 |
-
|
215 |
-
#########################
|
216 |
-
# 3. STREAMLIT MAIN APP #
|
217 |
-
#########################
|
218 |
-
|
219 |
-
def main():
|
220 |
-
st.subheader("INWI IA Chatbot - Arabe")
|
221 |
-
|
222 |
-
# Read local image and convert to Base64
|
223 |
-
img_base64 = get_base64_of_bin_file("./img/logo inwi celeverlytics.png")
|
224 |
-
css_logo = f"""
|
225 |
-
<style>
|
226 |
-
[data-testid="stSidebarNav"]::before {{
|
227 |
-
content: "";
|
228 |
-
display: block;
|
229 |
-
margin: 0 auto 20px auto;
|
230 |
-
width: 80%;
|
231 |
-
height: 100px;
|
232 |
-
background-image: url("data:image/png;base64,{img_base64}");
|
233 |
-
background-size: contain;
|
234 |
-
background-repeat: no-repeat;
|
235 |
-
background-position: center;
|
236 |
-
}}
|
237 |
-
</style>
|
238 |
-
"""
|
239 |
-
|
240 |
-
st.markdown(css_logo, unsafe_allow_html=True)
|
241 |
-
|
242 |
-
if "retriever_ar" not in st.session_state:
|
243 |
-
st.session_state["retriever_ar"] = None
|
244 |
-
|
245 |
-
st.sidebar.subheader("Vector Store Options (AR)")
|
246 |
-
|
247 |
-
if st.sidebar.button("Créer la Vector Store (AR)"):
|
248 |
-
with st.spinner("Extraction et création de la vector store AR..."):
|
249 |
-
excel_path = "Chatbot myinwi.xlsx"
|
250 |
-
persist_directory_ar = "./chroma_db_ar"
|
251 |
-
vectorstore_ar = load_excel_and_create_vectorstore_ar(
|
252 |
-
excel_path=excel_path,
|
253 |
-
persist_dir=persist_directory_ar
|
254 |
-
)
|
255 |
-
st.session_state["retriever_ar"] = vectorstore_ar.as_retriever(
|
256 |
-
search_type="mmr",
|
257 |
-
search_kwargs={"k": 5, "lambda_mult": 0.5}
|
258 |
-
)
|
259 |
-
st.success("Vector store FR créée et chargée avec succès !")
|
260 |
-
|
261 |
-
if st.sidebar.button("Charger la Vector Store existante (AR)"):
|
262 |
-
with st.spinner("Chargement de la vector store FR existante..."):
|
263 |
-
persist_directory_ar = "./chroma_db_ar"
|
264 |
-
vectorstore_ar = load_existing_vectorstore_ar(persist_directory_ar)
|
265 |
-
st.session_state["retriever_ar"] = vectorstore_ar.as_retriever(
|
266 |
-
search_type="mmr",
|
267 |
-
search_kwargs={"k": 5, "lambda_mult": 0.5}
|
268 |
-
)
|
269 |
-
st.success("Vector store AR chargée avec succès !")
|
270 |
-
|
271 |
-
st.write("""مرحباً! أنا هنا للإجابة على جميع أسئلتك المتعلقة بخدمات إنوي
|
272 |
-
وعروض الهاتف المحمول والإنترنت، وأي حلول أخرى قد تناسب احتياجاتك (AR).""")
|
273 |
-
|
274 |
-
user_query_ar = st.chat_input("Posez votre question ici (AR)...")
|
275 |
-
|
276 |
-
if user_query_ar:
|
277 |
-
if not st.session_state["retriever_ar"]:
|
278 |
-
st.warning("Veuillez d'abord créer ou charger la Vector Store (AR).")
|
279 |
-
return
|
280 |
-
|
281 |
-
# Récupération du contexte
|
282 |
-
context_ar_list = retrieve_context_ar(st.session_state["retriever_ar"], user_query_ar, top_k=5)
|
283 |
-
|
284 |
-
if context_ar_list:
|
285 |
-
with st.spinner("Génération de la réponse..."):
|
286 |
-
response_ar = llm_chain_ar.run({"context": "\n".join(context_ar_list), "query": user_query_ar})
|
287 |
-
response_ar = response_ar.split("[الإجابة]")[-1]
|
288 |
-
st.write("**سؤال العميل:**")
|
289 |
-
st.write(user_query_ar)
|
290 |
-
st.write("**الإجابة:**")
|
291 |
-
st.write(response_ar)
|
292 |
-
else:
|
293 |
-
st.write("Aucun contexte trouvé pour cette question. Essayez autre chose.")
|
294 |
-
|
295 |
-
if __name__ == "__main__":
|
296 |
-
main()
|
|
|
1 |
+
import streamlit as st
|
2 |
+
import pandas as pd
|
3 |
+
import os
|
4 |
+
from pathlib import Path
|
5 |
+
import base64
|
6 |
+
|
7 |
+
# LangChain & Hugging Face
|
8 |
+
from langchain.embeddings import HuggingFaceEmbeddings
|
9 |
+
from langchain.vectorstores import Chroma
|
10 |
+
from langchain.schema import Document
|
11 |
+
from langchain.prompts import PromptTemplate
|
12 |
+
from langchain.llms import HuggingFaceHub
|
13 |
+
from langchain.chains import LLMChain
|
14 |
+
|
15 |
+
import pysqlite3
|
16 |
+
import sys
|
17 |
+
sys.modules["sqlite3"] = pysqlite3
|
18 |
+
|
19 |
+
#####################
|
20 |
+
# 1. HELPER FUNCTIONS
|
21 |
+
#####################
|
22 |
+
|
23 |
+
def get_base64_of_bin_file(bin_file_path: str) -> str:
|
24 |
+
file_bytes = Path(bin_file_path).read_bytes()
|
25 |
+
return base64.b64encode(file_bytes).decode()
|
26 |
+
|
27 |
+
def find_parent_ar(data, r, col):
|
28 |
+
"""
|
29 |
+
Trouve la question parente pour une ligne et colonne donnée dans le DataFrame (version AR).
|
30 |
+
"""
|
31 |
+
i = r - 1
|
32 |
+
parent = None
|
33 |
+
while i >= 0 and pd.isna(parent):
|
34 |
+
parent = data.iloc[i, col]
|
35 |
+
i -= 1
|
36 |
+
return parent
|
37 |
+
|
38 |
+
def create_contextual_ar(df, category, strat_id=0):
|
39 |
+
"""
|
40 |
+
Crée un DataFrame avec questions-réponses contextuelles (version AR).
|
41 |
+
"""
|
42 |
+
rows = []
|
43 |
+
columns_qna = list(df.columns)
|
44 |
+
|
45 |
+
for r, row in df.iterrows():
|
46 |
+
for level, col in enumerate(df.columns):
|
47 |
+
question = row[col]
|
48 |
+
if pd.isna(question):
|
49 |
+
continue
|
50 |
+
|
51 |
+
# Si la question est un "leaf node"
|
52 |
+
if level == 4 or pd.isna(row[columns_qna[level + 1]]):
|
53 |
+
# Gérer des sous-questions multiples
|
54 |
+
if "\n*Si" in question or "\n *" in question or "\n*" in question:
|
55 |
+
questions = question.replace("\n*Si", "\n*").replace("\n *", "\n*").split("\n*")
|
56 |
+
for subquestion in questions:
|
57 |
+
if len(subquestion.strip()) == 0:
|
58 |
+
continue
|
59 |
+
|
60 |
+
context = []
|
61 |
+
for i in range(level - 1, -1, -1):
|
62 |
+
parent = df.iloc[r, i]
|
63 |
+
if pd.isna(parent):
|
64 |
+
parent = find_parent_ar(df, r, i)
|
65 |
+
if pd.notna(parent):
|
66 |
+
context = [parent] + context
|
67 |
+
|
68 |
+
rows.append({
|
69 |
+
"id": strat_id + len(rows) + 1,
|
70 |
+
"question": " > ".join(context),
|
71 |
+
"answer": subquestion.strip(),
|
72 |
+
"category": category,
|
73 |
+
})
|
74 |
+
else:
|
75 |
+
context = []
|
76 |
+
for i in range(level - 1, -1, -1):
|
77 |
+
parent = df.iloc[r, i]
|
78 |
+
if pd.isna(parent):
|
79 |
+
parent = find_parent_ar(df, r, i)
|
80 |
+
if pd.notna(parent):
|
81 |
+
context = [parent] + context
|
82 |
+
|
83 |
+
rows.append({
|
84 |
+
"id": strat_id + len(rows) + 1,
|
85 |
+
"question": " > ".join(context),
|
86 |
+
"answer": question.strip(),
|
87 |
+
"category": category,
|
88 |
+
})
|
89 |
+
|
90 |
+
return pd.DataFrame(rows)
|
91 |
+
|
92 |
+
def load_excel_and_create_vectorstore_ar(excel_path: str, persist_dir: str = "./chroma_db_ar"):
|
93 |
+
"""
|
94 |
+
Charge les données depuis plusieurs feuilles Excel (version AR),
|
95 |
+
construit & stocke un Chroma VectorStore.
|
96 |
+
"""
|
97 |
+
# 1. Charger les feuilles Excel
|
98 |
+
qna_tree_ar0 = pd.read_excel(excel_path, sheet_name="Prépayé (AR)", skiprows=1).iloc[:, :5]
|
99 |
+
qna_tree_ar1 = pd.read_excel(excel_path, sheet_name="Postpayé (AR)", skiprows=1).iloc[:, :5]
|
100 |
+
qna_tree_ar2 = pd.read_excel(excel_path, sheet_name="Wifi (AR)", skiprows=1).iloc[:, :5]
|
101 |
+
|
102 |
+
# 2. Construire le contexte
|
103 |
+
context_ar0 = create_contextual_ar(qna_tree_ar0, "دفع مسبق", strat_id = 0)
|
104 |
+
context_ar1 = create_contextual_ar(qna_tree_ar1, "دفع لاحق", strat_id = len(context_ar0))
|
105 |
+
context_ar2 = create_contextual_ar(qna_tree_ar2, "واي فاي", strat_id = len(context_ar0) + len(context_ar1))
|
106 |
+
|
107 |
+
# 3. Concaténer les DataFrame
|
108 |
+
context_ar = pd.concat([context_ar0, context_ar1, context_ar2], axis=0)
|
109 |
+
|
110 |
+
# 4. Créer une colonne "context"
|
111 |
+
context_ar["context"] = context_ar.apply(
|
112 |
+
lambda row: f"{row['question']} > {row['answer']}",
|
113 |
+
axis=1
|
114 |
+
)
|
115 |
+
|
116 |
+
# 5. Convertir chaque ligne en Document
|
117 |
+
documents_ar = [
|
118 |
+
Document(
|
119 |
+
page_content=row["context"],
|
120 |
+
metadata={"id": row["id"], "category": row["category"]}
|
121 |
+
)
|
122 |
+
for _, row in context_ar.iterrows()
|
123 |
+
]
|
124 |
+
|
125 |
+
# 6. Créer & persister le vecteur
|
126 |
+
embedding_model_ar = HuggingFaceEmbeddings(model_name="all-MiniLM-L6-v2")
|
127 |
+
vectorstore_ar = Chroma.from_documents(documents_ar, embedding_model_ar, persist_directory=persist_dir)
|
128 |
+
vectorstore_ar.persist()
|
129 |
+
|
130 |
+
return vectorstore_ar
|
131 |
+
|
132 |
+
def load_existing_vectorstore_ar(persist_dir: str = "./chroma_db_ar"):
|
133 |
+
"""
|
134 |
+
Charge un VectorStore Chroma déjà stocké (version AR).
|
135 |
+
"""
|
136 |
+
embedding_model_ar = HuggingFaceEmbeddings(model_name="all-MiniLM-L6-v2")
|
137 |
+
vectorstore_ar = Chroma(
|
138 |
+
persist_directory=persist_dir,
|
139 |
+
embedding_function=embedding_model_ar
|
140 |
+
)
|
141 |
+
return vectorstore_ar
|
142 |
+
|
143 |
+
def retrieve_context_ar(retriever_ar, query, top_k=5):
|
144 |
+
"""
|
145 |
+
Récupère les top_k résultats pour la question (version AR).
|
146 |
+
"""
|
147 |
+
results_ar = retriever_ar.get_relevant_documents(query)
|
148 |
+
context_ar_list = []
|
149 |
+
for _, result in enumerate(results_ar[:top_k], start=1):
|
150 |
+
context_ar_list.append(result.page_content)
|
151 |
+
return context_ar_list
|
152 |
+
|
153 |
+
|
154 |
+
#########################
|
155 |
+
# 2. PROMPT & LLM (AR) #
|
156 |
+
#########################
|
157 |
+
|
158 |
+
prompt_template_ar = PromptTemplate(
|
159 |
+
input_variables=["context", "query"],
|
160 |
+
template=(
|
161 |
+
"""[SYSTEM]
|
162 |
+
أنت مساعد لخدمة عملاء INWI، محترف وخبير ومتعاون. تتقن التعامل مع استفسارات ومشاكل العملاء.
|
163 |
+
استند فقط إلى المعلومات المتوفرة في السياقات التالية دون اختراع معلومات غير موجودة:
|
164 |
+
- استخدم تحية مهذبة وودّية، على سبيل المثال: "مرحباً، أنا المساعد الذكي من إنوي. كيف يمكنني خدمتك اليوم؟"
|
165 |
+
- تعرّف على احتياج العميل واطلب التوضيح إذا لزم الأمر بالاعتماد على المعلومات المتوفرة فقط.
|
166 |
+
- إن لم يكن السؤال ضمن سياق إنوي، أخبر العميل بلطف أنك غير قادر على الإجابة خارج سياق إنوي.
|
167 |
+
- إذا لم تجد إجابة واضحة في السياق، يمكنك إبلاغ العميل بعدم توفر المعلومات واقتراح الاتصال بخدمة العملاء على الرقم 120.
|
168 |
+
- احرص على أن تكون ردودك موجزة وفعالة. وتجنّب اختلاق أي تفاصيل غير موجودة في السياق.
|
169 |
+
- أخبر العميل بأنه يمكنه التواصل معك مجدداً لمزيد من المساعدة.
|
170 |
+
- لا تتحدث عن المنافسين الذين يقدمون نفس خدمات إنوي.
|
171 |
+
- امتنع تماماً عن أي إهانة أو رد على إهانة.
|
172 |
+
- لا تطلب أي معلومات شخصية أو هوية العميل.
|
173 |
+
- وجّه العميل إلى كتالوج موقع إنوي إذا كان سؤاله يتعلق بعروض من الكتالوج.
|
174 |
+
- قدّم حلولاً قياسية للمشكلات التقنية مع عرض الخيارات المتاحة.
|
175 |
+
- قبل إرسال الجواب، تجنب أي تنسيق مثل "[Action] [نص]" واحتفظ فقط بالمعلومات المفيدة.
|
176 |
+
- لا تتحدث عن المواضيع التالية إطلاقاً: [
|
177 |
+
"السياسة", "الانتخابات", "الأحزاب", "الحكومة", "القوانين", "الإصلاحات",
|
178 |
+
"الدين", "العقائد", "الممارسات الدينية", "علم اللاهوت",
|
179 |
+
"الأخلاق", "الجدل", "الفلسفة", "المعايير", "التمييز",
|
180 |
+
"المنافسة", "مقارنة إنوي مع شركات أخرى",
|
181 |
+
"الأمن", "الاحتيال", "الصحة", "الأدوية", "التشخيص الطبي",
|
182 |
+
"التمويل", "الاستثمار", "البورصة", "العملات الرقمية", "البنوك", "التأمين",
|
183 |
+
"العنف", "الكراهية", "المحتوى الفاضح", "الجنس",
|
184 |
+
"المخالفات القانونية", "الوثائق المزورة", "البث غير الشرعي"
|
185 |
+
]
|
186 |
+
إنوي (INWI) هي شركة اتصالات مغربية تقدم خدمات الهاتف المحمول والإنترنت وحلول الاتصالات للأفراد والشركات.
|
187 |
+
تتميز بالتزامها بتوفير خدمات عالية الجودة ومبتكرة، والمساهمة في التطور الرقمي في المغرب.
|
188 |
+
العملاء هم أولويتنا، وهدفنا مساعدتهم وحل مشاكلهم.
|
189 |
+
دورك هو تقديم خدمة عملاء احترافية وفعالة بدون اختراع معلومات من خارج السياق.
|
190 |
+
|
191 |
+
[السياق]
|
192 |
+
{context}
|
193 |
+
|
194 |
+
[سؤال العميل]
|
195 |
+
{query}
|
196 |
+
|
197 |
+
[الإجابة]"""
|
198 |
+
)
|
199 |
+
)
|
200 |
+
|
201 |
+
# Configuration du LLM HuggingFace (AR)
|
202 |
+
os.environ["HUGGINGFACEHUB_API"]
|
203 |
+
llm_ar = HuggingFaceHub(
|
204 |
+
repo_id="MBZUAI-Paris/Atlas-Chat-9B",
|
205 |
+
model_kwargs={
|
206 |
+
"temperature": 0.5,
|
207 |
+
"max_length": 500
|
208 |
+
}
|
209 |
+
)
|
210 |
+
|
211 |
+
# Chaîne AR
|
212 |
+
llm_chain_ar = LLMChain(llm=llm_ar, prompt=prompt_template_ar)
|
213 |
+
|
214 |
+
|
215 |
+
#########################
|
216 |
+
# 3. STREAMLIT MAIN APP #
|
217 |
+
#########################
|
218 |
+
|
219 |
+
def main():
|
220 |
+
st.subheader("INWI IA Chatbot - Arabe")
|
221 |
+
|
222 |
+
# Read local image and convert to Base64
|
223 |
+
img_base64 = get_base64_of_bin_file("./img/logo inwi celeverlytics.png")
|
224 |
+
css_logo = f"""
|
225 |
+
<style>
|
226 |
+
[data-testid="stSidebarNav"]::before {{
|
227 |
+
content: "";
|
228 |
+
display: block;
|
229 |
+
margin: 0 auto 20px auto;
|
230 |
+
width: 80%;
|
231 |
+
height: 100px;
|
232 |
+
background-image: url("data:image/png;base64,{img_base64}");
|
233 |
+
background-size: contain;
|
234 |
+
background-repeat: no-repeat;
|
235 |
+
background-position: center;
|
236 |
+
}}
|
237 |
+
</style>
|
238 |
+
"""
|
239 |
+
|
240 |
+
st.markdown(css_logo, unsafe_allow_html=True)
|
241 |
+
|
242 |
+
if "retriever_ar" not in st.session_state:
|
243 |
+
st.session_state["retriever_ar"] = None
|
244 |
+
|
245 |
+
st.sidebar.subheader("Vector Store Options (AR)")
|
246 |
+
|
247 |
+
if st.sidebar.button("Créer la Vector Store (AR)"):
|
248 |
+
with st.spinner("Extraction et création de la vector store AR..."):
|
249 |
+
excel_path = "Chatbot myinwi.xlsx"
|
250 |
+
persist_directory_ar = "./chroma_db_ar"
|
251 |
+
vectorstore_ar = load_excel_and_create_vectorstore_ar(
|
252 |
+
excel_path=excel_path,
|
253 |
+
persist_dir=persist_directory_ar
|
254 |
+
)
|
255 |
+
st.session_state["retriever_ar"] = vectorstore_ar.as_retriever(
|
256 |
+
search_type="mmr",
|
257 |
+
search_kwargs={"k": 5, "lambda_mult": 0.5}
|
258 |
+
)
|
259 |
+
st.success("Vector store FR créée et chargée avec succès !")
|
260 |
+
|
261 |
+
if st.sidebar.button("Charger la Vector Store existante (AR)"):
|
262 |
+
with st.spinner("Chargement de la vector store FR existante..."):
|
263 |
+
persist_directory_ar = "./chroma_db_ar"
|
264 |
+
vectorstore_ar = load_existing_vectorstore_ar(persist_directory_ar)
|
265 |
+
st.session_state["retriever_ar"] = vectorstore_ar.as_retriever(
|
266 |
+
search_type="mmr",
|
267 |
+
search_kwargs={"k": 5, "lambda_mult": 0.5}
|
268 |
+
)
|
269 |
+
st.success("Vector store AR chargée avec succès !")
|
270 |
+
|
271 |
+
st.write("""مرحباً! أنا هنا للإجابة على جميع أسئلتك المتعلقة بخدمات إنوي
|
272 |
+
وعروض الهاتف المحمول والإنترنت، وأي حلول أخرى قد تناسب احتياجاتك (AR).""")
|
273 |
+
|
274 |
+
user_query_ar = st.chat_input("Posez votre question ici (AR)...")
|
275 |
+
|
276 |
+
if user_query_ar:
|
277 |
+
if not st.session_state["retriever_ar"]:
|
278 |
+
st.warning("Veuillez d'abord créer ou charger la Vector Store (AR).")
|
279 |
+
return
|
280 |
+
|
281 |
+
# Récupération du contexte
|
282 |
+
context_ar_list = retrieve_context_ar(st.session_state["retriever_ar"], user_query_ar, top_k=5)
|
283 |
+
|
284 |
+
if context_ar_list:
|
285 |
+
with st.spinner("Génération de la réponse..."):
|
286 |
+
response_ar = llm_chain_ar.run({"context": "\n".join(context_ar_list), "query": user_query_ar})
|
287 |
+
response_ar = response_ar.split("[الإجابة]")[-1]
|
288 |
+
st.write("**سؤال العميل:**")
|
289 |
+
st.write(user_query_ar)
|
290 |
+
st.write("**الإجابة:**")
|
291 |
+
st.write(response_ar)
|
292 |
+
else:
|
293 |
+
st.write("Aucun contexte trouvé pour cette question. Essayez autre chose.")
|
294 |
+
|
295 |
+
if __name__ == "__main__":
|
296 |
+
main()
|