javier8306 commited on
Commit
680c71e
·
verified ·
1 Parent(s): 3a671c1

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +82 -66
app.py CHANGED
@@ -31,26 +31,22 @@ ENRUTADOR_MODELOS = {
31
  }
32
 
33
  # ==========================================
34
- # 2. LÓGICA DEL ORÁCULO AGÉNTICO
35
  # ==========================================
36
  def realizar_exegesis_web(consulta, canon_seleccionado):
37
  modelo_asignado = ENRUTADOR_MODELOS[canon_seleccionado]
 
38
 
39
  # ---------------------------------------------------------
40
- # FASE 1: AGENTE ANALISTA (Extracción Determinista de Entidades)
41
  # ---------------------------------------------------------
 
42
  prompt_extraccion = f"""
43
  Analiza la siguiente consulta del usuario. Extrae el autor o la obra mencionados y deduce las palabras clave temáticas.
44
  Si no se menciona ningún autor u obra concreta, deja ese campo estrictamente vacío ("").
45
  Responde ÚNICAMENTE con un objeto JSON válido, sin texto adicional.
46
-
47
  Consulta: "{consulta}"
48
-
49
- Formato esperado:
50
- {{
51
- "autor_obra": "Nombre del autor u obra (o vacío)",
52
- "palabras_clave": "concepto 1, concepto 2, tema principal"
53
- }}
54
  """
55
 
56
  try:
@@ -62,7 +58,6 @@ def realizar_exegesis_web(consulta, canon_seleccionado):
62
  ],
63
  temperature=0.1
64
  )
65
-
66
  texto_json = respuesta_analista.choices[0].message.content
67
  match_json = re.search(r'\{.*\}', texto_json, re.DOTALL)
68
 
@@ -71,72 +66,96 @@ def realizar_exegesis_web(consulta, canon_seleccionado):
71
  filtro_autor = datos.get("autor_obra", "").strip()
72
  claves_busqueda = datos.get("palabras_clave", consulta).strip()
73
  else:
74
- filtro_autor = ""
75
- claves_busqueda = consulta
 
 
76
 
77
  except Exception as e:
78
- filtro_autor = ""
79
- claves_busqueda = consulta
80
 
81
- # Formateamos el filtro para Supabase (None si está vacío para ignorarlo)
82
- filtro_db = filtro_autor if filtro_autor != "" else None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
83
 
84
  # ---------------------------------------------------------
85
- # FASE 2: EL MATEMÁTICO (Vectorización de palabras clave)
86
  # ---------------------------------------------------------
87
- # Vectorizamos la suma de las claves extraídas y la consulta para máxima precisión
88
  texto_a_vectorizar = f"{claves_busqueda}. {consulta}"
89
  vector_query = modelo_vectorial.encode(texto_a_vectorizar, convert_to_tensor=False).tolist()
 
 
 
90
 
91
- # ---------------------------------------------------------
92
- # FASE 3: EL ALMACÉN (Búsqueda Híbrida en Supabase)
93
- # ---------------------------------------------------------
94
  try:
95
  respuesta_db = supabase.rpc(
96
  "buscar_pasajes_clasicos",
97
  {
98
  "query_embedding": vector_query,
99
  "match_threshold": 0.15,
100
- "match_count": 15,
101
  "tabla_destino": canon_seleccionado,
102
  "filtro_autor_obra": filtro_db
103
  }
104
  ).execute()
105
 
106
  pasajes = respuesta_db.data
 
 
 
 
 
 
 
107
 
108
- if not pasajes:
109
- return "❌ No database matches found. / No se hallaron testimonios.", ""
 
 
 
 
 
 
 
 
110
 
111
- contexto_documentos = ""
112
- for i, frag in enumerate(pasajes):
113
- obra = frag.get("obra", "Desconocida")
114
- texto = frag.get("texto_original", "")
115
- contexto_documentos += f"\n[Documento {i+1} - Obra: {obra}]\n{texto}\n"
116
 
117
- # ---------------------------------------------------------
118
- # FASE 4: EL FILÓLOGO (Exégesis Final)
119
- # ---------------------------------------------------------
120
- prompt_sistema = """
121
- Eres un riguroso filólogo clásico e historiador.
122
-
123
- INSTRUCCIONES VITALES:
124
- 1. IDIOMA: Detecta el idioma de la "Consulta del Usuario". Toda tu respuesta DEBE estar exclusivamente en ese idioma (usa Chino Tradicional si aplica).
125
- 2. SELECCIÓN: Evalúa los [Documentos] antiguos proporcionados y escoge los más pertinentes.
126
- 3. TRANSPARENCIA: Escribe tu proceso lógico dentro de etiquetas <razonamiento> y </razonamiento>.
127
-
128
- FORMATO DE SALIDA ESPERADO:
129
- <razonamiento>
130
- (Tu proceso lógico aquí)
131
- </razonamiento>
132
- ### 📌 Documento seleccionado: [Nombre de la obra]
133
- > [Texto original del documento elegido]
134
- ---
135
- [Tu exégesis final]
136
- """
137
-
138
- prompt_usuario = f"Consulta del Usuario: {consulta}\n\nDocumentos recuperados de la base de datos:{contexto_documentos}"
139
 
 
 
 
140
  response = cliente_ia.chat.completions.create(
141
  model=modelo_asignado,
142
  messages=[
@@ -148,49 +167,46 @@ def realizar_exegesis_web(consulta, canon_seleccionado):
148
 
149
  respuesta_completa = response.choices[0].message.content
150
 
151
- # Bloque informativo para la interfaz gráfica (Muestra qué dedujo el Agente Analista)
152
- info_extraccion = f"**🔍 Diagnóstico del Agente Analista:**\n- **Autor/Obra Detectado:** `{filtro_autor if filtro_autor else 'Ninguno (Búsqueda global)'}`\n- **Claves Vectorizadas:** `{claves_busqueda}`\n\n---\n"
153
-
154
  match = re.search(r'<razonamiento>(.*?)</razonamiento>', respuesta_completa, re.DOTALL | re.IGNORECASE)
155
  if match:
156
- texto_razonamiento = info_extraccion + match.group(1).strip()
157
  texto_exegesis = respuesta_completa.replace(match.group(0), '').strip()
158
  else:
159
- texto_razonamiento = info_extraccion + "El modelo no proporcionó un rastro de razonamiento explícito."
160
  texto_exegesis = respuesta_completa
161
 
162
  return texto_exegesis, texto_razonamiento
163
 
164
  except Exception as e:
165
- return f"⚠️ Error general: {str(e)}", ""
166
 
167
  # ==========================================
168
- # 3. INTERFAZ GRÁFICA TRILINGÜE (Simplificada)
169
  # ==========================================
170
  with gr.Blocks() as demo:
171
- gr.Markdown("# 🏛️ Socrates - Philological Oracle / Oráculo Filológico / 哲學神諭")
172
- gr.Markdown("Agentic RAG System: Auto-metadata extraction & Hybrid Search enabled.")
173
 
174
  with gr.Row():
175
  with gr.Column(scale=1):
176
  canon_dropdown = gr.Dropdown(
177
  choices=["textos_clasicos_latin_v3", "textos_clasicos_griego_v3", "textos_clasicos_chino_v3"],
178
  value="textos_clasicos_latin_v3",
179
- label="📚 Canon / 經典"
180
  )
181
- # Hemos eliminado la caja de Filtro Manual; la IA lo hace sola ahora
182
  consulta_input = gr.Textbox(
183
- label="✍️ Query / Consulta / 查詢",
184
- placeholder="Ej: La crítica de Salustio a la decadencia de la res publica...",
185
  lines=4
186
  )
187
  boton_consultar = gr.Button("Invoke / Invocar / 召喚", variant="primary")
188
 
189
  with gr.Column(scale=2):
190
- with gr.Accordion("🧠 Reasoning Trace / Razonamiento del Sistema / 系統推理", open=False):
191
  salida_razonamiento = gr.Markdown(label="")
192
 
193
- salida_markdown = gr.Markdown(label="Exegesis / 詮釋")
194
 
195
  boton_consultar.click(
196
  fn=realizar_exegesis_web,
 
31
  }
32
 
33
  # ==========================================
34
+ # 2. LÓGICA DEL ORÁCULO (ENJAMBRE TRANSPARENTE)
35
  # ==========================================
36
  def realizar_exegesis_web(consulta, canon_seleccionado):
37
  modelo_asignado = ENRUTADOR_MODELOS[canon_seleccionado]
38
+ bitacora_agentes = [] # Aquí guardaremos el rastro de razonamiento transparente
39
 
40
  # ---------------------------------------------------------
41
+ # AGENTE 1: ANALISTA (Extracción de Entidades)
42
  # ---------------------------------------------------------
43
+ bitacora_agentes.append("🕵️ **Agente 1 (Analista):** Iniciando disección de la consulta...")
44
  prompt_extraccion = f"""
45
  Analiza la siguiente consulta del usuario. Extrae el autor o la obra mencionados y deduce las palabras clave temáticas.
46
  Si no se menciona ningún autor u obra concreta, deja ese campo estrictamente vacío ("").
47
  Responde ÚNICAMENTE con un objeto JSON válido, sin texto adicional.
 
48
  Consulta: "{consulta}"
49
+ Formato esperado: {{"autor_obra": "Nombre", "palabras_clave": "concepto 1, concepto 2"}}
 
 
 
 
 
50
  """
51
 
52
  try:
 
58
  ],
59
  temperature=0.1
60
  )
 
61
  texto_json = respuesta_analista.choices[0].message.content
62
  match_json = re.search(r'\{.*\}', texto_json, re.DOTALL)
63
 
 
66
  filtro_autor = datos.get("autor_obra", "").strip()
67
  claves_busqueda = datos.get("palabras_clave", consulta).strip()
68
  else:
69
+ filtro_autor, claves_busqueda = "", consulta
70
+
71
+ bitacora_agentes.append(f" ↳ *Autor detectado:* `{filtro_autor if filtro_autor else 'Ninguno'}`")
72
+ bitacora_agentes.append(f" ↳ *Claves vectoriales:* `{claves_busqueda}`")
73
 
74
  except Exception as e:
75
+ filtro_autor, claves_busqueda = "", consulta
76
+ bitacora_agentes.append(f" ⚠️ *Error del Agente 1:* {e}. Usando texto completo.")
77
 
78
+ # ---------------------------------------------------------
79
+ # AGENTE 2: VERIFICADOR DE ARCHIVOS (Búsqueda Determinista SQL)
80
+ # ---------------------------------------------------------
81
+ bitacora_agentes.append("\n🗄️ **Agente 2 (Archivero):** Verificando inventario en Supabase...")
82
+ conteo_autor = 0
83
+ if filtro_autor:
84
+ try:
85
+ # Hacemos un conteo exacto de pasajes que coinciden parcialmente con el nombre usando 'ilike'
86
+ res_count = supabase.table(canon_seleccionado)\
87
+ .select("id", count="exact")\
88
+ .ilike("obra", f"%{filtro_autor}%")\
89
+ .execute()
90
+
91
+ conteo_autor = res_count.count if res_count.count else 0
92
+ bitacora_agentes.append(f" ↳ ✅ **Éxito:** Se han localizado **{conteo_autor}** pasajes catalogados bajo '{filtro_autor}' en la tabla `{canon_seleccionado}`.")
93
+
94
+ if conteo_autor == 0:
95
+ bitacora_agentes.append(" ↳ ⚠️ *Aviso:* El autor no existe en la base de datos o está escrito de forma diferente (ej. 'Salustio' vs 'Cayo Salustio').")
96
+ except Exception as e:
97
+ bitacora_agentes.append(f" ↳ ⚠️ Error al contar pasajes: {e}")
98
+ else:
99
+ bitacora_agentes.append(" ↳ Búsqueda global autorizada (no se restringió a un autor específico).")
100
 
101
  # ---------------------------------------------------------
102
+ # AGENTE 3: EL MATEMÁTICO (Vectorización y Similitud)
103
  # ---------------------------------------------------------
104
+ bitacora_agentes.append("\n📐 **Agente 3 (Matemático):** Trazando coordenadas semánticas...")
105
  texto_a_vectorizar = f"{claves_busqueda}. {consulta}"
106
  vector_query = modelo_vectorial.encode(texto_a_vectorizar, convert_to_tensor=False).tolist()
107
+
108
+ filtro_db = filtro_autor if filtro_autor != "" else None
109
+ pasajes = []
110
 
 
 
 
111
  try:
112
  respuesta_db = supabase.rpc(
113
  "buscar_pasajes_clasicos",
114
  {
115
  "query_embedding": vector_query,
116
  "match_threshold": 0.15,
117
+ "match_count": 5,
118
  "tabla_destino": canon_seleccionado,
119
  "filtro_autor_obra": filtro_db
120
  }
121
  ).execute()
122
 
123
  pasajes = respuesta_db.data
124
+ if pasajes:
125
+ bitacora_agentes.append(f" ↳ ✅ **Éxito:** Se han rescatado **{len(pasajes)}** pasajes que superan el umbral de similitud semántica (0.15).")
126
+ else:
127
+ bitacora_agentes.append(f" ↳ ❌ **Fallo semántico:** Aunque hay {conteo_autor} pasajes del autor, ninguno de ellos coincide conceptualmente con las coordenadas de tus palabras clave. La distancia matemática es demasiado grande.")
128
+
129
+ except Exception as e:
130
+ bitacora_agentes.append(f" ↳ ⚠️ Error en la búsqueda vectorial: {e}")
131
 
132
+ # ---------------------------------------------------------
133
+ # AGENTE 4: EL FILÓLOGO (Explicador Transparente y Exégeta)
134
+ # ---------------------------------------------------------
135
+ bitacora_agentes.append("\n✍️ **Agente 4 (Filólogo):** Redactando respuesta final basada en la evidencia...")
136
+
137
+ contexto_documentos = ""
138
+ for i, frag in enumerate(pasajes or []):
139
+ obra = frag.get("obra", "Desconocida")
140
+ texto = frag.get("texto_original", "")
141
+ contexto_documentos += f"\n[Documento {i+1} - Obra: {obra}]\n{texto}\n"
142
 
143
+ # Convertimos la bitácora en texto para pasársela al LLM y que entienda todo lo que ha pasado
144
+ historial_transparente = "\n".join(bitacora_agentes)
 
 
 
145
 
146
+ prompt_sistema = """
147
+ Eres el Oráculo Filológico, el agente final de un enjambre de IA.
148
+
149
+ INSTRUCCIONES VITALES:
150
+ 1. IDIOMA: Responde EXCLUSIVAMENTE en el idioma de la "Consulta del Usuario" (Español o Chino Tradicional).
151
+ 2. TRANSPARENCIA: Lee el "Historial de los Agentes". Si no se encontraron documentos, DEBES explicarle al usuario amablemente qué pasó (por ejemplo: "El Agente Archivero encontró X textos del autor, pero el Agente Matemático determinó que ninguno habla de tus palabras clave...").
152
+ 3. EXÉGESIS: Si hay documentos recuperados, analízalos rigurosamente en relación con la consulta.
153
+ 4. Escribe tu proceso lógico final dentro de etiquetas <razonamiento> y </razonamiento>.
154
+ """
 
 
 
 
 
 
 
 
 
 
 
 
 
155
 
156
+ prompt_usuario = f"Consulta del Usuario: {consulta}\n\n=== Historial de los Agentes (Para tu conocimiento) ===\n{historial_transparente}\n\n=== Documentos Recuperados ===\n{contexto_documentos if pasajes else 'NINGUNO'}"
157
+
158
+ try:
159
  response = cliente_ia.chat.completions.create(
160
  model=modelo_asignado,
161
  messages=[
 
167
 
168
  respuesta_completa = response.choices[0].message.content
169
 
170
+ # Juntamos la bitácora técnica con el razonamiento del modelo para la interfaz
 
 
171
  match = re.search(r'<razonamiento>(.*?)</razonamiento>', respuesta_completa, re.DOTALL | re.IGNORECASE)
172
  if match:
173
+ texto_razonamiento = historial_transparente + "\n\n**💭 Reflexión final del Filólogo:**\n" + match.group(1).strip()
174
  texto_exegesis = respuesta_completa.replace(match.group(0), '').strip()
175
  else:
176
+ texto_razonamiento = historial_transparente + "\n\n**💭 Reflexión final del Filólogo:**\nNo se generaron etiquetas de razonamiento."
177
  texto_exegesis = respuesta_completa
178
 
179
  return texto_exegesis, texto_razonamiento
180
 
181
  except Exception as e:
182
+ return f"⚠️ Error en la fase de exégesis: {str(e)}", historial_transparente
183
 
184
  # ==========================================
185
+ # 3. INTERFAZ GRÁFICA
186
  # ==========================================
187
  with gr.Blocks() as demo:
188
+ gr.Markdown("# 🏛️ Socrates - Philological Oracle")
189
+ gr.Markdown("Agentic RAG System: *Glass-Box Swarm Architecture* (Transparencia Total)")
190
 
191
  with gr.Row():
192
  with gr.Column(scale=1):
193
  canon_dropdown = gr.Dropdown(
194
  choices=["textos_clasicos_latin_v3", "textos_clasicos_griego_v3", "textos_clasicos_chino_v3"],
195
  value="textos_clasicos_latin_v3",
196
+ label="📚 Canon"
197
  )
 
198
  consulta_input = gr.Textbox(
199
+ label="✍️ Query / Consulta",
200
+ placeholder="Ej: La decadencia moral en Salustio...",
201
  lines=4
202
  )
203
  boton_consultar = gr.Button("Invoke / Invocar / 召喚", variant="primary")
204
 
205
  with gr.Column(scale=2):
206
+ with gr.Accordion("🧠 Reasoning Trace (Bitácora del Enjambre) / 系統推理", open=True):
207
  salida_razonamiento = gr.Markdown(label="")
208
 
209
+ salida_markdown = gr.Markdown(label="Exegesis")
210
 
211
  boton_consultar.click(
212
  fn=realizar_exegesis_web,