DHEIVER commited on
Commit
2fa7d8f
·
verified ·
1 Parent(s): 43ba6e3

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +193 -133
app.py CHANGED
@@ -1,227 +1,287 @@
1
  import gradio as gr
 
2
  import PyPDF2
3
- from transformers import AutoTokenizer, AutoModel
4
- from sentence_transformers import util
5
- import torch
6
- import numpy as np
7
  import os
8
  import re
9
- from typing import List, Dict, Any
10
- import requests
 
 
11
 
12
- # Diretório para armazenar os PDFs
13
- PDF_DIR = "pdf_data"
 
14
  os.makedirs(PDF_DIR, exist_ok=True)
15
 
16
- # Modelo simples para embeddings
17
- tokenizer = AutoTokenizer.from_pretrained("sentence-transformers/all-MiniLM-L6-v2")
18
- model = AutoModel.from_pretrained("sentence-transformers/all-MiniLM-L6-v2")
 
 
 
 
 
19
 
20
- # Função para gerar embeddings
21
- def get_embeddings(texts):
22
- inputs = tokenizer(texts, padding=True, truncation=True, return_tensors="pt", max_length=512)
23
- with torch.no_grad():
24
- outputs = model(**inputs)
25
- embeddings = outputs.last_hidden_state.mean(dim=1)
26
- return embeddings / embeddings.norm(dim=1, keepdim=True)
27
-
28
- # Classe RAG simplificada
29
- class SimpleRAG:
30
  def __init__(self):
31
  self.documents = []
32
- self.embeddings = None
 
33
 
34
  def clear(self):
35
  self.documents = []
36
- self.embeddings = None
37
 
38
  def process_text(self, text):
39
- # Limpa texto e divide em parágrafos
 
40
  text = re.sub(r'\s+', ' ', text).strip()
41
- paragraphs = [p for p in text.split('\n') if len(p) > 50]
42
- return paragraphs
43
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
44
  def load_pdf(self, file_obj):
 
45
  try:
 
 
 
 
46
  # Salva o arquivo
47
- file_path = os.path.join(PDF_DIR, file_obj.name)
48
  with open(file_path, 'wb') as f:
49
  f.write(file_obj.read())
50
 
51
  # Extrai o texto
 
52
  with open(file_path, 'rb') as f:
53
- pdf = PyPDF2.PdfReader(f)
54
- text = ""
55
- for page in pdf.pages:
56
- text += page.extract_text() + "\n"
 
57
 
58
- # Processa o texto em chunks
 
 
 
59
  chunks = self.process_text(text)
 
 
60
 
61
  # Adiciona à base de conhecimento
62
- filename = os.path.basename(file_path)
63
- doc_chunks = [{"source": filename, "content": chunk} for chunk in chunks]
64
- self.documents.extend(doc_chunks)
 
 
 
 
 
65
 
66
- # Recalcula embeddings
67
- if self.documents:
68
- contents = [doc["content"] for doc in self.documents]
69
- self.embeddings = get_embeddings(contents)
70
 
71
- return f"Carregado: {filename} ({len(chunks)} segmentos)"
72
  except Exception as e:
73
  return f"Erro ao processar PDF: {str(e)}"
 
 
 
 
 
 
74
 
 
 
 
75
  def search(self, query, top_k=3):
76
- if not self.documents or self.embeddings is None:
 
77
  return []
78
 
79
- # Calcula embedding da query
80
- query_embedding = get_embeddings([query])
81
 
82
  # Calcula similaridade
83
- similarities = util.pytorch_cos_sim(query_embedding, self.embeddings)[0]
84
 
85
- # Encontra os top_k mais similares
86
- top_results = torch.topk(similarities, min(top_k, len(self.documents)))
87
 
88
  results = []
89
- for score, idx in zip(top_results.values, top_results.indices):
90
  results.append({
91
- "score": score.item(),
92
  "document": self.documents[idx]
93
  })
94
 
95
  return results
96
 
97
- # Inicializa o RAG
98
- rag = SimpleRAG()
99
-
100
- # Configurações para LLM
101
- LLM_API_URL = "https://api-inference.huggingface.co/models/mistralai/Mistral-7B-Instruct-v0.2"
102
- headers = {"Authorization": "Bearer hf_XXXXXXXXXXXXXXXXXXXXXXX"} # Substitua por sua API key
103
 
104
- def query_llm(prompt):
105
- payload = {
106
- "inputs": prompt,
107
- "parameters": {
108
- "max_new_tokens": 512,
109
- "temperature": 0.7,
110
- "top_p": 0.95
111
- }
112
- }
113
-
114
  try:
115
- response = requests.post(LLM_API_URL, headers=headers, json=payload)
116
- return response.json()[0]["generated_text"]
 
 
 
 
 
 
 
 
 
 
 
117
  except Exception as e:
118
- return f"Erro ao consultar o LLM: {str(e)}"
119
 
120
- # Função para processar a consulta
121
  def process_query(query, history):
122
- # Busca documentos relevantes
123
- results = rag.search(query)
 
 
124
 
125
- if not results:
126
- return "Por favor, carregue alguns PDFs primeiro.", "Nenhum documento disponível."
127
 
128
  # Formata o contexto
129
  context = ""
130
  for i, result in enumerate(results):
131
- context += f"[{i+1}] Fonte: {result['document']['source']}\n"
132
  context += f"Trecho: {result['document']['content'][:300]}...\n"
133
  context += f"Relevância: {result['score']:.2f}\n\n"
134
 
135
  # Constrói o prompt
136
- prompt = f"""<s>[INST]Você é um assistente de IA especializado em responder perguntas usando apenas
137
- o contexto fornecido. Considere apenas as informações nos documentos abaixo. Se a resposta não
138
- puder ser derivada do contexto, diga que não possui informações suficientes.
139
 
140
- CONTEXTO:
141
  {context}
142
 
143
- PERGUNTA: {query}[/INST]"""
144
-
145
- # Consulta o modelo
146
- response = query_llm(prompt)
147
 
148
- # Extrai a resposta real (removendo o prompt)
149
- actual_response = response.split("[/INST]")[-1].strip()
150
 
151
- return actual_response, context
152
 
153
  # Interface Gradio
154
- with gr.Blocks(title="RAG PDF Simplificado") as demo:
155
- with gr.Row():
156
- with gr.Column(scale=2):
157
- gr.Markdown("# RAG PDF Simplificado")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
158
 
159
- with gr.Row():
160
- with gr.Column():
161
- pdf_upload = gr.File(
162
- label="Carregar PDF",
163
- file_types=[".pdf"],
164
- file_count="single"
165
- )
166
- load_status = gr.Textbox(label="Status", interactive=False)
167
- clear_btn = gr.Button("Limpar Base de Conhecimento")
168
-
169
- with gr.Row():
170
- with gr.Column(scale=2):
171
- chatbot = gr.Chatbot(label="Conversa")
172
- query_input = gr.Textbox(
173
- label="Sua pergunta",
174
- placeholder="Digite sua pergunta sobre os documentos..."
175
- )
176
- query_btn = gr.Button("Enviar")
177
-
178
- with gr.Column(scale=1):
179
- context_display = gr.Textbox(
180
- label="Contexto Recuperado",
181
- interactive=False,
182
- lines=10
183
- )
184
-
185
- # Funções de callback
186
  def upload_pdf(file):
187
  if file is None:
188
  return "Nenhum arquivo selecionado."
189
- return rag.load_pdf(file)
190
 
191
  def clear_knowledge_base():
192
- rag.clear()
193
  return "Base de conhecimento limpa."
194
 
195
- def submit_query(query, history):
196
- history = history or []
197
- response, context = process_query(query, history)
198
- history.append((query, response))
199
- return history, "", context
 
 
 
 
 
200
 
201
  # Eventos
202
- pdf_upload.upload(
203
  upload_pdf,
204
  inputs=[pdf_upload],
205
- outputs=[load_status]
206
  )
207
 
208
  clear_btn.click(
209
  clear_knowledge_base,
210
  inputs=[],
211
- outputs=[load_status]
212
  )
213
 
214
- query_btn.click(
215
- submit_query,
216
- inputs=[query_input, chatbot],
217
- outputs=[chatbot, query_input, context_display]
218
  )
219
 
220
- query_input.submit(
221
- submit_query,
222
- inputs=[query_input, chatbot],
223
- outputs=[chatbot, query_input, context_display]
224
  )
225
 
226
  if __name__ == "__main__":
 
227
  demo.launch()
 
1
  import gradio as gr
2
+ from transformers import pipeline
3
  import PyPDF2
 
 
 
 
4
  import os
5
  import re
6
+ import numpy as np
7
+ from sklearn.feature_extraction.text import TfidfVectorizer
8
+ from sklearn.metrics.pairwise import cosine_similarity
9
+ import torch
10
 
11
+ # Configuração de diretórios
12
+ BASE_DIR = os.path.dirname(os.path.abspath(__file__))
13
+ PDF_DIR = os.path.join(BASE_DIR, "pdf_data")
14
  os.makedirs(PDF_DIR, exist_ok=True)
15
 
16
+ # Inicialização de modelos gratuitos
17
+ # Carregando o modelo de geração de texto
18
+ device = 0 if torch.cuda.is_available() else -1
19
+ summarizer = pipeline(
20
+ "summarization",
21
+ model="facebook/bart-large-cnn",
22
+ device=device
23
+ )
24
 
25
+ # Classe RAG totalmente gratuita
26
+ class FreeRAG:
 
 
 
 
 
 
 
 
27
  def __init__(self):
28
  self.documents = []
29
+ self.vectorizer = TfidfVectorizer(stop_words='english')
30
+ self.vectors = None
31
 
32
  def clear(self):
33
  self.documents = []
34
+ self.vectors = None
35
 
36
  def process_text(self, text):
37
+ """Processa e divide o texto em chunks de tamanho razoável"""
38
+ # Limpa espaços extras
39
  text = re.sub(r'\s+', ' ', text).strip()
 
 
40
 
41
+ # Divide por parágrafos
42
+ paragraphs = text.split('\n')
43
+ chunks = []
44
+
45
+ current_chunk = ""
46
+ for p in paragraphs:
47
+ p = p.strip()
48
+ if not p:
49
+ continue
50
+
51
+ # Se o parágrafo for muito grande, divide-o
52
+ if len(p) > 1000:
53
+ sentences = re.split(r'(?<=[.!?])\s+', p)
54
+ for sentence in sentences:
55
+ if len(current_chunk) + len(sentence) < 1000:
56
+ current_chunk += " " + sentence
57
+ else:
58
+ if current_chunk:
59
+ chunks.append(current_chunk.strip())
60
+ current_chunk = sentence
61
+ else:
62
+ if len(current_chunk) + len(p) < 1000:
63
+ current_chunk += " " + p
64
+ else:
65
+ if current_chunk:
66
+ chunks.append(current_chunk.strip())
67
+ current_chunk = p
68
+
69
+ if current_chunk:
70
+ chunks.append(current_chunk.strip())
71
+
72
+ return [c for c in chunks if len(c) > 100] # Filtra chunks muito pequenos
73
+
74
  def load_pdf(self, file_obj):
75
+ """Carrega um arquivo PDF"""
76
  try:
77
+ # Determina o caminho do arquivo
78
+ filename = os.path.basename(file_obj.name)
79
+ file_path = os.path.join(PDF_DIR, filename)
80
+
81
  # Salva o arquivo
 
82
  with open(file_path, 'wb') as f:
83
  f.write(file_obj.read())
84
 
85
  # Extrai o texto
86
+ text = ""
87
  with open(file_path, 'rb') as f:
88
+ reader = PyPDF2.PdfReader(f)
89
+ for page in reader.pages:
90
+ page_text = page.extract_text()
91
+ if page_text:
92
+ text += page_text + "\n"
93
 
94
+ if not text.strip():
95
+ return "Erro: Não foi possível extrair texto do PDF."
96
+
97
+ # Processa o texto
98
  chunks = self.process_text(text)
99
+ if not chunks:
100
+ return "Erro: Conteúdo do PDF não pôde ser processado adequadamente."
101
 
102
  # Adiciona à base de conhecimento
103
+ for chunk in chunks:
104
+ self.documents.append({
105
+ "source": filename,
106
+ "content": chunk
107
+ })
108
+
109
+ # Atualiza vetores TF-IDF
110
+ self._update_vectors()
111
 
112
+ return f"PDF carregado com sucesso: {filename} ({len(chunks)} segmentos)"
 
 
 
113
 
 
114
  except Exception as e:
115
  return f"Erro ao processar PDF: {str(e)}"
116
+
117
+ def _update_vectors(self):
118
+ """Atualiza os vetores TF-IDF para todos os documentos"""
119
+ if not self.documents:
120
+ self.vectors = None
121
+ return
122
 
123
+ texts = [doc["content"] for doc in self.documents]
124
+ self.vectors = self.vectorizer.fit_transform(texts)
125
+
126
  def search(self, query, top_k=3):
127
+ """Busca documentos relevantes para a query"""
128
+ if not self.documents or self.vectors is None:
129
  return []
130
 
131
+ # Vetoriza a query
132
+ query_vec = self.vectorizer.transform([query])
133
 
134
  # Calcula similaridade
135
+ similarity_scores = cosine_similarity(query_vec, self.vectors)[0]
136
 
137
+ # Encontra os top-k resultados
138
+ top_indices = similarity_scores.argsort()[-top_k:][::-1]
139
 
140
  results = []
141
+ for idx in top_indices:
142
  results.append({
143
+ "score": similarity_scores[idx],
144
  "document": self.documents[idx]
145
  })
146
 
147
  return results
148
 
149
+ # Instância do RAG
150
+ rag_engine = FreeRAG()
 
 
 
 
151
 
152
+ def generate_response(prompt, max_length=300):
153
+ """Gera uma resposta baseada no prompt usando o modelo carregado"""
 
 
 
 
 
 
 
 
154
  try:
155
+ # Limita o tamanho do prompt para evitar erros
156
+ if len(prompt) > 1024:
157
+ prompt = prompt[:1024]
158
+
159
+ # Gera a resposta
160
+ response = summarizer(
161
+ prompt,
162
+ max_length=max_length,
163
+ min_length=50,
164
+ do_sample=False
165
+ )[0]['summary_text']
166
+
167
+ return response
168
  except Exception as e:
169
+ return f"Erro ao gerar resposta: {str(e)}"
170
 
 
171
  def process_query(query, history):
172
+ """Processa uma consulta do usuário"""
173
+ # Verifica se há documentos carregados
174
+ if not rag_engine.documents:
175
+ return "Por favor, carregue alguns PDFs primeiro.", "Nenhum documento carregado."
176
 
177
+ # Busca documentos relevantes
178
+ results = rag_engine.search(query, top_k=3)
179
 
180
  # Formata o contexto
181
  context = ""
182
  for i, result in enumerate(results):
183
+ context += f"[{i+1}] Documento: {result['document']['source']}\n"
184
  context += f"Trecho: {result['document']['content'][:300]}...\n"
185
  context += f"Relevância: {result['score']:.2f}\n\n"
186
 
187
  # Constrói o prompt
188
+ prompt = f"""
189
+ Com base nos seguintes documentos, responda à pergunta de forma concisa e informativa.
190
+ Se a resposta não estiver nos documentos, diga que não informações suficientes.
191
 
192
+ DOCUMENTOS:
193
  {context}
194
 
195
+ PERGUNTA: {query}
196
+
197
+ RESPOSTA:
198
+ """
199
 
200
+ # Gera a resposta
201
+ response = generate_response(prompt)
202
 
203
+ return response, context
204
 
205
  # Interface Gradio
206
+ with gr.Blocks(title="RAG PDF Gratuito") as demo:
207
+ gr.Markdown("# Sistema de RAG PDF (Retrieval Augmented Generation)")
208
+ gr.Markdown("Carregue PDFs e faça perguntas sobre eles.")
209
+
210
+ with gr.Tab("Carregar PDFs"):
211
+ with gr.Row():
212
+ with gr.Column():
213
+ pdf_upload = gr.File(
214
+ label="Selecionar PDF",
215
+ file_types=[".pdf"],
216
+ file_count="single"
217
+ )
218
+ upload_btn = gr.Button("Carregar PDF")
219
+ clear_btn = gr.Button("Limpar Base de Conhecimento")
220
+ status = gr.Textbox(label="Status", interactive=False)
221
+
222
+ with gr.Tab("Consultar"):
223
+ with gr.Row():
224
+ with gr.Column(scale=2):
225
+ chatbot = gr.Chatbot(label="Conversa")
226
+ query = gr.Textbox(
227
+ label="Sua pergunta",
228
+ placeholder="Digite sua pergunta sobre os documentos..."
229
+ )
230
+ submit_btn = gr.Button("Enviar")
231
 
232
+ with gr.Column(scale=1):
233
+ context_box = gr.Textbox(
234
+ label="Contexto Recuperado",
235
+ interactive=False,
236
+ lines=15
237
+ )
238
+
239
+ # Callbacks
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
240
  def upload_pdf(file):
241
  if file is None:
242
  return "Nenhum arquivo selecionado."
243
+ return rag_engine.load_pdf(file)
244
 
245
  def clear_knowledge_base():
246
+ rag_engine.clear()
247
  return "Base de conhecimento limpa."
248
 
249
+ def handle_query(question, chat_history):
250
+ chat_history = chat_history or []
251
+
252
+ # Processa a consulta
253
+ answer, context = process_query(question, chat_history)
254
+
255
+ # Atualiza o histórico
256
+ chat_history.append((question, answer))
257
+
258
+ return chat_history, "", context
259
 
260
  # Eventos
261
+ upload_btn.click(
262
  upload_pdf,
263
  inputs=[pdf_upload],
264
+ outputs=[status]
265
  )
266
 
267
  clear_btn.click(
268
  clear_knowledge_base,
269
  inputs=[],
270
+ outputs=[status]
271
  )
272
 
273
+ submit_btn.click(
274
+ handle_query,
275
+ inputs=[query, chatbot],
276
+ outputs=[chatbot, query, context_box]
277
  )
278
 
279
+ query.submit(
280
+ handle_query,
281
+ inputs=[query, chatbot],
282
+ outputs=[chatbot, query, context_box]
283
  )
284
 
285
  if __name__ == "__main__":
286
+ # Inicializa a interface
287
  demo.launch()