import json import base64 import os from typing import Dict, List, Any, Optional, Union, Tuple from datetime import timedelta import requests from .llm import LLM class LlamaVision(LLM): """ Classe pour l'intégration avec le modèle Llama Vision 3.2 90B Cette classe hérite de la classe de base LLM et ajoute le support des images """ def __init__(self): """ Initialisation des attributs spécifiques à Llama Vision """ super().__init__() # Configuration du modèle self.Modele = "llama-vision:3.2-90b" # Attributs spécifiques pour Llama Vision self.image_path: str = "" self.image_data: Optional[bytes] = None self.json_data: Dict[str, Any] = {} # Paramètres de génération self.o_mirostat: int = 0 self.o_mirostat_eta: float = 0.1 self.o_mirostat_tau: float = 5 self.o_num_ctx: int = 4096 # Contexte plus large pour les images + texte self.o_repeat_last_n: int = 64 self.o_repeat_penalty: float = 1.1 self.o_temperature: float = 0.7 # Valeur par défaut adaptée pour les descriptions d'images self.o_seed: int = 0 self.o_stop: List[str] = [] self.o_num_predict: int = 1024 # Nombre de tokens à prédire self.o_top_k: int = 40 self.o_top_p: float = 0.9 self.o_min_p: float = 0 # Options supplémentaires self.keep_alive: timedelta = timedelta(minutes=5) self.raw: bool = False self._stream: bool = False # Stockage des résultats self.dernier_resultat: Dict[str, Any] = {} self.historique_resultats: List[Dict[str, Any]] = [] def urlBase(self) -> str: """ Retourne l'URL de base de l'API Ollama pour Llama Vision """ return "http:/217.182.105.173/:11434/" def cleAPI(self) -> str: """ Retourne la clé API (vide pour Ollama local) """ return "" def set_image(self, image_path: str) -> bool: """ Définit l'image à analyser à partir d'un chemin de fichier Retourne True si l'image a été chargée avec succès """ if not os.path.exists(image_path): return False try: with open(image_path, "rb") as f: self.image_data = f.read() self.image_path = image_path return True except Exception as e: print(f"Erreur lors du chargement de l'image: {e}") return False def set_image_data(self, image_data: bytes) -> bool: """ Définit directement les données de l'image à analyser """ try: self.image_data = image_data self.image_path = "" return True except Exception as e: print(f"Erreur lors de la définition des données d'image: {e}") return False def set_json_data(self, json_data: Dict[str, Any]) -> bool: """ Définit les données JSON à associer à l'image """ try: self.json_data = json_data return True except Exception as e: print(f"Erreur lors de la définition des données JSON: {e}") return False def Interroger(self, question: str) -> str: """ Interroge le modèle Llama Vision avec une question et l'image chargée """ if not self.image_data: return "Erreur: Aucune image n'a été chargée. Utilisez set_image() ou set_image_data() d'abord." return self.LLM_POST(question) def _even_LLM_POST(self, question: str) -> None: """ Préparation du contenu de la requête pour Llama Vision """ # Paramètres de base self._Contenu["model"] = self.Modele self._Contenu["system"] = self.prompt_system if self.prompt_system else "Tu es un assistant d'analyse d'images qui peut analyser des images et répondre à des questions en français. Sois précis et détaillé dans tes descriptions." self._Contenu["prompt"] = question self._Contenu["stream"] = self._stream self._Contenu["raw"] = self.raw # Ajout de l'image en base64 if self.image_data: base64_image = base64.b64encode(self.image_data).decode('utf-8') self._Contenu["images"] = [base64_image] # Conversion de timedelta en nombre de secondes pour keep_alive if isinstance(self.keep_alive, timedelta): self._Contenu["keep_alive"] = int(self.keep_alive.total_seconds()) # Ajout des données JSON dans le prompt si disponibles if self.json_data: json_str = json.dumps(self.json_data, ensure_ascii=False, indent=2) # On ajoute le JSON au prompt pour qu'il soit traité avec l'image self._Contenu["prompt"] = f"{question}\n\nVoici des données JSON associées à cette image:\n```json\n{json_str}\n```" # Options avancées pour la génération self._Contenu["options"] = { "mirostat": self.o_mirostat, "mirostat_eta": self.o_mirostat_eta, "mirostat_tau": self.o_mirostat_tau, "num_ctx": self.o_num_ctx, "repeat_last_n": self.o_repeat_last_n, "repeat_penalty": self.o_repeat_penalty, "temperature": self.o_temperature, "seed": self.o_seed, "stop": self.o_stop, "num_predict": self.o_num_predict, "top_k": self.o_top_k, "top_p": self.o_top_p, "min_p": self.o_min_p } def _interrogerRetourneReponse(self, reponse: str) -> str: """ Extraction de la réponse à partir du JSON retourné par Llama Vision """ try: data = json.loads(reponse) self.dernier_resultat = data self.historique_resultats.append(data) # Ollama retourne généralement la réponse dans la clé "response" if "response" in data: return data["response"] else: return str(data) except Exception as e: return f"Erreur lors de l'analyse de la réponse: {e}\n\nRéponse brute: {reponse}" def fusionner_json_avec_resultats(self) -> Dict[str, Any]: """ Fusionne les données JSON existantes avec les résultats de l'analyse d'image """ if not self.dernier_resultat: return self.json_data # Créer une copie du JSON original resultat_fusionne = self.json_data.copy() if self.json_data else {} # Ajouter le résultat de l'analyse d'image if "analyse_image" not in resultat_fusionne: resultat_fusionne["analyse_image"] = [] # Ajouter le résultat à la liste des analyses nouvelle_analyse = { "modele": self.Modele, "reponse": self.dernier_resultat.get("response", ""), "parametres": { "temperature": self.o_temperature, "top_p": self.o_top_p, "num_ctx": self.o_num_ctx } } resultat_fusionne["analyse_image"].append(nouvelle_analyse) return resultat_fusionne def sauvegarder_resultats(self, chemin_fichier: str) -> bool: """ Sauvegarde les résultats fusionnés dans un fichier JSON """ try: resultats_fusionnes = self.fusionner_json_avec_resultats() with open(chemin_fichier, 'w', encoding='utf-8') as f: json.dump(resultats_fusionnes, f, ensure_ascii=False, indent=2) return True except Exception as e: print(f"Erreur lors de la sauvegarde des résultats: {e}") return False