AxL95 commited on
Commit
3cf48cf
·
verified ·
1 Parent(s): 6fb9d3c

Update chat.py

Browse files
Files changed (1) hide show
  1. chat.py +366 -366
chat.py CHANGED
@@ -1,367 +1,367 @@
1
- from fastapi import APIRouter, Request, HTTPException, Depends
2
- from fastapi.responses import JSONResponse
3
- from datetime import datetime
4
- from bson.objectid import ObjectId
5
- from huggingface_hub import InferenceClient
6
- from sentence_transformers import SentenceTransformer
7
- from sklearn.metrics.pairwise import cosine_similarity
8
- import re
9
-
10
- from auth import get_current_user
11
- from database import get_db
12
- from config import HF_TOKEN, MAX_TOKENS, EMBEDDING_MODEL
13
-
14
- router = APIRouter(prefix="/api", tags=["Chat"])
15
- db=get_db()
16
- conversation_history = {}
17
- hf_client = InferenceClient(token=HF_TOKEN)
18
-
19
- try:
20
- from langchain_community.embeddings import HuggingFaceEmbeddings
21
- embedding_model = HuggingFaceEmbeddings(model_name=EMBEDDING_MODEL)
22
- print("Modèle d'embedding médical chargé avec succès")
23
- except Exception as e:
24
- print(f"Erreur chargement embedding: {str(e)}")
25
- embedding_model = None
26
-
27
- # Fonctions de RAG
28
- def retrieve_relevant_context(query, embedding_model, mongo_collection, k=5):
29
- query_embedding = embedding_model.embed_query(query)
30
-
31
- docs = list(mongo_collection.find({}, {"text": 1, "embedding": 1}))
32
-
33
- print(f"[DEBUG] Recherche de contexte pour: '{query}'")
34
- print(f"[DEBUG] {len(docs)} documents trouvés dans la base de données")
35
-
36
- if not docs:
37
- print("[DEBUG] Aucun document dans la collection. RAG désactivé.")
38
- return ""
39
-
40
- similarities = []
41
- for i, doc in enumerate(docs):
42
- if "embedding" not in doc or not doc["embedding"]:
43
- print(f"[DEBUG] Document {i} sans embedding")
44
- continue
45
-
46
- sim = cosine_similarity([query_embedding], [doc["embedding"]])[0][0]
47
- similarities.append((sim, i, doc["text"]))
48
-
49
- similarities.sort(reverse=True)
50
-
51
- print("\n=== CONTEXTE SÉLECTIONNÉ ===")
52
- top_k_docs = []
53
- for i, (score, idx, text) in enumerate(similarities[:k]):
54
- doc_preview = text[:100] + "..." if len(text) > 100 else text
55
- print(f"Document #{i+1} (score: {score:.4f}): {doc_preview}")
56
- top_k_docs.append(text)
57
- print("==========================\n")
58
-
59
- return "\n\n".join(top_k_docs)
60
-
61
- @router.post("/chat")
62
- async def chat(request: Request):
63
- global conversation_history
64
-
65
- data = await request.json()
66
- user_message = data.get("message", "").strip()
67
- conversation_id = data.get("conversation_id")
68
- skip_save = data.get("skip_save", False)
69
-
70
-
71
- if not skip_save and conversation_id and current_user:
72
- db.messages.insert_one({
73
- "conversation_id": conversation_id,
74
- "user_id": str(current_user["_id"]),
75
- "sender": "user",
76
- "text": user_message,
77
- "timestamp": datetime.utcnow()
78
- })
79
-
80
-
81
-
82
- if not user_message:
83
- raise HTTPException(status_code=400, detail="Le champ 'message' est requis.")
84
-
85
- current_user = None
86
- try:
87
- current_user = await get_current_user(request)
88
- except HTTPException:
89
- pass
90
-
91
- current_tokens = 0
92
- message_tokens = 0
93
- if current_user and conversation_id:
94
- conv = db.conversations.find_one({
95
- "_id": ObjectId(conversation_id),
96
- "user_id": str(current_user["_id"])
97
- })
98
- if conv:
99
- current_tokens = conv.get("token_count", 0)
100
- message_tokens = int(len(user_message.split()) * 1.3)
101
- MAX_TOKENS = 2000
102
- if current_tokens + message_tokens > MAX_TOKENS:
103
- return JSONResponse({
104
- "error": "token_limit_exceeded",
105
- "message": "Cette conversation a atteint sa limite de taille. Veuillez en créer une nouvelle.",
106
- "tokens_used": current_tokens,
107
- "tokens_limit": MAX_TOKENS
108
- }, status_code=403)
109
-
110
-
111
-
112
- is_history_question = any(
113
- phrase in user_message.lower()
114
- for phrase in [
115
- "ma première question", "ma précédente question", "ma dernière question",
116
- "ce que j'ai demandé", "j'ai dit quoi", "quelles questions",
117
- "c'était quoi ma", "quelle était ma", "mes questions", "questions précédentes"
118
- ]
119
- ) or re.search(r"(?:quelle|quelles|quoi).*?(\d+)[a-z]{2}.*?question", user_message.lower()) \
120
- or re.search(r"derni[eè]re question", user_message.lower()) \
121
- or re.search(r"premi[eè]re question", user_message.lower()) \
122
- or re.search(r"question pr[eé]c[eé]dente", user_message.lower()) \
123
- or re.search(r"(toutes|liste|quelles|quoi).*questions", user_message.lower())
124
-
125
- if conversation_id not in conversation_history:
126
- conversation_history[conversation_id] = []
127
- if current_user and conversation_id:
128
- previous_messages = list(db.messages.find(
129
- {"conversation_id": conversation_id}
130
- ).sort("timestamp", 1))
131
-
132
- for msg in previous_messages:
133
- if msg["sender"] == "user":
134
- conversation_history[conversation_id].append(f"Question : {msg['text']}")
135
- else:
136
- conversation_history[conversation_id].append(f"Réponse : {msg['text']}")
137
-
138
- if is_history_question:
139
- actual_questions = []
140
-
141
- if conversation_id in conversation_history:
142
- for msg in conversation_history[conversation_id]:
143
- if msg.startswith("Question : "):
144
- q_text = msg.replace("Question : ", "")
145
- is_meta = any(phrase in q_text.lower() for phrase in [
146
- "ma première question", "ma précédente question", "ma dernière question",
147
- "ce que j'ai demandé", "j'ai dit quoi", "quelles questions",
148
- "c'était quoi ma", "quelle était ma", "mes questions"
149
- ]) or re.search(r"(?:quelle|quelles|quoi).*?(\d+)[a-z]{2}.*?question", q_text.lower()) \
150
- or re.search(r"derni[eè]re question", q_text.lower()) \
151
- or re.search(r"premi[eè]re question", q_text.lower()) \
152
- or re.search(r"question pr[eé]c[eé]dente", q_text.lower()) \
153
- or re.search(r"(toutes|liste|quelles|quoi).*questions", q_text.lower())
154
- if not is_meta:
155
- actual_questions.append(q_text)
156
-
157
- if not actual_questions:
158
- return JSONResponse({
159
- "response": "Vous n'avez pas encore posé de question dans cette conversation. C'est notre premier échange."
160
- })
161
-
162
- if re.search(r"derni[eè]re question", user_message.lower()):
163
- return JSONResponse({
164
- "response": f"Votre dernière question était : « {actual_questions[-1]} »"
165
- })
166
-
167
- if re.search(r"question pr[eé]c[eé]dente", user_message.lower()):
168
- if len(actual_questions) >= 2:
169
- return JSONResponse({
170
- "response": f"Votre question précédente était : « {actual_questions[-2]} »"
171
- })
172
- else:
173
- return JSONResponse({
174
- "response": "Il n'y a pas encore de question précédente dans notre conversation."
175
- })
176
-
177
- if re.search(r"premi[eè]re question", user_message.lower()) or any(p in user_message.lower() for p in ["première question", "1ère question", "1ere question"]):
178
- return JSONResponse({
179
- "response": f"Votre première question était : « {actual_questions[0]} »"
180
- })
181
-
182
- match_nth = re.search(r"(?:quelle|quelles|quoi).*?(\d+)[a-z]{2}.*?question", user_message.lower())
183
- if match_nth:
184
- try:
185
- question_number = int(match_nth.group(1))
186
- if 0 < question_number <= len(actual_questions):
187
- return JSONResponse({
188
- "response": f"Votre {question_number}{'ère' if question_number == 1 else 'ème'} question était : « {actual_questions[question_number-1]} »"
189
- })
190
- else:
191
- return JSONResponse({
192
- "response": f"Vous n'avez pas encore posé {question_number} questions dans cette conversation."
193
- })
194
- except:
195
- pass
196
-
197
- question_number = None
198
- if any(p in user_message.lower() for p in ["deuxième question", "2ème question", "2eme question", "seconde question"]):
199
- question_number = 2
200
- else:
201
- match = re.search(r'(\d+)[eèiéê]*m*e* question', user_message.lower())
202
- if match:
203
- try:
204
- question_number = int(match.group(1))
205
- except:
206
- pass
207
-
208
- if question_number is not None:
209
- if 0 < question_number <= len(actual_questions):
210
- suffix = "ère" if question_number == 1 else "ème"
211
- return JSONResponse({
212
- "response": f"Votre {question_number}{suffix} question était : « {actual_questions[question_number-1]} »"
213
- })
214
- else:
215
- return JSONResponse({
216
- "response": f"Vous n'avez pas encore posé {question_number} questions dans cette conversation."
217
- })
218
-
219
- if len(actual_questions) == 1:
220
- return JSONResponse({
221
- "response": f"Vous avez posé une seule question jusqu'à présent : « {actual_questions[0]} »"
222
- })
223
- else:
224
- question_list = "\n".join([f"{i+1}. {q}" for i, q in enumerate(actual_questions)])
225
- return JSONResponse({
226
- "response": f"Voici les questions que vous avez posées dans cette conversation :\n\n{question_list}"
227
- })
228
-
229
- context = None
230
- if not is_history_question and embedding_model:
231
- context = retrieve_relevant_context(user_message, embedding_model, db.connaissances, k=5)
232
- if context and conversation_id:
233
- conversation_history[conversation_id].append(f"Contexte : {context}")
234
-
235
- if conversation_id:
236
- conversation_history[conversation_id].append(f"Question : {user_message}")
237
-
238
- system_prompt = (
239
- "Tu es un chatbot spécialisé dans la santé mentale, et plus particulièrement la schizophrénie. "
240
- "Tu réponds de façon fiable, claire et empathique, en t'appuyant uniquement sur des sources médicales et en français. "
241
- "IMPORTANT: Fais particulièrement attention aux questions de suivi. Si l'utilisateur pose une question qui ne précise "
242
- "pas clairement le sujet mais qui fait suite à votre échange précédent, comprends que cette question fait référence "
243
- "au contexte de la conversation précédente. Par exemple, si l'utilisateur demande 'Comment les traite-t-on?' après "
244
- "avoir parlé des symptômes positifs de la schizophrénie, ta réponse doit porter spécifiquement sur le traitement "
245
- "des symptômes positifs, et non sur la schizophrénie en général.IMPORTANT: Vise tes réponses sous forme de Markdown."
246
- )
247
-
248
- enriched_context = ""
249
-
250
- if conversation_id in conversation_history:
251
- actual_questions = []
252
- for msg in conversation_history[conversation_id]:
253
- if msg.startswith("Question : "):
254
- q_text = msg.replace("Question : ", "")
255
- is_meta = any(phrase in q_text.lower() for phrase in [
256
- "ma première question", "ma précédente question", "ma dernière question",
257
- "ce que j'ai demandé", "j'ai dit quoi", "quelles questions",
258
- "c'était quoi ma", "quelle était ma", "mes questions"
259
- ]) or re.search(r"(?:quelle|quelles|quoi).*?(\d+)[a-z]{2}.*?question", q_text.lower()) \
260
- or re.search(r"derni[eè]re question", q_text.lower()) \
261
- or re.search(r"premi[eè]re question", q_text.lower()) \
262
- or re.search(r"question pr[eé]c[eé]dente", q_text.lower()) \
263
- or re.search(r"(toutes|liste|quelles|quoi).*questions", q_text.lower())
264
- if not is_meta and q_text != user_message:
265
- actual_questions.append(q_text)
266
-
267
- if actual_questions:
268
- recent_questions = actual_questions[-5:]
269
- enriched_context += "Historique récent des questions:\n"
270
- for i, q in enumerate(recent_questions):
271
- enriched_context += f"- Question précédente {len(recent_questions)-i}: {q}\n"
272
- enriched_context += "\n"
273
-
274
- if context:
275
- enriched_context += "Contexte médical pertinent:\n"
276
- enriched_context += context
277
- enriched_context += "\n\n"
278
-
279
- if enriched_context:
280
- system_prompt += (
281
- f"\n\n{enriched_context}\n\n"
282
- "Utilise ces informations pour répondre de manière plus précise et contextuelle. "
283
- "Ne pas inventer d'informations. Si tu ne sais pas, redirige vers un professionnel de santé. "
284
- "Tu dois donner une réponse complète, bien structurée et ne jamais couper ta réponse brutalement. "
285
- "Si tu n'as pas assez de place pour finir, indique-le clairement à l'utilisateur."
286
- )
287
- else:
288
- system_prompt += (
289
- "Tu dois répondre uniquement à partir de connaissances médicales factuelles. "
290
- "Si tu ne sais pas répondre, indique-le clairement et suggère de consulter un professionnel de santé. "
291
- "Tu dois donner une réponse complète et bien structurée."
292
- )
293
-
294
- messages = [{"role": "system", "content": system_prompt}]
295
-
296
- if conversation_id and len(conversation_history.get(conversation_id, [])) > 0:
297
- history = conversation_history[conversation_id]
298
- for i in range(0, min(20, len(history)-1), 2):
299
- if i+1 < len(history):
300
- if history[i].startswith("Question :"):
301
- user_text = history[i].replace("Question : ", "")
302
- messages.append({"role": "user", "content": user_text})
303
-
304
- if history[i+1].startswith("Réponse :"):
305
- assistant_text = history[i+1].replace("Réponse : ", "")
306
- messages.append({"role": "assistant", "content": assistant_text})
307
-
308
- messages.append({"role": "user", "content": user_message})
309
-
310
- try:
311
- completion = hf_client.chat.completions.create(
312
- model="mistralai/Mistral-Small-24B-Instruct-2501",
313
- messages=messages,
314
- max_tokens=1024,
315
- temperature=0.7,
316
- timeout=15,
317
- )
318
- bot_response = completion.choices[0].message["content"].strip()
319
- if bot_response.endswith((".", "!", "?")) == False and len(bot_response) > 500:
320
- bot_response += "\n\n(Note: Ma réponse a été limitée par des contraintes de taille. N'hésitez pas à me demander de poursuivre si vous souhaitez plus d'informations.)"
321
- except Exception:
322
- try:
323
- fallback = hf_client.text_generation(
324
- model="mistralai/Mistral-7B-Instruct-v0.3",
325
- prompt=f"<s>[INST] {system_prompt}\n\nQuestion: {user_message} [/INST]",
326
- max_new_tokens=512,
327
- temperature=0.7
328
- )
329
- bot_response = fallback
330
- except Exception:
331
- bot_response = "Je suis désolé, je rencontre actuellement des difficultés techniques. Pourriez-vous reformuler votre question ou réessayer dans quelques instants?"
332
-
333
- if conversation_id:
334
- conversation_history[conversation_id].append(f"Réponse : {bot_response}")
335
-
336
- if len(conversation_history[conversation_id]) > 50:
337
- conversation_history[conversation_id] = conversation_history[conversation_id][-50:]
338
-
339
- if not skip_save and conversation_id and current_user:
340
- db.messages.insert_one({
341
- "conversation_id": conversation_id,
342
- "user_id": str(current_user["_id"]),
343
- "sender": "bot",
344
- "text": bot_response,
345
- "timestamp": datetime.utcnow()
346
- })
347
-
348
- if conversation_id and current_user:
349
- db.messages.insert_one({
350
- "conversation_id": conversation_id,
351
- "user_id": str(current_user["_id"]),
352
- "sender": "bot",
353
- "text": bot_response,
354
- "timestamp": datetime.utcnow()
355
- })
356
- response_tokens = int(len(bot_response.split()) * 1.3)
357
- total_tokens = current_tokens + message_tokens + response_tokens
358
- db.conversations.update_one(
359
- {"_id": ObjectId(conversation_id)},
360
- {"$set": {
361
- "last_message": bot_response,
362
- "updated_at": datetime.utcnow(),
363
- "token_count": total_tokens
364
- }}
365
- )
366
-
367
  return {"response": bot_response}
 
1
+ from fastapi import APIRouter, Request, HTTPException, Depends
2
+ from fastapi.responses import JSONResponse
3
+ from datetime import datetime
4
+ from bson.objectid import ObjectId
5
+ from huggingface_hub import InferenceClient
6
+ from sentence_transformers import SentenceTransformer
7
+ from sklearn.metrics.pairwise import cosine_similarity
8
+ import re
9
+
10
+ from auth import get_current_user
11
+ from database import get_db
12
+ from config import HF_TOKEN, MAX_TOKENS, EMBEDDING_MODEL
13
+
14
+ router = APIRouter(prefix="/api", tags=["Chat"])
15
+ db=get_db()
16
+ conversation_history = {}
17
+ hf_client = InferenceClient(token=HF_TOKEN)
18
+
19
+ try:
20
+ from langchain_community.embeddings import HuggingFaceEmbeddings
21
+ embedding_model = HuggingFaceEmbeddings(model_name=EMBEDDING_MODEL)
22
+ print("Modèle d'embedding médical chargé avec succès")
23
+ except Exception as e:
24
+ print(f"Erreur chargement embedding: {str(e)}")
25
+ embedding_model = None
26
+
27
+ # Fonctions de RAG
28
+ def retrieve_relevant_context(query, embedding_model, mongo_collection, k=5):
29
+ query_embedding = embedding_model.embed_query(query)
30
+
31
+ docs = list(mongo_collection.find({}, {"text": 1, "embedding": 1}))
32
+
33
+ print(f"[DEBUG] Recherche de contexte pour: '{query}'")
34
+ print(f"[DEBUG] {len(docs)} documents trouvés dans la base de données")
35
+
36
+ if not docs:
37
+ print("[DEBUG] Aucun document dans la collection. RAG désactivé.")
38
+ return ""
39
+
40
+ similarities = []
41
+ for i, doc in enumerate(docs):
42
+ if "embedding" not in doc or not doc["embedding"]:
43
+ print(f"[DEBUG] Document {i} sans embedding")
44
+ continue
45
+
46
+ sim = cosine_similarity([query_embedding], [doc["embedding"]])[0][0]
47
+ similarities.append((sim, i, doc["text"]))
48
+
49
+ similarities.sort(reverse=True)
50
+
51
+ print("\n=== CONTEXTE SÉLECTIONNÉ ===")
52
+ top_k_docs = []
53
+ for i, (score, idx, text) in enumerate(similarities[:k]):
54
+ doc_preview = text[:100] + "..." if len(text) > 100 else text
55
+ print(f"Document #{i+1} (score: {score:.4f}): {doc_preview}")
56
+ top_k_docs.append(text)
57
+ print("==========================\n")
58
+
59
+ return "\n\n".join(top_k_docs)
60
+
61
+ @router.post("/chat")
62
+ async def chat(request: Request):
63
+ global conversation_history
64
+
65
+ data = await request.json()
66
+ user_message = data.get("message", "").strip()
67
+ conversation_id = data.get("conversation_id")
68
+ skip_save = data.get("skip_save", False)
69
+
70
+
71
+ if not skip_save and conversation_id and current_user:
72
+ db.messages.insert_one({
73
+ "conversation_id": conversation_id,
74
+ "user_id": str(current_user["_id"]),
75
+ "sender": "user",
76
+ "text": user_message,
77
+ "timestamp": datetime.utcnow()
78
+ })
79
+
80
+
81
+
82
+ if not user_message:
83
+ raise HTTPException(status_code=400, detail="Le champ 'message' est requis.")
84
+
85
+ current_user = None
86
+ try:
87
+ current_user = await get_current_user(request)
88
+ except HTTPException:
89
+ pass
90
+
91
+ current_tokens = 0
92
+ message_tokens = 0
93
+ if current_user and conversation_id:
94
+ conv = db.conversations.find_one({
95
+ "_id": ObjectId(conversation_id),
96
+ "user_id": str(current_user["_id"])
97
+ })
98
+ if conv:
99
+ current_tokens = conv.get("token_count", 0)
100
+ message_tokens = int(len(user_message.split()) * 1.3)
101
+ MAX_TOKENS = 2000
102
+ if current_tokens + message_tokens > MAX_TOKENS:
103
+ return JSONResponse({
104
+ "error": "token_limit_exceeded",
105
+ "message": "Cette conversation a atteint sa limite de taille. Veuillez en créer une nouvelle.",
106
+ "tokens_used": current_tokens,
107
+ "tokens_limit": MAX_TOKENS
108
+ }, status_code=403)
109
+
110
+
111
+
112
+ is_history_question = any(
113
+ phrase in user_message.lower()
114
+ for phrase in [
115
+ "ma première question", "ma précédente question", "ma dernière question",
116
+ "ce que j'ai demandé", "j'ai dit quoi", "quelles questions",
117
+ "c'était quoi ma", "quelle était ma", "mes questions", "questions précédentes"
118
+ ]
119
+ ) or re.search(r"(?:quelle|quelles|quoi).*?(\d+)[a-z]{2}.*?question", user_message.lower()) \
120
+ or re.search(r"derni[eè]re question", user_message.lower()) \
121
+ or re.search(r"premi[eè]re question", user_message.lower()) \
122
+ or re.search(r"question pr[eé]c[eé]dente", user_message.lower()) \
123
+ or re.search(r"(toutes|liste|quelles|quoi).*questions", user_message.lower())
124
+
125
+ if conversation_id not in conversation_history:
126
+ conversation_history[conversation_id] = []
127
+ if current_user and conversation_id:
128
+ previous_messages = list(db.messages.find(
129
+ {"conversation_id": conversation_id}
130
+ ).sort("timestamp", 1))
131
+
132
+ for msg in previous_messages:
133
+ if msg["sender"] == "user":
134
+ conversation_history[conversation_id].append(f"Question : {msg['text']}")
135
+ else:
136
+ conversation_history[conversation_id].append(f"Réponse : {msg['text']}")
137
+
138
+ if is_history_question:
139
+ actual_questions = []
140
+
141
+ if conversation_id in conversation_history:
142
+ for msg in conversation_history[conversation_id]:
143
+ if msg.startswith("Question : "):
144
+ q_text = msg.replace("Question : ", "")
145
+ is_meta = any(phrase in q_text.lower() for phrase in [
146
+ "ma première question", "ma précédente question", "ma dernière question",
147
+ "ce que j'ai demandé", "j'ai dit quoi", "quelles questions",
148
+ "c'était quoi ma", "quelle était ma", "mes questions"
149
+ ]) or re.search(r"(?:quelle|quelles|quoi).*?(\d+)[a-z]{2}.*?question", q_text.lower()) \
150
+ or re.search(r"derni[eè]re question", q_text.lower()) \
151
+ or re.search(r"premi[eè]re question", q_text.lower()) \
152
+ or re.search(r"question pr[eé]c[eé]dente", q_text.lower()) \
153
+ or re.search(r"(toutes|liste|quelles|quoi).*questions", q_text.lower())
154
+ if not is_meta:
155
+ actual_questions.append(q_text)
156
+
157
+ if not actual_questions:
158
+ return JSONResponse({
159
+ "response": "Vous n'avez pas encore posé de question dans cette conversation. C'est notre premier échange."
160
+ })
161
+
162
+ if re.search(r"derni[eè]re question", user_message.lower()):
163
+ return JSONResponse({
164
+ "response": f"Votre dernière question était : « {actual_questions[-1]} »"
165
+ })
166
+
167
+ if re.search(r"question pr[eé]c[eé]dente", user_message.lower()):
168
+ if len(actual_questions) >= 2:
169
+ return JSONResponse({
170
+ "response": f"Votre question précédente était : « {actual_questions[-2]} »"
171
+ })
172
+ else:
173
+ return JSONResponse({
174
+ "response": "Il n'y a pas encore de question précédente dans notre conversation."
175
+ })
176
+
177
+ if re.search(r"premi[eè]re question", user_message.lower()) or any(p in user_message.lower() for p in ["première question", "1ère question", "1ere question"]):
178
+ return JSONResponse({
179
+ "response": f"Votre première question était : « {actual_questions[0]} »"
180
+ })
181
+
182
+ match_nth = re.search(r"(?:quelle|quelles|quoi).*?(\d+)[a-z]{2}.*?question", user_message.lower())
183
+ if match_nth:
184
+ try:
185
+ question_number = int(match_nth.group(1))
186
+ if 0 < question_number <= len(actual_questions):
187
+ return JSONResponse({
188
+ "response": f"Votre {question_number}{'ère' if question_number == 1 else 'ème'} question était : « {actual_questions[question_number-1]} »"
189
+ })
190
+ else:
191
+ return JSONResponse({
192
+ "response": f"Vous n'avez pas encore posé {question_number} questions dans cette conversation."
193
+ })
194
+ except:
195
+ pass
196
+
197
+ question_number = None
198
+ if any(p in user_message.lower() for p in ["deuxième question", "2ème question", "2eme question", "seconde question"]):
199
+ question_number = 2
200
+ else:
201
+ match = re.search(r'(\d+)[eèiéê]*m*e* question', user_message.lower())
202
+ if match:
203
+ try:
204
+ question_number = int(match.group(1))
205
+ except:
206
+ pass
207
+
208
+ if question_number is not None:
209
+ if 0 < question_number <= len(actual_questions):
210
+ suffix = "ère" if question_number == 1 else "ème"
211
+ return JSONResponse({
212
+ "response": f"Votre {question_number}{suffix} question était : « {actual_questions[question_number-1]} »"
213
+ })
214
+ else:
215
+ return JSONResponse({
216
+ "response": f"Vous n'avez pas encore posé {question_number} questions dans cette conversation."
217
+ })
218
+
219
+ if len(actual_questions) == 1:
220
+ return JSONResponse({
221
+ "response": f"Vous avez posé une seule question jusqu'à présent : « {actual_questions[0]} »"
222
+ })
223
+ else:
224
+ question_list = "\n".join([f"{i+1}. {q}" for i, q in enumerate(actual_questions)])
225
+ return JSONResponse({
226
+ "response": f"Voici les questions que vous avez posées dans cette conversation :\n\n{question_list}"
227
+ })
228
+
229
+ context = None
230
+ if not is_history_question and embedding_model:
231
+ context = retrieve_relevant_context(user_message, embedding_model, db.connaissances, k=5)
232
+ if context and conversation_id:
233
+ conversation_history[conversation_id].append(f"Contexte : {context}")
234
+
235
+ if conversation_id:
236
+ conversation_history[conversation_id].append(f"Question : {user_message}")
237
+
238
+ system_prompt = (
239
+ "Tu es un chatbot spécialisé dans la santé mentale, et plus particulièrement la schizophrénie. "
240
+ "Tu réponds de façon fiable, claire et empathique, en t'appuyant uniquement sur des sources médicales et en français. "
241
+ "IMPORTANT: Fais particulièrement attention aux questions de suivi. Si l'utilisateur pose une question qui ne précise "
242
+ "pas clairement le sujet mais qui fait suite à votre échange précédent, comprends que cette question fait référence "
243
+ "au contexte de la conversation précédente. Par exemple, si l'utilisateur demande 'Comment les traite-t-on?' après "
244
+ "avoir parlé des symptômes positifs de la schizophrénie, ta réponse doit porter spécifiquement sur le traitement "
245
+ "des symptômes positifs, et non sur la schizophrénie en général.IMPORTANT: Vise tes réponses sous forme de Markdown."
246
+ )
247
+
248
+ enriched_context = ""
249
+
250
+ if conversation_id in conversation_history:
251
+ actual_questions = []
252
+ for msg in conversation_history[conversation_id]:
253
+ if msg.startswith("Question : "):
254
+ q_text = msg.replace("Question : ", "")
255
+ is_meta = any(phrase in q_text.lower() for phrase in [
256
+ "ma première question", "ma précédente question", "ma dernière question",
257
+ "ce que j'ai demandé", "j'ai dit quoi", "quelles questions",
258
+ "c'était quoi ma", "quelle était ma", "mes questions"
259
+ ]) or re.search(r"(?:quelle|quelles|quoi).*?(\d+)[a-z]{2}.*?question", q_text.lower()) \
260
+ or re.search(r"derni[eè]re question", q_text.lower()) \
261
+ or re.search(r"premi[eè]re question", q_text.lower()) \
262
+ or re.search(r"question pr[eé]c[eé]dente", q_text.lower()) \
263
+ or re.search(r"(toutes|liste|quelles|quoi).*questions", q_text.lower())
264
+ if not is_meta and q_text != user_message:
265
+ actual_questions.append(q_text)
266
+
267
+ if actual_questions:
268
+ recent_questions = actual_questions[-5:]
269
+ enriched_context += "Historique récent des questions:\n"
270
+ for i, q in enumerate(recent_questions):
271
+ enriched_context += f"- Question précédente {len(recent_questions)-i}: {q}\n"
272
+ enriched_context += "\n"
273
+
274
+ if context:
275
+ enriched_context += "Contexte médical pertinent:\n"
276
+ enriched_context += context
277
+ enriched_context += "\n\n"
278
+
279
+ if enriched_context:
280
+ system_prompt += (
281
+ f"\n\n{enriched_context}\n\n"
282
+ "Utilise ces informations pour répondre de manière plus précise et contextuelle. "
283
+ "Ne pas inventer d'informations. Si tu ne sais pas, redirige vers un professionnel de santé. "
284
+ "Tu dois donner une réponse complète, bien structurée et ne jamais couper ta réponse brutalement. "
285
+ "Si tu n'as pas assez de place pour finir, indique-le clairement à l'utilisateur."
286
+ )
287
+ else:
288
+ system_prompt += (
289
+ "Tu dois répondre uniquement à partir de connaissances médicales factuelles. "
290
+ "Si tu ne sais pas répondre, indique-le clairement et suggère de consulter un professionnel de santé. "
291
+ "Tu dois donner une réponse complète et bien structurée."
292
+ )
293
+
294
+ messages = [{"role": "system", "content": system_prompt}]
295
+
296
+ if conversation_id and len(conversation_history.get(conversation_id, [])) > 0:
297
+ history = conversation_history[conversation_id]
298
+ for i in range(0, min(20, len(history)-1), 2):
299
+ if i+1 < len(history):
300
+ if history[i].startswith("Question :"):
301
+ user_text = history[i].replace("Question : ", "")
302
+ messages.append({"role": "user", "content": user_text})
303
+
304
+ if history[i+1].startswith("Réponse :"):
305
+ assistant_text = history[i+1].replace("Réponse : ", "")
306
+ messages.append({"role": "assistant", "content": assistant_text})
307
+
308
+ messages.append({"role": "user", "content": user_message})
309
+
310
+ try:
311
+ completion = hf_client.chat.completions.create(
312
+ model="mistralai/Mistral-Small-24B-Instruct-2501",
313
+ messages=messages,
314
+ max_tokens=1024,
315
+ temperature=0.7,
316
+ timeout=15,
317
+ )
318
+ bot_response = completion.choices[0].message["content"].strip()
319
+ if bot_response.endswith((".", "!", "?")) == False and len(bot_response) > 500:
320
+ bot_response += "\n\n(Note: Ma réponse a été limitée par des contraintes de taille. N'hésitez pas à me demander de poursuivre si vous souhaitez plus d'informations.)"
321
+ except Exception:
322
+ try:
323
+ fallback = hf_client.text_generation(
324
+ model="mistralai/Mistral-7B-Instruct-v0.3",
325
+ prompt=f"<s>[INST] {system_prompt}\n\nQuestion: {user_message} [/INST]",
326
+ max_new_tokens=512,
327
+ temperature=0.7
328
+ )
329
+ bot_response = fallback
330
+ except Exception:
331
+ bot_response = "Je suis désolé, je rencontre actuellement des difficultés techniques. Pourriez-vous reformuler votre question ou réessayer dans quelques instants?"
332
+
333
+ if conversation_id:
334
+ conversation_history[conversation_id].append(f"Réponse : {bot_response}")
335
+
336
+ if len(conversation_history[conversation_id]) > 50:
337
+ conversation_history[conversation_id] = conversation_history[conversation_id][-50:]
338
+
339
+ if not skip_save and conversation_id and current_user:
340
+ db.messages.insert_one({
341
+ "conversation_id": conversation_id,
342
+ "user_id": str(current_user["_id"]),
343
+ "sender": "bot",
344
+ "text": bot_response,
345
+ "timestamp": datetime.utcnow()
346
+ })
347
+
348
+ if conversation_id and current_user:
349
+ db.messages.insert_one({
350
+ "conversation_id": conversation_id,
351
+ "user_id": str(current_user["_id"]),
352
+ "sender": "bot",
353
+ "text": bot_response,
354
+ "timestamp": datetime.utcnow()
355
+ })
356
+ response_tokens = int(len(bot_response.split()) * 1.3)
357
+ total_tokens = current_tokens + message_tokens + response_tokens
358
+ db.conversations.update_one(
359
+ {"_id": ObjectId(conversation_id)},
360
+ {"$set": {
361
+ "last_message": bot_response,
362
+ "updated_at": datetime.utcnow(),
363
+ "token_count": total_tokens
364
+ }}
365
+ )
366
+
367
  return {"response": bot_response}