diff --git a/plugins/ai.py b/plugins/ai.py index 22f1dcd..13e3c5f 100644 --- a/plugins/ai.py +++ b/plugins/ai.py @@ -1,221 +1,90 @@ import requests -import logging -import re import os +import re from datetime import datetime -# Configuración de logging -logging.basicConfig(level=logging.INFO) -logger = logging.getLogger("AI-Plugin") - -# Variables de entorno necesarias HUGGINGFACE_API_KEY = os.getenv("HUGGINGFACE_API_KEY") BRAVE_API_KEY = os.getenv("BRAVE_API_KEY") MODEL_ID = os.getenv("HUGGINGFACE_MODEL", "deepseek/deepseek-v3-0324") -# Endpoints HF_API_URL = "https://router.huggingface.co/novita/v3/openai/chat/completions" BRAVE_URL = "https://api.search.brave.com/res/v1/web/search" -# Headers HEADERS_HF = { "Authorization": f"Bearer {HUGGINGFACE_API_KEY}", "Content-Type": "application/json" } + HEADERS_BRAVE = { "Accept": "application/json", "X-Subscription-Token": BRAVE_API_KEY } - -def respuesta_desactualizada(texto): - """Detecta si la respuesta contiene fechas antiguas en relación al mes/año actual.""" - texto = texto.lower() - ahora = datetime.now() - - # Lista de años pasados (2020 hasta el año anterior) - anyos_pasados = [str(a) for a in range(2020, ahora.year)] - if any(a in texto for a in anyos_pasados): - return True - - # Lista de meses pasados del mismo año (enero - mes anterior) - meses_ordenados = [ - "enero", "febrero", "marzo", "abril", "mayo", "junio", - "julio", "agosto", "septiembre", "octubre", "noviembre", "diciembre" - ] - mes_actual_idx = ahora.month - 1 - meses_pasados = meses_ordenados[:mes_actual_idx] - - for mes in meses_pasados: - if f"{mes} de {ahora.year}" in texto: - return True - - return False +def buscar_en_brave(consulta, max_results=3): + params = {"q": consulta, "count": max_results, "safesearch": "moderate"} + try: + res = requests.get(BRAVE_URL, headers=HEADERS_BRAVE, params=params, timeout=10) + res.raise_for_status() + data = res.json() + resultados = data.get("web", {}).get("results", []) + return [ + f"{r.get('title', '').strip()}: {r.get('description', '').strip()}" + for r in resultados if r.get("description") + ] + except Exception as e: + return [f"⚠️ Error al consultar Brave: {e}"] -def sanitize_response(text, remove_first_line=True): - """Limpia etiquetas HTML y fragmentos redundantes.""" - clean_text = re.sub(r"<.*?>", "", text) - clean_text = re.sub(r""", '"', clean_text) - clean_text = re.sub(r"&", "&", clean_text) - clean_text = clean_text.strip() - - instrucciones_a_eliminar = [ - "Resume y humaniza esta información en lenguaje natural:", - "Responde en español:", - "A continuación tienes un conjunto de textos o fragmentos sacados de sitios web. Tu tarea es analizarlos, eliminar información redundante o poco útil, y resumir todo en un único párrafo claro y natural para un humano. No repitas los títulos ni hagas listas. Explica como si le contaras a un amigo:" - ] - - for instruccion in instrucciones_a_eliminar: - if clean_text.lower().startswith(instruccion.lower()): - clean_text = clean_text[len(instruccion):].strip() - - return clean_text - - -def query_huggingface(prompt, is_humanization=False): - """Consulta el modelo DeepSeek en Hugging Face Router.""" +def inferir_con_huggingface(pregunta, resultados_web=None): if not HUGGINGFACE_API_KEY: - logger.error("No se ha definido la variable HUGGINGFACE_API_KEY.") - return None + return "⚠️ Falta la variable HUGGINGFACE_API_KEY." - # Incluir fecha real en el prompt del usuario - fecha_actual = datetime.now().strftime("%d de %B de %Y") # ej: "11 de abril de 2025" - prompt_con_fecha = f"Hoy es {fecha_actual}. {prompt.strip()}" + fecha_actual = datetime.now().strftime("%d de %B de %Y") - # Prompt de sistema personalizado - if is_humanization: - system_prompt = ( - "Tu tarea es analizar fragmentos de sitios web y resumir la información relevante " - "en un único párrafo claro, sin repetir títulos ni hacer listas. Sé directo, preciso y " - "habla como si lo explicaras a un amigo en español." - ) - else: - system_prompt = ( - "Eres un asistente útil que siempre responde en español. " - "Ignora nombres de usuario como 'teraflops'. " - "Si no conoces información actualizada a la fecha de hoy, indica que no estás seguro. " - "No inventes datos si no los sabes." - ) + system_prompt = ( + "Eres un asistente útil que responde siempre en español claro y natural. " + "Responde con base únicamente en los datos proporcionados. Si no hay información clara, di que no puedes confirmarlo." + ) + + user_prompt = f"Fecha actual: {fecha_actual}.\nPregunta: {pregunta}" + + if resultados_web: + contexto = "\n".join(f"{i+1}. {r}" for i, r in enumerate(resultados_web)) + user_prompt += f"\n\nResultados web:\n{contexto}" payload = { "model": MODEL_ID, "messages": [ {"role": "system", "content": system_prompt}, - {"role": "user", "content": prompt_con_fecha} + {"role": "user", "content": user_prompt} ], "temperature": 0.7, - "max_tokens": 300 + "max_tokens": 400 } try: - response = requests.post(HF_API_URL, headers=HEADERS_HF, json=payload, timeout=120) - response.raise_for_status() - content = response.json()["choices"][0]["message"]["content"] - logger.info(f"[DEBUG] Respuesta cruda de Hugging Face:\n{content}") - return sanitize_response(content) - except requests.exceptions.RequestException as e: - logger.error(f"[ERROR] Fallo al conectar con Hugging Face: {e}") - return None - - -def buscar_en_brave(consulta): - """Consulta Brave Search y decide si humanizar o mostrar mensaje de fallback.""" - if not BRAVE_API_KEY: - logger.error("No se ha definido la variable BRAVE_API_KEY.") - return None - - params = {"q": consulta, "count": 3, "safesearch": "moderate"} - - try: - logger.info(f"[DEBUG] Consultando Brave Search con: {consulta}") - response = requests.get(BRAVE_URL, headers=HEADERS_BRAVE, params=params, timeout=30) - response.raise_for_status() - data = response.json() - resultados = data.get("web", {}).get("results", []) - - if not resultados: - logger.warning("Brave no devolvió resultados.") - return "No encontré información relevante en Brave Search." - - informacion_bruta = "\n".join( - f"{res.get('title', 'Sin título')}: {res.get('description', 'Sin descripción')}" - for res in resultados - ) - - logger.info(f"[DEBUG] Resultados de Brave crudos:\n{informacion_bruta}") - - # Palabras clave genéricas que suelen indicar contenido útil - palabras_utiles = [ - "2025", "abril", "marzo", "versión", "version", "released", "linux", - "resultado", "marcador", "gol", "victoria", "derrota", "ganó", "1-0", "2-1", - "empate", "directo", "esta noche", "hoy", "final", "resumen", "publicado", - "disponible", "estreno", "actualización", "presentación", "valencia", "sevilla" - ] - - texto_brave = informacion_bruta.lower() - pistas_utiles = any(p in texto_brave for p in palabras_utiles) - - if not pistas_utiles: - logger.info("[DEBUG] Brave devolvió resultados ambiguos.") - return ( - "📡 Brave Search encontró resultados poco claros. " - "No se halló información confirmada. Puedes revisar sitios oficiales para más detalles." - ) - - # Si hay contenido con pistas útiles, lo pasamos a Hugging Face para humanizar - resumen = query_huggingface(sanitize_response(informacion_bruta), is_humanization=True) - return resumen - - except requests.exceptions.RequestException as e: - logger.error(f"[ERROR] Fallo en Brave Search: {e}") - return None + res = requests.post(HF_API_URL, headers=HEADERS_HF, json=payload, timeout=60) + res.raise_for_status() + return res.json()["choices"][0]["message"]["content"].strip() + except Exception as e: + return f"⚠️ Error al consultar Hugging Face: {e}" def procesar_consulta(prompt): - """Intenta responder usando HF, y si falla, pregunta a Brave.""" - logger.info(f"[DEBUG] ⏳ Consultando Hugging Face con: {prompt}") - respuesta_hf = query_huggingface(prompt) - - if not respuesta_hf: - return "No se obtuvo respuesta de Hugging Face." - - patron_generica = re.compile( - r"(no hay información.*?|" - r"no tengo.*?información|" - r"consulta.*?sitios? web oficiales|" - r"aún no se ha anunciado|" - r"lo siento, pero no puedo responder a eso|" - r"no tengo suficiente información|" - r"no puedo proporcionar esa información|" - r"te recomiendo verificar fuentes oficiales)", - re.IGNORECASE - ) - - # 🔧 Este bloque estaba fuera de la función — lo metemos correctamente dentro - if patron_generica.search(respuesta_hf) or respuesta_desactualizada(respuesta_hf): - logger.info("[DEBUG] Hugging Face dio respuesta genérica o desactualizada, consultando Brave...") - respuesta_brave = buscar_en_brave(prompt) - return f"📡 Fuente: Brave Search\n{respuesta_brave or 'No se encontró información útil.'}" - - return f"🧠 Fuente: Hugging Face\n{respuesta_hf}" - - - - -def run(sender, *args): - """Manejador del comando .ai""" - if not args: - return "Por favor, proporciona una consulta." - - consulta = " ".join(args).strip() - logger.info(f"[.ai] Procesando consulta: {consulta}") - - respuesta = procesar_consulta(consulta) + resultados = buscar_en_brave(prompt) + respuesta = inferir_con_huggingface(prompt, resultados if resultados else None) return respuesta or "No se encontró información disponible." -def help(): - return "Uso: .ai - Responde con IA usando Hugging Face y Brave Search si es necesario." +def run(sender, *args): + if not args: + return "Uso: .ai " + + consulta = " ".join(args).strip() + return procesar_consulta(consulta) + + +def help(): + return "Uso: .ai - Responde con Hugging Face y contexto de Brave Search si es necesario." diff --git a/plugins/chatgpt.py b/plugins/chatgpt.py new file mode 100644 index 0000000..51b1182 --- /dev/null +++ b/plugins/chatgpt.py @@ -0,0 +1,102 @@ +import requests +import os +import re +from datetime import datetime + +OPENAI_API_KEY = os.getenv("OPENAI_API_KEY") +BRAVE_API_KEY = os.getenv("BRAVE_API_KEY") +OPENAI_MODEL = os.getenv("OPENAI_MODEL", "gpt-4o") + +OPENAI_API_URL = "https://api.openai.com/v1/chat/completions" +BRAVE_URL = "https://api.search.brave.com/res/v1/web/search" + +HEADERS_OPENAI = { + "Authorization": f"Bearer {OPENAI_API_KEY}", + "Content-Type": "application/json" +} + +HEADERS_BRAVE = { + "Accept": "application/json", + "X-Subscription-Token": BRAVE_API_KEY +} + + +def buscar_en_brave(consulta, max_results=3): + """Busca en Brave Search y devuelve los mejores resultados web.""" + params = {"q": consulta, "count": max_results, "safesearch": "moderate"} + + try: + res = requests.get(BRAVE_URL, headers=HEADERS_BRAVE, params=params, timeout=10) + res.raise_for_status() + data = res.json() + resultados = data.get("web", {}).get("results", []) + return [ + f"{r.get('title', '').strip()}: {r.get('description', '').strip()}" + for r in resultados if r.get("description") + ] + except Exception as e: + return [f"⚠️ Error al consultar Brave: {e}"] + + +def inferir_con_openai(pregunta, resultados_web=None): + """Genera una respuesta usando OpenAI, con contexto opcional.""" + fecha_actual = datetime.now().strftime("%d de %B de %Y") + + system_prompt = ( + "Eres un asistente que responde preguntas basándote en resultados web proporcionados. " + "Debes analizar cuidadosamente si hay suficiente información para confirmar una respuesta. " + "Si encuentras frases como 'X ganó Y', 'X venció a Y', 'X es campeón', considera que eso es suficiente para afirmar el resultado, " + "siempre que provenga de una fuente confiable. " + "Si los resultados son vagos o contradictorios, di que no puedes confirmarlo aún." + ) + + + user_prompt = f"Fecha actual: {fecha_actual}.\nPregunta: {pregunta}" + + if resultados_web: + contexto = "\n".join(f"{i+1}. {r}" for i, r in enumerate(resultados_web)) + user_prompt += f"\n\nResultados web:\n{contexto}" + + payload = { + "model": OPENAI_MODEL, + "messages": [ + {"role": "system", "content": system_prompt}, + {"role": "user", "content": user_prompt} + ], + "temperature": 0.7, + "max_tokens": 400 + } + + try: + res = requests.post(OPENAI_API_URL, headers=HEADERS_OPENAI, json=payload, timeout=60) + res.raise_for_status() + return res.json()["choices"][0]["message"]["content"].strip() + except Exception as e: + return f"⚠️ Error al consultar OpenAI: {e}" + + +def procesar_consulta(prompt): + """Busca en Brave y deja que OpenAI razone libremente con esos resultados.""" + resultados = buscar_en_brave(prompt) + + # Aunque no haya resultados útiles, seguimos con OpenAI para que juzgue + respuesta = inferir_con_openai(prompt, resultados if resultados else None) + + return respuesta or "No se encontró información disponible." + +def help(): + return ( + "Uso: .chatgpt \n" + "Consulta la IA de OpenAI con contexto de Brave Search si está disponible.\n" + "Ejemplo: .chatgpt ¿Quién ganó la Champions League 2025?" + ) + + +def run(sender, *args): + if not args: + return "Uso: .ai " + + consulta = " ".join(args).strip() + return procesar_consulta(consulta) + +