DHEIVER commited on
Commit
6f1dbe9
·
verified ·
1 Parent(s): b576e4e

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +125 -82
app.py CHANGED
@@ -1,5 +1,5 @@
1
  import os
2
- from typing import Optional
3
  import gradio as gr
4
  from langchain_community.document_loaders import PyPDFLoader, DirectoryLoader
5
  from langchain.text_splitter import RecursiveCharacterTextSplitter
@@ -10,24 +10,22 @@ from langchain.chains import RetrievalQA
10
  from transformers import AutoTokenizer, AutoModelForSeq2SeqLM, pipeline
11
  import torch
12
  import tempfile
 
13
 
14
  # Configurações
15
  EMBEDDING_MODEL = "sentence-transformers/all-mpnet-base-v2"
16
- LLM_MODEL = "google/flan-t5-large" # Modelo aberto e sem necessidade de autenticação
17
  DOCS_DIR = "documents"
18
 
19
  class RAGSystem:
20
  def __init__(self):
21
- # Inicializa o modelo de linguagem
22
- print("Carregando modelo de linguagem...")
23
  self.tokenizer = AutoTokenizer.from_pretrained(LLM_MODEL)
24
  self.model = AutoModelForSeq2SeqLM.from_pretrained(
25
  LLM_MODEL,
26
  device_map="auto",
27
- torch_dtype=torch.float32 # T5 funciona bem com float32
28
  )
29
 
30
- # Configura o pipeline
31
  pipe = pipeline(
32
  "text2text-generation",
33
  model=self.model,
@@ -37,28 +35,19 @@ class RAGSystem:
37
  top_p=0.95
38
  )
39
 
40
- # Configura o modelo LangChain
41
  self.llm = HuggingFacePipeline(pipeline=pipe)
42
-
43
- # Configura embeddings
44
- print("Configurando embeddings...")
45
  self.embeddings = HuggingFaceEmbeddings(
46
  model_name=EMBEDDING_MODEL,
47
  model_kwargs={'device': 'cpu'}
48
  )
49
-
50
- # Carrega a base de conhecimento permanente
51
  self.base_db = self.load_base_knowledge()
52
 
53
  def load_base_knowledge(self) -> Optional[FAISS]:
54
- """Carrega a base de conhecimento permanente da pasta de documentos"""
55
  try:
56
  if not os.path.exists(DOCS_DIR):
57
- print(f"Pasta {DOCS_DIR} não encontrada. Criando...")
58
  os.makedirs(DOCS_DIR)
59
  return None
60
 
61
- # Carrega todos os PDFs da pasta
62
  loader = DirectoryLoader(
63
  DOCS_DIR,
64
  glob="**/*.pdf",
@@ -67,46 +56,35 @@ class RAGSystem:
67
  documents = loader.load()
68
 
69
  if not documents:
70
- print("Nenhum documento encontrado na pasta base.")
71
  return None
72
 
73
- # Divide o texto em chunks
74
  text_splitter = RecursiveCharacterTextSplitter(
75
- chunk_size=500, # Chunks menores para o T5
76
  chunk_overlap=100,
77
  length_function=len,
78
  separators=["\n\n", "\n", ".", " ", ""]
79
  )
80
  texts = text_splitter.split_documents(documents)
81
 
82
- # Cria base de conhecimento
83
- print(f"Criando base de conhecimento com {len(texts)} chunks...")
84
- db = FAISS.from_documents(texts, self.embeddings)
85
- return db
86
 
87
  except Exception as e:
88
  print(f"Erro ao carregar base de conhecimento: {str(e)}")
89
  return None
90
 
91
  def process_pdf(self, file_content: bytes) -> Optional[FAISS]:
92
- """Processa o PDF do usuário"""
93
  try:
94
- # Cria arquivo temporário
95
  with tempfile.NamedTemporaryFile(delete=False, suffix='.pdf') as tmp_file:
96
  tmp_file.write(file_content)
97
  tmp_path = tmp_file.name
98
 
99
- # Carrega e processa o PDF
100
  loader = PyPDFLoader(tmp_path)
101
  documents = loader.load()
102
-
103
- # Remove arquivo temporário
104
  os.unlink(tmp_path)
105
 
106
  if not documents:
107
  return None
108
 
109
- # Divide o texto em chunks
110
  text_splitter = RecursiveCharacterTextSplitter(
111
  chunk_size=500,
112
  chunk_overlap=100,
@@ -115,10 +93,8 @@ class RAGSystem:
115
  )
116
  texts = text_splitter.split_documents(documents)
117
 
118
- # Cria base de conhecimento
119
  db = FAISS.from_documents(texts, self.embeddings)
120
 
121
- # Se existir uma base permanente, mescla com ela
122
  if self.base_db is not None:
123
  db.merge_from(self.base_db)
124
 
@@ -128,37 +104,37 @@ class RAGSystem:
128
  print(f"Erro ao processar PDF: {str(e)}")
129
  return None
130
 
131
- def generate_response(self, file_obj, query: str) -> str:
132
- """Gera resposta para a consulta"""
133
  if not query.strip():
134
- return "Por favor, insira uma pergunta."
135
 
 
136
  try:
137
- # Se tiver arquivo do usuário, processa e mescla com a base
 
 
 
138
  if file_obj is not None:
139
  db = self.process_pdf(file_obj)
140
  if db is None:
141
- return "Não foi possível processar o PDF."
142
- # Se não tiver arquivo do usuário, usa só a base permanente
143
  elif self.base_db is not None:
144
  db = self.base_db
145
  else:
146
- return "Nenhuma base de conhecimento disponível. Por favor, faça upload de um PDF ou adicione documentos à pasta base."
147
 
148
- # Configura o chain RAG
149
  qa_chain = RetrievalQA.from_chain_type(
150
  llm=self.llm,
151
  chain_type="stuff",
152
  retriever=db.as_retriever(
153
- search_kwargs={
154
- "k": 4, # Aumentamos o k para ter mais contexto
155
- "fetch_k": 6
156
- }
157
  ),
158
  return_source_documents=True
159
  )
160
 
161
- # Adiciona contexto sobre a fonte da resposta
162
  prompt = f"""Baseado nos documentos fornecidos, responda em português à seguinte pergunta:
163
  {query}
164
 
@@ -166,60 +142,127 @@ class RAGSystem:
166
  Se a resposta vier do PDF enviado, indique isso no início.
167
  Se não encontrar informações suficientes, indique isso claramente."""
168
 
169
- # Gera resposta
170
  result = qa_chain({"query": prompt})
171
- return result["result"]
 
 
 
172
 
173
  except Exception as e:
174
- return f"Erro ao gerar resposta: {str(e)}"
 
175
 
176
- # Interface Gradio
177
  def create_demo():
178
  rag = RAGSystem()
179
 
180
- with gr.Blocks() as demo:
181
- gr.Markdown("# 📚 Sistema RAG de Consulta a Documentos")
182
- gr.Markdown(f"""
183
- ### Como usar:
184
- 1. Os documentos da pasta `{DOCS_DIR}` são usados como base de conhecimento
185
- 2. Você pode fazer upload de PDFs adicionais para consulta
186
- 3. Digite sua pergunta e aguarde a resposta
187
- 4. As respostas são baseadas no conteúdo dos documentos
188
- """)
189
-
190
- with gr.Row():
191
- with gr.Column(scale=1):
192
- file_input = gr.File(
193
- label="Upload do PDF (opcional)",
194
- type="binary",
195
- file_types=[".pdf"]
196
- )
197
- query_input = gr.Textbox(
198
- label="Sua Pergunta",
199
- placeholder="Digite sua pergunta sobre o documento...",
200
- lines=3
201
- )
202
- submit_btn = gr.Button("🔍 Buscar Resposta", variant="primary")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
203
 
204
- with gr.Column(scale=1):
205
- output = gr.Textbox(
206
- label="Resposta",
207
- lines=10
 
 
 
 
 
 
208
  )
 
 
 
 
 
 
 
 
 
 
 
 
209
 
 
210
  submit_btn.click(
211
  fn=rag.generate_response,
212
  inputs=[file_input, query_input],
213
- outputs=output
 
 
 
 
 
214
  )
215
 
216
- gr.Examples(
217
- examples=[
218
- [None, "Qual é o tema principal dos documentos?"],
219
- [None, "Pode fazer um resumo dos pontos principais?"],
220
- [None, "Quais são as principais conclusões?"]
221
- ],
222
- inputs=[file_input, query_input]
223
  )
224
 
225
  return demo
 
1
  import os
2
+ from typing import Optional, Tuple
3
  import gradio as gr
4
  from langchain_community.document_loaders import PyPDFLoader, DirectoryLoader
5
  from langchain.text_splitter import RecursiveCharacterTextSplitter
 
10
  from transformers import AutoTokenizer, AutoModelForSeq2SeqLM, pipeline
11
  import torch
12
  import tempfile
13
+ import time
14
 
15
  # Configurações
16
  EMBEDDING_MODEL = "sentence-transformers/all-mpnet-base-v2"
17
+ LLM_MODEL = "google/flan-t5-large"
18
  DOCS_DIR = "documents"
19
 
20
  class RAGSystem:
21
  def __init__(self):
 
 
22
  self.tokenizer = AutoTokenizer.from_pretrained(LLM_MODEL)
23
  self.model = AutoModelForSeq2SeqLM.from_pretrained(
24
  LLM_MODEL,
25
  device_map="auto",
26
+ torch_dtype=torch.float32
27
  )
28
 
 
29
  pipe = pipeline(
30
  "text2text-generation",
31
  model=self.model,
 
35
  top_p=0.95
36
  )
37
 
 
38
  self.llm = HuggingFacePipeline(pipeline=pipe)
 
 
 
39
  self.embeddings = HuggingFaceEmbeddings(
40
  model_name=EMBEDDING_MODEL,
41
  model_kwargs={'device': 'cpu'}
42
  )
 
 
43
  self.base_db = self.load_base_knowledge()
44
 
45
  def load_base_knowledge(self) -> Optional[FAISS]:
 
46
  try:
47
  if not os.path.exists(DOCS_DIR):
 
48
  os.makedirs(DOCS_DIR)
49
  return None
50
 
 
51
  loader = DirectoryLoader(
52
  DOCS_DIR,
53
  glob="**/*.pdf",
 
56
  documents = loader.load()
57
 
58
  if not documents:
 
59
  return None
60
 
 
61
  text_splitter = RecursiveCharacterTextSplitter(
62
+ chunk_size=500,
63
  chunk_overlap=100,
64
  length_function=len,
65
  separators=["\n\n", "\n", ".", " ", ""]
66
  )
67
  texts = text_splitter.split_documents(documents)
68
 
69
+ return FAISS.from_documents(texts, self.embeddings)
 
 
 
70
 
71
  except Exception as e:
72
  print(f"Erro ao carregar base de conhecimento: {str(e)}")
73
  return None
74
 
75
  def process_pdf(self, file_content: bytes) -> Optional[FAISS]:
 
76
  try:
 
77
  with tempfile.NamedTemporaryFile(delete=False, suffix='.pdf') as tmp_file:
78
  tmp_file.write(file_content)
79
  tmp_path = tmp_file.name
80
 
 
81
  loader = PyPDFLoader(tmp_path)
82
  documents = loader.load()
 
 
83
  os.unlink(tmp_path)
84
 
85
  if not documents:
86
  return None
87
 
 
88
  text_splitter = RecursiveCharacterTextSplitter(
89
  chunk_size=500,
90
  chunk_overlap=100,
 
93
  )
94
  texts = text_splitter.split_documents(documents)
95
 
 
96
  db = FAISS.from_documents(texts, self.embeddings)
97
 
 
98
  if self.base_db is not None:
99
  db.merge_from(self.base_db)
100
 
 
104
  print(f"Erro ao processar PDF: {str(e)}")
105
  return None
106
 
107
+ def generate_response(self, file_obj, query: str, progress=gr.Progress()) -> Tuple[str, str, str]:
108
+ """Retorna (resposta, status, tempo_decorrido)"""
109
  if not query.strip():
110
+ return "Por favor, insira uma pergunta.", "⚠️ Aguardando pergunta", "0s"
111
 
112
+ start_time = time.time()
113
  try:
114
+ progress(0, desc="Iniciando processamento...")
115
+
116
+ # Processa documento
117
+ progress(0.2, desc="Processando documento...")
118
  if file_obj is not None:
119
  db = self.process_pdf(file_obj)
120
  if db is None:
121
+ return "Não foi possível processar o PDF.", "❌ Erro no processamento", "0s"
 
122
  elif self.base_db is not None:
123
  db = self.base_db
124
  else:
125
+ return "Nenhuma base de conhecimento disponível.", "❌ Sem documentos", "0s"
126
 
127
+ progress(0.4, desc="Buscando informações relevantes...")
128
  qa_chain = RetrievalQA.from_chain_type(
129
  llm=self.llm,
130
  chain_type="stuff",
131
  retriever=db.as_retriever(
132
+ search_kwargs={"k": 4, "fetch_k": 6}
 
 
 
133
  ),
134
  return_source_documents=True
135
  )
136
 
137
+ progress(0.6, desc="Gerando resposta...")
138
  prompt = f"""Baseado nos documentos fornecidos, responda em português à seguinte pergunta:
139
  {query}
140
 
 
142
  Se a resposta vier do PDF enviado, indique isso no início.
143
  Se não encontrar informações suficientes, indique isso claramente."""
144
 
 
145
  result = qa_chain({"query": prompt})
146
+ elapsed_time = f"{time.time() - start_time:.1f}s"
147
+
148
+ progress(1.0, desc="Concluído!")
149
+ return result["result"], "✅ Sucesso", elapsed_time
150
 
151
  except Exception as e:
152
+ elapsed_time = f"{time.time() - start_time:.1f}s"
153
+ return f"Erro ao gerar resposta: {str(e)}", "❌ Erro", elapsed_time
154
 
 
155
  def create_demo():
156
  rag = RAGSystem()
157
 
158
+ with gr.Blocks(theme=gr.themes.Soft()) as demo:
159
+ with gr.Column(elem_id="container"):
160
+ # Cabeçalho
161
+ gr.Markdown(
162
+ """
163
+ # 🤖 Assistente de Documentos Inteligente
164
+
165
+ Este sistema usa tecnologia RAG (Retrieval-Augmented Generation) para responder perguntas sobre seus documentos.
166
+ """
167
+ )
168
+
169
+ # Área principal
170
+ with gr.Row():
171
+ # Coluna de entrada
172
+ with gr.Column():
173
+ with gr.Group():
174
+ gr.Markdown("### 📄 Documentos")
175
+ file_input = gr.File(
176
+ label="Upload de PDF (opcional)",
177
+ type="binary",
178
+ file_types=[".pdf"],
179
+ height=100,
180
+ )
181
+ info = gr.Markdown(
182
+ f"""
183
+ ℹ️ Além do upload, o sistema também consulta a pasta `{DOCS_DIR}`
184
+ """
185
+ )
186
+
187
+ with gr.Group():
188
+ gr.Markdown("### ❓ Sua Pergunta")
189
+ query_input = gr.Textbox(
190
+ placeholder="Digite sua pergunta aqui...",
191
+ lines=3,
192
+ max_lines=6,
193
+ show_label=False,
194
+ )
195
+
196
+ with gr.Row():
197
+ clear_btn = gr.Button("🗑️ Limpar", variant="secondary")
198
+ submit_btn = gr.Button("🔍 Enviar Pergunta", variant="primary")
199
+
200
+ # Coluna de saída
201
+ with gr.Column():
202
+ with gr.Group():
203
+ gr.Markdown("### 📝 Resposta")
204
+ with gr.Row():
205
+ status_output = gr.Textbox(
206
+ label="Status",
207
+ value="⏳ Aguardando...",
208
+ interactive=False,
209
+ show_label=False,
210
+ )
211
+ time_output = gr.Textbox(
212
+ label="Tempo",
213
+ value="0s",
214
+ interactive=False,
215
+ show_label=False,
216
+ )
217
+
218
+ response_output = gr.Textbox(
219
+ label="Resposta",
220
+ placeholder="A resposta aparecerá aqui...",
221
+ interactive=False,
222
+ lines=12,
223
+ show_label=False,
224
+ )
225
 
226
+ # Exemplos
227
+ with gr.Accordion("📚 Exemplos de Perguntas", open=False):
228
+ gr.Examples(
229
+ examples=[
230
+ [None, "Qual é o tema principal dos documentos?"],
231
+ [None, "Pode resumir os pontos principais?"],
232
+ [None, "Quais são as principais conclusões?"],
233
+ [None, "Explique o contexto deste documento."],
234
+ ],
235
+ inputs=[file_input, query_input],
236
  )
237
+
238
+ # Rodapé
239
+ gr.Markdown(
240
+ """
241
+ ---
242
+ ### 🔧 Sobre o Sistema
243
+ * Usa modelo T5 para geração de respostas
244
+ * Processamento de documentos com tecnologia RAG
245
+ * Suporte a múltiplos documentos PDF
246
+ * Respostas baseadas apenas no conteúdo dos documentos
247
+ """
248
+ )
249
 
250
+ # Eventos
251
  submit_btn.click(
252
  fn=rag.generate_response,
253
  inputs=[file_input, query_input],
254
+ outputs=[response_output, status_output, time_output],
255
+ )
256
+
257
+ clear_btn.click(
258
+ lambda: (None, "", "⏳ Aguardando...", "0s"),
259
+ outputs=[file_input, query_input, status_output, time_output],
260
  )
261
 
262
+ # Limpa a resposta quando a pergunta muda
263
+ query_input.change(
264
+ lambda: ("", " Aguardando...", "0s"),
265
+ outputs=[response_output, status_output, time_output],
 
 
 
266
  )
267
 
268
  return demo