File size: 11,018 Bytes
d5f44d7
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
249
250
251
252
253
254
255
256
257
258
259

##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})