MathieuGAL commited on
Commit
6edab5f
·
verified ·
1 Parent(s): 4ff76a6

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +83 -137
app.py CHANGED
@@ -14,20 +14,13 @@ import time
14
  # ======================================================================
15
 
16
  DATA_FILE_PATH = "data/QR.csv"
17
-
18
- # CORRECTION CRITIQUE: Déplacement de la DB vers /tmp
19
- # Ce répertoire est le seul garanti en écriture sur Hugging Face Spaces.
20
  CHROMA_DB_PATH = "/tmp/bdd_ChromaDB"
21
  COLLECTION_NAME = "qr_data_dual_embeddings"
22
-
23
  Q_COLUMN_NAME = "Question"
24
  R_COLUMN_NAME = "Reponse"
25
  SYSTEM_PROMPT_PATH = "data/system_prompt.txt"
26
-
27
- # Les chemins des modèles sont conservés (ils se mettront en cache dans /tmp grâce au Dockerfile)
28
  SRC_CROSS_ENCODER = "models/mmarco-mMiniLMv2-L12-H384-v1"
29
  SRC_PARAPHRASE = "models/paraphrase-mpnet-base-v2"
30
-
31
  N_RESULTS_RETRIEVAL = 10
32
  N_RESULTS_RERANK = 3
33
 
@@ -39,10 +32,8 @@ GEMINI_API_KEY_DIRECT = "AIzaSyCpG2G3K0cZmTxWFO-c4OoOrW1fcTYQwgo"
39
  GEMINI_MODEL = "gemini-2.5-flash"
40
 
41
  MAX_CONVERSATION_HISTORY = 10
42
-
43
- # Configuration pour l'accès externe (host et port)
44
  API_HOST = '0.0.0.0'
45
- API_PORT = 1212 # Le port 1212 est conservé, il doit être configuré dans le README.md
46
 
47
  # ======================================================================
48
  # VARIABLES GLOBALES
@@ -53,20 +44,19 @@ model_paraphrase: SentenceTransformer = None
53
  collection: chromadb.Collection = None
54
  system_prompt: str = None
55
  gemini_client_rag: genai.Client = None # Client pour la route RAG
56
- gemini_client_direct: genai.Client = None # Client pour la route directe
57
 
58
  conversation_histories: Dict[str, List[Dict[str, str]]] = {}
59
  conversation_start_times: Dict[str, str] = {}
60
 
61
  # ======================================================================
62
- # CHARGEMENT DES RESSOURCES
63
  # ======================================================================
64
 
65
  def load_models():
66
  """Charge les modèles SentenceTransformer et CrossEncoder."""
67
  print("⏳ Chargement des modèles...")
68
  try:
69
- # Tente de charger localement, sinon télécharge (le cache se fera dans /tmp)
70
  cross_encoder = CrossEncoder(
71
  SRC_CROSS_ENCODER if os.path.exists(SRC_CROSS_ENCODER)
72
  else "cross-encoder/mmarco-mMiniLMv2-L12-H384-v1"
@@ -79,7 +69,6 @@ def load_models():
79
  return cross_encoder, paraphrase
80
  except Exception as e:
81
  print(f"❌ Erreur chargement modèles: {e}")
82
- # Note: L'erreur de PermissionError est maintenant gérée par le Dockerfile
83
  raise
84
 
85
  def load_data():
@@ -123,16 +112,13 @@ def initialize_gemini_client(api_key, client_name):
123
  raise
124
 
125
  # ======================================================================
126
- # CHROMADB SETUP
127
  # ======================================================================
128
 
129
  def setup_chromadb_collection(client, df, model_paraphrase):
130
  """Configure et remplit la collection ChromaDB."""
131
  total_docs = len(df) * 2
132
-
133
- # S'assurer que le répertoire de la DB existe
134
  os.makedirs(CHROMA_DB_PATH, exist_ok=True)
135
-
136
  try:
137
  collection = client.get_or_create_collection(name=COLLECTION_NAME)
138
  except Exception as e:
@@ -148,7 +134,6 @@ def setup_chromadb_collection(client, df, model_paraphrase):
148
  return collection
149
 
150
  print(f"⏳ Remplissage de ChromaDB ({len(df)} lignes) à l'emplacement: {CHROMA_DB_PATH}...")
151
-
152
  docs, metadatas, ids = [], [], []
153
 
154
  for i, row in df.iterrows():
@@ -166,7 +151,6 @@ def setup_chromadb_collection(client, df, model_paraphrase):
166
 
167
  embeddings = model_paraphrase.encode(docs, show_progress_bar=False).tolist()
168
 
169
- # Nettoyage et recréation (pour le cas où les données CSV ont changé)
170
  try:
171
  client.delete_collection(name=COLLECTION_NAME)
172
  except:
@@ -179,11 +163,12 @@ def setup_chromadb_collection(client, df, model_paraphrase):
179
  return collection
180
 
181
  # ======================================================================
182
- # RAG - RETRIEVAL & RERANKING
183
  # ======================================================================
184
 
185
  def retrieve_and_rerank(query_text, collection, model_paraphrase, model_cross_encoder):
186
  """Récupère et rerank les résultats."""
 
187
  print(f"🔍 Récupération pour: '{query_text[:40]}...'")
188
 
189
  query_emb = model_paraphrase.encode([query_text]).tolist()
@@ -232,15 +217,13 @@ def generate_rag_prompt(query_text, df_results, conversation_history):
232
  history_str = ""
233
  if conversation_history:
234
  history_str = "HISTORIQUE:\n"
235
- # Ajout du contexte pour le LLM, mais on ne veut pas l'historique complet
236
- # On va limiter l'historique à l'affichage si on dépasse MAX_CONVERSATION_HISTORY
237
  display_history = conversation_history[-(MAX_CONVERSATION_HISTORY * 2):]
238
  for msg in display_history:
239
  role = "USER" if msg["role"] == "user" else "ASSISTANT"
240
- # On utilise 'content' pour le texte du message
241
  history_str += f"{role}: {msg['content']}\n"
242
  history_str += "\n"
243
 
 
244
  return f"""{history_str}UTILISATEUR: {query_text}
245
 
246
  CONTEXTE (si utile):
@@ -254,7 +237,7 @@ INSTRUCTIONS:
254
  - Mentionne obligatoirement Mathieu dans ta réponse"""
255
 
256
  # ======================================================================
257
- # GESTION HISTORIQUE
258
  # ======================================================================
259
 
260
  def get_conversation_history(session_id):
@@ -268,7 +251,6 @@ def add_to_history(session_id, role, content):
268
 
269
  conversation_histories[session_id].append({"role": role, "content": content})
270
 
271
- # Limiter la taille de l'historique conservé en mémoire
272
  if len(conversation_histories[session_id]) > MAX_CONVERSATION_HISTORY * 2:
273
  conversation_histories[session_id] = conversation_histories[session_id][-(MAX_CONVERSATION_HISTORY * 2):]
274
 
@@ -277,76 +259,7 @@ def clear_history(session_id):
277
  conversation_histories[session_id] = []
278
 
279
  # ======================================================================
280
- # CALL GEMINI
281
- # ======================================================================
282
-
283
- def call_gemini(final_prompt, system_prompt, gemini_client):
284
- """
285
- Appelle Google Gemini avec une logique de réessai en cas d'échec de l'API.
286
- Maximum de 10 tentatives.
287
- """
288
- MAX_RETRIES = 10
289
-
290
- # S'assurer que le client est bien initialisé
291
- if gemini_client is None:
292
- return "Erreur: Client Gemini non initialisé."
293
-
294
- for attempt in range(MAX_RETRIES):
295
- try:
296
- print(f" 📞 Tentative d'appel Gemini #{attempt + 1}...")
297
- # L'API Python de Google lève des exceptions `APIError` pour les échecs,
298
- # y compris ceux qui correspondent aux 5xx.
299
- response = gemini_client.models.generate_content(
300
- model=GEMINI_MODEL,
301
- contents=f"{system_prompt}\n\n{final_prompt}"
302
- )
303
- # Si la réponse réussit, on sort de la boucle
304
- return response.text.replace("*", "")
305
-
306
- except Exception as e:
307
- # Ici, on capture toute erreur d'API ou de connexion.
308
- # On considère cela comme une erreur de service transitoire pour les réessais.
309
- error_message = str(e)
310
- print(f" ❌ Erreur Gemini (Tentative {attempt + 1}/{MAX_RETRIES}): {error_message}")
311
-
312
- if attempt < MAX_RETRIES - 1:
313
- # Si ce n'est pas la dernière tentative, on attend avant de réessayer
314
- sleep_time = 2 # Attente de 2 secondes
315
- print(f" 😴 Attente de {sleep_time} secondes avant de réessayer...")
316
- time.sleep(sleep_time)
317
- else:
318
- # Dernière tentative échouée
319
- print(" 🛑 Toutes les tentatives de réessai ont échoué.")
320
- return f"Erreur fatale après {MAX_RETRIES} tentatives: {error_message}"
321
-
322
- # Ne devrait jamais être atteint, mais par sécurité
323
- return "Erreur inconnue dans la boucle de réessai de Gemini."
324
-
325
- # ======================================================================
326
- # PROCESSUS DE RÉPONSE - RAG
327
- # ======================================================================
328
-
329
- def get_answer_rag_process(query_text, collection, model_paraphrase, model_cross_encoder, conversation_history):
330
- """Exécute le processus RAG complet."""
331
- print(f"\n{'='*50}")
332
- print(f"🚀 Traitement RAG: '{query_text}'")
333
- print(f"{'='*50}")
334
-
335
- df_results = retrieve_and_rerank(query_text, collection, model_paraphrase, model_cross_encoder)
336
- final_prompt = generate_rag_prompt(query_text, df_results, conversation_history)
337
-
338
- return final_prompt
339
-
340
- # ======================================================================
341
- # PROCESSUS DE RÉPONSE - DIRECT
342
- # ======================================================================
343
-
344
- def get_answer_direct_process(query_text):
345
- """Génère le prompt direct sans RAG."""
346
- return f"UTILISATEUR: {query_text}"
347
-
348
- # ======================================================================
349
- # INITIALISATION GLOBALE
350
  # ======================================================================
351
 
352
  def initialize_global_resources():
@@ -362,16 +275,14 @@ def initialize_global_resources():
362
  model_cross_encoder, model_paraphrase = load_models()
363
  df = load_data()
364
  system_prompt = load_system_prompt()
365
- # Initialisation des deux clients
366
  gemini_client_rag = initialize_gemini_client(GEMINI_API_KEY_RAG, "RAG (Env/Default)")
 
367
  gemini_client_direct = initialize_gemini_client(GEMINI_API_KEY_DIRECT, "Direct (Hardcoded)")
368
  except Exception:
369
- # L'erreur est déjà print dans les fonctions de chargement
370
  return False
371
 
372
  try:
373
  print(f"⏳ Initialisation de ChromaDB à l'emplacement: {CHROMA_DB_PATH}")
374
- # Le PersistentClient créera les fichiers dans le chemin spécifié (maintenant dans /tmp)
375
  chroma_client = chromadb.PersistentClient(path=CHROMA_DB_PATH)
376
  collection = setup_chromadb_collection(chroma_client, df, model_paraphrase)
377
  print("✅ INITIALISATION COMPLÈTE\n")
@@ -385,7 +296,6 @@ def initialize_global_resources():
385
  # ======================================================================
386
 
387
  app = Flask(__name__)
388
- # CORS activé, permet les requêtes depuis n'importe quelle origine
389
  CORS(app)
390
 
391
  @app.route('/status', methods=['GET'])
@@ -395,10 +305,9 @@ def api_status():
395
 
396
  @app.route('/api/get_answer', methods=['POST'])
397
  def api_get_answer():
398
- """Endpoint principal pour obtenir une réponse avec RAG."""
399
- # Le client RAG utilise la clé d'environnement/par défaut
400
  if any(x is None for x in [model_cross_encoder, model_paraphrase, collection, system_prompt, gemini_client_rag]):
401
- return jsonify({"error": "Ressources RAG non chargées. Veuillez vérifier les logs d'initialisation."}), 500
402
 
403
  try:
404
  data = request.get_json()
@@ -406,19 +315,34 @@ def api_get_answer():
406
  session_id = data.get('session_id', 'archive')
407
 
408
  if not query_text:
409
- generic_message = "Problème avec l'API, veuillez réessayer plus tard."
410
- return jsonify({"error": generic_message}), 500
411
 
412
- # Récupère historique
413
  history = get_conversation_history(session_id)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
414
 
415
- # Génère prompt RAG
416
- rag_prompt = get_answer_rag_process(query_text, collection, model_paraphrase, model_cross_encoder, history)
417
-
418
- # Appelle Gemini avec le client RAG
419
- response = call_gemini(rag_prompt, system_prompt, gemini_client_rag)
420
-
421
- # Sauvegarde réponse
422
  add_to_history(session_id, "user", query_text)
423
  add_to_history(session_id, "assistant", response)
424
 
@@ -426,37 +350,63 @@ def api_get_answer():
426
 
427
  except Exception as e:
428
  print(f"❌ Erreur générale de l'API RAG: {e}")
429
- generic_message = "Problème avec l'API RAG, veuillez réessayer plus tard."
430
- return jsonify({"error": generic_message}), 500
 
 
 
431
 
432
  @app.route('/api/gemini_only', methods=['POST'])
433
  def api_gemini_only():
434
- """NOUVELLE ROUTE : Endpoint pour les requêtes directes à Gemini sans RAG. Utilise la clé mise en dur."""
435
- # Le client direct utilise la clé mise en dur
436
- if gemini_client_direct is None:
 
 
 
 
437
  return jsonify({"error": "Client Gemini direct non initialisé. Vérifiez les logs."}), 500
438
 
439
  try:
440
  data = request.get_json()
441
  query_text = data.get('query_text')
442
- # On peut optionally récupérer un 'system_prompt_direct' pour customiser, sinon on utilise le prompt par défaut
443
  custom_system_prompt = data.get('system_prompt', system_prompt)
444
 
445
  if not query_text:
446
  return jsonify({"error": "Paramètre 'query_text' manquant."}), 400
447
 
448
  print(f"\n{'='*50}")
449
- print(f"⚡ Traitement Direct: '{query_text}'")
450
  print(f"{'='*50}")
451
 
452
- # Génère le prompt final (juste la question)
453
- final_prompt = get_answer_direct_process(query_text)
454
-
455
- # Appelle Gemini avec le client direct
456
- # On utilise le 'system_prompt' par défaut ou un custom s'il est fourni
457
- response = call_gemini(final_prompt, custom_system_prompt, gemini_client_direct)
458
-
459
- # Pas d'ajout à l'historique de conversation ici car c'est une route directe sans session RAG/Historique
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
460
 
461
  return jsonify({"generated_response": response})
462
 
@@ -465,10 +415,9 @@ def api_gemini_only():
465
  generic_message = "Problème avec l'API directe, veuillez réessayer plus tard."
466
  return jsonify({"error": generic_message}), 500
467
 
468
-
469
  @app.route('/api/clear_history', methods=['POST'])
470
  def api_clear_history():
471
- """Efface l'historique d'une session."""
472
  try:
473
  data = request.get_json()
474
  session_id = data.get('session_id', 'archive')
@@ -480,33 +429,30 @@ def api_clear_history():
480
  return jsonify({"error": generic_message}), 500
481
 
482
  # ======================================================================
483
- # MAIN
484
  # ======================================================================
485
 
486
  if __name__ == '__main__':
487
  print("start app.py")
488
  if initialize_global_resources():
489
 
490
- # Récupération de l'adresse IP si possible (pour l'affichage)
491
  try:
492
  import socket
493
  s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
494
- s.connect(("8.8.8.8", 80)) # Connecte à un serveur externe pour trouver l'IP locale utilisée
495
  local_ip = s.getsockname()[0]
496
  s.close()
497
  except Exception:
498
- local_ip = "127.0.0.1" # Fallback si échec
499
 
500
  print("\n" + "="*50)
501
  print("🌐 SERVEUR DÉMARRÉ")
502
  print(f"✅ API accessible à l'URL (via l'interface réseau locale): http://{local_ip}:{API_PORT}")
503
- print(f"✅ Route Status: http://{local_ip}:{API_PORT}/status")
504
  print(f"✅ Route RAG (avec Historique): http://{local_ip}:{API_PORT}/api/get_answer")
505
- print(f"✅ Route DIRECTE (Clé spéciale): http://{local_ip}:{API_PORT}/api/gemini_only")
506
  print(f"💡 N'oubliez pas de configurer 'app_port: 1212' et 'sdk: docker' dans votre README.md !")
507
  print("="*50 + "\n")
508
 
509
- # L'utilisation de host='0.0.0.0' dans app.run() permet l'accès depuis l'extérieur
510
  app.run(host=API_HOST, port=API_PORT, debug=False)
511
  else:
512
  print("❌ Impossible de démarrer le serveur. Veuillez vérifier les logs pour les erreurs d'initialisation.")
 
14
  # ======================================================================
15
 
16
  DATA_FILE_PATH = "data/QR.csv"
 
 
 
17
  CHROMA_DB_PATH = "/tmp/bdd_ChromaDB"
18
  COLLECTION_NAME = "qr_data_dual_embeddings"
 
19
  Q_COLUMN_NAME = "Question"
20
  R_COLUMN_NAME = "Reponse"
21
  SYSTEM_PROMPT_PATH = "data/system_prompt.txt"
 
 
22
  SRC_CROSS_ENCODER = "models/mmarco-mMiniLMv2-L12-H384-v1"
23
  SRC_PARAPHRASE = "models/paraphrase-mpnet-base-v2"
 
24
  N_RESULTS_RETRIEVAL = 10
25
  N_RESULTS_RERANK = 3
26
 
 
32
  GEMINI_MODEL = "gemini-2.5-flash"
33
 
34
  MAX_CONVERSATION_HISTORY = 10
 
 
35
  API_HOST = '0.0.0.0'
36
+ API_PORT = 1212
37
 
38
  # ======================================================================
39
  # VARIABLES GLOBALES
 
44
  collection: chromadb.Collection = None
45
  system_prompt: str = None
46
  gemini_client_rag: genai.Client = None # Client pour la route RAG
47
+ gemini_client_direct: genai.Client = None # Client pour la route directe (CELUI UTILISÉ PAR api_gemini_only)
48
 
49
  conversation_histories: Dict[str, List[Dict[str, str]]] = {}
50
  conversation_start_times: Dict[str, str] = {}
51
 
52
  # ======================================================================
53
+ # CHARGEMENT DES RESSOURCES (Inchangé, car les ressources RAG sont nécessaires)
54
  # ======================================================================
55
 
56
  def load_models():
57
  """Charge les modèles SentenceTransformer et CrossEncoder."""
58
  print("⏳ Chargement des modèles...")
59
  try:
 
60
  cross_encoder = CrossEncoder(
61
  SRC_CROSS_ENCODER if os.path.exists(SRC_CROSS_ENCODER)
62
  else "cross-encoder/mmarco-mMiniLMv2-L12-H384-v1"
 
69
  return cross_encoder, paraphrase
70
  except Exception as e:
71
  print(f"❌ Erreur chargement modèles: {e}")
 
72
  raise
73
 
74
  def load_data():
 
112
  raise
113
 
114
  # ======================================================================
115
+ # CHROMADB SETUP (Inchangé)
116
  # ======================================================================
117
 
118
  def setup_chromadb_collection(client, df, model_paraphrase):
119
  """Configure et remplit la collection ChromaDB."""
120
  total_docs = len(df) * 2
 
 
121
  os.makedirs(CHROMA_DB_PATH, exist_ok=True)
 
122
  try:
123
  collection = client.get_or_create_collection(name=COLLECTION_NAME)
124
  except Exception as e:
 
134
  return collection
135
 
136
  print(f"⏳ Remplissage de ChromaDB ({len(df)} lignes) à l'emplacement: {CHROMA_DB_PATH}...")
 
137
  docs, metadatas, ids = [], [], []
138
 
139
  for i, row in df.iterrows():
 
151
 
152
  embeddings = model_paraphrase.encode(docs, show_progress_bar=False).tolist()
153
 
 
154
  try:
155
  client.delete_collection(name=COLLECTION_NAME)
156
  except:
 
163
  return collection
164
 
165
  # ======================================================================
166
+ # RAG - RETRIEVAL & RERANKING (Inchangé, mais seulement utilisé par api_get_answer)
167
  # ======================================================================
168
 
169
  def retrieve_and_rerank(query_text, collection, model_paraphrase, model_cross_encoder):
170
  """Récupère et rerank les résultats."""
171
+ # ... (logique inchangée) ...
172
  print(f"🔍 Récupération pour: '{query_text[:40]}...'")
173
 
174
  query_emb = model_paraphrase.encode([query_text]).tolist()
 
217
  history_str = ""
218
  if conversation_history:
219
  history_str = "HISTORIQUE:\n"
 
 
220
  display_history = conversation_history[-(MAX_CONVERSATION_HISTORY * 2):]
221
  for msg in display_history:
222
  role = "USER" if msg["role"] == "user" else "ASSISTANT"
 
223
  history_str += f"{role}: {msg['content']}\n"
224
  history_str += "\n"
225
 
226
+ # Le prompt customisé RAG (avec Mathieu, NOVA, etc.)
227
  return f"""{history_str}UTILISATEUR: {query_text}
228
 
229
  CONTEXTE (si utile):
 
237
  - Mentionne obligatoirement Mathieu dans ta réponse"""
238
 
239
  # ======================================================================
240
+ # GESTION HISTORIQUE (Inchangé)
241
  # ======================================================================
242
 
243
  def get_conversation_history(session_id):
 
251
 
252
  conversation_histories[session_id].append({"role": role, "content": content})
253
 
 
254
  if len(conversation_histories[session_id]) > MAX_CONVERSATION_HISTORY * 2:
255
  conversation_histories[session_id] = conversation_histories[session_id][-(MAX_CONVERSATION_HISTORY * 2):]
256
 
 
259
  conversation_histories[session_id] = []
260
 
261
  # ======================================================================
262
+ # INITIALISATION GLOBALE (Inchangé)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
263
  # ======================================================================
264
 
265
  def initialize_global_resources():
 
275
  model_cross_encoder, model_paraphrase = load_models()
276
  df = load_data()
277
  system_prompt = load_system_prompt()
 
278
  gemini_client_rag = initialize_gemini_client(GEMINI_API_KEY_RAG, "RAG (Env/Default)")
279
+ # Client Direct
280
  gemini_client_direct = initialize_gemini_client(GEMINI_API_KEY_DIRECT, "Direct (Hardcoded)")
281
  except Exception:
 
282
  return False
283
 
284
  try:
285
  print(f"⏳ Initialisation de ChromaDB à l'emplacement: {CHROMA_DB_PATH}")
 
286
  chroma_client = chromadb.PersistentClient(path=CHROMA_DB_PATH)
287
  collection = setup_chromadb_collection(chroma_client, df, model_paraphrase)
288
  print("✅ INITIALISATION COMPLÈTE\n")
 
296
  # ======================================================================
297
 
298
  app = Flask(__name__)
 
299
  CORS(app)
300
 
301
  @app.route('/status', methods=['GET'])
 
305
 
306
  @app.route('/api/get_answer', methods=['POST'])
307
  def api_get_answer():
308
+ """Endpoint pour obtenir une réponse avec RAG (Inchangé)."""
 
309
  if any(x is None for x in [model_cross_encoder, model_paraphrase, collection, system_prompt, gemini_client_rag]):
310
+ return jsonify({"error": "Ressources RAG non chargées."}), 500
311
 
312
  try:
313
  data = request.get_json()
 
315
  session_id = data.get('session_id', 'archive')
316
 
317
  if not query_text:
318
+ return jsonify({"error": "Problème avec l'API, veuillez réessayer plus tard."}), 500
 
319
 
 
320
  history = get_conversation_history(session_id)
321
+ df_results = retrieve_and_rerank(query_text, collection, model_paraphrase, model_cross_encoder)
322
+ rag_prompt = generate_rag_prompt(query_text, df_results, history)
323
+
324
+ # Logique d'appel Gemini (intégrée de l'ancienne fonction call_gemini)
325
+ MAX_RETRIES = 10
326
+ response = "Erreur: Toutes les tentatives ont échoué."
327
+ client = gemini_client_rag
328
+
329
+ for attempt in range(MAX_RETRIES):
330
+ try:
331
+ print(f" 📞 Tentative d'appel Gemini RAG #{attempt + 1}...")
332
+ response_obj = client.models.generate_content(
333
+ model=GEMINI_MODEL,
334
+ contents=f"{system_prompt}\n\n{rag_prompt}"
335
+ )
336
+ response = response_obj.text.replace("*", "")
337
+ break # Succès
338
+ except Exception as e:
339
+ error_message = str(e)
340
+ print(f" ❌ Erreur Gemini (Tentative {attempt + 1}/{MAX_RETRIES}): {error_message}")
341
+ if attempt < MAX_RETRIES - 1:
342
+ time.sleep(2)
343
+ else:
344
+ response = f"Erreur fatale après {MAX_RETRIES} tentatives: {error_message}"
345
 
 
 
 
 
 
 
 
346
  add_to_history(session_id, "user", query_text)
347
  add_to_history(session_id, "assistant", response)
348
 
 
350
 
351
  except Exception as e:
352
  print(f"❌ Erreur générale de l'API RAG: {e}")
353
+ return jsonify({"error": "Problème avec l'API RAG, veuillez réessayer plus tard."}), 500
354
+
355
+ # ======================================================================
356
+ # NOUVELLE ROUTE : TOTALEMENT AUTONOME
357
+ # ======================================================================
358
 
359
  @app.route('/api/gemini_only', methods=['POST'])
360
  def api_gemini_only():
361
+ """
362
+ ROUTE TOTALE : Endpoint pour les requêtes directes à Gemini sans RAG.
363
+ Contient toute la logique d'appel, y compris les réessais.
364
+ """
365
+ # Vérification du client
366
+ client = gemini_client_direct
367
+ if client is None:
368
  return jsonify({"error": "Client Gemini direct non initialisé. Vérifiez les logs."}), 500
369
 
370
  try:
371
  data = request.get_json()
372
  query_text = data.get('query_text')
373
+ # Utilisation du system_prompt par défaut si non spécifié
374
  custom_system_prompt = data.get('system_prompt', system_prompt)
375
 
376
  if not query_text:
377
  return jsonify({"error": "Paramètre 'query_text' manquant."}), 400
378
 
379
  print(f"\n{'='*50}")
380
+ print(f"⚡ Traitement Direct Intégral: '{query_text}'")
381
  print(f"{'='*50}")
382
 
383
+ # LOGIQUE INTÉGRÉE 1 : Préparation du prompt (remplace get_answer_direct_process)
384
+ final_prompt = f"UTILISATEUR: {query_text}"
385
+
386
+ # LOGIQUE INTÉGRÉE 2 : Appel Gemini avec logique de réessai (remplace call_gemini)
387
+ MAX_RETRIES = 10
388
+ response = "Erreur: Toutes les tentatives ont échoué."
389
+
390
+ for attempt in range(MAX_RETRIES):
391
+ try:
392
+ print(f" 📞 Tentative d'appel Gemini Direct #{attempt + 1}...")
393
+ response_obj = client.models.generate_content(
394
+ model=GEMINI_MODEL,
395
+ contents=f"{custom_system_prompt}\n\n{final_prompt}"
396
+ )
397
+ response = response_obj.text.replace("*", "")
398
+ break # Succès
399
+ except Exception as e:
400
+ error_message = str(e)
401
+ print(f" ❌ Erreur Gemini (Tentative {attempt + 1}/{MAX_RETRIES}): {error_message}")
402
+ if attempt < MAX_RETRIES - 1:
403
+ # Attente de 2 secondes avant de réessayer
404
+ print(" 😴 Attente de 2 secondes avant de réessayer...")
405
+ time.sleep(2)
406
+ else:
407
+ response = f"Erreur fatale après {MAX_RETRIES} tentatives: {error_message}"
408
+
409
+ # Pas d'ajout à l'historique
410
 
411
  return jsonify({"generated_response": response})
412
 
 
415
  generic_message = "Problème avec l'API directe, veuillez réessayer plus tard."
416
  return jsonify({"error": generic_message}), 500
417
 
 
418
  @app.route('/api/clear_history', methods=['POST'])
419
  def api_clear_history():
420
+ """Efface l'historique d'une session (Inchangé)."""
421
  try:
422
  data = request.get_json()
423
  session_id = data.get('session_id', 'archive')
 
429
  return jsonify({"error": generic_message}), 500
430
 
431
  # ======================================================================
432
+ # MAIN (Inchangé)
433
  # ======================================================================
434
 
435
  if __name__ == '__main__':
436
  print("start app.py")
437
  if initialize_global_resources():
438
 
 
439
  try:
440
  import socket
441
  s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
442
+ s.connect(("8.8.8.8", 80))
443
  local_ip = s.getsockname()[0]
444
  s.close()
445
  except Exception:
446
+ local_ip = "127.0.0.1"
447
 
448
  print("\n" + "="*50)
449
  print("🌐 SERVEUR DÉMARRÉ")
450
  print(f"✅ API accessible à l'URL (via l'interface réseau locale): http://{local_ip}:{API_PORT}")
 
451
  print(f"✅ Route RAG (avec Historique): http://{local_ip}:{API_PORT}/api/get_answer")
452
+ print(f"✅ Route DIRECTE TOTALE (Clé spéciale): http://{local_ip}:{API_PORT}/api/gemini_only")
453
  print(f"💡 N'oubliez pas de configurer 'app_port: 1212' et 'sdk: docker' dans votre README.md !")
454
  print("="*50 + "\n")
455
 
 
456
  app.run(host=API_HOST, port=API_PORT, debug=False)
457
  else:
458
  print("❌ Impossible de démarrer le serveur. Veuillez vérifier les logs pour les erreurs d'initialisation.")