jeysshon commited on
Commit
1478636
·
verified ·
1 Parent(s): 907fa7a

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +118 -57
app.py CHANGED
@@ -6,7 +6,7 @@ import PyPDF2
6
  from langchain.embeddings.openai import OpenAIEmbeddings
7
  from langchain.text_splitter import RecursiveCharacterTextSplitter
8
  from langchain.vectorstores import Chroma
9
- from langchain.chains import RetrievalQAWithSourcesChain
10
  from langchain.chat_models import ChatOpenAI
11
  from langchain.prompts.chat import (
12
  ChatPromptTemplate,
@@ -36,34 +36,52 @@ if not OPENAI_API_KEY:
36
  "No se encontró la variable de entorno 'OPENAI_API_KEY'. Defínela en tu entorno o en los secrets."
37
  )
38
 
39
- # Configuración del text splitter (modo in-memory, sin persistencia)
40
  text_splitter = RecursiveCharacterTextSplitter(chunk_size=500, chunk_overlap=100)
41
 
42
- # Plantilla del sistema para el prompt (en español)
43
- system_template = """Utiliza las siguientes piezas de contexto para responder la pregunta del usuario de manera breve y concisa.
44
- Si no sabes la respuesta, simplemente di que no lo sabes, no intentes inventarla.
45
- SIEMPRE incluye una parte "FUENTES" en tu respuesta, donde se indique el documento del cual obtuviste la información.
46
 
47
- Ejemplo:
48
- La respuesta es foo
49
- FUENTES: xyz
 
 
 
 
 
 
 
50
 
51
  ----------------
52
- {summaries}"""
 
53
 
54
- messages = [
55
  SystemMessagePromptTemplate.from_template(system_template),
56
  HumanMessagePromptTemplate.from_template("{question}")
57
  ]
58
- prompt = ChatPromptTemplate.from_messages(messages)
59
- chain_type_kwargs = {"prompt": prompt}
 
 
 
 
 
 
 
 
 
 
 
 
60
 
61
- # --- EVENTO AL INICIAR EL CHAT ---
62
  @cl.on_chat_start
63
  async def on_chat_start():
64
- await cl.Message(content="Bienvenido a la gestion de conflictos espero les agrade William , German , Carlos ").send()
65
-
66
- # Rutas de los PDFs (asegúrate de que estén en el directorio actual o ajusta las rutas)
 
 
67
  pdf_paths = [
68
  "gestios de conflictos.pdf",
69
  "Managing Conflict with Your Boss .pdf"
@@ -82,78 +100,121 @@ async def on_chat_start():
82
  text = page.extract_text()
83
  if text:
84
  pdf_text += text
 
85
  chunks = text_splitter.split_text(pdf_text)
86
  all_texts.extend(chunks)
87
  all_metadatas.extend([{"source": base_name} for _ in chunks])
88
 
89
  # Crear la base vectorial usando nuestra clase personalizada de embeddings
90
- # Al no especificar persist_directory se utiliza el modo in-memory, evitando la necesidad de configurar un tenant
91
  embeddings = CustomOpenAIEmbeddings(openai_api_key=OPENAI_API_KEY)
92
  docsearch = await cl.make_async(Chroma.from_texts)(
93
- all_texts,
94
- embeddings,
95
- metadatas=all_metadatas,
96
- persist_directory="./chroma_db" # Directorio de persistencia
97
- )
98
 
99
- # Crear la cadena de QA utilizando ChatOpenAI
100
- chain = RetrievalQAWithSourcesChain.from_chain_type(
101
- ChatOpenAI(temperature=0, openai_api_key=OPENAI_API_KEY, max_tokens=400),
 
 
 
 
 
102
  chain_type="stuff",
103
  retriever=docsearch.as_retriever(),
104
- chain_type_kwargs=chain_type_kwargs
105
  )
106
 
 
 
 
 
 
 
 
 
 
 
 
107
  # Guardar en la sesión de usuario
108
- cl.user_session.set("chain", chain)
 
109
  cl.user_session.set("metadatas", all_metadatas)
110
  cl.user_session.set("texts", all_texts)
111
 
112
- await cl.Message(content="¡Listo! Ya puedes hacer tus preguntas de manera breve.").send()
 
113
 
114
- # --- EVENTO AL RECIBIR UN MENSAJE DEL USUARIO ---
115
  @cl.on_message
116
  async def main(message: cl.Message):
117
  query = message.content
118
- chain = cl.user_session.get("chain")
 
 
 
 
 
119
  cb = cl.AsyncLangchainCallbackHandler(
120
  stream_final_answer=True,
121
  answer_prefix_tokens=["FINAL", "ANSWER"]
122
  )
123
  cb.answer_reached = True
124
 
125
- res = await chain.acall(query, callbacks=[cb])
126
-
127
  answer = res["answer"]
128
  sources = res["sources"].strip()
129
- source_elements = []
130
-
131
- metadatas = cl.user_session.get("metadatas")
132
- all_sources = [m["source"] for m in metadatas]
133
- texts = cl.user_session.get("texts")
134
-
135
- if sources:
136
- found_sources = []
137
- for src in sources.split(","):
138
- source_name = src.strip().replace(".", "")
139
- try:
140
- index = all_sources.index(source_name)
141
- except ValueError:
142
- continue
143
- found_sources.append(source_name)
144
- source_elements.append(cl.Text(content=texts[index], name=source_name))
145
- if found_sources:
146
- answer += f"\nFUENTES: {', '.join(found_sources)}"
147
- else:
148
- answer += "\nNo se encontraron fuentes."
149
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
150
  if cb.has_streamed_final_answer:
151
- cb.final_stream.elements = source_elements
152
- await cb.final_stream.update()
153
  else:
154
- await cl.Message(content=answer, elements=source_elements).send()
 
155
 
156
- # --- EJECUCIÓN ---
157
  if __name__ == "__main__":
158
  from chainlit.cli import run_chainlit
159
  file_name = __file__ if '__file__' in globals() else "app.py"
 
6
  from langchain.embeddings.openai import OpenAIEmbeddings
7
  from langchain.text_splitter import RecursiveCharacterTextSplitter
8
  from langchain.vectorstores import Chroma
9
+ from langchain.chains import RetrievalQAWithSourcesChain, LLMChain
10
  from langchain.chat_models import ChatOpenAI
11
  from langchain.prompts.chat import (
12
  ChatPromptTemplate,
 
36
  "No se encontró la variable de entorno 'OPENAI_API_KEY'. Defínela en tu entorno o en los secrets."
37
  )
38
 
39
+ # Configuración del text splitter (puedes ajustar chunk_size y chunk_overlap según tus necesidades)
40
  text_splitter = RecursiveCharacterTextSplitter(chunk_size=500, chunk_overlap=100)
41
 
42
+ # --- PROMPTS Y PLANTILLAS ---
 
 
 
43
 
44
+ # Plantilla del sistema para consultas basadas en PDF + Conocimiento General
45
+ system_template = """\
46
+ Eres un asistente en español basado en ChatGPT-4 con grandes capacidades de razonamiento y análisis.
47
+ Tienes acceso a los siguientes documentos, y también cuentas con conocimientos generales para responder
48
+ toda clase de preguntas, tanto del contexto provisto como de tu conocimiento general.
49
+
50
+ - Si la pregunta está claramente respondida por el contenido de los textos, proporciona la información relevante y cita tus fuentes.
51
+ - Si no está respondida por los textos, utiliza tu conocimiento general y responde de forma analítica, extensa y detallada.
52
+ - Siempre que utilices información proveniente de los PDFs, al final de tu respuesta indica las fuentes de la forma:
53
+ FUENTES: nombre_del_pdf
54
 
55
  ----------------
56
+ {summaries}
57
+ """
58
 
59
+ messages_pdf = [
60
  SystemMessagePromptTemplate.from_template(system_template),
61
  HumanMessagePromptTemplate.from_template("{question}")
62
  ]
63
+ pdf_prompt = ChatPromptTemplate.from_messages(messages_pdf)
64
+
65
+ # Cadena/prompt para conocimiento general (fallback), en caso de que no haya nada relevante en los PDF
66
+ fallback_system_template = """\
67
+ Eres ChatGPT-4, un modelo de lenguaje altamente analítico y con amplio conocimiento.
68
+ Responde en español de manera extensa, detallada y muy analítica.
69
+ """
70
+
71
+ messages_fallback = [
72
+ SystemMessagePromptTemplate.from_template(fallback_system_template),
73
+ HumanMessagePromptTemplate.from_template("{question}")
74
+ ]
75
+ fallback_prompt = ChatPromptTemplate.from_messages(messages_fallback)
76
+
77
 
 
78
  @cl.on_chat_start
79
  async def on_chat_start():
80
+ await cl.Message(
81
+ content="¡Bienvenido! Estoy listo para ayudarte con gestión de conflictos y cualquier otra pregunta que tengas."
82
+ ).send()
83
+
84
+ # Rutas de los PDFs
85
  pdf_paths = [
86
  "gestios de conflictos.pdf",
87
  "Managing Conflict with Your Boss .pdf"
 
100
  text = page.extract_text()
101
  if text:
102
  pdf_text += text
103
+
104
  chunks = text_splitter.split_text(pdf_text)
105
  all_texts.extend(chunks)
106
  all_metadatas.extend([{"source": base_name} for _ in chunks])
107
 
108
  # Crear la base vectorial usando nuestra clase personalizada de embeddings
 
109
  embeddings = CustomOpenAIEmbeddings(openai_api_key=OPENAI_API_KEY)
110
  docsearch = await cl.make_async(Chroma.from_texts)(
111
+ all_texts,
112
+ embeddings,
113
+ metadatas=all_metadatas,
114
+ persist_directory="./chroma_db" # Directorio de persistencia (ajústalo si necesitas)
115
+ )
116
 
117
+ # Cadena para preguntas que tengan match en los PDFs
118
+ pdf_chain = RetrievalQAWithSourcesChain.from_chain_type(
119
+ llm=ChatOpenAI(
120
+ temperature=0.7,
121
+ model_name="gpt-4", # Asegúrate de que tu cuenta tenga acceso a GPT-4
122
+ openai_api_key=OPENAI_API_KEY,
123
+ max_tokens=2000
124
+ ),
125
  chain_type="stuff",
126
  retriever=docsearch.as_retriever(),
127
+ chain_type_kwargs={"prompt": pdf_prompt}
128
  )
129
 
130
+ # Cadena de fallback para preguntas fuera de contexto PDF (conocimiento general)
131
+ fallback_chain = LLMChain(
132
+ llm=ChatOpenAI(
133
+ temperature=0.7,
134
+ model_name="gpt-4", # Asegúrate de que tu cuenta tenga acceso a GPT-4
135
+ openai_api_key=OPENAI_API_KEY,
136
+ max_tokens=2000
137
+ ),
138
+ prompt=fallback_prompt
139
+ )
140
+
141
  # Guardar en la sesión de usuario
142
+ cl.user_session.set("pdf_chain", pdf_chain)
143
+ cl.user_session.set("fallback_chain", fallback_chain)
144
  cl.user_session.set("metadatas", all_metadatas)
145
  cl.user_session.set("texts", all_texts)
146
 
147
+ await cl.Message(content="¡Listo! Puedes comenzar a hacer tus preguntas.").send()
148
+
149
 
 
150
  @cl.on_message
151
  async def main(message: cl.Message):
152
  query = message.content
153
+ pdf_chain = cl.user_session.get("pdf_chain")
154
+ fallback_chain = cl.user_session.get("fallback_chain")
155
+ metadatas = cl.user_session.get("metadatas")
156
+ texts = cl.user_session.get("texts")
157
+
158
+ # Callback para hacer streaming de la respuesta
159
  cb = cl.AsyncLangchainCallbackHandler(
160
  stream_final_answer=True,
161
  answer_prefix_tokens=["FINAL", "ANSWER"]
162
  )
163
  cb.answer_reached = True
164
 
165
+ # 1) Intentar obtener respuesta del PDF chain
166
+ res = await pdf_chain.acall(query, callbacks=[cb])
167
  answer = res["answer"]
168
  sources = res["sources"].strip()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
169
 
170
+ # Verificamos si la respuesta indica que no se encontró nada relevante
171
+ # o si la cadena devolvió algo muy corto que parezca "No lo sé".
172
+ # Ajusta la condición según tu preferencia.
173
+ if ("no lo sé" in answer.lower()) or ("no sé" in answer.lower()) or (len(answer) < 30):
174
+ # 2) Fallback a la cadena de conocimiento general
175
+ res_fallback = await fallback_chain.acall({"question": query})
176
+ answer = res_fallback["text"]
177
+ # En fallback no tenemos "FUENTES", pues responde con conocimiento general
178
+ sources = ""
179
+ else:
180
+ # Agregar fuentes si las hay
181
+ if sources:
182
+ # Buscamos los fragmentos correspondientes
183
+ found_sources = []
184
+ source_elements = []
185
+
186
+ all_sources = [m["source"] for m in metadatas]
187
+ for src in sources.split(","):
188
+ src_name = src.strip().replace(".", "")
189
+ try:
190
+ index = all_sources.index(src_name)
191
+ except ValueError:
192
+ continue
193
+ found_sources.append(src_name)
194
+ source_elements.append(cl.Text(content=texts[index], name=src_name))
195
+
196
+ if found_sources:
197
+ answer += f"\n\nFUENTES: {', '.join(found_sources)}"
198
+
199
+ # Si estamos haciendo streaming, actualizamos el mensaje con los elementos
200
+ if cb.has_streamed_final_answer:
201
+ cb.final_stream.elements = source_elements
202
+ await cb.final_stream.update()
203
+ return
204
+ else:
205
+ # Si no hubo streaming, mandamos el mensaje completo al final
206
+ await cl.Message(content=answer, elements=source_elements).send()
207
+ return
208
+
209
+ # Si llegamos aquí, simplemente enviamos la respuesta (sea PDF o fallback)
210
+ # y no hay fuentes que mostrar (o ya se procesaron).
211
  if cb.has_streamed_final_answer:
212
+ # Si fue streaming, actualizamos el mensaje final sin fuentes
213
+ await cb.final_stream.update(content=answer)
214
  else:
215
+ await cl.Message(content=answer).send()
216
+
217
 
 
218
  if __name__ == "__main__":
219
  from chainlit.cli import run_chainlit
220
  file_name = __file__ if '__file__' in globals() else "app.py"