bentosmau commited on
Commit
e6a2928
Β·
1 Parent(s): f6c9a20

Add internet search and summarization capabilities to the AI

Browse files

Integrates DuckDuckGo search and a summarization module into the AI's response generation, allowing it to answer questions beyond its pre-existing knowledge base.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: e3ff2484-bbd8-4aba-bea0-1940769b874a
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: 7188c1b8-fc90-43a9-bbb7-0d608c4caa23
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/1739408b-93a5-479b-a658-30f2493b0467/e3ff2484-bbd8-4aba-bea0-1940769b874a/3MbPI1V
Replit-Helium-Checkpoint-Created: true

chat-app/app.py CHANGED
@@ -6,6 +6,8 @@ from logica import (
6
  buscar_respuesta_personalizada, generar_explicacion_juego,
7
  detectar_roblox, modo_calculadora_activo, extraer_texto_content,
8
  )
 
 
9
  from roblox_api import buscar_jugador, buscar_juego, formatear_jugador, formatear_juego
10
  from matematicas import (
11
  es_solicitud_calculadora, es_operacion_matematica,
@@ -99,7 +101,20 @@ def responder(mensaje, historial):
99
  yield h, ""
100
  return
101
 
102
- historial = aΓ±adir_turno(historial, mensaje, "πŸ€– NEO-1 aΓΊn no tiene una respuesta para eso. Puedes enseΓ±arle mΓ‘s temas agregando respuestas al modelo.")
 
 
 
 
 
 
 
 
 
 
 
 
 
103
  yield historial, ""
104
 
105
  with gr.Blocks(title="mdfjbots-neo-1") as demo:
 
6
  buscar_respuesta_personalizada, generar_explicacion_juego,
7
  detectar_roblox, modo_calculadora_activo, extraer_texto_content,
8
  )
9
+ from buscador import buscar as buscar_web
10
+ from resumidor import resumir
11
  from roblox_api import buscar_jugador, buscar_juego, formatear_jugador, formatear_juego
12
  from matematicas import (
13
  es_solicitud_calculadora, es_operacion_matematica,
 
101
  yield h, ""
102
  return
103
 
104
+ # ── NEO-2: bΓΊsqueda en internet ──────────────────────────────────────────
105
+ historial = añadir_turno(historial, mensaje, "🌐 Buscando en internet...")
106
+ yield historial, ""
107
+
108
+ resultado_web = buscar_web(mensaje)
109
+ if resultado_web.get("encontrado"):
110
+ resumen = resumir(mensaje, resultado_web)
111
+ if resumen:
112
+ historial[-1]["content"] = ""
113
+ for h in stream_tokens(resumen, historial):
114
+ yield h, ""
115
+ return
116
+
117
+ historial[-1]["content"] = "πŸ€– No encontrΓ© informaciΓ³n sobre eso ni en mi base de conocimiento ni en internet. Intenta reformular tu pregunta."
118
  yield historial, ""
119
 
120
  with gr.Blocks(title="mdfjbots-neo-1") as demo:
chat-app/buscador.py ADDED
@@ -0,0 +1,63 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ buscador.py β€” NEO-2 Motor de bΓΊsqueda
3
+ --------------------------------------
4
+ Consulta la DuckDuckGo Instant Answer API (sin API key).
5
+ Devuelve abstract, snippets relacionados y metadatos.
6
+ """
7
+
8
+ import re
9
+ import requests
10
+
11
+ _TIMEOUT = 6
12
+ _DDG_URL = "https://api.duckduckgo.com/"
13
+
14
+ def buscar(query: str) -> dict:
15
+ """
16
+ Busca `query` en DuckDuckGo y devuelve:
17
+ {
18
+ "titulo": str,
19
+ "abstract": str,
20
+ "snippets": [str, ...],
21
+ "url": str,
22
+ "encontrado": bool,
23
+ }
24
+ """
25
+ try:
26
+ params = {
27
+ "q": query,
28
+ "format": "json",
29
+ "no_html": 1,
30
+ "skip_disambig": 1,
31
+ "no_redirect": 1,
32
+ }
33
+ r = requests.get(_DDG_URL, params=params, timeout=_TIMEOUT)
34
+ r.raise_for_status()
35
+ data = r.json()
36
+ except Exception as e:
37
+ return {"encontrado": False, "error": str(e)}
38
+
39
+ abstract = data.get("AbstractText", "").strip()
40
+ titulo = data.get("Heading", "").strip()
41
+ url = data.get("AbstractURL", "").strip()
42
+
43
+ snippets = []
44
+ for topic in data.get("RelatedTopics", []):
45
+ if isinstance(topic, dict) and topic.get("Text"):
46
+ texto = topic["Text"].strip()
47
+ if len(texto) > 30:
48
+ snippets.append(texto)
49
+ elif isinstance(topic, dict):
50
+ for subtopic in topic.get("Topics", []):
51
+ if isinstance(subtopic, dict) and subtopic.get("Text"):
52
+ texto = subtopic["Text"].strip()
53
+ if len(texto) > 30:
54
+ snippets.append(texto)
55
+
56
+ encontrado = bool(abstract or snippets)
57
+ return {
58
+ "titulo": titulo,
59
+ "abstract": abstract,
60
+ "snippets": snippets[:8],
61
+ "url": url,
62
+ "encontrado": encontrado,
63
+ }
chat-app/logica.py CHANGED
@@ -10,6 +10,8 @@ from matematicas import (
10
  es_solicitud_calculadora, es_operacion_matematica,
11
  resolver_operacion, formatear_resultado, extraer_nombre_usuario,
12
  )
 
 
13
 
14
  # ── BANCO DE VARIACIONES DE LENGUAJE ──────────────────────────────────────────
15
 
@@ -355,4 +357,11 @@ def respuesta_final(mensaje, historial):
355
  if respuesta:
356
  return respuesta
357
 
358
- return "πŸ€– NEO-1 aΓΊn no tiene una respuesta para eso. Puedes enseΓ±arle mΓ‘s temas agregando respuestas al modelo."
 
 
 
 
 
 
 
 
10
  es_solicitud_calculadora, es_operacion_matematica,
11
  resolver_operacion, formatear_resultado, extraer_nombre_usuario,
12
  )
13
+ from buscador import buscar as buscar_web
14
+ from resumidor import resumir
15
 
16
  # ── BANCO DE VARIACIONES DE LENGUAJE ──────────────────────────────────────────
17
 
 
357
  if respuesta:
358
  return respuesta
359
 
360
+ # ── NEO-2: bΓΊsqueda en internet como respaldo ─────────────────────────────
361
+ resultado_web = buscar_web(mensaje)
362
+ if resultado_web.get("encontrado"):
363
+ resumen = resumir(mensaje, resultado_web)
364
+ if resumen:
365
+ return resumen
366
+
367
+ return "πŸ€– No encontrΓ© informaciΓ³n sobre eso ni en mi base de conocimiento ni en internet. Intenta reformular tu pregunta."
chat-app/resumidor.py ADDED
@@ -0,0 +1,127 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ resumidor.py β€” NEO-2 Motor de resumen
3
+ ---------------------------------------
4
+ Toma datos crudos de DuckDuckGo y genera una respuesta estructurada
5
+ siguiendo el estilo de NEO-2: secciones, bullets, emojis, sin copiar
6
+ el texto literal β€” lo reformatea con sus propias palabras.
7
+ """
8
+
9
+ import re
10
+ import random
11
+ import unicodedata
12
+
13
+ # ── Utilidades de texto ────────────────────────────────────────────────────────
14
+
15
+ def _normalizar(texto: str) -> str:
16
+ texto = unicodedata.normalize("NFD", texto)
17
+ texto = "".join(c for c in texto if unicodedata.category(c) != "Mn")
18
+ return texto.lower()
19
+
20
+ def _dividir_oraciones(texto: str) -> list[str]:
21
+ partes = re.split(r"(?<=[.!?])\s+", texto)
22
+ return [p.strip() for p in partes if len(p.strip()) > 25]
23
+
24
+ def _puntuar(oracion: str, query_tokens: list[str]) -> float:
25
+ norm = _normalizar(oracion)
26
+ score = sum(1.5 for t in query_tokens if t in norm)
27
+ # Bonus por longitud ideal
28
+ largo = len(oracion)
29
+ if 40 <= largo <= 150:
30
+ score += 1.0
31
+ elif largo < 40:
32
+ score -= 0.5
33
+ return score
34
+
35
+ # ── Generador de encabezados dinΓ‘micos ────────────────────────────────────────
36
+
37
+ _ENCABEZADOS = [
38
+ "πŸ“‘ Esto es lo que encontrΓ© en internet:",
39
+ "🌐 Información actualizada de la web:",
40
+ "πŸ” AquΓ­ tienes lo que hallΓ©:",
41
+ "πŸ“° Datos frescos de internet:",
42
+ "🧠 BasÑndome en fuentes actuales:",
43
+ ]
44
+
45
+ _CIERRES_WEB = [
46
+ "\n\n_Fuente: bΓΊsqueda en internet en tiempo real._",
47
+ "\n\n_InformaciΓ³n obtenida de fuentes web actuales._",
48
+ "\n\n_Datos traΓ­dos directamente de internet para ti._",
49
+ "",
50
+ ]
51
+
52
+ _CONECTORES_BULLET = ["", "AdemΓ‘s, ", "TambiΓ©n: ", "Cabe destacar: ", "Dato clave: "]
53
+
54
+ # ── Resumidor principal ────────────────────────────────────────────────────────
55
+
56
+ def resumir(query: str, resultado: dict, max_ideas: int = 5) -> str | None:
57
+ """
58
+ Genera una respuesta resumida a partir de los datos de DuckDuckGo.
59
+
60
+ Estrategia:
61
+ 1. Extrae oraciones del abstract y los snippets.
62
+ 2. Las puntΓΊa por relevancia al query (keyword overlap).
63
+ 3. Selecciona las mejores ideas (sin duplicados).
64
+ 4. Las reformatea en una respuesta estructurada estilo NEO-2.
65
+ """
66
+ abstract = resultado.get("abstract", "")
67
+ snippets = resultado.get("snippets", [])
68
+ titulo = resultado.get("titulo", "")
69
+ url = resultado.get("url", "")
70
+
71
+ if not abstract and not snippets:
72
+ return None
73
+
74
+ query_tokens = _normalizar(query).split()
75
+
76
+ # Recopilar todas las oraciones de todas las fuentes
77
+ pool: list[str] = []
78
+ if abstract:
79
+ pool += _dividir_oraciones(abstract)
80
+ for s in snippets:
81
+ pool += _dividir_oraciones(s)
82
+
83
+ if not pool:
84
+ # Si no hay oraciones pero sΓ­ texto corto, devolverlo tal cual
85
+ texto_base = abstract or snippets[0] if snippets else ""
86
+ if texto_base:
87
+ return f"{random.choice(_ENCABEZADOS)}\n\n{texto_base}"
88
+ return None
89
+
90
+ # Puntuar, deduplicar y seleccionar las mejores ideas
91
+ puntuadas = sorted(pool, key=lambda o: -_puntuar(o, query_tokens))
92
+
93
+ seleccionadas = []
94
+ vistas = set()
95
+ for oracion in puntuadas:
96
+ clave = _normalizar(oracion)[:60]
97
+ if clave not in vistas:
98
+ vistas.add(clave)
99
+ seleccionadas.append(oracion)
100
+ if len(seleccionadas) >= max_ideas:
101
+ break
102
+
103
+ if not seleccionadas:
104
+ return None
105
+
106
+ # Formatear la respuesta
107
+ encabezado = random.choice(_ENCABEZADOS)
108
+
109
+ if titulo:
110
+ encabezado_titulo = f"**{titulo}**\n\n"
111
+ else:
112
+ encabezado_titulo = ""
113
+
114
+ if len(seleccionadas) == 1:
115
+ cuerpo = seleccionadas[0]
116
+ else:
117
+ bullets = []
118
+ for i, idea in enumerate(seleccionadas):
119
+ conector = random.choice(_CONECTORES_BULLET) if i > 0 else ""
120
+ bullets.append(f"- {conector}{idea}")
121
+ cuerpo = "\n".join(bullets)
122
+
123
+ cierre = random.choice(_CIERRES_WEB)
124
+ if url:
125
+ cierre += f"\nπŸ”— {url}"
126
+
127
+ return f"{encabezado}\n\n{encabezado_titulo}{cuerpo}{cierre}".strip()