| | |
| | import sys |
| | from google import genai |
| | from google.genai.errors import APIError |
| | from google.genai.types import Part |
| | import os |
| | from typing import Generator, List, Dict, Union |
| | from config import GEMINI_MODEL_NAME, DEFAULT_MAX_TOKENS, DEFAULT_TEMPERATURE, SYSTEM_PROMPT |
| | import base64 |
| |
|
| |
|
| | |
| |
|
| | client = None |
| | MODEL_LOADED_SUCCESSFULLY = False |
| |
|
| |
|
| | def run_api_self_test(client_instance): |
| | """Effectue un test simple pour vérifier si l'API fonctionne.""" |
| | try: |
| | print("INFO: Exécution du test de connexion à l'API Gemini...", file=sys.stderr) |
| | |
| | |
| | test_content = ["Salutation brève"] |
| | |
| | response = client_instance.models.generate_content( |
| | model=GEMINI_MODEL_NAME, |
| | contents=test_content, |
| | config=genai.types.GenerateContentConfig(max_output_tokens=10) |
| | ) |
| | |
| | |
| | if response is not None: |
| | response_text_info = response.text[:30] if response.text else "[Réponse sans texte]" |
| | print("INFO: ✅ Test de connexion API réussi. Le modèle est joignable.", file=sys.stderr) |
| | print(f"INFO: Réponse du test (début): {response_text_info}...", file=sys.stderr) |
| | return True |
| | else: |
| | print("ALERTE: ❌ Test de connexion API échoué. Objet de réponse None.", file=sys.stderr) |
| | return False |
| | |
| | except APIError as e: |
| | |
| | print(f"ALERTE: ❌ Test de connexion API échoué (APIError). Vérifiez la validité de la clé: {e.message}", file=sys.stderr) |
| | return False |
| | except Exception as e: |
| | |
| | print(f"ALERTE: ❌ Test de connexion API échoué (Erreur Générale): {e}", file=sys.stderr) |
| | return False |
| |
|
| |
|
| | def get_model_instance(): |
| | """Initialise ou retourne l'instance du client Gemini.""" |
| | global client, MODEL_LOADED_SUCCESSFULLY |
| | |
| | if client is None: |
| | |
| | print("INFO: Tentative d'initialisation du client Gemini...", file=sys.stderr) |
| | api_key = os.environ.get("GEMINI_API_KEY") |
| | |
| | if not api_key: |
| | print("ALERTE: ❌ Variable d'environnement 'GEMINI_API_KEY' non trouvée/vide.", file=sys.stderr) |
| | MODEL_LOADED_SUCCESSFULLY = False |
| | return |
| | |
| | print("INFO: Clé API 'GEMINI_API_KEY' trouvée dans les variables d'environnement.", file=sys.stderr) |
| | |
| | try: |
| | |
| | client = genai.Client(api_key=api_key) |
| | print("INFO: Client Gemini initialisé. Tentative de self-test...", file=sys.stderr) |
| | |
| | |
| | if run_api_self_test(client): |
| | MODEL_LOADED_SUCCESSFULLY = True |
| | print(f"INFO: Configuration API réussie. Modèle LLM prêt. Utilisant {GEMINI_MODEL_NAME}.", file=sys.stderr) |
| | else: |
| | |
| | client = None |
| | MODEL_LOADED_SUCCESSFULLY = False |
| | print("ALERTE: ❌ Client Gemini non initialisé. Le self-test a échoué.", file=sys.stderr) |
| | |
| | except Exception as e: |
| | |
| | print(f"ALERTE: ❌ Échec critique de l'initialisation du client Gemini: {e}", file=sys.stderr) |
| | client = None |
| | MODEL_LOADED_SUCCESSFULLY = False |
| |
|
| |
|
| | |
| | def create_file_part(file_data: Dict) -> Part: |
| | """Crée un objet Part à partir d'un dictionnaire de données de fichier.""" |
| | |
| | |
| | |
| | file_bytes = base64.b64decode(file_data.get('content')) |
| | mime_type = file_data.get('mime_type') |
| | |
| | |
| | return Part.from_bytes( |
| | data=file_bytes, |
| | mime_type=mime_type |
| | ) |
| |
|
| |
|
| | |
| | def normalize_history_for_gemini( |
| | history: List[Dict], |
| | new_prompt: str, |
| | files: List[Dict] = [] |
| | ) -> List[Dict]: |
| | """ |
| | Convertit l'historique de conversation et les nouveaux fichiers/prompt en |
| | le format Contents attendu par l'API Gemini. |
| | """ |
| | gemini_contents = [] |
| | |
| | |
| | for conversation_turn in history: |
| | for message in conversation_turn.get('messages', []): |
| | role = message.get('role') |
| | text = message.get('text') |
| | |
| | gemini_role = 'model' if role == 'assistant' else 'user' |
| | |
| | if text and gemini_role in ['user', 'model']: |
| | gemini_contents.append({ |
| | "role": gemini_role, |
| | "parts": [{"text": text}] |
| | }) |
| |
|
| | |
| | user_parts = [] |
| | |
| | |
| | for file_data in files: |
| | |
| | try: |
| | part = create_file_part(file_data) |
| | user_parts.append(part) |
| | except Exception as e: |
| | |
| | print(f"Erreur de traitement de fichier: {e}", file=sys.stderr) |
| | |
| | user_parts.append({"text": f"(Erreur: Le fichier {file_data.get('filename', 'inconnu')} n'a pas pu être lu)"}) |
| |
|
| | |
| | if new_prompt: |
| | user_parts.append({"text": new_prompt}) |
| | |
| | |
| | if user_parts: |
| | gemini_contents.append({ |
| | "role": "user", |
| | "parts": user_parts |
| | }) |
| | |
| | return gemini_contents |
| |
|
| |
|
| | |
| | def generate_text_response( |
| | prompt: str, |
| | history: List[Dict] = [], |
| | files: List[Dict] = [] |
| | ) -> Generator[str, None, None]: |
| | """ |
| | Génère une réponse en streaming de l'API Gemini en utilisant l'historique et les fichiers. |
| | """ |
| | |
| | get_model_instance() |
| | if not MODEL_LOADED_SUCCESSFULLY or client is None: |
| | yield "Erreur: Le service Gemini n'est pas initialisé. Vérifiez la clé API." |
| | return |
| |
|
| | |
| | if not prompt and not files: |
| | yield "Erreur: Le contenu de la requête (prompt et/ou fichiers) est vide." |
| | return |
| |
|
| | |
| | config = genai.types.GenerateContentConfig( |
| | system_instruction=SYSTEM_PROMPT, |
| | max_output_tokens=DEFAULT_MAX_TOKENS, |
| | temperature=DEFAULT_TEMPERATURE, |
| | ) |
| | |
| | |
| | try: |
| | contents = normalize_history_for_gemini(history, prompt, files) |
| | except Exception as e: |
| | error_message = f"Erreur lors de la normalisation de l'historique et des fichiers pour Gemini: {e}" |
| | print(f"Erreur de normalisation: {error_message}", file=sys.stderr) |
| | yield f"Erreur: {error_message}" |
| | return |
| | |
| | try: |
| | |
| | response_stream = client.models.generate_content_stream( |
| | model=GEMINI_MODEL_NAME, |
| | contents=contents, |
| | config=config, |
| | ) |
| |
|
| | for chunk in response_stream: |
| | if chunk.text: |
| | yield chunk.text |
| |
|
| | except APIError as e: |
| | error_message = f"Erreur de l'API Gemini: {e.message}" |
| | print(f"Erreur API: {error_message}", file=sys.stderr) |
| | yield f"Erreur: {error_message}" |
| | |
| | except Exception as e: |
| | error_message = f"Erreur inattendue lors de la génération: {str(e)}" |
| | print(f"Erreur Générale: {error_message}", file=sys.stderr) |
| | yield f"Erreur: {error_message}" |