Spaces:
Running
Running
File size: 11,832 Bytes
4688c1b ebbb108 3a671c1 4688c1b 1bae81a 4688c1b 4b22d62 ebbb108 1bae81a 4688c1b 6483092 4688c1b 1bae81a 4688c1b 1bae81a 3a671c1 1bae81a 3a671c1 1bae81a 3a671c1 1bae81a 3a671c1 1bae81a 3a671c1 1bae81a 3a671c1 5a1414f 680c71e 1bae81a 3a671c1 5a1414f 1bae81a 3a671c1 680c71e 1bae81a 680c71e 1bae81a 5a1414f 1bae81a 5a1414f 1bae81a 680c71e 1bae81a 3a671c1 1bae81a 3a671c1 1bae81a 3a671c1 680c71e 5a1414f 1bae81a 3a671c1 1bae81a 4688c1b 680c71e 1bae81a 680c71e 1bae81a 680c71e 1bae81a 680c71e 1bae81a ebbb108 680c71e ebbb108 680c71e 1bae81a 680c71e 1bae81a 680c71e 4688c1b 1bae81a 680c71e 4688c1b ebbb108 4688c1b ebbb108 4688c1b 1bae81a ebbb108 1bae81a ebbb108 1bae81a ebbb108 1bae81a ebbb108 4688c1b 1bae81a 4688c1b 1bae81a 4688c1b ebbb108 680c71e 1bae81a 4688c1b 1bae81a 4688c1b 1bae81a 4688c1b 1bae81a 4688c1b 1bae81a ebbb108 680c71e 4688c1b 3a671c1 8c8f9f9 4688c1b ebbb108 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 | import os
import re
import json
import gradio as gr
from supabase import create_client, Client
from sentence_transformers import SentenceTransformer
from openai import OpenAI
# ==========================================
# 1. CREDENTIALS AND CONNECTIONS (CLOUD)
# ==========================================
SUPABASE_URL = os.environ.get("SUPABASE_URL")
SUPABASE_KEY = os.environ.get("SUPABASE_KEY")
OPENROUTER_API_KEY = os.environ.get("OPENROUTER_API_KEY")
if not SUPABASE_URL or not SUPABASE_KEY or not OPENROUTER_API_KEY:
raise ValueError("❌ Missing credentials. Check Hugging Face Secrets.")
supabase: Client = create_client(SUPABASE_URL, SUPABASE_KEY)
modelo_vectorial = SentenceTransformer('sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2', device='cpu')
cliente_ia = OpenAI(
base_url="https://openrouter.ai/api/v1",
api_key=OPENROUTER_API_KEY,
)
ENRUTADOR_MODELOS = {
"textos_clasicos_latin_v3": "nvidia/nemotron-3-ultra-550b-a55b:free",
"textos_clasicos_griego_v3": "nvidia/nemotron-3-ultra-550b-a55b:free",
"textos_clasicos_chino_v3": "google/gemma-4-26b-a4b-it:free"
}
# ==========================================
# 2. ORACLE LOGIC (GLASS-BOX SWARM)
# ==========================================
def realizar_exegesis_web(consulta, canones_seleccionados, progress=gr.Progress()):
if not canones_seleccionados:
return "⚠️ Please select at least one database.", "No databases selected."
# Pop-up notification for the user
gr.Info("The swarm is processing your request. This may take a few minutes. Please wait...")
# We use the model assigned to the first selected database for the final output
modelo_asignado = ENRUTADOR_MODELOS[canones_seleccionados[0]]
bitacora_agentes = []
# ---------------------------------------------------------
# AGENT 1: ANALYST (Alias Expansion)
# ---------------------------------------------------------
progress(0.1, desc="Agent 1: Analyzing and expanding aliases...")
bitacora_agentes.append("🕵️ **Agent 1 (Analyst):** Initiating dissection and alias expansion...")
prompt_extraccion = f"""
Analyze the query. Extract the author or the work and generate a list with ALL possible spelling variants (English, Latin/Greek, full name, surnames).
If there is no author, return an empty list [].
Respond ONLY with a valid JSON object.
Query: "{consulta}"
Expected format: {{"author_variants": ["Sallust", "Sallustius", "Gaius Sallustius Crispus"], "keywords": "concept 1, concept 2"}}
"""
try:
respuesta_analista = cliente_ia.chat.completions.create(
model=modelo_asignado,
messages=[
{"role": "system", "content": "You are a JSON structured data analyzer."},
{"role": "user", "content": prompt_extraccion}
],
temperature=0.1
)
texto_json = respuesta_analista.choices[0].message.content
match_json = re.search(r'\{.*\}', texto_json, re.DOTALL)
if match_json:
datos = json.loads(match_json.group(0))
variantes_autor = datos.get("author_variants", [])
claves_busqueda = datos.get("keywords", consulta).strip()
else:
variantes_autor, claves_busqueda = [], consulta
bitacora_agentes.append(f" ↳ *Aliases detected:* `{variantes_autor if variantes_autor else 'None'}`")
bitacora_agentes.append(f" ↳ *Vector keywords:* `{claves_busqueda}`")
except Exception as e:
variantes_autor, claves_busqueda = [], consulta
bitacora_agentes.append(f" ⚠️ *Agent 1 Error:* {e}. Using full text.")
# ---------------------------------------------------------
# AGENT 2: ARCHIVIST (Multi-Alias & Multi-DB Python Search)
# ---------------------------------------------------------
progress(0.3, desc="Agent 2: Searching databases...")
bitacora_agentes.append("\n🗄️ **Agent 2 (Archivist):** Searching for alias matches in selected databases...")
conteo_autor_total = 0
if variantes_autor:
for canon in canones_seleccionados:
try:
filtros_or = ",".join([f"obra.ilike.%{alias}%" for alias in variantes_autor])
res_count = supabase.table(canon)\
.select("id", count="exact")\
.or_(filtros_or)\
.execute()
conteo_actual = res_count.count if res_count.count else 0
conteo_autor_total += conteo_actual
if conteo_actual > 0:
bitacora_agentes.append(f" ↳ ✅ **Success in [{canon}]:** Located **{conteo_actual}** passages using the alias network.")
except Exception as e:
bitacora_agentes.append(f" ↳ ⚠️ Error counting passages in [{canon}]: {e}")
if conteo_autor_total == 0:
bitacora_agentes.append(f" ↳ ⚠️ **Warning:** None of the aliases {variantes_autor} yielded results. The author is not in the selected corpus.")
else:
bitacora_agentes.append(" ↳ Global search authorized (no specific author restricted).")
# ---------------------------------------------------------
# AGENT 3: MATHEMATICIAN (Vectorization & Similarity)
# ---------------------------------------------------------
progress(0.6, desc="Agent 3: Mapping semantic coordinates...")
bitacora_agentes.append("\n📐 **Agent 3 (Mathematician):** Mapping semantic coordinates...")
texto_a_vectorizar = f"{claves_busqueda}. {consulta}"
vector_query = modelo_vectorial.encode(texto_a_vectorizar, convert_to_tensor=False).tolist()
filtro_db = None
pasajes_totales = []
for canon in canones_seleccionados:
try:
respuesta_db = supabase.rpc(
"buscar_pasajes_clasicos",
{
"query_embedding": vector_query,
"match_threshold": 0.15,
"match_count": 5,
"tabla_destino": canon,
"filtro_autor_obra": filtro_db
}
).execute()
if respuesta_db.data:
# Add canon identifier to each passage for context
for passage in respuesta_db.data:
passage["canon_origen"] = canon
pasajes_totales.extend(respuesta_db.data)
except Exception as e:
bitacora_agentes.append(f" ↳ ⚠️ Error in vector search for [{canon}]: {e}")
if pasajes_totales:
bitacora_agentes.append(f" ↳ ✅ **Success:** Retrieved a total of **{len(pasajes_totales)}** passages surpassing the semantic similarity threshold (0.15) across selected databases.")
else:
bitacora_agentes.append(f" ↳ ❌ **Semantic failure:** Although there might be passages of the author, none conceptually matched your keyword coordinates.")
# ---------------------------------------------------------
# AGENT 4: PHILOLOGIST (Transparent Explainer & Exegete)
# ---------------------------------------------------------
progress(0.8, desc="Agent 4: Drafting final exegesis...")
bitacora_agentes.append("\n✍️ **Agent 4 (Philologist):** Drafting final response based on evidence...")
contexto_documentos = ""
for i, frag in enumerate(pasajes_totales):
obra = frag.get("obra", "Unknown")
texto = frag.get("texto_original", "")
canon_origen = frag.get("canon_origen", "Unknown Database")
contexto_documentos += f"\n[Document {i+1} - Work: {obra} | Source: {canon_origen}]\n{texto}\n"
historial_transparente = "\n".join(bitacora_agentes)
prompt_sistema = """
You are the Philological Oracle, the final agent of an AI swarm.
VITAL INSTRUCTIONS:
1. LANGUAGE: Respond EXCLUSIVELY in the language of the "User Query" (e.g., if the user asks in English, reply in English; if in Spanish, reply in Spanish).
2. TRANSPARENCY: Read the "Agent History". If no documents were found for the requested author, politely explain the limitation and rely on your general philological knowledge, clearly distinguishing it from retrieved data.
3. EXEGESIS: If documents were retrieved, analyze them rigorously in relation to the query.
4. REASONING: Write your final logical process inside <reasoning> and </reasoning> tags.
"""
prompt_usuario = f"User Query: {consulta}\n\n=== Agent History (For your knowledge) ===\n{historial_transparente}\n\n=== Retrieved Documents ===\n{contexto_documentos if pasajes_totales else 'NONE'}"
try:
response = cliente_ia.chat.completions.create(
model=modelo_asignado,
messages=[
{"role": "system", "content": prompt_sistema},
{"role": "user", "content": prompt_usuario}
],
temperature=0.3
)
respuesta_completa = response.choices[0].message.content
match = re.search(r'<reasoning>(.*?)</reasoning>', respuesta_completa, re.DOTALL | re.IGNORECASE)
if match:
texto_razonamiento = historial_transparente + "\n\n**💭 Philologist's Final Reflection:**\n" + match.group(1).strip()
texto_exegesis = respuesta_completa.replace(match.group(0), '').strip()
else:
texto_razonamiento = historial_transparente + "\n\n**💭 Philologist's Final Reflection:**\nNo reasoning tags were generated."
texto_exegesis = respuesta_completa
progress(1.0, desc="Done!")
return texto_exegesis, texto_razonamiento
except Exception as e:
return f"⚠️ Error in the exegesis phase: {str(e)}", historial_transparente
# ==========================================
# 3. GRAPHICAL INTERFACE
# ==========================================
with gr.Blocks() as demo:
gr.Markdown("# 🏛️ Socrates - Philological Oracle")
gr.Markdown("### Agentic RAG System: *Glass-Box Swarm Architecture*")
gr.Markdown(
"**About this system:** This is a Retrieval-Augmented Generation (RAG) tool designed to retrieve, "
"cross-reference, and deeply analyze information exclusively from classical texts in **Latin**, **Ancient Greek**, "
"and **Classical Chinese**. The swarm utilizes multiple AI agents to ensure rigorous historical and philological accuracy."
)
with gr.Row():
with gr.Column(scale=1):
canon_dropdown = gr.Dropdown(
choices=["textos_clasicos_latin_v3", "textos_clasicos_griego_v3", "textos_clasicos_chino_v3"],
value=["textos_clasicos_latin_v3"], # Selects Latin by default
multiselect=True,
label="📚 Select Database(s) / Canon"
)
consulta_input = gr.Textbox(
label="✍️ Query",
placeholder="e.g. The moral decadence in Sallust...",
lines=4
)
boton_consultar = gr.Button("Invoke Oracle", variant="primary")
gr.Markdown("*Note: The system performs deep architectural searches. Processing may take a few minutes.*")
with gr.Column(scale=2):
with gr.Accordion("🧠 Reasoning Trace (Swarm Log)", open=True):
salida_razonamiento = gr.Markdown(label="")
salida_markdown = gr.Markdown(label="Exegesis")
boton_consultar.click(
fn=realizar_exegesis_web,
inputs=[consulta_input, canon_dropdown],
outputs=[salida_markdown, salida_razonamiento]
)
if __name__ == "__main__":
demo.launch(theme=gr.themes.Soft()) |