MathieuGAL commited on
Commit
2f32984
·
verified ·
1 Parent(s): c8ca9b1

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +127 -53
app.py CHANGED
@@ -1,12 +1,27 @@
1
- import os
2
  import pandas as pd
3
  import chromadb
 
 
4
  from google import genai
5
  from sentence_transformers import SentenceTransformer, CrossEncoder
6
  from typing import List, Dict
7
  from flask import Flask, request, jsonify
8
- from flask_cors import CORS
9
  from datetime import datetime
 
 
 
 
 
 
 
 
 
 
 
 
 
10
 
11
  # ======================================================================
12
  # CONFIGURATION
@@ -16,7 +31,7 @@ DATA_FILE_PATH = "data/QR.csv"
16
 
17
  # CORRECTION CRITIQUE: Déplacement de la DB vers /tmp
18
  # Ce répertoire est le seul garanti en écriture sur Hugging Face Spaces.
19
- CHROMA_DB_PATH = "/tmp/bdd_ChromaDB"
20
  COLLECTION_NAME = "qr_data_dual_embeddings"
21
 
22
  Q_COLUMN_NAME = "Question"
@@ -32,7 +47,7 @@ N_RESULTS_RERANK = 3
32
 
33
  # Récupération de la clé depuis l'environnement (Hugging Face Secrets)
34
  # Si non trouvée, utilise la clé de placeholder.
35
- GEMINI_API_KEY = os.getenv("GEMINI_API_KEY", "AIzaSyDXXY7uSXryTxZ51jQFsSLcPnC_Ivt9V1g")
36
  GEMINI_MODEL = "gemini-2.5-flash"
37
 
38
  MAX_CONVERSATION_HISTORY = 10
@@ -54,6 +69,56 @@ gemini_client: genai.Client = None
54
  conversation_histories: Dict[str, List[Dict[str, str]]] = {}
55
  conversation_start_times: Dict[str, str] = {}
56
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
57
  # ======================================================================
58
  # CHARGEMENT DES RESSOURCES
59
  # ======================================================================
@@ -64,11 +129,11 @@ def load_models():
64
  try:
65
  # Tente de charger localement, sinon télécharge (le cache se fera dans /tmp)
66
  cross_encoder = CrossEncoder(
67
- SRC_CROSS_ENCODER if os.path.exists(SRC_CROSS_ENCODER)
68
  else "cross-encoder/mmarco-mMiniLMv2-L12-H384-v1"
69
  )
70
  paraphrase = SentenceTransformer(
71
- SRC_PARAPHRASE if os.path.exists(SRC_PARAPHRASE)
72
  else "sentence-transformers/paraphrase-mpnet-base-v2"
73
  )
74
  print("✅ Modèles chargés avec succès.")
@@ -122,52 +187,52 @@ def initialize_gemini_client():
122
  def setup_chromadb_collection(client, df, model_paraphrase):
123
  """Configure et remplit la collection ChromaDB."""
124
  total_docs = len(df) * 2
125
-
126
  # S'assurer que le répertoire de la DB existe
127
  os.makedirs(CHROMA_DB_PATH, exist_ok=True)
128
-
129
  try:
130
  collection = client.get_or_create_collection(name=COLLECTION_NAME)
131
  except Exception as e:
132
  print(f"❌ Erreur lors de l'accès à la collection ChromaDB: {e}")
133
  raise
134
-
135
  if collection.count() == total_docs and total_docs > 0:
136
  print(f"✅ Collection déjà remplie ({collection.count()} docs) dans {CHROMA_DB_PATH}.")
137
  return collection
138
-
139
  if total_docs == 0:
140
  print("⚠️ DataFrame vide. Collection non remplie.")
141
  return collection
142
-
143
  print(f"⏳ Remplissage de ChromaDB ({len(df)} lignes) à l'emplacement: {CHROMA_DB_PATH}...")
144
-
145
  docs, metadatas, ids = [], [], []
146
-
147
  for i, row in df.iterrows():
148
  question = str(row[Q_COLUMN_NAME])
149
  reponse = str(row[R_COLUMN_NAME])
150
  meta = {Q_COLUMN_NAME: question, R_COLUMN_NAME: reponse, "source_row": i}
151
-
152
  docs.append(question)
153
  metadatas.append({**meta, "type": "question"})
154
  ids.append(f"id_{i}_Q")
155
-
156
  docs.append(reponse)
157
  metadatas.append({**meta, "type": "reponse"})
158
  ids.append(f"id_{i}_R")
159
-
160
  embeddings = model_paraphrase.encode(docs, show_progress_bar=False).tolist()
161
-
162
  # Nettoyage et recréation (pour le cas où les données CSV ont changé)
163
  try:
164
  client.delete_collection(name=COLLECTION_NAME)
165
  except:
166
  pass
167
-
168
  collection = client.get_or_create_collection(name=COLLECTION_NAME)
169
  collection.add(embeddings=embeddings, documents=docs, metadatas=metadatas, ids=ids)
170
-
171
  print(f"✅ Collection remplie: {collection.count()} documents.")
172
  return collection
173
 
@@ -178,21 +243,21 @@ def setup_chromadb_collection(client, df, model_paraphrase):
178
  def retrieve_and_rerank(query_text, collection, model_paraphrase, model_cross_encoder):
179
  """Récupère et rerank les résultats."""
180
  print(f"🔍 Récupération pour: '{query_text[:40]}...'")
181
-
182
  query_emb = model_paraphrase.encode([query_text]).tolist()
183
  results = collection.query(
184
  query_embeddings=query_emb,
185
  n_results=N_RESULTS_RETRIEVAL,
186
  include=['documents', 'metadatas', 'distances']
187
  )
188
-
189
  if not results['ids'][0]:
190
  print("⚠️ Aucun résultat trouvé.")
191
  return pd.DataFrame()
192
-
193
  candidates = []
194
  cross_input = []
195
-
196
  for i, doc in enumerate(results['documents'][0]):
197
  meta = results['metadatas'][0][i]
198
  candidates.append({
@@ -203,14 +268,14 @@ def retrieve_and_rerank(query_text, collection, model_paraphrase, model_cross_en
203
  'initial_distance': results['distances'][0][i]
204
  })
205
  cross_input.append([query_text, doc])
206
-
207
  scores = model_cross_encoder.predict(cross_input)
208
  for i, score in enumerate(scores):
209
  candidates[i]['rerank_score'] = score
210
-
211
  df = pd.DataFrame(candidates).sort_values('rerank_score', ascending=False)
212
  df = df.drop_duplicates(subset=['question', 'reponse'], keep='first')
213
-
214
  return df.head(N_RESULTS_RERANK)
215
 
216
  def generate_rag_prompt(query_text, df_results, conversation_history):
@@ -219,21 +284,21 @@ def generate_rag_prompt(query_text, df_results, conversation_history):
219
  if not df_results.empty:
220
  for _, row in df_results.iterrows():
221
  context.append(f"Q: {row['question']}\nR: {row['reponse']}")
222
-
223
  context_str = "\n---\n".join(context)
224
-
225
  history_str = ""
226
  if conversation_history:
227
  history_str = "HISTORIQUE:\n"
228
  # Ajout du contexte pour le LLM, mais on ne veut pas l'historique complet
229
  # On va limiter l'historique à l'affichage si on dépasse MAX_CONVERSATION_HISTORY
230
- display_history = conversation_history[-(MAX_CONVERSATION_HISTORY * 2):]
231
  for msg in display_history:
232
  role = "USER" if msg["role"] == "user" else "ASSISTANT"
233
  # On utilise 'content' pour le texte du message
234
  history_str += f"{role}: {msg['content']}\n"
235
  history_str += "\n"
236
-
237
  return f"""{history_str}UTILISATEUR: {query_text}
238
 
239
  CONTEXTE (si utile):
@@ -258,9 +323,9 @@ def add_to_history(session_id, role, content):
258
  """Ajoute un message à l'historique."""
259
  if session_id not in conversation_histories:
260
  conversation_histories[session_id] = []
261
-
262
  conversation_histories[session_id].append({"role": role, "content": content})
263
-
264
  # Limiter la taille de l'historique conservé en mémoire
265
  if len(conversation_histories[session_id]) > MAX_CONVERSATION_HISTORY * 2:
266
  conversation_histories[session_id] = conversation_histories[session_id][-(MAX_CONVERSATION_HISTORY * 2):]
@@ -294,10 +359,10 @@ def get_answer(query_text, collection, model_paraphrase, model_cross_encoder, co
294
  print(f"\n{'='*50}")
295
  print(f"🚀 Traitement: '{query_text}'")
296
  print(f"{'='*50}")
297
-
298
  df_results = retrieve_and_rerank(query_text, collection, model_paraphrase, model_cross_encoder)
299
  final_prompt = generate_rag_prompt(query_text, df_results, conversation_history)
300
-
301
  # On retourne le prompt final RAG pour référence, mais l'appel Gemini est fait après
302
  return final_prompt
303
 
@@ -308,13 +373,13 @@ def get_answer(query_text, collection, model_paraphrase, model_cross_encoder, co
308
  def initialize_global_resources():
309
  """Initialise tous les modèles et ressources."""
310
  global model_cross_encoder, model_paraphrase, collection, system_prompt, gemini_client
311
-
312
  print("\n" + "="*50)
313
  print("⚙️ INITIALISATION RAG")
314
  print("="*50)
315
-
316
  # Le répertoire /tmp est géré par la variable CHROMA_DB_PATH
317
-
318
  try:
319
  model_cross_encoder, model_paraphrase = load_models()
320
  df = load_data()
@@ -323,7 +388,7 @@ def initialize_global_resources():
323
  except Exception:
324
  # L'erreur est déjà print dans les fonctions de chargement
325
  return False
326
-
327
  try:
328
  print(f"⏳ Initialisation de ChromaDB à l'emplacement: {CHROMA_DB_PATH}")
329
  # Le PersistentClient créera les fichiers dans le chemin spécifié (maintenant dans /tmp)
@@ -341,7 +406,7 @@ def initialize_global_resources():
341
 
342
  app = Flask(__name__)
343
  # CORS activé, permet les requêtes depuis n'importe quelle origine
344
- CORS(app)
345
 
346
  @app.route('/status', methods=['GET'])
347
  def api_status():
@@ -353,31 +418,41 @@ def api_get_answer():
353
  """Endpoint principal pour obtenir une réponse."""
354
  if any(x is None for x in [model_cross_encoder, model_paraphrase, collection, system_prompt, gemini_client]):
355
  return jsonify({"error": "Ressources non chargées. Veuillez vérifier les logs d'initialisation."}), 500
356
-
357
  try:
358
  data = request.get_json()
359
  query_text = data.get('query_text')
360
  session_id = data.get('session_id', 'archive')
361
-
362
  if not query_text:
363
  generic_message = "Problème avec l'API, veuillez réessayer plus tard."
364
  return jsonify({"error": generic_message}), 500
365
-
366
  # Récupère historique
367
  history = get_conversation_history(session_id)
368
-
369
  # Génère prompt RAG
370
  rag_prompt = get_answer(query_text, collection, model_paraphrase, model_cross_encoder, history)
371
-
372
  # Appelle Gemini
373
  response = call_gemini(rag_prompt, system_prompt, gemini_client)
374
-
375
  # Sauvegarde réponse
376
  add_to_history(session_id, "user", query_text)
377
  add_to_history(session_id, "assistant", response)
378
-
 
 
 
 
 
 
 
 
 
 
379
  return jsonify({"generated_response": response})
380
-
381
  except Exception as e:
382
  print(f"❌ Erreur générale de l'API: {e}")
383
  generic_message = "Problème avec l'API, veuillez réessayer plus tard."
@@ -390,7 +465,7 @@ def api_clear_history():
390
  data = request.get_json()
391
  session_id = data.get('session_id', 'archive')
392
  clear_history(session_id)
393
-
394
  return jsonify({"message": f"Historique effacé: {session_id}"})
395
  except Exception as e:
396
  generic_message = "Problème avec l'API, veuillez réessayer plus tard."
@@ -403,7 +478,7 @@ def api_clear_history():
403
  if __name__ == '__main__':
404
  print("start app.py")
405
  if initialize_global_resources():
406
-
407
  # Récupération de l'adresse IP si possible (pour l'affichage)
408
  try:
409
  import socket
@@ -413,16 +488,15 @@ if __name__ == '__main__':
413
  s.close()
414
  except Exception:
415
  local_ip = "127.0.0.1" # Fallback si échec
416
-
417
  print("\n" + "="*50)
418
  print("🌐 SERVEUR DÉMARRÉ")
419
  print(f"✅ API accessible à l'URL (via l'interface réseau locale): http://{local_ip}:{API_PORT}")
420
  print(f"✅ Route Status: http://{local_ip}:{API_PORT}/status")
421
  print(f"💡 N'oubliez pas de configurer 'app_port: 1212' et 'sdk: docker' dans votre README.md !")
422
  print("="*50 + "\n")
423
-
424
  # L'utilisation de host='0.0.0.0' dans app.run() permet l'accès depuis l'extérieur
425
  app.run(host=API_HOST, port=API_PORT, debug=False)
426
  else:
427
- print("❌ Impossible de démarrer le serveur. Veuillez vérifier les logs pour les erreurs d'initialisation.")
428
-
 
1
+ import os
2
  import pandas as pd
3
  import chromadb
4
+ import requests # Ajouté pour l'API Telegram
5
+ import json # Ajouté pour l'API Telegram
6
  from google import genai
7
  from sentence_transformers import SentenceTransformer, CrossEncoder
8
  from typing import List, Dict
9
  from flask import Flask, request, jsonify
10
+ from flask_cors import CORS
11
  from datetime import datetime
12
+ import time # Ajouté pour le timestamp Telegram
13
+
14
+ # ======================================================================
15
+ # ⚙️ CONFIGURATION TÉLÉGRAM
16
+ # ======================================================================
17
+
18
+ # 1. Votre Token API fourni par BotFather
19
+ TELEGRAM_TOKEN = "8584350410:AAEuXqopGMfgdZ1BvLntA-e6FpoZl5uunEk"
20
+ # 2. Votre Chat ID (où la notification sera envoyée)
21
+ TELEGRAM_CHAT_ID = "1278265595"
22
+ # Activer/Désactiver l'envoi de notifications
23
+ TELEGRAM_NOTIFICATIONS_ENABLED = True
24
+
25
 
26
  # ======================================================================
27
  # CONFIGURATION
 
31
 
32
  # CORRECTION CRITIQUE: Déplacement de la DB vers /tmp
33
  # Ce répertoire est le seul garanti en écriture sur Hugging Face Spaces.
34
+ CHROMA_DB_PATH = "/tmp/bdd_ChromaDB"
35
  COLLECTION_NAME = "qr_data_dual_embeddings"
36
 
37
  Q_COLUMN_NAME = "Question"
 
47
 
48
  # Récupération de la clé depuis l'environnement (Hugging Face Secrets)
49
  # Si non trouvée, utilise la clé de placeholder.
50
+ GEMINI_API_KEY = os.getenv("GEMINI_API_KEY", "AIzaSyDXXY7uSXryTxZ51jQFsSLcPnC_Ivt9V1g")
51
  GEMINI_MODEL = "gemini-2.5-flash"
52
 
53
  MAX_CONVERSATION_HISTORY = 10
 
69
  conversation_histories: Dict[str, List[Dict[str, str]]] = {}
70
  conversation_start_times: Dict[str, str] = {}
71
 
72
+ # ======================================================================
73
+ # 🤖 FONCTION D'ENVOI TÉLÉGRAM
74
+ # ======================================================================
75
+
76
+ def send_llm_interaction_to_telegram(question: str, reponse_llm: str, session_id: str, token: str, chat_id: str):
77
+ """
78
+ Envoie une notification d'interaction Question/Réponse à Telegram.
79
+ """
80
+ if not TELEGRAM_NOTIFICATIONS_ENABLED:
81
+ return
82
+
83
+ # Construction du message formaté
84
+ MESSAGE = f"""
85
+ *🔔 Nouvelle Interaction LLM 🔔*
86
+ *Session ID:* `{session_id}`
87
+ *Heure:* {time.strftime('%Y-%m-%d %H:%M:%S')}
88
+
89
+ *Question (Utilisateur):*
90
+ {question}
91
+
92
+ *Réponse (LLM - Début):*
93
+ {reponse_llm[:200]}...
94
+ """
95
+
96
+ # Construction de l'URL pour la méthode 'sendMessage'
97
+ url = f"https://api.telegram.org/bot{token}/sendMessage"
98
+
99
+ # Paramètres de la requête
100
+ params = {
101
+ "chat_id": chat_id,
102
+ "text": MESSAGE,
103
+ "parse_mode": "Markdown"
104
+ }
105
+
106
+ try:
107
+ # Envoi de la requête POST (sans bloquer le processus principal)
108
+ response = requests.post(url, params=params, timeout=5)
109
+ response.raise_for_status()
110
+
111
+ if response.json().get("ok"):
112
+ print(f"✅ Notification Telegram envoyée pour la session {session_id}.")
113
+ else:
114
+ print(f"❌ Échec envoi Telegram: {response.json().get('description')}")
115
+
116
+ except requests.exceptions.RequestException as e:
117
+ print(f"❌ Erreur connexion Telegram: {e}")
118
+ except Exception as e:
119
+ print(f"❌ Erreur inattendue Telegram: {e}")
120
+
121
+
122
  # ======================================================================
123
  # CHARGEMENT DES RESSOURCES
124
  # ======================================================================
 
129
  try:
130
  # Tente de charger localement, sinon télécharge (le cache se fera dans /tmp)
131
  cross_encoder = CrossEncoder(
132
+ SRC_CROSS_ENCODER if os.path.exists(SRC_CROSS_ENCODER)
133
  else "cross-encoder/mmarco-mMiniLMv2-L12-H384-v1"
134
  )
135
  paraphrase = SentenceTransformer(
136
+ SRC_PARAPHRASE if os.path.exists(SRC_PARAPHRASE)
137
  else "sentence-transformers/paraphrase-mpnet-base-v2"
138
  )
139
  print("✅ Modèles chargés avec succès.")
 
187
  def setup_chromadb_collection(client, df, model_paraphrase):
188
  """Configure et remplit la collection ChromaDB."""
189
  total_docs = len(df) * 2
190
+
191
  # S'assurer que le répertoire de la DB existe
192
  os.makedirs(CHROMA_DB_PATH, exist_ok=True)
193
+
194
  try:
195
  collection = client.get_or_create_collection(name=COLLECTION_NAME)
196
  except Exception as e:
197
  print(f"❌ Erreur lors de l'accès à la collection ChromaDB: {e}")
198
  raise
199
+
200
  if collection.count() == total_docs and total_docs > 0:
201
  print(f"✅ Collection déjà remplie ({collection.count()} docs) dans {CHROMA_DB_PATH}.")
202
  return collection
203
+
204
  if total_docs == 0:
205
  print("⚠️ DataFrame vide. Collection non remplie.")
206
  return collection
207
+
208
  print(f"⏳ Remplissage de ChromaDB ({len(df)} lignes) à l'emplacement: {CHROMA_DB_PATH}...")
209
+
210
  docs, metadatas, ids = [], [], []
211
+
212
  for i, row in df.iterrows():
213
  question = str(row[Q_COLUMN_NAME])
214
  reponse = str(row[R_COLUMN_NAME])
215
  meta = {Q_COLUMN_NAME: question, R_COLUMN_NAME: reponse, "source_row": i}
216
+
217
  docs.append(question)
218
  metadatas.append({**meta, "type": "question"})
219
  ids.append(f"id_{i}_Q")
220
+
221
  docs.append(reponse)
222
  metadatas.append({**meta, "type": "reponse"})
223
  ids.append(f"id_{i}_R")
224
+
225
  embeddings = model_paraphrase.encode(docs, show_progress_bar=False).tolist()
226
+
227
  # Nettoyage et recréation (pour le cas où les données CSV ont changé)
228
  try:
229
  client.delete_collection(name=COLLECTION_NAME)
230
  except:
231
  pass
232
+
233
  collection = client.get_or_create_collection(name=COLLECTION_NAME)
234
  collection.add(embeddings=embeddings, documents=docs, metadatas=metadatas, ids=ids)
235
+
236
  print(f"✅ Collection remplie: {collection.count()} documents.")
237
  return collection
238
 
 
243
  def retrieve_and_rerank(query_text, collection, model_paraphrase, model_cross_encoder):
244
  """Récupère et rerank les résultats."""
245
  print(f"🔍 Récupération pour: '{query_text[:40]}...'")
246
+
247
  query_emb = model_paraphrase.encode([query_text]).tolist()
248
  results = collection.query(
249
  query_embeddings=query_emb,
250
  n_results=N_RESULTS_RETRIEVAL,
251
  include=['documents', 'metadatas', 'distances']
252
  )
253
+
254
  if not results['ids'][0]:
255
  print("⚠️ Aucun résultat trouvé.")
256
  return pd.DataFrame()
257
+
258
  candidates = []
259
  cross_input = []
260
+
261
  for i, doc in enumerate(results['documents'][0]):
262
  meta = results['metadatas'][0][i]
263
  candidates.append({
 
268
  'initial_distance': results['distances'][0][i]
269
  })
270
  cross_input.append([query_text, doc])
271
+
272
  scores = model_cross_encoder.predict(cross_input)
273
  for i, score in enumerate(scores):
274
  candidates[i]['rerank_score'] = score
275
+
276
  df = pd.DataFrame(candidates).sort_values('rerank_score', ascending=False)
277
  df = df.drop_duplicates(subset=['question', 'reponse'], keep='first')
278
+
279
  return df.head(N_RESULTS_RERANK)
280
 
281
  def generate_rag_prompt(query_text, df_results, conversation_history):
 
284
  if not df_results.empty:
285
  for _, row in df_results.iterrows():
286
  context.append(f"Q: {row['question']}\nR: {row['reponse']}")
287
+
288
  context_str = "\n---\n".join(context)
289
+
290
  history_str = ""
291
  if conversation_history:
292
  history_str = "HISTORIQUE:\n"
293
  # Ajout du contexte pour le LLM, mais on ne veut pas l'historique complet
294
  # On va limiter l'historique à l'affichage si on dépasse MAX_CONVERSATION_HISTORY
295
+ display_history = conversation_history[-(MAX_CONVERSATION_HISTORY * 2):]
296
  for msg in display_history:
297
  role = "USER" if msg["role"] == "user" else "ASSISTANT"
298
  # On utilise 'content' pour le texte du message
299
  history_str += f"{role}: {msg['content']}\n"
300
  history_str += "\n"
301
+
302
  return f"""{history_str}UTILISATEUR: {query_text}
303
 
304
  CONTEXTE (si utile):
 
323
  """Ajoute un message à l'historique."""
324
  if session_id not in conversation_histories:
325
  conversation_histories[session_id] = []
326
+
327
  conversation_histories[session_id].append({"role": role, "content": content})
328
+
329
  # Limiter la taille de l'historique conservé en mémoire
330
  if len(conversation_histories[session_id]) > MAX_CONVERSATION_HISTORY * 2:
331
  conversation_histories[session_id] = conversation_histories[session_id][-(MAX_CONVERSATION_HISTORY * 2):]
 
359
  print(f"\n{'='*50}")
360
  print(f"🚀 Traitement: '{query_text}'")
361
  print(f"{'='*50}")
362
+
363
  df_results = retrieve_and_rerank(query_text, collection, model_paraphrase, model_cross_encoder)
364
  final_prompt = generate_rag_prompt(query_text, df_results, conversation_history)
365
+
366
  # On retourne le prompt final RAG pour référence, mais l'appel Gemini est fait après
367
  return final_prompt
368
 
 
373
  def initialize_global_resources():
374
  """Initialise tous les modèles et ressources."""
375
  global model_cross_encoder, model_paraphrase, collection, system_prompt, gemini_client
376
+
377
  print("\n" + "="*50)
378
  print("⚙️ INITIALISATION RAG")
379
  print("="*50)
380
+
381
  # Le répertoire /tmp est géré par la variable CHROMA_DB_PATH
382
+
383
  try:
384
  model_cross_encoder, model_paraphrase = load_models()
385
  df = load_data()
 
388
  except Exception:
389
  # L'erreur est déjà print dans les fonctions de chargement
390
  return False
391
+
392
  try:
393
  print(f"⏳ Initialisation de ChromaDB à l'emplacement: {CHROMA_DB_PATH}")
394
  # Le PersistentClient créera les fichiers dans le chemin spécifié (maintenant dans /tmp)
 
406
 
407
  app = Flask(__name__)
408
  # CORS activé, permet les requêtes depuis n'importe quelle origine
409
+ CORS(app)
410
 
411
  @app.route('/status', methods=['GET'])
412
  def api_status():
 
418
  """Endpoint principal pour obtenir une réponse."""
419
  if any(x is None for x in [model_cross_encoder, model_paraphrase, collection, system_prompt, gemini_client]):
420
  return jsonify({"error": "Ressources non chargées. Veuillez vérifier les logs d'initialisation."}), 500
421
+
422
  try:
423
  data = request.get_json()
424
  query_text = data.get('query_text')
425
  session_id = data.get('session_id', 'archive')
426
+
427
  if not query_text:
428
  generic_message = "Problème avec l'API, veuillez réessayer plus tard."
429
  return jsonify({"error": generic_message}), 500
430
+
431
  # Récupère historique
432
  history = get_conversation_history(session_id)
433
+
434
  # Génère prompt RAG
435
  rag_prompt = get_answer(query_text, collection, model_paraphrase, model_cross_encoder, history)
436
+
437
  # Appelle Gemini
438
  response = call_gemini(rag_prompt, system_prompt, gemini_client)
439
+
440
  # Sauvegarde réponse
441
  add_to_history(session_id, "user", query_text)
442
  add_to_history(session_id, "assistant", response)
443
+
444
+ # 🚀 AJOUT DE LA NOTIFICATION TÉLÉGRAM
445
+ send_llm_interaction_to_telegram(
446
+ question=query_text,
447
+ reponse_llm=response,
448
+ session_id=session_id,
449
+ token=TELEGRAM_TOKEN,
450
+ chat_id=TELEGRAM_CHAT_ID
451
+ )
452
+ # ------------------------------------
453
+
454
  return jsonify({"generated_response": response})
455
+
456
  except Exception as e:
457
  print(f"❌ Erreur générale de l'API: {e}")
458
  generic_message = "Problème avec l'API, veuillez réessayer plus tard."
 
465
  data = request.get_json()
466
  session_id = data.get('session_id', 'archive')
467
  clear_history(session_id)
468
+
469
  return jsonify({"message": f"Historique effacé: {session_id}"})
470
  except Exception as e:
471
  generic_message = "Problème avec l'API, veuillez réessayer plus tard."
 
478
  if __name__ == '__main__':
479
  print("start app.py")
480
  if initialize_global_resources():
481
+
482
  # Récupération de l'adresse IP si possible (pour l'affichage)
483
  try:
484
  import socket
 
488
  s.close()
489
  except Exception:
490
  local_ip = "127.0.0.1" # Fallback si échec
491
+
492
  print("\n" + "="*50)
493
  print("🌐 SERVEUR DÉMARRÉ")
494
  print(f"✅ API accessible à l'URL (via l'interface réseau locale): http://{local_ip}:{API_PORT}")
495
  print(f"✅ Route Status: http://{local_ip}:{API_PORT}/status")
496
  print(f"💡 N'oubliez pas de configurer 'app_port: 1212' et 'sdk: docker' dans votre README.md !")
497
  print("="*50 + "\n")
498
+
499
  # L'utilisation de host='0.0.0.0' dans app.run() permet l'accès depuis l'extérieur
500
  app.run(host=API_HOST, port=API_PORT, debug=False)
501
  else:
502
+ print("❌ Impossible de démarrer le serveur. Veuillez vérifier les logs pour les erreurs d'initialisation.")