from typing import Optional, Dict, Any import os import google.generativeai as genai from huggingface_hub import HfApi import logging from smolagents import HfApiModel logger = logging.getLogger(__name__) class ModelWrapper: """Спрощена обгортка для моделей""" def __init__(self, model, model_type): self.model = model self.model_type = model_type def __call__(self, prompt, **kwargs): try: if self.model_type == 'gemini': # Якщо prompt - це словник з роллю і контентом if isinstance(prompt, dict) and 'content' in prompt: text = prompt['content'] # Якщо prompt - це список повідомлень elif isinstance(prompt, list): # Беремо останнє повідомлення last_message = prompt[-1] text = last_message.get('content', '') if isinstance(last_message, dict) else str(last_message) else: text = str(prompt) logger.info(f"Prompt для Gemini: {text[:100]}...") response = self.model.generate_content(text) return response.text else: kwargs.pop('stop_sequences', None) # Видаляємо stop_sequences для HF моделей return self.model(prompt, **kwargs) except Exception as e: logger.error(f"Помилка ModelWrapper: {str(e)}") logger.error(f"Тип prompt: {type(prompt)}") logger.error(f"Prompt: {str(prompt)[:200]}") raise """Обгортка для уніфікації інтерфейсу різних моделей""" def __init__(self, model, model_type): self.model = model self.model_type = model_type def _extract_text_from_input(self, input_data): """Витягує текст з різних форматів вхідних даних""" try: if isinstance(input_data, str): return input_data elif isinstance(input_data, list): # Обробка списку повідомлень messages = [] for msg in input_data: if isinstance(msg, dict): # Витягуємо контент з повідомлення content = msg.get('content', '') if isinstance(content, list): # Якщо контент - список, обробляємо кожен елемент for item in content: if isinstance(item, dict) and 'text' in item: messages.append(item['text']) else: messages.append(str(item)) else: messages.append(str(content)) else: messages.append(str(msg)) return ' '.join(messages) elif isinstance(input_data, dict): # Обробка одиночного повідомлення content = input_data.get('content', '') if isinstance(content, list): return ' '.join(item.get('text', str(item)) for item in content if isinstance(item, dict)) return str(content) return str(input_data) except Exception as e: logger.error(f"Помилка при обробці вхідних даних: {e}") logger.error(f"Тип даних: {type(input_data)}") logger.error(f"Дані: {str(input_data)[:200]}") return str(input_data) def __call__(self, prompt: str, **kwargs) -> str: """ Виклик моделі з підтримкою додаткових параметрів. Args: prompt: Текст запиту **kwargs: Додаткові параметри (ігноруються для Gemini) """ try: if self.model_type == 'gemini': # Для Gemini ігноруємо додаткові параметри і просто передаємо текст text = self._extract_text_from_input(prompt) logger.info(f"Gemini отримав запит: {text[:200]}...") # Логуємо перші 200 символів response = self.model.generate_content(text) if response and hasattr(response, 'text'): logger.info("Gemini успішно згенерував відповідь") return response.text else: error_msg = "Gemini повернув порожню або неправильну відповідь" logger.error(error_msg) raise ValueError(error_msg) else: # huggingface model # Видаляємо stop_sequences, якщо він є kwargs.pop('stop_sequences', None) return self.model(prompt, **kwargs) except Exception as e: logger.error(f"Помилка при виклику моделі {self.model_type}: {e}") logger.error(f"Тип вхідних даних: {type(prompt)}") logger.error(f"Вміст вхідних даних: {str(prompt)[:200]}") raise """Обгортка для уніфікації інтерфейсу різних моделей""" def __init__(self, model, model_type): self.model = model self.model_type = model_type def _extract_text_from_input(self, input_data): """Витягує текст з різних форматів вхідних даних""" if isinstance(input_data, str): return input_data elif isinstance(input_data, dict): return input_data.get('content', str(input_data)) elif isinstance(input_data, list): return ' '.join(self._extract_text_from_input(item) for item in input_data) return str(input_data) def __call__(self, prompt: str, **kwargs) -> str: """ Виклик моделі з підтримкою додаткових параметрів. Args: prompt: Текст запиту **kwargs: Додаткові параметри (ігноруються для Gemini) """ try: if self.model_type == 'gemini': # Для Gemini ігноруємо додаткові параметри і просто передаємо текст text = self._extract_text_from_input(prompt) logger.info(f"Gemini отримав запит: {text[:200]}...") # Логуємо перші 200 символів response = self.model.generate_content(text) if response and hasattr(response, 'text'): logger.info("Gemini успішно згенерував відповідь") return response.text else: error_msg = "Gemini повернув порожню або неправильну відповідь" logger.error(error_msg) raise ValueError(error_msg) else: # huggingface model # Видаляємо stop_sequences, якщо він є kwargs.pop('stop_sequences', None) return self.model(prompt, **kwargs) except Exception as e: logger.error(f"Помилка при виклику моделі {self.model_type}: {e}") logger.error(f"Тип вхідних даних: {type(prompt)}") logger.error(f"Вміст вхідних даних: {str(prompt)[:200]}") # Логуємо перші 200 символів raise class ModelInitializer: def __init__(self, config_path: str = 'models_config.json'): self.config = self._load_config(config_path) self._setup_api_keys() def _setup_api_keys(self): """Налаштування API ключів""" self.hf_api_token = os.getenv('HF_API_TOKEN') self.gemini_api_key = os.getenv('GEMINI_API_KEY') if self.gemini_api_key: genai.configure(api_key=self.gemini_api_key) logger.info("API ключ Gemini успішно налаштовано") else: logger.warning("API ключ Gemini не знайдено") def _load_config(self, config_path: str) -> Dict[str, Any]: """Завантаження конфігурації моделей""" import json try: with open(config_path, 'r', encoding='utf-8') as f: config = json.load(f) logger.info(f"Конфігурацію успішно завантажено з {config_path}") return config except Exception as e: logger.error(f"Помилка завантаження конфігурації: {e}") raise def initialize_model(self, model_key: Optional[str] = None) -> Any: """Ініціалізація вибраної моделі""" try: if model_key is None: model_key = self.config['default_model'] logger.info(f"Використовуємо модель за замовчуванням: {model_key}") model_config = self.config['models'].get(model_key) if not model_config: error_msg = f"Модель {model_key} не знайдена в конфігурації" logger.error(error_msg) raise ValueError(error_msg) if model_key == 'gemini-flash': model = self._initialize_gemini(model_config) logger.info("Ініціалізовано Gemini модель") return ModelWrapper(model, 'gemini') else: model = self._initialize_huggingface(model_config) logger.info("Ініціалізовано HuggingFace модель") return ModelWrapper(model, 'huggingface') except Exception as e: error_msg = f"Помилка ініціалізації моделі: {e}" logger.error(error_msg) raise ValueError(error_msg) def _initialize_gemini(self, config: Dict[str, Any]) -> Any: """Ініціалізація Gemini моделі""" if not self.gemini_api_key: raise ValueError("GEMINI_API_KEY не знайдено в змінних середовища") try: # Налаштування безпеки (вимикаємо всі обмеження для наукових досліджень) safety_settings = [ { "category": "HARM_CATEGORY_HARASSMENT", "threshold": "BLOCK_NONE" }, { "category": "HARM_CATEGORY_HATE_SPEECH", "threshold": "BLOCK_NONE" }, { "category": "HARM_CATEGORY_SEXUALLY_EXPLICIT", "threshold": "BLOCK_NONE" }, { "category": "HARM_CATEGORY_DANGEROUS_CONTENT", "threshold": "BLOCK_NONE" } ] # Створюємо модель з мінімальними обмеженнями model = genai.GenerativeModel( model_name='gemini-pro', safety_settings=safety_settings ) # Тестуємо модель try: logger.info("Тестування з'єднання з Gemini...") test_response = model.generate_content("Test connection") if test_response and hasattr(test_response, 'text'): logger.info("Gemini модель успішно ініціалізовано та протестовано") return model else: raise ValueError("Тестова генерація не повернула текст") except Exception as e: raise ValueError(f"Помилка тестування Gemini: {str(e)}") except Exception as e: error_msg = f"Помилка ініціалізації Gemini: {str(e)}" logger.error(error_msg) raise ValueError(error_msg) def _initialize_huggingface(self, config: Dict[str, Any]) -> Any: """Ініціалізація Hugging Face моделі""" if not self.hf_api_token: raise ValueError("HF_API_TOKEN не знайдено в змінних середовища") try: model = HfApiModel( model_id=config['model_id'], token=self.hf_api_token, temperature=config['parameters']['temperature'], max_tokens=config['parameters']['max_tokens'] ) return model except Exception as e: error_msg = f"Помилка ініціалізації HuggingFace: {str(e)}" logger.error(error_msg) raise ValueError(error_msg) def get_available_models(self) -> list: """Отримання списку доступних моделей""" return [(key, model['description']) for key, model in self.config['models'].items()]