# core/llm/llm_manager.py import os import base64 from openai import OpenAI from dotenv import load_dotenv from loguru import logger load_dotenv(dotenv_path="config/.env") class LLMManager: """Gestor de interacción con modelos de lenguaje compatibles con la API de OpenAI.""" def __init__(self): self.api_key = os.getenv("LLM_API_KEY") self.base_url = os.getenv("LLM_BASE_URL") self.model = os.getenv("LLM_MODEL_NAME") self.client = OpenAI(api_key=self.api_key, base_url=self.base_url) self.prompt_system = self._load_system_prompt() def _load_system_prompt(self) -> str: """Carga el prompt del sistema desde 'config/prompt_system.txt'.""" path_system_prompt = os.getenv("PATH_SYSTEM_PROMPT") try: with open(path_system_prompt, "r", encoding="utf-8") as f: logger.info("✅ Prompt del sistema cargado correctamente.") return f.read().strip() except FileNotFoundError: logger.warning( f"⚠️ No se encontró '{path_system_prompt}'. Se usará un prompt por defecto." ) return "Eres un asistente educativo del MINEDU." def _encode_image(self, image_bytes: bytes) -> str: """Convierte bytes de imagen a Base64.""" logger.debug("🔄 Codificando imagen a Base64.") return base64.b64encode(image_bytes).decode("utf-8") def generate_response( self, user_query: str, context: str = "", image: bytes = None ) -> str: """Genera respuesta multimodal (texto + imagen) o solo texto.""" try: logger.info("🔹 Generando respuesta para la consulta del usuario.") messages = [] # Añadir prompt del sistema if self.prompt_system: messages.append({"role": "system", "content": self.prompt_system}) # Si es imagen (multimodal) if image: logger.debug("🖼️ Procesando entrada multimodal con imagen.") base64_image = self._encode_image(image) messages.append( { "role": "user", "content": [ { "type": "text", "text": user_query if user_query else "Describe esta imagen con enfoque educativo.", }, { "type": "image_url", "image_url": { "url": f"data:image/png;base64,{base64_image}" }, }, ], } ) else: # Solo texto, con posible contexto full_prompt = user_query if context: logger.debug("➕ Añadiendo contexto al mensaje.") full_prompt = f"{context}\n\nPregunta: {user_query}" messages.append({"role": "user", "content": full_prompt}) # Llamada al modelo response = self.client.chat.completions.create( model=self.model, messages=messages, ) logger.success("✅ Respuesta generada correctamente.") return response.choices[0].message.content except Exception as e: logger.error(f"❌ Error al generar respuesta: {str(e)}") return f"Error al generar respuesta: {str(e)}"