[FIXED] Corregir error de conexión con la API de Perplexity en rate_update.py

- Mejorar la función query_perplexity con mejor manejo de errores y debugging
- Actualizar el modelo por defecto de 'o1' a 'sonar' que es compatible con la API
- Expandir get_perplexity_api_key para buscar la clave en múltiples ubicaciones
- Implementar sistema de control de errores consecutivos para detener el proceso
- Agregar validación del formato de la API key
- Mejorar registro de depuración para identificar problemas de conexión
- Incluir pausa entre solicitudes tras errores para evitar limitaciones de la API

🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Mauro Rosero P. 2025-03-12 10:37:27 -05:00
parent 233fda5661
commit 1617385d3d
Signed by: mrosero
GPG key ID: 83BD2A5F674B7E26

View file

@ -18,6 +18,7 @@ import requests
import sys import sys
import subprocess import subprocess
import importlib.util import importlib.util
import time
from pathlib import Path from pathlib import Path
# Configuración de logging # Configuración de logging
@ -91,11 +92,47 @@ def get_ai_model():
return "o1" return "o1"
def get_perplexity_api_key(): 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') 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: 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) 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 return api_key
def query_perplexity(prompt, model="o1"): def query_perplexity(prompt, model="o1"):
@ -108,6 +145,12 @@ def query_perplexity(prompt, model="o1"):
"Content-Type": "application/json" "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 = { data = {
"model": model, "model": model,
"messages": [ "messages": [
@ -124,22 +167,52 @@ def query_perplexity(prompt, model="o1"):
} }
try: 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 = 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() result = response.json()
# Extraer solo el valor numérico de la respuesta # Extraer solo el valor numérico de la respuesta
content = result.get('choices', [{}])[0].get('message', {}).get('content', '') 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 # Intentar encontrar un número con 2 decimales en la respuesta
match = re.search(r'\$?(\d+\.\d{2})', content) match = re.search(r'\$?(\d+\.\d{2})', content)
if match: 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 # Si no encuentra un formato exacto, intentar convertir toda la respuesta a float
try: try:
# Eliminar cualquier símbolo de moneda y espacios # Eliminar cualquier símbolo de moneda y espacios
cleaned_content = re.sub(r'[^\d.]', '', content) 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: except ValueError:
logger.error(f"No se pudo extraer un valor numérico de la respuesta: {content}") logger.error(f"No se pudo extraer un valor numérico de la respuesta: {content}")
return None return None
@ -147,6 +220,9 @@ def query_perplexity(prompt, model="o1"):
except requests.exceptions.RequestException as e: except requests.exceptions.RequestException as e:
logger.error(f"Error al conectar con la API de Perplexity: {e}") logger.error(f"Error al conectar con la API de Perplexity: {e}")
return None 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 # Variable global para indicar si pycountry está disponible
pycountry_available = False pycountry_available = False
@ -289,7 +365,17 @@ def update_rate_files():
rate_files = glob.glob(str(CONFIG_DIR / '*.rate')) rate_files = glob.glob(str(CONFIG_DIR / '*.rate'))
logger.info(f"Encontrados {len(rate_files)} archivos de tarifas para actualizar.") 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: 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) programmer_type, region_code = parse_rate_filename(rate_file)
if programmer_type is None: if programmer_type is None:
@ -309,17 +395,25 @@ def update_rate_files():
logger.info(f"Consultando tarifa para {programmer_type}" + logger.info(f"Consultando tarifa para {programmer_type}" +
(f" en región {region_code}" if region_code else "")) (f" en región {region_code}" if region_code else ""))
# Consultar a Perplexity try:
rate = query_perplexity(prompt, model) # Consultar a Perplexity
rate = query_perplexity(prompt, model)
if rate is not None:
# Guardar el resultado en el archivo if rate is not None:
with open(rate_file, 'w') as f: # Guardar el resultado en el archivo
f.write(f"{rate:.2f}\n") with open(rate_file, 'w') as f:
logger.info(f"Actualizado {os.path.basename(rate_file)} con valor: {rate:.2f}") f.write(f"{rate:.2f}\n")
else: logger.info(f"Actualizado {os.path.basename(rate_file)} con valor: {rate:.2f}")
logger.error(f"No se pudo obtener la tarifa para {programmer_type}" + consecutive_errors = 0 # Reiniciar contador de errores tras un éxito
(f" en región {region_code}" if region_code else "")) 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__": if __name__ == "__main__":