File size: 5,991 Bytes
530b6e9
f220abb
530b6e9
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
b0ab61b
530b6e9
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
f220abb
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
530b6e9
 
 
 
b0ab61b
530b6e9
b0ab61b
 
530b6e9
 
 
b0ab61b
530b6e9
b0ab61b
 
530b6e9
 
 
 
 
 
 
 
f220abb
530b6e9
 
b0ab61b
530b6e9
 
 
 
 
 
 
 
 
 
b0ab61b
530b6e9
 
f220abb
 
 
530b6e9
f220abb
 
 
530b6e9
f220abb
 
4760535
f220abb
 
530b6e9
f220abb
530b6e9
f220abb
 
530b6e9
f220abb
530b6e9
f220abb
 
 
 
 
530b6e9
f220abb
530b6e9
 
 
 
 
 
f220abb
530b6e9
 
 
 
 
 
 
 
 
f220abb
 
 
 
 
530b6e9
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
import os
import re
import requests
import gradio as gr
import faiss
import numpy as np

from pypdf import PdfReader
from sentence_transformers import SentenceTransformer

# --------------------------------------------------
# CONFIGURACIÓN
# --------------------------------------------------

PDF_URL = "https://www.sanidad.gob.es/gabinetePrensa/notaPrensa/pdf/ComeSanoyMuevete12decisionesSaludables.pdf"
PDF_PATH = "documento.pdf"

EMBEDDING_MODEL = "sentence-transformers/paraphrase-multilingual-mpnet-base-v2"

CHUNK_SIZE = 800
CHUNK_OVERLAP = 100
TOP_K = 6

# --------------------------------------------------
# PDF
# --------------------------------------------------

def descargar_pdf():
    if not os.path.exists(PDF_PATH):
        r = requests.get(PDF_URL, timeout=30)
        r.raise_for_status()
        with open(PDF_PATH, "wb") as f:
            f.write(r.content)

def extraer_paginas(pdf_path):
    reader = PdfReader(pdf_path)
    paginas = []
    for i, page in enumerate(reader.pages):
        texto = page.extract_text()
        if texto:
            paginas.append({"page": i + 1, "text": texto})
    return paginas

# --------------------------------------------------
# CHUNKING
# --------------------------------------------------

def dividir_texto(texto, chunk_size=800, chunk_overlap=100):
    chunks = []
    inicio = 0
    texto = texto.strip()

    while inicio < len(texto):
        fin = inicio + chunk_size
        chunk = texto[inicio:fin]
        chunks.append(chunk)
        inicio += chunk_size - chunk_overlap

    return chunks

def construir_chunks(paginas):
    textos = []
    metas = []

    for pagina in paginas:
        trozos = dividir_texto(pagina["text"], CHUNK_SIZE, CHUNK_OVERLAP)
        for trozo in trozos:
            textos.append(trozo)
            metas.append({"page": pagina["page"]})

    return textos, metas

# --------------------------------------------------
# LIMPIEZA DE TEXTO
# --------------------------------------------------

def limpiar_texto(texto):
    texto = texto.replace("\n", " ")
    texto = re.sub(r"\s+", " ", texto)
    texto = re.sub(r"\?+", "", texto)
    texto = re.sub(r"\!+", "", texto)
    return texto.strip()

def extraer_frases(texto, max_frases=3):
    texto = limpiar_texto(texto)
    frases = re.split(r"(?<=[\.\:\;])\s+", texto)

    frases_validas = []
    for f in frases:
        f = f.strip()
        if len(f) > 40:
            frases_validas.append(f)

    return frases_validas[:max_frases]

# --------------------------------------------------
# CARGA DEL SISTEMA
# --------------------------------------------------

print("Descargando PDF...")
descargar_pdf()

print("Extrayendo texto del documento...")
paginas = extraer_paginas(PDF_PATH)
chunk_texts, chunk_meta = construir_chunks(paginas)

print("Cargando modelo de embeddings...")
embedder = SentenceTransformer(EMBEDDING_MODEL)

print("Generando embeddings...")
embeddings = embedder.encode(chunk_texts, convert_to_numpy=True, show_progress_bar=False)
embeddings = embeddings.astype("float32")

dimension = embeddings.shape[1]
index = faiss.IndexFlatL2(dimension)
index.add(embeddings)

# --------------------------------------------------
# RECUPERACIÓN
# --------------------------------------------------

def recuperar_contexto(query, top_k=6):
    query_emb = embedder.encode([query], convert_to_numpy=True).astype("float32")
    distances, indices = index.search(query_emb, top_k)

    resultados = []
    for idx, dist in zip(indices[0], distances[0]):
        resultados.append({
            "text": chunk_texts[idx],
            "page": chunk_meta[idx]["page"],
            "score": float(dist)
        })

    return resultados

# --------------------------------------------------
# RESPUESTA
# --------------------------------------------------

def construir_respuesta_desde_contexto(query, resultados):
    if not resultados:
        return "No tengo información suficiente para responder a esta pregunta."

    mejor = resultados[0]["text"]
    frases = extraer_frases(mejor, max_frases=3)

    if not frases:
        return "No tengo información suficiente para responder a esta pregunta."

    respuesta = "Según el documento, " + " ".join(frases)

    if len(respuesta) > 900:
        respuesta = respuesta[:900] + "..."

    return respuesta

def responder(query):
    if not query.strip():
        return "Escribe una pregunta.", "", ""

    resultados = recuperar_contexto(query, top_k=TOP_K)

    respuesta = construir_respuesta_desde_contexto(query, resultados)

    fuentes = "\n".join(
        [f"Página {r['page']} | score={r['score']:.4f}" for r in resultados]
    )

    contexto_mostrar = "\n\n".join(
        [f"[Página {r['page']}]\n{limpiar_texto(r['text'])[:500]}..." for r in resultados]
    )

    return respuesta, fuentes, contexto_mostrar

# --------------------------------------------------
# INTERFAZ
# --------------------------------------------------

examples = [
    ["¿Qué dice el documento sobre el desayuno?"],
    ["¿Qué dice el documento sobre beber agua?"],
    ["¿Qué recomendaciones da sobre frutas, verduras y fibra?"],
    ["¿Qué indica el documento sobre la sal y las grasas?"],
    ["¿Qué dice el documento sobre la actividad física?"]
]

with gr.Blocks() as demo:
    gr.Markdown("# Práctica 9 - Sistema RAG")
    gr.Markdown(
        "Haz una pregunta sobre el documento "
        "**Come sano y muévete: 12 decisiones saludables**."
    )

    pregunta = gr.Textbox(
        label="Pregunta",
        placeholder="Escribe aquí tu pregunta..."
    )

    boton = gr.Button("Generar respuesta")

    respuesta = gr.Textbox(label="Respuesta")
    fuentes = gr.Textbox(label="Fuentes recuperadas")
    contexto = gr.Textbox(label="Contexto recuperado", lines=14)

    gr.Examples(examples=examples, inputs=pregunta)

    boton.click(
        fn=responder,
        inputs=pregunta,
        outputs=[respuesta, fuentes, contexto]
    )

demo.launch()