MathieuGAL commited on
Commit
f1231aa
·
verified ·
1 Parent(s): 8306651

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +21 -37
app.py CHANGED
@@ -1,15 +1,15 @@
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
@@ -24,13 +24,12 @@ TELEGRAM_NOTIFICATIONS_ENABLED = True
24
 
25
 
26
  # ======================================================================
27
- # CONFIGURATION
28
  # ======================================================================
29
 
30
  DATA_FILE_PATH = "data/QR.csv"
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
 
@@ -38,15 +37,13 @@ Q_COLUMN_NAME = "Question"
38
  R_COLUMN_NAME = "Reponse"
39
  SYSTEM_PROMPT_PATH = "data/system_prompt.txt"
40
 
41
- # Les chemins des modèles sont conservés (ils se mettront en cache dans /tmp grâce au Dockerfile)
42
  SRC_CROSS_ENCODER = "models/mmarco-mMiniLMv2-L12-H384-v1"
43
  SRC_PARAPHRASE = "models/paraphrase-mpnet-base-v2"
44
 
45
  N_RESULTS_RETRIEVAL = 10
46
  N_RESULTS_RERANK = 3
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
 
@@ -54,7 +51,7 @@ MAX_CONVERSATION_HISTORY = 10
54
 
55
  # Configuration pour l'accès externe (host et port)
56
  API_HOST = '0.0.0.0'
57
- API_PORT = 1212 # Le port 1212 est conservé, il doit être configuré dans le README.md
58
 
59
  # ======================================================================
60
  # VARIABLES GLOBALES
@@ -76,6 +73,7 @@ conversation_start_times: Dict[str, str] = {}
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
@@ -90,7 +88,7 @@ def send_llm_interaction_to_telegram(question: str, reponse_llm: str, session_id
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'
@@ -104,17 +102,18 @@ def send_llm_interaction_to_telegram(question: str, reponse_llm: str, session_id
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
 
@@ -127,7 +126,6 @@ def load_models():
127
  """Charge les modèles SentenceTransformer et CrossEncoder."""
128
  print("⏳ Chargement des modèles...")
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"
@@ -140,7 +138,6 @@ def load_models():
140
  return cross_encoder, paraphrase
141
  except Exception as e:
142
  print(f"❌ Erreur chargement modèles: {e}")
143
- # Note: L'erreur de PermissionError est maintenant gérée par le Dockerfile
144
  raise
145
 
146
  def load_data():
@@ -188,7 +185,6 @@ 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:
@@ -224,7 +220,6 @@ def setup_chromadb_collection(client, df, model_paraphrase):
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:
@@ -290,12 +285,9 @@ def generate_rag_prompt(query_text, df_results, conversation_history):
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
 
@@ -363,7 +355,6 @@ def get_answer(query_text, collection, model_paraphrase, model_cross_encoder, co
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
 
369
  # ======================================================================
@@ -378,20 +369,16 @@ def initialize_global_resources():
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()
386
  system_prompt = load_system_prompt()
387
  gemini_client = initialize_gemini_client()
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)
395
  chroma_client = chromadb.PersistentClient(path=CHROMA_DB_PATH)
396
  collection = setup_chromadb_collection(chroma_client, df, model_paraphrase)
397
  print("✅ INITIALISATION COMPLÈTE\n")
@@ -405,7 +392,6 @@ def initialize_global_resources():
405
  # ======================================================================
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'])
@@ -415,18 +401,19 @@ def api_status():
415
 
416
  @app.route('/api/get_answer', methods=['POST'])
417
  def api_get_answer():
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)
@@ -437,11 +424,12 @@ def api_get_answer():
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,
@@ -449,7 +437,7 @@ def api_get_answer():
449
  token=TELEGRAM_TOKEN,
450
  chat_id=TELEGRAM_CHAT_ID
451
  )
452
- # ------------------------------------
453
 
454
  return jsonify({"generated_response": response})
455
 
@@ -479,24 +467,20 @@ 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
485
  s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
486
- s.connect(("8.8.8.8", 80)) # Connecte à un serveur externe pour trouver l'IP locale utilisée
487
  local_ip = s.getsockname()[0]
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.")
 
1
  import os
2
  import pandas as pd
3
  import chromadb
4
+ import requests
5
+ import json
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
13
 
14
  # ======================================================================
15
  # ⚙️ CONFIGURATION TÉLÉGRAM
 
24
 
25
 
26
  # ======================================================================
27
+ # CONFIGURATION RAG
28
  # ======================================================================
29
 
30
  DATA_FILE_PATH = "data/QR.csv"
31
 
32
  # CORRECTION CRITIQUE: Déplacement de la DB vers /tmp
 
33
  CHROMA_DB_PATH = "/tmp/bdd_ChromaDB"
34
  COLLECTION_NAME = "qr_data_dual_embeddings"
35
 
 
37
  R_COLUMN_NAME = "Reponse"
38
  SYSTEM_PROMPT_PATH = "data/system_prompt.txt"
39
 
 
40
  SRC_CROSS_ENCODER = "models/mmarco-mMiniLMv2-L12-H384-v1"
41
  SRC_PARAPHRASE = "models/paraphrase-mpnet-base-v2"
42
 
43
  N_RESULTS_RETRIEVAL = 10
44
  N_RESULTS_RERANK = 3
45
 
46
+ # Récupération de la clé depuis l'environnement
 
47
  GEMINI_API_KEY = os.getenv("GEMINI_API_KEY", "AIzaSyDXXY7uSXryTxZ51jQFsSLcPnC_Ivt9V1g")
48
  GEMINI_MODEL = "gemini-2.5-flash"
49
 
 
51
 
52
  # Configuration pour l'accès externe (host et port)
53
  API_HOST = '0.0.0.0'
54
+ API_PORT = 1212
55
 
56
  # ======================================================================
57
  # VARIABLES GLOBALES
 
73
  def send_llm_interaction_to_telegram(question: str, reponse_llm: str, session_id: str, token: str, chat_id: str):
74
  """
75
  Envoie une notification d'interaction Question/Réponse à Telegram.
76
+ C'est la fonction principale pour l'archivage.
77
  """
78
  if not TELEGRAM_NOTIFICATIONS_ENABLED:
79
  return
 
88
  {question}
89
 
90
  *Réponse (LLM - Début):*
91
+ {reponse_llm[:200]}... (Voir le log pour la réponse complète)
92
  """
93
 
94
  # Construction de l'URL pour la méthode 'sendMessage'
 
102
  }
103
 
104
  try:
105
+ # Envoi de la requête POST (avec un timeout pour ne pas ralentir l'API principale)
106
  response = requests.post(url, params=params, timeout=5)
107
  response.raise_for_status()
108
 
109
  if response.json().get("ok"):
110
+ # Affiche dans le log du serveur (pas dans le terminal)
111
  print(f"✅ Notification Telegram envoyée pour la session {session_id}.")
112
  else:
113
  print(f"❌ Échec envoi Telegram: {response.json().get('description')}")
114
 
115
  except requests.exceptions.RequestException as e:
116
+ print(f"❌ Erreur connexion Telegram (Vérifiez le TOKEN/Réseau): {e}")
117
  except Exception as e:
118
  print(f"❌ Erreur inattendue Telegram: {e}")
119
 
 
126
  """Charge les modèles SentenceTransformer et CrossEncoder."""
127
  print("⏳ Chargement des modèles...")
128
  try:
 
129
  cross_encoder = CrossEncoder(
130
  SRC_CROSS_ENCODER if os.path.exists(SRC_CROSS_ENCODER)
131
  else "cross-encoder/mmarco-mMiniLMv2-L12-H384-v1"
 
138
  return cross_encoder, paraphrase
139
  except Exception as e:
140
  print(f"❌ Erreur chargement modèles: {e}")
 
141
  raise
142
 
143
  def load_data():
 
185
  """Configure et remplit la collection ChromaDB."""
186
  total_docs = len(df) * 2
187
 
 
188
  os.makedirs(CHROMA_DB_PATH, exist_ok=True)
189
 
190
  try:
 
220
 
221
  embeddings = model_paraphrase.encode(docs, show_progress_bar=False).tolist()
222
 
 
223
  try:
224
  client.delete_collection(name=COLLECTION_NAME)
225
  except:
 
285
  history_str = ""
286
  if conversation_history:
287
  history_str = "HISTORIQUE:\n"
 
 
288
  display_history = conversation_history[-(MAX_CONVERSATION_HISTORY * 2):]
289
  for msg in display_history:
290
  role = "USER" if msg["role"] == "user" else "ASSISTANT"
 
291
  history_str += f"{role}: {msg['content']}\n"
292
  history_str += "\n"
293
 
 
355
  df_results = retrieve_and_rerank(query_text, collection, model_paraphrase, model_cross_encoder)
356
  final_prompt = generate_rag_prompt(query_text, df_results, conversation_history)
357
 
 
358
  return final_prompt
359
 
360
  # ======================================================================
 
369
  print("⚙️ INITIALISATION RAG")
370
  print("="*50)
371
 
 
 
372
  try:
373
  model_cross_encoder, model_paraphrase = load_models()
374
  df = load_data()
375
  system_prompt = load_system_prompt()
376
  gemini_client = initialize_gemini_client()
377
  except Exception:
 
378
  return False
379
 
380
  try:
381
  print(f"⏳ Initialisation de ChromaDB à l'emplacement: {CHROMA_DB_PATH}")
 
382
  chroma_client = chromadb.PersistentClient(path=CHROMA_DB_PATH)
383
  collection = setup_chromadb_collection(chroma_client, df, model_paraphrase)
384
  print("✅ INITIALISATION COMPLÈTE\n")
 
392
  # ======================================================================
393
 
394
  app = Flask(__name__)
 
395
  CORS(app)
396
 
397
  @app.route('/status', methods=['GET'])
 
401
 
402
  @app.route('/api/get_answer', methods=['POST'])
403
  def api_get_answer():
404
+ """Endpoint principal pour obtenir une réponse et envoyer la notification Telegram."""
405
  if any(x is None for x in [model_cross_encoder, model_paraphrase, collection, system_prompt, gemini_client]):
406
  return jsonify({"error": "Ressources non chargées. Veuillez vérifier les logs d'initialisation."}), 500
407
 
408
  try:
409
  data = request.get_json()
410
  query_text = data.get('query_text')
411
+ # Utiliser un ID par défaut si non fourni
412
  session_id = data.get('session_id', 'archive')
413
 
414
  if not query_text:
415
+ generic_message = "Requête vide."
416
+ return jsonify({"error": generic_message}), 400
417
 
418
  # Récupère historique
419
  history = get_conversation_history(session_id)
 
424
  # Appelle Gemini
425
  response = call_gemini(rag_prompt, system_prompt, gemini_client)
426
 
427
+ # Sauvegarde réponse dans l'historique de la session
428
  add_to_history(session_id, "user", query_text)
429
  add_to_history(session_id, "assistant", response)
430
 
431
+ # 🚀 ÉTAPE CRUCIALE : ENVOI DE LA NOTIFICATION TÉLÉGRAM
432
+ # Ceci archive l'interaction (Question + Réponse)
433
  send_llm_interaction_to_telegram(
434
  question=query_text,
435
  reponse_llm=response,
 
437
  token=TELEGRAM_TOKEN,
438
  chat_id=TELEGRAM_CHAT_ID
439
  )
440
+ # --------------------------------------------------------
441
 
442
  return jsonify({"generated_response": response})
443
 
 
467
  print("start app.py")
468
  if initialize_global_resources():
469
 
 
470
  try:
471
  import socket
472
  s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
473
+ s.connect(("8.8.8.8", 80))
474
  local_ip = s.getsockname()[0]
475
  s.close()
476
  except Exception:
477
+ local_ip = "127.0.0.1"
478
 
479
  print("\n" + "="*50)
480
  print("🌐 SERVEUR DÉMARRÉ")
481
  print(f"✅ API accessible à l'URL (via l'interface réseau locale): http://{local_ip}:{API_PORT}")
 
 
482
  print("="*50 + "\n")
483
 
 
484
  app.run(host=API_HOST, port=API_PORT, debug=False)
485
  else:
486
  print("❌ Impossible de démarrer le serveur. Veuillez vérifier les logs pour les erreurs d'initialisation.")