ai/chatgpt improved
This commit is contained in:
parent
252e76ce0c
commit
20d09cd1a2
219
plugins/ai.py
219
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"
|
||||
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")
|
||||
]
|
||||
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
|
||||
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."
|
||||
"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 <pregunta> - Responde con IA usando Hugging Face y Brave Search si es necesario."
|
||||
def run(sender, *args):
|
||||
if not args:
|
||||
return "Uso: .ai <pregunta>"
|
||||
|
||||
consulta = " ".join(args).strip()
|
||||
return procesar_consulta(consulta)
|
||||
|
||||
|
||||
def help():
|
||||
return "Uso: .ai <pregunta> - Responde con Hugging Face y contexto de Brave Search si es necesario."
|
||||
|
||||
|
102
plugins/chatgpt.py
Normal file
102
plugins/chatgpt.py
Normal file
@ -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 <pregunta>\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 <pregunta>"
|
||||
|
||||
consulta = " ".join(args).strip()
|
||||
return procesar_consulta(consulta)
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user