##Instalación de paquetes necesarios import streamlit as st import os import time import torch from utils import * from dotenv import load_dotenv load_dotenv() ##import nest_asyncio ##nest_asyncio.apply() from llama_parse import LlamaParse from llama_index.llms.openai import OpenAI from llama_index.embeddings.openai import OpenAIEmbedding from llama_index.core import VectorStoreIndex, ServiceContext from llama_index.core import SimpleDirectoryReader from llama_index.core import Settings ###### ## titulos y cabeceras st.set_page_config('compare PDF por LLM') st.title("Comparar PDFs mediante LLM") st.subheader("Campos a comparar en tu PDF",divider='rainbow') OPENAI_API_KEY = st.text_input('OpenAI API Key', type='password') LLAMA_CLOUD_API_KEY = st.text_input('LLAMA API Key', type='password') #### Inicializar mensajes de chat if "messages" not in st.session_state.keys(): st.session_state.messages = [ {"role": "assistant", "content": "Ask me a question about PDFs files you provided me"} ] @st.cache_resource(show_spinner=False) # Añade decorador de caché def cargar_embedmodel_y_llmmodel(): return True #esta variable es para tener aqui un listado de aquellos ficheros que se han ido subiendo archivos = [] ## carga y almacenamiento de ficheros almacenada, acepta varios. with st.sidebar: archivos = load_name_files(FILE_LIST) files_uploaded = st.file_uploader( "Carga tus ficheros PDF", type="pdf", accept_multiple_files=True, on_change=st.cache_resource.clear ) if st.button("Guardar y procesar por LLM", type="secondary",help="donde buscará lo que comparará"): for pdf in files_uploaded: if pdf is not None and pdf.name not in archivos: archivos.append(pdf.name) archivos = save_name_files(FILE_LIST, archivos) if len(archivos)>0: st.write('Los archivos PDF se han cargados:') lista_documentos = st.empty() with lista_documentos.container(): for arch in archivos: st.write(arch) if st.button('Borrar ficheros'): archivos = [] clean_files(FILE_LIST) lista_documentos.empty() # comprueba que hay archivos a ser tratados if len(archivos)>0: # comprueba que hay consulta a responder if user_question := st.chat_input("Realizar consulta:"): st.session_state.messages.append({"role": "user", "content": user_question}) if user_question: for message in st.session_state.messages: # Muestra anteriores mensajes with st.chat_message(message["role"]): st.write(message["content"]) alert = st.warning("Sea paciente") # Mensaje de aviso o warning al usuario time.sleep(3) # establece tiempo espera en 3 segundos alert.empty() # borra el aviso # se define el analizador-parser de los documentos. os.environ["OPENAI_API_KEY"] = OPENAI_API_KEY parser = LlamaParse( ## api_key=os.environ["LLAMA_CLOUD_API_KEY"], ##API de acceso a Cloud de LlamaIndex api_key=LLAMA_CLOUD_API_KEY, result_type="markdown", # se toma "markdown", tambien hay text disponible verbose=True, ) cargar_embedmodel_y_llmmodel() #se parametrizan los modelos de embedding y LLM embed_model=OpenAIEmbedding(model="text-embedding-3-small") #embeddings para base de conocimiento llm = OpenAI(model="gpt-3.5-turbo-0125") #modelo LLM usado Settings.llm = llm Settings.embed_model = embed_model tratar = load_name_files(FILE_LIST) ##variable que tomará los ficheros a tratar recuperados de funcion # st.write(tratar[0]) # se puede desasteriscar en desarrollo para apoyo # st.write(tratar[1]) # se puede desasteriscar en desarrollo para apoyo # Carga de los ficheros mediante LlamaParse, se ejecutará job para cada analizador-parser de los mismos docs_202401 = parser.load_data( f'{tratar[0]}') docs_202402 = parser.load_data( f'{tratar[1]}') #uso de MarkdownElementNodeParser para analizar la salida de LlamaParse mediante un motor de consultas de recuperación(recursivo) from llama_index.core.node_parser import MarkdownElementNodeParser node_parser = MarkdownElementNodeParser(llm=OpenAI(model="gpt-3.5-turbo-0125"), num_workers=8) import pickle from llama_index.postprocessor.flag_embedding_reranker import FlagEmbeddingReranker # se parametriza el modelo reranker reranker = FlagEmbeddingReranker( top_n=5, model="BAAI/bge-reranker-large", ) #funcion para Facilitar el motor de consultas sobre el almacén de vectores, y poderse realizar la recuperación. def create_query_engine_over_doc(docs, nodes_save_path=None): """Big function to go from document path -> recursive retriever.""" if nodes_save_path is not None and os.path.exists(nodes_save_path): raw_nodes = pickle.load(open(nodes_save_path, "rb")) else: raw_nodes = node_parser.get_nodes_from_documents(docs) if nodes_save_path is not None: pickle.dump(raw_nodes, open(nodes_save_path, "wb")) base_nodes, objects = node_parser.get_nodes_and_objects( raw_nodes ) ### Recuperador-retriever # indice y motor vector_index = VectorStoreIndex(nodes=base_nodes+objects) query_engine = vector_index.as_query_engine( similarity_top_k=15, node_postprocessors=[reranker] ) return query_engine, base_nodes, vector_index ###devuelve motor de consultas y nodos ## motores de consulta y nodos para cada documento usando la función anterior. ## En los ficheros .pkl se puede ver la estructura de los documentos que ha conformado o analizado y será con la que trabajará. query_engine_202401, nodes_202401,vindex1 = create_query_engine_over_doc( docs_202401, nodes_save_path="202401_nodes.pkl" ) query_engine_202402, nodes_202402,vindex2 = create_query_engine_over_doc( docs_202402, nodes_save_path="202402_nodes.pkl" ) from llama_index.core.tools import QueryEngineTool, ToolMetadata from llama_index.core.query_engine import SubQuestionQueryEngine from llama_index.core.llms import ChatMessage # motor de consulta como tool, configuración y contexto de los datos que deberá proveer por los que será consultado # debajo se usa como motor de subconsultas SubQuestionQueryEngine query_engine_tools = [ QueryEngineTool( query_engine=query_engine_202401, metadata=ToolMetadata( name="pdf_ENERO", description=( # "Provides information about Datos del Producto for ENERO" # "Provides information about values of fields of Datos del Producto, Titular, Fabricante,Composicion,Envases, Usos y Dosis Autorizados,Plazos de Seguridad" """\ The documents provided are plant protection product data sheets in PDF format. Provides information about values of fields of Datos del Producto, Titular, Fabricante,Composicion,Envases, Usos y Dosis Autorizados,Plazos de Seguridad: # Datos del Producto |Numero de Registro| |Estado| |Fechas Inscripción| |Renovación| |Caducidad| |Nombre Comercial| # Titular # Fabricante # Composición # Envases # Usos y Dosis Autorizados |USO| |AGENTE| |Dosis| |Condic. Especifico| """ ), ), ), QueryEngineTool( query_engine=query_engine_202402, metadata=ToolMetadata( name="pdf_FEBRERO", description=( # "Provides information about Datos del Producto for FEBRERO" # "Provides information about values of fields of Datos del Producto, Titular, Fabricante,Composicion,Envases, Usos y Dosis Autorizados,Plazos de Seguridad" """\ The documents provided are plant protection product data sheets in PDF format. Provides information about values of fields of Datos del Producto, Titular, Fabricante,Composicion,Envases, Usos y Dosis Autorizados,Plazos de Seguridad: # Datos del Producto |Numero de Registro| |Estado| |Fechas Inscripción| |Renovación| |Caducidad| |Nombre Comercial| # Titular # Fabricante # Composición # Envases # Usos y Dosis Autorizados |USO| |AGENTE| |Dosis| |Condic. Especifico| """ ), ), ), ] # subconsultas con tool creada a través de SubQuestionQueryEngine sub_query_engine = SubQuestionQueryEngine.from_defaults( query_engine_tools=query_engine_tools, llm=llm ) if "chat_engine" not in st.session_state.keys(): # Initializa motor chat # para que generen las subconsultas con la consulta-query del usuario streaming_response = sub_query_engine.query(user_question) ## If last message is not from assistant, generate a new response if st.session_state.messages[-1]["role"] != "assistant": with st.chat_message("assistant"): with st.spinner("Thinking..."): #figura del spinner de streamlit mientras se ejecuta bloque response = st.write(streaming_response.response) #respuesta entregada a la query-consulta del usuario st.session_state.messages.append({"role": "assistant", "content": response})