Spaces:
Running
Running
Update app.py
Browse files
app.py
CHANGED
|
@@ -31,9 +31,11 @@ SRC_PARAPHRASE = "models/paraphrase-mpnet-base-v2"
|
|
| 31 |
N_RESULTS_RETRIEVAL = 10
|
| 32 |
N_RESULTS_RERANK = 3
|
| 33 |
|
| 34 |
-
#
|
| 35 |
-
|
| 36 |
-
|
|
|
|
|
|
|
| 37 |
GEMINI_MODEL = "gemini-2.5-flash"
|
| 38 |
|
| 39 |
MAX_CONVERSATION_HISTORY = 10
|
|
@@ -50,7 +52,8 @@ model_cross_encoder: CrossEncoder = None
|
|
| 50 |
model_paraphrase: SentenceTransformer = None
|
| 51 |
collection: chromadb.Collection = None
|
| 52 |
system_prompt: str = None
|
| 53 |
-
|
|
|
|
| 54 |
|
| 55 |
conversation_histories: Dict[str, List[Dict[str, str]]] = {}
|
| 56 |
conversation_start_times: Dict[str, str] = {}
|
|
@@ -106,14 +109,17 @@ def load_system_prompt():
|
|
| 106 |
print(f"⚠️ System prompt non trouvé à {SYSTEM_PROMPT_PATH}. Utilisation du prompt par défaut.")
|
| 107 |
return default
|
| 108 |
|
| 109 |
-
def initialize_gemini_client():
|
| 110 |
-
"""Initialise
|
| 111 |
-
if
|
| 112 |
-
print("⚠️ AVIS: Clé Gemini par défaut/placeholder détectée.
|
|
|
|
|
|
|
| 113 |
try:
|
| 114 |
-
|
|
|
|
| 115 |
except Exception as e:
|
| 116 |
-
print(f"❌ Erreur lors de l'initialisation du client Gemini: {e}")
|
| 117 |
raise
|
| 118 |
|
| 119 |
# ======================================================================
|
|
@@ -273,26 +279,26 @@ def clear_history(session_id):
|
|
| 273 |
# ======================================================================
|
| 274 |
# CALL GEMINI
|
| 275 |
# ======================================================================
|
| 276 |
-
|
|
|
|
| 277 |
"""
|
| 278 |
Appelle Google Gemini avec une logique de réessai en cas d'échec de l'API.
|
| 279 |
Maximum de 10 tentatives.
|
| 280 |
"""
|
| 281 |
MAX_RETRIES = 10
|
| 282 |
|
| 283 |
-
#
|
| 284 |
-
|
| 285 |
-
|
| 286 |
-
# pour capturer toutes les erreurs potentielles liées à l'appel.
|
| 287 |
|
| 288 |
for attempt in range(MAX_RETRIES):
|
| 289 |
try:
|
| 290 |
-
print(f"
|
| 291 |
# L'API Python de Google lève des exceptions `APIError` pour les échecs,
|
| 292 |
# y compris ceux qui correspondent aux 5xx.
|
| 293 |
response = gemini_client.models.generate_content(
|
| 294 |
model=GEMINI_MODEL,
|
| 295 |
-
contents=f"{system_prompt}\n\n{
|
| 296 |
)
|
| 297 |
# Si la réponse réussit, on sort de la boucle
|
| 298 |
return response.text.replace("*", "")
|
|
@@ -301,56 +307,64 @@ def call_gemini(rag_prompt, system_prompt, gemini_client):
|
|
| 301 |
# Ici, on capture toute erreur d'API ou de connexion.
|
| 302 |
# On considère cela comme une erreur de service transitoire pour les réessais.
|
| 303 |
error_message = str(e)
|
| 304 |
-
print(f"
|
| 305 |
|
| 306 |
if attempt < MAX_RETRIES - 1:
|
| 307 |
# Si ce n'est pas la dernière tentative, on attend avant de réessayer
|
| 308 |
sleep_time = 2 # Attente de 2 secondes
|
| 309 |
-
print(f"
|
| 310 |
time.sleep(sleep_time)
|
| 311 |
else:
|
| 312 |
# Dernière tentative échouée
|
| 313 |
-
print("
|
| 314 |
return f"Erreur fatale après {MAX_RETRIES} tentatives: {error_message}"
|
| 315 |
|
| 316 |
# Ne devrait jamais être atteint, mais par sécurité
|
| 317 |
return "Erreur inconnue dans la boucle de réessai de Gemini."
|
| 318 |
|
| 319 |
# ======================================================================
|
| 320 |
-
#
|
| 321 |
# ======================================================================
|
| 322 |
|
| 323 |
-
def
|
| 324 |
"""Exécute le processus RAG complet."""
|
| 325 |
print(f"\n{'='*50}")
|
| 326 |
-
print(f"🚀 Traitement: '{query_text}'")
|
| 327 |
print(f"{'='*50}")
|
| 328 |
|
| 329 |
df_results = retrieve_and_rerank(query_text, collection, model_paraphrase, model_cross_encoder)
|
| 330 |
final_prompt = generate_rag_prompt(query_text, df_results, conversation_history)
|
| 331 |
|
| 332 |
-
# On retourne le prompt final RAG pour référence, mais l'appel Gemini est fait après
|
| 333 |
return final_prompt
|
| 334 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 335 |
# ======================================================================
|
| 336 |
# INITIALISATION GLOBALE
|
| 337 |
# ======================================================================
|
| 338 |
|
| 339 |
def initialize_global_resources():
|
| 340 |
"""Initialise tous les modèles et ressources."""
|
| 341 |
-
global model_cross_encoder, model_paraphrase, collection, system_prompt
|
|
|
|
| 342 |
|
| 343 |
print("\n" + "="*50)
|
| 344 |
-
print("⚙️ INITIALISATION RAG")
|
| 345 |
print("="*50)
|
| 346 |
|
| 347 |
-
# Le répertoire /tmp est géré par la variable CHROMA_DB_PATH
|
| 348 |
-
|
| 349 |
try:
|
| 350 |
model_cross_encoder, model_paraphrase = load_models()
|
| 351 |
df = load_data()
|
| 352 |
system_prompt = load_system_prompt()
|
| 353 |
-
|
|
|
|
|
|
|
| 354 |
except Exception:
|
| 355 |
# L'erreur est déjà print dans les fonctions de chargement
|
| 356 |
return False
|
|
@@ -381,9 +395,10 @@ def api_status():
|
|
| 381 |
|
| 382 |
@app.route('/api/get_answer', methods=['POST'])
|
| 383 |
def api_get_answer():
|
| 384 |
-
"""Endpoint principal pour obtenir une réponse."""
|
| 385 |
-
|
| 386 |
-
|
|
|
|
| 387 |
|
| 388 |
try:
|
| 389 |
data = request.get_json()
|
|
@@ -398,10 +413,10 @@ def api_get_answer():
|
|
| 398 |
history = get_conversation_history(session_id)
|
| 399 |
|
| 400 |
# Génère prompt RAG
|
| 401 |
-
rag_prompt =
|
| 402 |
|
| 403 |
-
# Appelle Gemini
|
| 404 |
-
response = call_gemini(rag_prompt, system_prompt,
|
| 405 |
|
| 406 |
# Sauvegarde réponse
|
| 407 |
add_to_history(session_id, "user", query_text)
|
|
@@ -410,10 +425,47 @@ def api_get_answer():
|
|
| 410 |
return jsonify({"generated_response": response})
|
| 411 |
|
| 412 |
except Exception as e:
|
| 413 |
-
print(f"❌ Erreur générale de l'API: {e}")
|
| 414 |
-
generic_message = "Problème avec l'API, veuillez réessayer plus tard."
|
| 415 |
return jsonify({"error": generic_message}), 500
|
| 416 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 417 |
@app.route('/api/clear_history', methods=['POST'])
|
| 418 |
def api_clear_history():
|
| 419 |
"""Efface l'historique d'une session."""
|
|
@@ -449,6 +501,8 @@ if __name__ == '__main__':
|
|
| 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 Status: http://{local_ip}:{API_PORT}/status")
|
|
|
|
|
|
|
| 452 |
print(f"💡 N'oubliez pas de configurer 'app_port: 1212' et 'sdk: docker' dans votre README.md !")
|
| 453 |
print("="*50 + "\n")
|
| 454 |
|
|
|
|
| 31 |
N_RESULTS_RETRIEVAL = 10
|
| 32 |
N_RESULTS_RERANK = 3
|
| 33 |
|
| 34 |
+
# Clé pour la route RAG (récupérée de l'environnement ou par défaut)
|
| 35 |
+
GEMINI_API_KEY_RAG = os.getenv("GEMINI_API_KEY", "AIzaSyDXXY7uSXryTxZ51jQFsSLcPnC_Ivt9V1g")
|
| 36 |
+
# NOUVELLE CLÉ demandée, mise en dur pour la route directe
|
| 37 |
+
GEMINI_API_KEY_DIRECT = "AIzaSyCpG2G3K0cZmTxWFO-c4OoOrW1fcTYQwgo"
|
| 38 |
+
|
| 39 |
GEMINI_MODEL = "gemini-2.5-flash"
|
| 40 |
|
| 41 |
MAX_CONVERSATION_HISTORY = 10
|
|
|
|
| 52 |
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] = {}
|
|
|
|
| 109 |
print(f"⚠️ System prompt non trouvé à {SYSTEM_PROMPT_PATH}. Utilisation du prompt par défaut.")
|
| 110 |
return default
|
| 111 |
|
| 112 |
+
def initialize_gemini_client(api_key, client_name):
|
| 113 |
+
"""Initialise un client Google Gemini."""
|
| 114 |
+
if api_key == "AIzaSyDXXY7uSXryTxZ51jQFsSLcPnC_Ivt9V1g":
|
| 115 |
+
print(f"⚠️ AVIS pour {client_name}: Clé Gemini par défaut/placeholder détectée.")
|
| 116 |
+
if api_key == "AIzaSyCpG2G3K0cZmTxWFO-c4OoOrW1fcTYQwgo":
|
| 117 |
+
print(f"💡 AVIS pour {client_name}: Clé Gemini directe mise en dur détectée.")
|
| 118 |
try:
|
| 119 |
+
print(f"✅ Client Gemini '{client_name}' initialisé.")
|
| 120 |
+
return genai.Client(api_key=api_key)
|
| 121 |
except Exception as e:
|
| 122 |
+
print(f"❌ Erreur lors de l'initialisation du client Gemini '{client_name}': {e}")
|
| 123 |
raise
|
| 124 |
|
| 125 |
# ======================================================================
|
|
|
|
| 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("*", "")
|
|
|
|
| 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():
|
| 353 |
"""Initialise tous les modèles et ressources."""
|
| 354 |
+
global model_cross_encoder, model_paraphrase, collection, system_prompt
|
| 355 |
+
global gemini_client_rag, gemini_client_direct
|
| 356 |
|
| 357 |
print("\n" + "="*50)
|
| 358 |
+
print("⚙️ INITIALISATION RAG & Clients Gemini")
|
| 359 |
print("="*50)
|
| 360 |
|
|
|
|
|
|
|
| 361 |
try:
|
| 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
|
|
|
|
| 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()
|
|
|
|
| 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)
|
|
|
|
| 425 |
return jsonify({"generated_response": response})
|
| 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 |
+
|
| 463 |
+
except Exception as e:
|
| 464 |
+
print(f"❌ Erreur générale de l'API Direct: {e}")
|
| 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."""
|
|
|
|
| 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 |
|