Spaces:
Build error
Build error
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() | |