MathieuGAL commited on
Commit
bccf607
·
verified ·
1 Parent(s): 2063ded

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +42 -43
app.py CHANGED
@@ -13,27 +13,33 @@ from datetime import datetime
13
  # ======================================================================
14
 
15
  DATA_FILE_PATH = "data/QR.csv"
16
- CHROMA_DB_PATH = "data/bdd_ChromaDB"
 
 
 
17
  COLLECTION_NAME = "qr_data_dual_embeddings"
18
 
19
  Q_COLUMN_NAME = "Question"
20
  R_COLUMN_NAME = "Reponse"
21
  SYSTEM_PROMPT_PATH = "data/system_prompt.txt"
22
 
 
23
  SRC_CROSS_ENCODER = "models/mmarco-mMiniLMv2-L12-H384-v1"
24
  SRC_PARAPHRASE = "models/paraphrase-mpnet-base-v2"
25
 
26
  N_RESULTS_RETRIEVAL = 10
27
  N_RESULTS_RERANK = 3
28
 
29
- GEMINI_API_KEY = "AIzaSyDXXY7uSXryTxZ51jQFsSLcPnC_Ivt9V1g"
 
 
30
  GEMINI_MODEL = "gemini-2.5-flash"
31
 
32
  MAX_CONVERSATION_HISTORY = 10
33
 
34
  # Configuration pour l'accès externe (host et port)
35
  API_HOST = '0.0.0.0'
36
- API_PORT = 1212
37
 
38
  # ======================================================================
39
  # VARIABLES GLOBALES
@@ -56,6 +62,7 @@ def load_models():
56
  """Charge les modèles SentenceTransformer et CrossEncoder."""
57
  print("⏳ Chargement des modèles...")
58
  try:
 
59
  cross_encoder = CrossEncoder(
60
  SRC_CROSS_ENCODER if os.path.exists(SRC_CROSS_ENCODER)
61
  else "cross-encoder/mmarco-mMiniLMv2-L12-H384-v1"
@@ -68,6 +75,7 @@ def load_models():
68
  return cross_encoder, paraphrase
69
  except Exception as e:
70
  print(f"❌ Erreur chargement modèles: {e}")
 
71
  raise
72
 
73
  def load_data():
@@ -94,19 +102,17 @@ def load_system_prompt():
94
  return f.read().strip()
95
  except FileNotFoundError:
96
  default = "Tu es un assistant utile et concis. Réponds à la requête de l'utilisateur."
97
- print(f"⚠️ System prompt non trouvé. Utilisation du prompt par défaut.")
98
  return default
99
 
100
  def initialize_gemini_client():
101
  """Initialise le client Google Gemini."""
 
 
102
  try:
103
- # NOTE: Using a placeholder key in the code. A real API key
104
- # should be loaded from an environment variable or a secret.
105
- if GEMINI_API_KEY == "AIzaSyDXXY7uSXryTxZ51jQFsSLcPnC_Ivt9V1g":
106
- print("⚠️ Clé Gemini par défaut. Assurez-vous d'utiliser une clé valide ou un secret.")
107
  return genai.Client(api_key=GEMINI_API_KEY)
108
  except Exception as e:
109
- print(f"❌ Erreur Gemini: {e}")
110
  raise
111
 
112
  # ======================================================================
@@ -117,21 +123,24 @@ def setup_chromadb_collection(client, df, model_paraphrase):
117
  """Configure et remplit la collection ChromaDB."""
118
  total_docs = len(df) * 2
119
 
 
 
 
120
  try:
121
  collection = client.get_or_create_collection(name=COLLECTION_NAME)
122
  except Exception as e:
123
- print(f"❌ Erreur get/create collection ChromaDB: {e}")
124
  raise
125
 
126
  if collection.count() == total_docs and total_docs > 0:
127
- print(f"✅ Collection déjà remplie ({collection.count()} docs).")
128
  return collection
129
 
130
  if total_docs == 0:
131
- print("⚠️ DataFrame vide.")
132
  return collection
133
 
134
- print(f"⏳ Remplissage de ChromaDB ({len(df)} lignes)...")
135
 
136
  docs, metadatas, ids = [], [], []
137
 
@@ -148,26 +157,16 @@ def setup_chromadb_collection(client, df, model_paraphrase):
148
  metadatas.append({**meta, "type": "reponse"})
149
  ids.append(f"id_{i}_R")
150
 
151
- # --- Potentielle source d'erreur: Encodage ---
152
- try:
153
- embeddings = model_paraphrase.encode(docs, show_progress_bar=False).tolist()
154
- except Exception as e:
155
- print(f"❌ Erreur d'encodage des documents pour ChromaDB: {e}")
156
- raise
157
 
 
158
  try:
159
- # Tentative de suppression et recréation pour forcer la mise à jour
160
  client.delete_collection(name=COLLECTION_NAME)
161
- except Exception:
162
- # Ignorer si la collection n'existe pas ou si la suppression échoue
163
  pass
164
 
165
- try:
166
- collection = client.get_or_create_collection(name=COLLECTION_NAME)
167
- collection.add(embeddings=embeddings, documents=docs, metadatas=metadatas, ids=ids)
168
- except Exception as e:
169
- print(f"❌ Erreur d'ajout des données à la collection ChromaDB: {e}")
170
- raise
171
 
172
  print(f"✅ Collection remplie: {collection.count()} documents.")
173
  return collection
@@ -226,8 +225,12 @@ def generate_rag_prompt(query_text, df_results, conversation_history):
226
  history_str = ""
227
  if conversation_history:
228
  history_str = "HISTORIQUE:\n"
229
- for msg in conversation_history:
 
 
 
230
  role = "USER" if msg["role"] == "user" else "ASSISTANT"
 
231
  history_str += f"{role}: {msg['content']}\n"
232
  history_str += "\n"
233
 
@@ -258,6 +261,7 @@ def add_to_history(session_id, role, content):
258
 
259
  conversation_histories[session_id].append({"role": role, "content": content})
260
 
 
261
  if len(conversation_histories[session_id]) > MAX_CONVERSATION_HISTORY * 2:
262
  conversation_histories[session_id] = conversation_histories[session_id][-(MAX_CONVERSATION_HISTORY * 2):]
263
 
@@ -294,6 +298,7 @@ def get_answer(query_text, collection, model_paraphrase, model_cross_encoder, co
294
  df_results = retrieve_and_rerank(query_text, collection, model_paraphrase, model_cross_encoder)
295
  final_prompt = generate_rag_prompt(query_text, df_results, conversation_history)
296
 
 
297
  return final_prompt
298
 
299
  # ======================================================================
@@ -308,32 +313,26 @@ def initialize_global_resources():
308
  print("⚙️ INITIALISATION RAG")
309
  print("="*50)
310
 
311
- os.makedirs(CHROMA_DB_PATH, exist_ok=True)
312
 
313
  try:
314
- # 1. Chargement des modèles, données et prompt
315
  model_cross_encoder, model_paraphrase = load_models()
316
  df = load_data()
317
  system_prompt = load_system_prompt()
318
  gemini_client = initialize_gemini_client()
319
  except Exception:
320
- # Si une erreur se produit ici (modèles/données), on ne continue pas.
321
  return False
322
 
323
  try:
324
- # 2. Initialisation et configuration de ChromaDB
325
  print(f"⏳ Initialisation de ChromaDB à l'emplacement: {CHROMA_DB_PATH}")
326
- # Note : Le chemin doit être accessible en écriture
327
- chroma_client = chromadb.PersistentClient(path=CHROMA_DB_PATH)
328
  collection = setup_chromadb_collection(chroma_client, df, model_paraphrase)
329
-
330
  print("✅ INITIALISATION COMPLÈTE\n")
331
  return True
332
  except Exception as e:
333
- # 3. Gérer spécifiquement les erreurs ChromaDB
334
  print(f"❌ Erreur lors de l'initialisation de ChromaDB ou du remplissage: {e}")
335
- # Pour les déploiements sur Hugging Face Spaces, vérifiez que 'data/bdd_ChromaDB'
336
- # est accessible en écriture, ou essayez de le placer dans '/tmp/bdd_ChromaDB'.
337
  return False
338
 
339
  # ======================================================================
@@ -353,7 +352,7 @@ def api_status():
353
  def api_get_answer():
354
  """Endpoint principal pour obtenir une réponse."""
355
  if any(x is None for x in [model_cross_encoder, model_paraphrase, collection, system_prompt, gemini_client]):
356
- return jsonify({"error": "Ressources non chargées"}), 500
357
 
358
  try:
359
  data = request.get_json()
@@ -379,7 +378,7 @@ def api_get_answer():
379
  return jsonify({"generated_response": response})
380
 
381
  except Exception as e:
382
- print(f"❌ Erreur: {e}")
383
  return jsonify({"error": str(e)}), 500
384
 
385
  @app.route('/api/clear_history', methods=['POST'])
@@ -416,10 +415,10 @@ if __name__ == '__main__':
416
  print("🌐 SERVEUR DÉMARRÉ")
417
  print(f"✅ API accessible à l'URL (via l'interface réseau locale): http://{local_ip}:{API_PORT}")
418
  print(f"✅ Route Status: http://{local_ip}:{API_PORT}/status")
419
- print(f"💡 Pour un accès depuis l'extérieur, utilisez l'adresse IP publique de votre machine et assurez-vous que le port {API_PORT} est ouvert.")
420
  print("="*50 + "\n")
421
 
422
  # L'utilisation de host='0.0.0.0' dans app.run() permet l'accès depuis l'extérieur
423
  app.run(host=API_HOST, port=API_PORT, debug=False)
424
  else:
425
- print("❌ Impossible de démarrer le serveur")
 
13
  # ======================================================================
14
 
15
  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"
23
  R_COLUMN_NAME = "Reponse"
24
  SYSTEM_PROMPT_PATH = "data/system_prompt.txt"
25
 
26
+ # Les chemins des modèles sont conservés (ils se mettront en cache dans /tmp grâce au Dockerfile)
27
  SRC_CROSS_ENCODER = "models/mmarco-mMiniLMv2-L12-H384-v1"
28
  SRC_PARAPHRASE = "models/paraphrase-mpnet-base-v2"
29
 
30
  N_RESULTS_RETRIEVAL = 10
31
  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
39
 
40
  # Configuration pour l'accès externe (host et port)
41
  API_HOST = '0.0.0.0'
42
+ API_PORT = 1212 # Le port 1212 est conservé, il doit être configuré dans le README.md
43
 
44
  # ======================================================================
45
  # VARIABLES GLOBALES
 
62
  """Charge les modèles SentenceTransformer et CrossEncoder."""
63
  print("⏳ Chargement des modèles...")
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"
 
75
  return cross_encoder, paraphrase
76
  except Exception as e:
77
  print(f"❌ Erreur chargement modèles: {e}")
78
+ # Note: L'erreur de PermissionError est maintenant gérée par le Dockerfile
79
  raise
80
 
81
  def load_data():
 
102
  return f.read().strip()
103
  except FileNotFoundError:
104
  default = "Tu es un assistant utile et concis. Réponds à la requête de l'utilisateur."
105
+ print(f"⚠️ System prompt non trouvé à {SYSTEM_PROMPT_PATH}. Utilisation du prompt par défaut.")
106
  return default
107
 
108
  def initialize_gemini_client():
109
  """Initialise le client Google Gemini."""
110
+ if GEMINI_API_KEY == "AIzaSyDXXY7uSXryTxZ51jQFsSLcPnC_Ivt9V1g":
111
+ print("⚠️ AVIS: Clé Gemini par défaut/placeholder détectée. Veuillez la remplacer par un secret d'environnement nommé 'GEMINI_API_KEY' pour la production.")
112
  try:
 
 
 
 
113
  return genai.Client(api_key=GEMINI_API_KEY)
114
  except Exception as e:
115
+ print(f"❌ Erreur lors de l'initialisation du client Gemini: {e}")
116
  raise
117
 
118
  # ======================================================================
 
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
 
 
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
 
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
 
 
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):]
267
 
 
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
 
304
  # ======================================================================
 
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()
321
  system_prompt = load_system_prompt()
322
  gemini_client = initialize_gemini_client()
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)
330
+ chroma_client = chromadb.PersistentClient(path=CHROMA_DB_PATH)
331
  collection = setup_chromadb_collection(chroma_client, df, model_paraphrase)
 
332
  print("✅ INITIALISATION COMPLÈTE\n")
333
  return True
334
  except Exception as e:
 
335
  print(f"❌ Erreur lors de l'initialisation de ChromaDB ou du remplissage: {e}")
 
 
336
  return False
337
 
338
  # ======================================================================
 
352
  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()
 
378
  return jsonify({"generated_response": response})
379
 
380
  except Exception as e:
381
+ print(f"❌ Erreur générale de l'API: {e}")
382
  return jsonify({"error": str(e)}), 500
383
 
384
  @app.route('/api/clear_history', methods=['POST'])
 
415
  print("🌐 SERVEUR DÉMARRÉ")
416
  print(f"✅ API accessible à l'URL (via l'interface réseau locale): http://{local_ip}:{API_PORT}")
417
  print(f"✅ Route Status: http://{local_ip}:{API_PORT}/status")
418
+ print(f"💡 N'oubliez pas de configurer 'app_port: 1212' et 'sdk: docker' dans votre README.md !")
419
  print("="*50 + "\n")
420
 
421
  # L'utilisation de host='0.0.0.0' dans app.run() permet l'accès depuis l'extérieur
422
  app.run(host=API_HOST, port=API_PORT, debug=False)
423
  else:
424
+ print("❌ Impossible de démarrer le serveur. Veuillez vérifier les logs pour les erreurs d'initialisation.")