File size: 6,881 Bytes
f585950
6723742
f585950
 
 
6723742
 
f585950
 
6723742
 
f585950
 
 
 
6723742
 
 
f585950
 
 
6723742
f585950
6723742
f585950
 
 
 
 
 
 
 
6723742
f585950
6723742
 
f585950
6723742
 
 
f585950
 
 
6723742
f585950
6723742
f585950
 
 
 
6723742
f585950
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6723742
 
f585950
 
 
6723742
 
 
 
f585950
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6723742
f585950
 
6723742
f585950
 
 
6723742
f585950
 
 
6723742
f585950
 
 
6723742
f585950
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6723742
 
f585950
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
import os
import streamlit as st
from dotenv import load_dotenv

# Lectura y procesamiento de PDFs
from PyPDF2 import PdfReader
from langchain.text_splitter import RecursiveCharacterTextSplitter

# Embeddings y VectorStores
from langchain_community.embeddings.spacy_embeddings import SpacyEmbeddings
from langchain_community.vectorstores import FAISS

# LLM y Herramientas
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
from langchain.tools.retriever import create_retriever_tool
from langchain.agents import AgentExecutor, create_tool_calling_agent

# Cargar variables de entorno
load_dotenv()
os.environ["KMP_DUPLICATE_LIB_OK"] = "TRUE"  # A veces necesario en Windows o entornos concretos

# Inicializamos el embedding con spaCy
embeddings = SpacyEmbeddings(model_name="en_core_web_sm")

# -----------------------------------------------------------
# Funciones auxiliares
# -----------------------------------------------------------
def pdf_read(pdf_docs):
    """
    Lee cada PDF y concatena su texto.
    """
    text = ""
    for pdf in pdf_docs:
        pdf_reader = PdfReader(pdf)
        for page in pdf_reader.pages:
            text += page.extract_text() or ""
    return text

def get_chunks(text):
    """
    Divide el texto en chunks para indexarlo en FAISS.
    """
    text_splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=200)
    return text_splitter.split_text(text)

def create_vector_store(text_chunks):
    """
    Crea un FAISS VectorStore a partir de los chunks.
    """
    vector_store = FAISS.from_texts(text_chunks, embedding=embeddings)
    return vector_store

def get_conversational_chain(tool, question):
    """
    Genera la respuesta a la pregunta usando la herramienta de recuperación.
    """
    api_key = os.getenv("OPENAI_API_KEY")

    # Modelo LLM (adaptar model_name según lo que tengas disponible)
    llm = ChatOpenAI(
        model_name="gpt-4o-mini",  # O "gpt-3.5-turbo", etc.
        temperature=0.4,
        api_key=api_key
    )

    # Plantilla de prompt
    prompt = ChatPromptTemplate.from_messages([
        (
            "system",
            """Eres un asistente útil. Responde la pregunta de la forma más completa posible 
            utilizando solo el contexto disponible. Si la respuesta no está en el contexto, 
            di: "answer is not available in the context"."""
        ),
        ("placeholder", "{chat_history}"),
        ("human", "{input}"),
        ("placeholder", "{agent_scratchpad}"),
    ])

    # Creamos el agente con la herramienta y ejecutamos
    agent = create_tool_calling_agent(llm, tools=[tool], prompt=prompt)
    agent_executor = AgentExecutor(agent=agent, tools=[tool], verbose=False)
    response = agent_executor.invoke({"input": question})
    return response["output"]

def generate_answer(user_question):
    """
    Usa la base vectorial en session_state y retorna la respuesta.
    """
    # Verifica si tenemos FAISS cargado
    if "faiss_db" not in st.session_state or st.session_state["faiss_db"] is None:
        return "No hay PDF(s) procesado(s). Por favor, carga y procesa algún PDF."

    # Crea la herramienta de recuperación
    db = st.session_state["faiss_db"]
    retriever = db.as_retriever()
    retrieval_tool = create_retriever_tool(
        retriever, 
        name="pdf_extractor",
        description="This tool gives answers to queries from the PDF(s)."
    )

    # Obtiene la respuesta final usando la cadena conversacional
    answer = get_conversational_chain(retrieval_tool, user_question)
    return answer

# -----------------------------------------------------------
# Aplicación principal
# -----------------------------------------------------------
def main():
    st.set_page_config(page_title="Chat PDF", layout="wide")
    st.header("RAG-based Chat con PDF")

    # Inicializa el historial de mensajes en session_state si no existe
    if "messages" not in st.session_state:
        st.session_state["messages"] = []

    # Inicializa la base vectorial (None si aún no se ha creado)
    if "faiss_db" not in st.session_state:
        st.session_state["faiss_db"] = None

    # ----------------------------------------------------------------
    # SIDEBAR: subir y procesar PDFs
    # ----------------------------------------------------------------
    with st.sidebar:
        st.title("Menú:")
        pdf_docs = st.file_uploader(
            "Sube tus archivos PDF y haz clic en 'Procesar PDFs'.",
            accept_multiple_files=True
        )

        if st.button("Procesar PDFs"):
            if pdf_docs:
                with st.spinner("Procesando..."):
                    # Leemos y fragmentamos los PDFs en chunks
                    raw_text = pdf_read(pdf_docs)
                    text_chunks = get_chunks(raw_text)
                    # Creamos la base vectorial FAISS y la guardamos en session_state
                    new_vector_store = create_vector_store(text_chunks)
                    st.session_state["faiss_db"] = new_vector_store
                st.success("¡Hecho! Se han indexado los PDF.")
            else:
                st.warning("No has seleccionado ningún PDF.")

        # Opción para borrar la base vectorial y subir otros PDFs
        if st.button("Borrar vector store"):
            st.session_state["faiss_db"] = None
            st.info("Vector store borrado. Ahora puedes subir nuevos PDFs.")

    # ----------------------------------------------------------------
    # MAIN CHAT
    # ----------------------------------------------------------------
    st.subheader("Chat")

    # Muestra los mensajes previos del historial
    for msg in st.session_state["messages"]:
        # Si quieres un formato sencillo:
        st.write(f"**{msg['role'].capitalize()}:** {msg['content']}")
        
        # O bien, podrías usar el componente experimental de chat si tu versión de Streamlit lo soporta:
        # if msg["role"] == "user":
        #     with st.chat_message("user"):
        #         st.write(msg["content"])
        # else:
        #     with st.chat_message("assistant"):
        #         st.write(msg["content"])

    # Input de chat del usuario
    user_input = st.text_input("Escribe tu pregunta aquí...")

    if user_input:
        # Guarda el mensaje del usuario
        st.session_state["messages"].append({"role": "user", "content": user_input})
        
        # Genera la respuesta
        answer = generate_answer(user_input)
        
        # Guarda la respuesta en el historial
        st.session_state["messages"].append({"role": "assistant", "content": answer})
        
        # Para forzar el refresco (opcional en Streamlit 1.x). 
        # Puedes comentarlo si te da problemas o no lo necesitas.
        #st.experimental_rerun()

if __name__ == "__main__":
    main()