jeysshon commited on
Commit
907fa7a
verified
1 Parent(s): 79de3bb

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +145 -91
app.py CHANGED
@@ -1,106 +1,160 @@
1
- import openai
 
2
  import chainlit as cl
3
  import PyPDF2
4
- import numpy as np
5
- from sentence_transformers import SentenceTransformer
6
 
7
- # Configuraci贸n de la API DeepSeek
8
- DEEPSEEK_API_KEY = "TU_API_KEY_DEEPSEEK"
9
- openai.api_base = "https://api.deepseek.com/v1"
10
- openai.api_key = DEEPSEEK_API_KEY
11
- MODEL_NAME = "deepseek-chat" # Modelo de DeepSeek a usar
 
 
 
 
 
12
 
13
- # Funciones auxiliares: leer PDFs y fragmentar texto
14
- def split_text(text, max_words=200):
15
- words = text.split()
16
- chunks = []
17
- current_chunk = []
18
- for word in words:
19
- current_chunk.append(word)
20
- if len(current_chunk) >= max_words:
21
- chunks.append(" ".join(current_chunk))
22
- current_chunk = []
23
- if current_chunk:
24
- chunks.append(" ".join(current_chunk))
25
- return chunks
26
 
27
- def load_pdfs(pdf_paths):
28
- doc_chunks = []
29
- for path in pdf_paths:
30
- try:
31
- reader = PyPDF2.PdfReader(path)
32
- except Exception as e:
33
- print(f"Error al leer {path}: {e}")
34
- continue
35
- doc_name = path.split("/")[-1].replace(".pdf", "")
36
- full_text = ""
37
- for page in reader.pages:
38
- text = page.extract_text() or ""
39
- text = text.replace("\n", " ")
40
- full_text += " " + text
41
- for chunk in split_text(full_text, max_words=200):
42
- doc_chunks.append({"doc": doc_name, "text": chunk})
43
- return doc_chunks
44
 
45
- # Carga de documentos (especificar las rutas de los PDFs a usar)
46
- pdf_files = ['Managing Conflict with Your Boss .pdf', 'gestios de conflictos.pdf'] # etc.
47
- documents = load_pdfs(pdf_files)
48
 
49
- # Preparar modelo de embeddings e indexar los fragmentos
50
- embedder = SentenceTransformer("sentence-transformers/distiluse-base-multilingual-cased-v2")
51
- def index_documents(doc_chunks):
52
- texts = [entry["text"] for entry in doc_chunks]
53
- embeddings = embedder.encode(texts, convert_to_numpy=True)
54
- norms = np.linalg.norm(embeddings, axis=1, keepdims=True)
55
- embeddings_norm = embeddings / norms
56
- return embeddings_norm
57
 
58
- doc_embeddings = index_documents(documents)
 
 
59
 
60
- # Funci贸n de b煤squeda de fragmentos relevantes
61
- def retrieve_relevant(query, doc_chunks, doc_embeddings, top_k=3):
62
- query_vec = embedder.encode(query, convert_to_numpy=True)
63
- query_vec = query_vec / np.linalg.norm(query_vec)
64
- sims = np.dot(doc_embeddings, query_vec)
65
- top_idx = np.argsort(sims)[::-1][:top_k]
66
- top_chunks = [doc_chunks[i] for i in top_idx]
67
- return top_chunks
68
 
69
- # Funci贸n de generaci贸n de respuesta con DeepSeek
70
- def generate_answer(question, relevant_chunks):
71
- # Armar texto de contexto con etiquetas de documento
72
- context_text = ""
73
- for chunk in relevant_chunks:
74
- context_text += f"[{chunk['doc']}] {chunk['text']}\n"
75
- system_message = {
76
- "role": "system",
77
- "content": (
78
- "Eres un asistente experto que responde de forma profunda y anal铆tica. "
79
- "Tienes acceso a informaci贸n de varios documentos proporcionados. "
80
- "Usa el contenido dado como contexto, pero no te limites a copiarlo: "
81
- "responde de forma argumentada, sintetizando la informaci贸n y aportando visi贸n cr铆tica."
82
- )
83
- }
84
- user_message = {
85
- "role": "user",
86
- "content": f"Contexto:\n{context_text}\nPregunta: {question}"
87
- }
88
- response = openai.ChatCompletion.create(
89
- model=MODEL_NAME,
90
- messages=[system_message, user_message],
91
- temperature=0.7
92
- )
93
- answer = response["choices"][0]["message"]["content"]
94
- return answer
95
 
96
- # Integraci贸n con Chainlit
97
  @cl.on_chat_start
98
  async def on_chat_start():
99
- await cl.Message(content="馃 Asistente listo. Puede preguntarme sobre los documentos.").send()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
100
 
 
101
  @cl.on_message
102
- async def on_message(message: cl.Message):
103
- query = message.content.strip()
104
- top_chunks = retrieve_relevant(query, documents, doc_embeddings, top_k=3)
105
- answer = generate_answer(query, top_chunks)
106
- await cl.Message(content=answer).send()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import inspect
3
  import chainlit as cl
4
  import PyPDF2
 
 
5
 
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,
13
+ SystemMessagePromptTemplate,
14
+ HumanMessagePromptTemplate,
15
+ )
16
 
17
+ # Clase personalizada que cumple con la nueva interfaz de EmbeddingFunction de Chroma
18
+ class CustomOpenAIEmbeddings(OpenAIEmbeddings):
19
+ def __call__(self, input):
20
+ # Llama al m茅todo embed_documents para generar las embeddings a partir de una lista de textos
21
+ return self.embed_documents(input)
 
 
 
 
 
 
 
 
22
 
23
+ # Forzamos la firma de __call__ para que tenga exactamente ("self", "input")
24
+ CustomOpenAIEmbeddings.__call__.__signature__ = inspect.Signature(
25
+ parameters=[
26
+ inspect.Parameter("self", inspect.Parameter.POSITIONAL_OR_KEYWORD),
27
+ inspect.Parameter("input", inspect.Parameter.POSITIONAL_OR_KEYWORD)
28
+ ]
29
+ )
30
+
31
+ # --- CONFIGURACI脫N ---
32
+ # Obtenemos la API key de OpenAI desde las variables de entorno
33
+ OPENAI_API_KEY = os.environ.get("OPENAI_API_KEY")
34
+ if not OPENAI_API_KEY:
35
+ raise ValueError(
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"
70
+ ]
71
+
72
+ all_texts = []
73
+ all_metadatas = []
74
+
75
+ # Procesar cada PDF: extraer texto, dividirlo en fragmentos y asignar metadata
76
+ for path in pdf_paths:
77
+ base_name = os.path.basename(path)
78
+ with open(path, "rb") as f:
79
+ reader = PyPDF2.PdfReader(f)
80
+ pdf_text = ""
81
+ for page in reader.pages:
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"
160
+ run_chainlit(file_name)