diff --git a/bin/rate_update.py b/bin/rate_update.py index f01b42c..99be45e 100755 --- a/bin/rate_update.py +++ b/bin/rate_update.py @@ -18,6 +18,7 @@ import requests import sys import subprocess import importlib.util +import time from pathlib import Path # Configuración de logging @@ -91,11 +92,47 @@ def get_ai_model(): return "o1" def get_perplexity_api_key(): - """Obtener la clave API de Perplexity desde una variable de entorno.""" + """Obtener la clave API de Perplexity desde una variable de entorno o archivo.""" + # Intentar obtener la clave de la variable de entorno api_key = os.environ.get('PERPLEXITY_API_KEY') + + # Si no está en la variable de entorno, intentar cargarla desde un archivo if not api_key: - logger.error("No se encontró la clave API de Perplexity. Establezca la variable de entorno PERPLEXITY_API_KEY.") + # Rutas posibles para el archivo de API key + possible_paths = [ + os.path.expanduser('~/.perplexity/api_key'), + os.path.expanduser('~/.config/perplexity/api_key'), + os.path.join(BASE_DIR, 'bin', 'config', 'perplexity_api_key') + ] + + for path in possible_paths: + try: + if os.path.exists(path): + logger.info(f"Intentando cargar API key desde: {path}") + with open(path, 'r') as f: + api_key = f.read().strip() + if api_key: + logger.info("API key de Perplexity cargada desde archivo.") + break + except Exception as e: + logger.warning(f"Error al leer el archivo de API key {path}: {e}") + continue + + # Si seguimos sin tener API key, mostrar error y salir + if not api_key: + logger.error("No se encontró la clave API de Perplexity.") + logger.error("Opciones para configurarla:") + logger.error("1. Establecer la variable de entorno PERPLEXITY_API_KEY") + logger.error("2. Crear un archivo ~/.perplexity/api_key con la clave") + logger.error("3. Crear un archivo ~/.config/perplexity/api_key con la clave") + logger.error("4. Crear un archivo bin/config/perplexity_api_key en el directorio del proyecto") sys.exit(1) + + # Validar formato básico de la API key + if not api_key.startswith('pplx-'): + logger.warning("El formato de la API key de Perplexity parece incorrecto.") + logger.warning("Las API keys de Perplexity suelen comenzar con 'pplx-'.") + return api_key def query_perplexity(prompt, model="o1"): @@ -108,6 +145,12 @@ def query_perplexity(prompt, model="o1"): "Content-Type": "application/json" } + # Verificar los modelos disponibles en Perplexity - 2025 + # Modelos válidos: sonar, mistral-7b, llama-3-sonar-small, llama-3-sonar-medium, llama-3-70b, mixtral-8x7b, codellama-70b + # Si 'o1' no funciona, probar con 'sonar' o 'mistral-7b' + if model == "o1": + model = "sonar" # Usar sonar como fallback en caso de que o1 no esté disponible + data = { "model": model, "messages": [ @@ -124,22 +167,52 @@ def query_perplexity(prompt, model="o1"): } try: + logger.info(f"Enviando consulta a Perplexity usando modelo: {model}") + logger.info(f"Prompt: {prompt}") + + # Convertir el diccionario a JSON para obtener la representación exacta que se enviará + request_body = json.dumps(data, indent=2) + logger.info(f"Cuerpo de la solicitud: {request_body}") + response = requests.post(url, headers=headers, json=data) - response.raise_for_status() + + # Mostrar información de respuesta + logger.info(f"Código de estado HTTP: {response.status_code}") + logger.info(f"Respuesta (raw): {response.text[:200]}...") # Mostrar los primeros 200 caracteres + + # Si la respuesta no es 200, mostrar el error completo + if response.status_code != 200: + logger.error(f"Error de API Perplexity ({response.status_code}): {response.text}") + # Intentar analizar el error para obtener más información + try: + error_data = response.json() + error_message = error_data.get("error", {}).get("message", "Mensaje de error no disponible") + logger.error(f"Mensaje de error: {error_message}") + except: + pass + return None + + # Continuar si la respuesta es exitosa result = response.json() # Extraer solo el valor numérico de la respuesta content = result.get('choices', [{}])[0].get('message', {}).get('content', '') + logger.info(f"Contenido de la respuesta: {content}") + # Intentar encontrar un número con 2 decimales en la respuesta match = re.search(r'\$?(\d+\.\d{2})', content) if match: - return float(match.group(1)) + value = float(match.group(1)) + logger.info(f"Valor extraído: {value}") + return value # Si no encuentra un formato exacto, intentar convertir toda la respuesta a float try: # Eliminar cualquier símbolo de moneda y espacios cleaned_content = re.sub(r'[^\d.]', '', content) - return round(float(cleaned_content), 2) + value = round(float(cleaned_content), 2) + logger.info(f"Valor limpiado y extraído: {value}") + return value except ValueError: logger.error(f"No se pudo extraer un valor numérico de la respuesta: {content}") return None @@ -147,6 +220,9 @@ def query_perplexity(prompt, model="o1"): except requests.exceptions.RequestException as e: logger.error(f"Error al conectar con la API de Perplexity: {e}") return None + except Exception as e: + logger.error(f"Error inesperado en la consulta a Perplexity: {e}") + return None # Variable global para indicar si pycountry está disponible pycountry_available = False @@ -289,7 +365,17 @@ def update_rate_files(): rate_files = glob.glob(str(CONFIG_DIR / '*.rate')) logger.info(f"Encontrados {len(rate_files)} archivos de tarifas para actualizar.") + # Control de errores para limitar los intentos de API + consecutive_errors = 0 + max_consecutive_errors = 3 + for rate_file in rate_files: + # Si hemos tenido demasiados errores consecutivos, detener el proceso + if consecutive_errors >= max_consecutive_errors: + logger.error(f"Se detectaron {consecutive_errors} errores consecutivos. Deteniendo el proceso.") + logger.error("Verifique su conexión a internet y la API key de Perplexity.") + break + programmer_type, region_code = parse_rate_filename(rate_file) if programmer_type is None: @@ -309,17 +395,25 @@ def update_rate_files(): logger.info(f"Consultando tarifa para {programmer_type}" + (f" en región {region_code}" if region_code else "")) - # Consultar a Perplexity - rate = query_perplexity(prompt, model) - - if rate is not None: - # Guardar el resultado en el archivo - with open(rate_file, 'w') as f: - f.write(f"{rate:.2f}\n") - logger.info(f"Actualizado {os.path.basename(rate_file)} con valor: {rate:.2f}") - else: - logger.error(f"No se pudo obtener la tarifa para {programmer_type}" + - (f" en región {region_code}" if region_code else "")) + try: + # Consultar a Perplexity + rate = query_perplexity(prompt, model) + + if rate is not None: + # Guardar el resultado en el archivo + with open(rate_file, 'w') as f: + f.write(f"{rate:.2f}\n") + logger.info(f"Actualizado {os.path.basename(rate_file)} con valor: {rate:.2f}") + consecutive_errors = 0 # Reiniciar contador de errores tras un éxito + else: + logger.error(f"No se pudo obtener la tarifa para {programmer_type}" + + (f" en región {region_code}" if region_code else "")) + consecutive_errors += 1 + except Exception as e: + logger.error(f"Error al actualizar {os.path.basename(rate_file)}: {e}") + consecutive_errors += 1 + # Pequeña pausa tras un error para evitar sobrecargar la API + time.sleep(2) if __name__ == "__main__":