DHEIVER commited on
Commit
1c52417
·
verified ·
1 Parent(s): 92209cf

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +371 -172
app.py CHANGED
@@ -3,316 +3,515 @@ from transformers import AutoModelForQuestionAnswering, AutoTokenizer, pipeline
3
  import PyPDF2
4
  import torch
5
  import re
6
- from typing import List, Dict, Tuple
7
  import nltk
8
  from nltk.tokenize import sent_tokenize
9
  import fitz # PyMuPDF
10
  import logging
11
  from tqdm import tqdm
12
  import os
 
 
 
 
 
 
 
 
13
 
14
- # Configurar logging
15
- logging.basicConfig(level=logging.INFO)
 
 
 
 
 
 
 
16
  logger = logging.getLogger(__name__)
17
 
18
- class NLTKDownloader:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
19
  @staticmethod
20
- def download_nltk_resources():
21
- """
22
- Download recursos NLTK necessários e configura o diretório de dados
23
- """
24
  try:
25
- # Configurar diretório de dados NLTK
26
- nltk_data_dir = os.path.join(os.path.expanduser("~"), "nltk_data")
27
- if not os.path.exists(nltk_data_dir):
28
- os.makedirs(nltk_data_dir)
29
-
30
- nltk.data.path.append(nltk_data_dir)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
31
 
32
- # Lista de recursos necessários
33
- resources = ['punkt']
 
 
 
 
 
 
 
 
34
 
35
- for resource in resources:
36
  try:
37
  nltk.data.find(f'tokenizers/{resource}')
38
- logger.info(f"Recurso NLTK '{resource}' já está instalado")
39
  except LookupError:
40
  logger.info(f"Baixando recurso NLTK '{resource}'...")
41
- nltk.download(resource, download_dir=nltk_data_dir, quiet=True)
42
-
43
- return True
44
- except Exception as e:
45
- logger.error(f"Erro ao baixar recursos NLTK: {e}")
46
- return False
47
-
48
- class PDFQuestionAnswering:
49
- def __init__(self):
50
- # Inicializar recursos NLTK
51
- if not NLTKDownloader.download_nltk_resources():
52
- logger.warning("Alguns recursos NLTK podem não estar disponíveis")
53
-
54
- # Usar modelo multilíngue mais avançado
55
- self.model_name = "deepset/roberta-base-squad2"
56
- try:
57
- self.tokenizer = AutoTokenizer.from_pretrained(self.model_name)
58
- self.model = AutoModelForQuestionAnswering.from_pretrained(self.model_name)
59
- self.nlp = pipeline('question-answering',
60
- model=self.model,
61
- tokenizer=self.tokenizer,
62
- device=0 if torch.cuda.is_available() else -1)
63
- logger.info(f"Modelo {self.model_name} carregado com sucesso")
64
  except Exception as e:
65
- logger.error(f"Erro ao carregar o modelo: {e}")
66
  raise
67
 
68
- def split_text_simple(self, text: str, max_length: int = 512) -> List[str]:
69
- """
70
- Método alternativo de divisão de texto caso o NLTK não esteja disponível
71
- """
72
- words = text.split()
73
- chunks = []
74
- current_chunk = []
75
- current_length = 0
76
-
77
- for word in words:
78
- if current_length + len(word) + 1 <= max_length:
79
- current_chunk.append(word)
80
- current_length += len(word) + 1
81
- else:
82
- chunks.append(' '.join(current_chunk))
83
- current_chunk = [word]
84
- current_length = len(word) + 1
85
-
86
- if current_chunk:
87
- chunks.append(' '.join(current_chunk))
88
-
89
- return chunks
90
 
91
- def split_into_chunks(self, text: str, max_length: int = 512) -> List[str]:
92
- """
93
- Divide o texto em chunks menores, com fallback para método simples
94
- """
95
- try:
96
- return [chunk for chunk in self.split_text_simple(text, max_length)]
97
- except Exception as e:
98
- logger.warning(f"Erro ao dividir texto com NLTK: {e}. Usando método simples.")
99
- return self.split_text_simple(text, max_length)
100
 
101
- def extract_text_from_pdf(self, pdf_file: str) -> Tuple[str, Dict[int, str]]:
102
- """
103
- Extrai texto do PDF com fallback para PyPDF2
104
- """
105
  try:
106
- # Tentar primeiro com PyMuPDF
107
- doc = fitz.open(pdf_file)
108
  full_text = ""
109
  page_text = {}
110
 
111
  for page_num in range(len(doc)):
112
  page = doc[page_num]
113
  text = page.get_text("text")
114
- page_text[page_num] = text
115
- full_text += text + "\n"
116
-
117
- doc.close()
118
  return full_text, page_text
119
-
120
  except Exception as e:
121
- logger.warning(f"Erro com PyMuPDF: {e}. Tentando PyPDF2...")
122
- try:
123
- # Fallback para PyPDF2
124
- with open(pdf_file, "rb") as file:
125
- reader = PyPDF2.PdfReader(file)
126
- full_text = ""
127
- page_text = {}
128
-
129
- for i, page in enumerate(reader.pages):
130
- text = page.extract_text()
 
 
 
 
 
 
 
131
  page_text[i] = text
132
  full_text += text + "\n"
133
 
134
  return full_text, page_text
135
-
 
 
 
 
 
 
 
 
 
 
 
 
 
136
  except Exception as e2:
137
- logger.error(f"Erro ao extrair texto do PDF: {e2}")
138
  raise
139
 
 
 
 
 
 
 
 
 
140
  def preprocess_text(self, text: str) -> str:
141
- """
142
- Pré-processa o texto removendo caracteres especiais e formatação indesejada
143
- """
144
  try:
 
 
145
  # Remover quebras de linha extras
146
  text = re.sub(r'\n+', ' ', text)
147
  # Remover espaços múltiplos
148
  text = re.sub(r'\s+', ' ', text)
149
- # Remover caracteres especiais mas manter acentos
150
- text = re.sub(r'[^\w\s.,!?-áéíóúâêîôûãõçà]', '', text)
 
151
  return text.strip()
152
  except Exception as e:
153
  logger.warning(f"Erro no pré-processamento: {e}")
154
- return text # Retorna texto original em caso de erro
155
 
156
- def get_best_answer(self, question: str, chunks: List[str]) -> Dict:
157
- """
158
- Obtém a melhor resposta considerando todos os chunks de texto
159
- """
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
160
  try:
161
  if not chunks:
162
  return {
163
- 'answer': "Não foi possível processar o texto do documento.",
164
  'score': 0,
165
- 'context': ""
 
 
166
  }
167
-
168
  answers = []
169
- for chunk in chunks:
170
- if not chunk.strip():
171
- continue
172
-
173
- try:
174
- result = self.nlp(question=question, context=chunk)
175
- answers.append(result)
176
- except Exception as e:
177
- logger.warning(f"Erro ao processar chunk: {e}")
178
- continue
179
 
180
  if not answers:
181
  return {
182
- 'answer': "Não foi possível encontrar uma resposta no documento.",
183
  'score': 0,
184
- 'context': ""
 
 
185
  }
186
-
187
- # Ordenar por score
188
  best_answer = max(answers, key=lambda x: x['score'])
189
 
190
  return {
191
  'answer': best_answer['answer'],
192
  'score': best_answer['score'],
193
- 'context': best_answer['context']
 
 
194
  }
195
  except Exception as e:
196
  logger.error(f"Erro ao processar resposta: {e}")
197
  return {
198
- 'answer': "Ocorreu um erro ao processar sua pergunta.",
199
  'score': 0,
200
- 'context': ""
 
 
201
  }
202
 
203
- def answer_question(self, pdf_file: gr.File, question: str) -> Dict:
204
- """
205
- Processa o PDF e responde à pergunta
206
- """
 
 
 
 
 
 
207
  try:
208
  if not pdf_file or not question:
209
  return {
210
  'answer': "Por favor, forneça um arquivo PDF e uma pergunta.",
211
  'score': 0,
212
  'confidence': "0%",
213
- 'context': ""
 
214
  }
215
-
216
  # Extrair texto do PDF
217
- full_text, page_text = self.extract_text_from_pdf(pdf_file.name)
218
 
219
  if not full_text.strip():
220
  return {
221
- 'answer': "Não foi possível extrair texto do PDF fornecido.",
222
  'score': 0,
223
  'confidence': "0%",
224
- 'context': ""
 
225
  }
226
-
227
  # Pré-processar texto
228
- processed_text = self.preprocess_text(full_text)
229
 
230
  # Dividir em chunks
231
- chunks = self.split_into_chunks(processed_text)
232
 
233
  # Obter melhor resposta
234
- result = self.get_best_answer(question, chunks)
235
 
236
- # Adicionar informações extras
237
- result['confidence'] = f"{result['score']*100:.2f}%"
238
 
239
  return result
 
240
  except Exception as e:
241
  logger.error(f"Erro ao processar pergunta: {e}")
242
  return {
243
  'answer': "Ocorreu um erro ao processar sua pergunta.",
244
  'score': 0,
245
  'confidence': "0%",
246
- 'context': ""
 
247
  }
248
 
249
- def create_interface():
 
250
  qa_system = PDFQuestionAnswering()
251
 
252
- # Interface mais elaborada com Gradio
253
  with gr.Blocks(title="Sistema Avançado de QA sobre PDFs") as iface:
254
- gr.Markdown("""
255
- # Sistema de Perguntas e Respostas sobre PDFs
256
-
257
- Este sistema utiliza um modelo de linguagem avançado para responder perguntas sobre documentos PDF.
258
- Carregue um PDF e faça suas perguntas!
259
- """)
260
 
261
  with gr.Row():
262
  with gr.Column():
263
  pdf_input = gr.File(
264
  label="Carregar PDF",
265
- file_types=[".pdf"]
 
266
  )
267
  question_input = gr.Textbox(
268
  label="Sua Pergunta",
269
- placeholder="Digite sua pergunta aqui..."
 
270
  )
271
  submit_btn = gr.Button("Obter Resposta", variant="primary")
272
 
273
  with gr.Column():
274
- answer_output = gr.Textbox(label="Resposta")
275
- confidence_output = gr.Textbox(label="Confiança da Resposta")
 
 
 
 
 
276
  context_output = gr.Textbox(
277
  label="Contexto da Resposta",
278
  lines=5
279
  )
 
 
 
 
280
 
281
  def process_question(pdf, question):
282
  result = qa_system.answer_question(pdf, question)
283
  return (
284
  result['answer'],
285
  result['confidence'],
286
- result['context']
 
287
  )
288
 
289
  submit_btn.click(
290
  fn=process_question,
291
  inputs=[pdf_input, question_input],
292
- outputs=[answer_output, confidence_output, context_output]
293
  )
294
 
295
  gr.Markdown("""
296
- ### Dicas de Uso
297
- - Faça perguntas específicas e diretas
298
- - O sistema funciona melhor com PDFs bem formatados
299
- - A confiança indica o quanto o sistema está seguro da resposta
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
300
  """)
301
 
302
  return iface
303
 
304
- if __name__ == "__main__":
305
- # Criar e iniciar a interface
306
- demo = create_interface()
307
  try:
308
- # Tentar lançar com configurações padrão
309
- demo.launch(
310
- share=False,
311
- debug=True,
312
- server_name="0.0.0.0", # Permite acesso de qualquer IP
313
- server_port=7860 # Porta padrão do Gradio
 
 
314
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
315
  except Exception as e:
316
- logger.error(f"Erro ao iniciar interface: {e}")
317
- # Tentar fallback com menos opções
318
- demo.launch()
 
 
 
3
  import PyPDF2
4
  import torch
5
  import re
6
+ from typing import List, Dict, Tuple, Optional, Union
7
  import nltk
8
  from nltk.tokenize import sent_tokenize
9
  import fitz # PyMuPDF
10
  import logging
11
  from tqdm import tqdm
12
  import os
13
+ import tempfile
14
+ from pathlib import Path
15
+ import shutil
16
+ import gc
17
+ import threading
18
+ from concurrent.futures import ThreadPoolExecutor
19
+ import warnings
20
+ from typing_extensions import TypedDict
21
 
22
+ # Configurar logging avançado
23
+ logging.basicConfig(
24
+ level=logging.INFO,
25
+ format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
26
+ handlers=[
27
+ logging.StreamHandler(),
28
+ logging.FileHandler('qa_system.log')
29
+ ]
30
+ )
31
  logger = logging.getLogger(__name__)
32
 
33
+ # Suprimir warnings desnecessários
34
+ warnings.filterwarnings('ignore', category=UserWarning, module='gradio')
35
+
36
+ class QAResult(TypedDict):
37
+ answer: str
38
+ score: float
39
+ confidence: str
40
+ context: str
41
+ page_number: Optional[int]
42
+
43
+ class ResourceManager:
44
+ """Gerencia recursos do sistema e limpeza de memória"""
45
+
46
+ @staticmethod
47
+ def clear_gpu_memory():
48
+ """Limpa memória GPU se disponível"""
49
+ if torch.cuda.is_available():
50
+ torch.cuda.empty_cache()
51
+ gc.collect()
52
+
53
+ @staticmethod
54
+ def create_temp_directory() -> Path:
55
+ """Cria diretório temporário para arquivos"""
56
+ temp_dir = Path(tempfile.mkdtemp())
57
+ return temp_dir
58
+
59
  @staticmethod
60
+ def cleanup_temp_directory(temp_dir: Path):
61
+ """Remove diretório temporário e seus arquivos"""
 
 
62
  try:
63
+ shutil.rmtree(temp_dir)
64
+ except Exception as e:
65
+ logger.warning(f"Erro ao limpar diretório temporário: {e}")
66
+
67
+ class NLTKManager:
68
+ """Gerencia recursos NLTK"""
69
+
70
+ _instance = None
71
+ _lock = threading.Lock()
72
+
73
+ def __new__(cls):
74
+ with cls._lock:
75
+ if cls._instance is None:
76
+ cls._instance = super().__new__(cls)
77
+ cls._instance._initialized = False
78
+ return cls._instance
79
+
80
+ def __init__(self):
81
+ if self._initialized:
82
+ return
83
 
84
+ self._initialized = True
85
+ self.nltk_data_dir = Path.home() / "nltk_data"
86
+ self.required_resources = ['punkt']
87
+ self.download_resources()
88
+
89
+ def download_resources(self):
90
+ """Download e verifica recursos NLTK necessários"""
91
+ try:
92
+ self.nltk_data_dir.mkdir(parents=True, exist_ok=True)
93
+ nltk.data.path.append(str(self.nltk_data_dir))
94
 
95
+ for resource in self.required_resources:
96
  try:
97
  nltk.data.find(f'tokenizers/{resource}')
98
+ logger.debug(f"Recurso NLTK '{resource}' já instalado")
99
  except LookupError:
100
  logger.info(f"Baixando recurso NLTK '{resource}'...")
101
+ nltk.download(resource, download_dir=str(self.nltk_data_dir), quiet=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
102
  except Exception as e:
103
+ logger.error(f"Erro ao configurar NLTK: {e}")
104
  raise
105
 
106
+ class PDFExtractor:
107
+ """Classe especializada em extração de texto de PDFs"""
108
+
109
+ def __init__(self):
110
+ self.temp_dir = ResourceManager.create_temp_directory()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
111
 
112
+ def __del__(self):
113
+ ResourceManager.cleanup_temp_directory(self.temp_dir)
 
 
 
 
 
 
 
114
 
115
+ def extract_with_pymupdf(self, pdf_path: str) -> Tuple[str, Dict[int, str]]:
116
+ """Extrai texto usando PyMuPDF"""
117
+ doc = None
 
118
  try:
119
+ doc = fitz.open(pdf_path)
 
120
  full_text = ""
121
  page_text = {}
122
 
123
  for page_num in range(len(doc)):
124
  page = doc[page_num]
125
  text = page.get_text("text")
126
+ if text.strip(): # Ignorar páginas vazias
127
+ page_text[page_num] = text
128
+ full_text += text + "\n"
129
+
130
  return full_text, page_text
 
131
  except Exception as e:
132
+ logger.error(f"Erro PyMuPDF: {e}")
133
+ raise
134
+ finally:
135
+ if doc:
136
+ doc.close()
137
+
138
+ def extract_with_pypdf2(self, pdf_path: str) -> Tuple[str, Dict[int, str]]:
139
+ """Extrai texto usando PyPDF2 como fallback"""
140
+ try:
141
+ with open(pdf_path, "rb") as file:
142
+ reader = PyPDF2.PdfReader(file)
143
+ full_text = ""
144
+ page_text = {}
145
+
146
+ for i, page in enumerate(reader.pages):
147
+ text = page.extract_text()
148
+ if text.strip(): # Ignorar páginas vazias
149
  page_text[i] = text
150
  full_text += text + "\n"
151
 
152
  return full_text, page_text
153
+ except Exception as e:
154
+ logger.error(f"Erro PyPDF2: {e}")
155
+ raise
156
+
157
+ def extract_text(self, pdf_file: Union[str, Path]) -> Tuple[str, Dict[int, str]]:
158
+ """Extrai texto do PDF com fallback"""
159
+ pdf_path = str(pdf_file)
160
+
161
+ try:
162
+ return self.extract_with_pymupdf(pdf_path)
163
+ except Exception as e1:
164
+ logger.warning(f"Falha PyMuPDF, tentando PyPDF2: {e1}")
165
+ try:
166
+ return self.extract_with_pypdf2(pdf_path)
167
  except Exception as e2:
168
+ logger.error(f"Falha total na extração: {e2}")
169
  raise
170
 
171
+ class TextProcessor:
172
+ """Processa e prepara texto para análise"""
173
+
174
+ def __init__(self):
175
+ self.nltk_manager = NLTKManager()
176
+ self.max_chunk_size = 512
177
+ self.overlap = 50
178
+
179
  def preprocess_text(self, text: str) -> str:
180
+ """Pré-processa texto removendo formatação problemática"""
 
 
181
  try:
182
+ # Normalizar quebras de linha
183
+ text = re.sub(r'\r\n', '\n', text)
184
  # Remover quebras de linha extras
185
  text = re.sub(r'\n+', ' ', text)
186
  # Remover espaços múltiplos
187
  text = re.sub(r'\s+', ' ', text)
188
+ # Remover caracteres especiais mantendo pontuação importante
189
+ text = re.sub(r'[^\w\s.,!?;:()[\]{}\-–—""''´`^~#$%&*+=@/\\áéíóúâêîôûãõçàèìòùäëïöüÁÉÍÓÚÂÊÎÔÛÃÕÇÀÈÌÒÙÄËÏÖÜ]',
190
+ ' ', text)
191
  return text.strip()
192
  except Exception as e:
193
  logger.warning(f"Erro no pré-processamento: {e}")
194
+ return text
195
 
196
+ def split_into_chunks(self, text: str) -> List[str]:
197
+ """Divide texto em chunks com sobreposição"""
198
+ try:
199
+ sentences = sent_tokenize(text)
200
+ chunks = []
201
+ current_chunk = []
202
+ current_length = 0
203
+
204
+ for sentence in sentences:
205
+ sentence_length = len(sentence)
206
+
207
+ if current_length + sentence_length <= self.max_chunk_size:
208
+ current_chunk.append(sentence)
209
+ current_length += sentence_length
210
+ else:
211
+ if current_chunk:
212
+ chunks.append(' '.join(current_chunk))
213
+ current_chunk = [sentence]
214
+ current_length = sentence_length
215
+
216
+ if current_chunk:
217
+ chunks.append(' '.join(current_chunk))
218
+
219
+ # Adicionar sobreposição
220
+ overlapped_chunks = []
221
+ for i in range(len(chunks)):
222
+ if i > 0:
223
+ prefix = chunks[i-1][-self.overlap:]
224
+ chunks[i] = prefix + ' ' + chunks[i]
225
+ overlapped_chunks.append(chunks[i])
226
+
227
+ return overlapped_chunks
228
+ except Exception as e:
229
+ logger.error(f"Erro ao dividir texto: {e}")
230
+ return [text]
231
+
232
+ class ModelManager:
233
+ """Gerencia o modelo de IA e processamento de perguntas"""
234
+
235
+ def __init__(self):
236
+ self.model_name = "deepset/roberta-base-squad2"
237
+ self.device = 0 if torch.cuda.is_available() else -1
238
+ self.load_model()
239
+
240
+ def load_model(self):
241
+ """Carrega o modelo com tratamento de erros"""
242
+ try:
243
+ self.tokenizer = AutoTokenizer.from_pretrained(self.model_name)
244
+ self.model = AutoModelForQuestionAnswering.from_pretrained(self.model_name)
245
+ self.nlp = pipeline('question-answering',
246
+ model=self.model,
247
+ tokenizer=self.tokenizer,
248
+ device=self.device)
249
+ logger.info(f"Modelo {self.model_name} carregado com sucesso")
250
+ except Exception as e:
251
+ logger.error(f"Erro ao carregar modelo: {e}")
252
+ raise
253
+
254
+ def get_answer(self, question: str, context: str) -> Dict:
255
+ """Processa uma única pergunta/contexto"""
256
+ try:
257
+ return self.nlp(question=question, context=context)
258
+ except Exception as e:
259
+ logger.error(f"Erro ao processar resposta: {e}")
260
+ return {
261
+ 'answer': '',
262
+ 'score': 0,
263
+ 'start': 0,
264
+ 'end': 0
265
+ }
266
+
267
+ def get_best_answer(self, question: str, chunks: List[str]) -> QAResult:
268
+ """Obtém a melhor resposta de múltiplos chunks"""
269
  try:
270
  if not chunks:
271
  return {
272
+ 'answer': "Não foi possível processar o documento.",
273
  'score': 0,
274
+ 'confidence': "0%",
275
+ 'context': "",
276
+ 'page_number': None
277
  }
278
+
279
  answers = []
280
+ with ThreadPoolExecutor() as executor:
281
+ futures = [executor.submit(self.get_answer, question, chunk)
282
+ for chunk in chunks]
283
+ answers = [future.result() for future in futures]
284
+
285
+ # Filtrar respostas vazias
286
+ answers = [ans for ans in answers if ans['answer'].strip()]
 
 
 
287
 
288
  if not answers:
289
  return {
290
+ 'answer': "Não foi possível encontrar uma resposta.",
291
  'score': 0,
292
+ 'confidence': "0%",
293
+ 'context': "",
294
+ 'page_number': None
295
  }
296
+
 
297
  best_answer = max(answers, key=lambda x: x['score'])
298
 
299
  return {
300
  'answer': best_answer['answer'],
301
  'score': best_answer['score'],
302
+ 'confidence': f"{best_answer['score']*100:.2f}%",
303
+ 'context': best_answer.get('context', ""),
304
+ 'page_number': None # TODO: Implementar rastreamento de página
305
  }
306
  except Exception as e:
307
  logger.error(f"Erro ao processar resposta: {e}")
308
  return {
309
+ 'answer': "Erro ao processar a pergunta.",
310
  'score': 0,
311
+ 'confidence': "0%",
312
+ 'context': "",
313
+ 'page_number': None
314
  }
315
 
316
+ class PDFQuestionAnswering:
317
+ """Classe principal que coordena o sistema de QA"""
318
+
319
+ def __init__(self):
320
+ self.pdf_extractor = PDFExtractor()
321
+ self.text_processor = TextProcessor()
322
+ self.model_manager = ModelManager()
323
+
324
+ def answer_question(self, pdf_file: gr.File, question: str) -> QAResult:
325
+ """Processa PDF e responde pergunta"""
326
  try:
327
  if not pdf_file or not question:
328
  return {
329
  'answer': "Por favor, forneça um arquivo PDF e uma pergunta.",
330
  'score': 0,
331
  'confidence': "0%",
332
+ 'context': "",
333
+ 'page_number': None
334
  }
335
+
336
  # Extrair texto do PDF
337
+ full_text, page_text = self.pdf_extractor.extract_text(pdf_file.name)
338
 
339
  if not full_text.strip():
340
  return {
341
+ 'answer': "Não foi possível extrair texto do PDF.",
342
  'score': 0,
343
  'confidence': "0%",
344
+ 'context': "",
345
+ 'page_number': None
346
  }
347
+
348
  # Pré-processar texto
349
+ processed_text = self.text_processor.preprocess_text(full_text)
350
 
351
  # Dividir em chunks
352
+ chunks = self.text_processor.split_into_chunks(processed_text)
353
 
354
  # Obter melhor resposta
355
+ result = self.model_manager.get_best_answer(question, chunks)
356
 
357
+ # Limpar GPU se necessário
358
+ ResourceManager.clear_gpu_memory()
359
 
360
  return result
361
+
362
  except Exception as e:
363
  logger.error(f"Erro ao processar pergunta: {e}")
364
  return {
365
  'answer': "Ocorreu um erro ao processar sua pergunta.",
366
  'score': 0,
367
  'confidence': "0%",
368
+ 'context': "",
369
+ 'page_number': None
370
  }
371
 
372
+ def create_interface() -> gr.Blocks:
373
+ """Cria interface Gradio"""
374
  qa_system = PDFQuestionAnswering()
375
 
 
376
  with gr.Blocks(title="Sistema Avançado de QA sobre PDFs") as iface:
377
+ # Interface HTML/Markdown aqui...
 
 
 
 
 
378
 
379
  with gr.Row():
380
  with gr.Column():
381
  pdf_input = gr.File(
382
  label="Carregar PDF",
383
+ file_types=[".pdf"],
384
+ type="file"
385
  )
386
  question_input = gr.Textbox(
387
  label="Sua Pergunta",
388
+ placeholder="Digite sua pergunta aqui...",
389
+ lines=2
390
  )
391
  submit_btn = gr.Button("Obter Resposta", variant="primary")
392
 
393
  with gr.Column():
394
+ answer_output = gr.Textbox(
395
+ label="Resposta",
396
+ lines=3
397
+ )
398
+ confidence_output = gr.Textbox(
399
+ label="Confiança da Resposta"
400
+ )
401
  context_output = gr.Textbox(
402
  label="Contexto da Resposta",
403
  lines=5
404
  )
405
+ page_output = gr.Textbox(
406
+ label="Página da Resposta",
407
+ visible=False # TODO: Implementar
408
+ )
409
 
410
  def process_question(pdf, question):
411
  result = qa_system.answer_question(pdf, question)
412
  return (
413
  result['answer'],
414
  result['confidence'],
415
+ result['context'],
416
+ f"Página {result['page_number']}" if result['page_number'] else "Página não disponível"
417
  )
418
 
419
  submit_btn.click(
420
  fn=process_question,
421
  inputs=[pdf_input, question_input],
422
+ outputs=[answer_output, confidence_output, context_output, page_output]
423
  )
424
 
425
  gr.Markdown("""
426
+ ### Dicas para Perguntas sobre Metrologia e Normas
427
+
428
+ #### Documentos Recomendados:
429
+ - Normas técnicas (ABNT, ISO, etc.)
430
+ - Procedimentos de calibração
431
+ - Manuais de qualidade
432
+ - Relatórios técnicos
433
+ - Documentos do sistema de gestão
434
+
435
+ #### Tipos de Perguntas:
436
+
437
+ **Metrologia e Calibração:**
438
+ - "Quais são os requisitos de calibração para [instrumento]?"
439
+ - "Qual é a incerteza de medição especificada para [processo]?"
440
+ - "Como é feita a rastreabilidade metrológica?"
441
+ - "Quais são os intervalos de calibração recomendados?"
442
+ - "Que condições ambientais são especificadas?"
443
+
444
+ **Normas e Requisitos:**
445
+ - "Quais são os requisitos para [processo específico]?"
446
+ - "Como a norma define [termo técnico]?"
447
+ - "Quais são os critérios de aceitação?"
448
+ - "Que documentação é exigida?"
449
+ - "Quais são as referências normativas?"
450
+
451
+ **Sistema de Gestão:**
452
+ - "Como é feito o controle de qualidade?"
453
+ - "Quais são os requisitos de competência?"
454
+ - "Como são tratadas as não conformidades?"
455
+ - "Que registros são obrigatórios?"
456
+ - "Como é feita a validação?"
457
+
458
+ #### Boas Práticas:
459
+ 1. Use termos técnicos precisos
460
+ 2. Referencie seções específicas
461
+ 3. Pergunte sobre um tópico por vez
462
+ 4. Verifique o nível de confiança
463
+ 5. Confirme informações críticas
464
+
465
+ #### Interpretação:
466
+ - Confiança > 90%: Alta probabilidade de resposta correta
467
+ - Confiança 70-90%: Resposta provável, verificar contexto
468
+ - Confiança < 70%: Considerar reformular a pergunta
469
  """)
470
 
471
  return iface
472
 
473
+ def main():
474
+ """Função principal"""
 
475
  try:
476
+ # Configurar logging
477
+ logging.basicConfig(
478
+ level=logging.INFO,
479
+ format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
480
+ handlers=[
481
+ logging.StreamHandler(),
482
+ logging.FileHandler('qa_system.log')
483
+ ]
484
  )
485
+
486
+ # Criar e iniciar interface
487
+ demo = create_interface()
488
+
489
+ # Configurações de lançamento
490
+ launch_kwargs = {
491
+ 'server_name': "0.0.0.0", # Permite acesso externo
492
+ 'server_port': 7860, # Porta padrão
493
+ 'share': False, # Não criar túnel público
494
+ 'debug': True, # Modo debug ativado
495
+ 'show_error': True, # Mostrar erros detalhados
496
+ 'enable_queue': True # Habilitar fila de requisições
497
+ }
498
+
499
+ # Tentar lançar com configurações completas
500
+ try:
501
+ demo.launch(**launch_kwargs)
502
+ except TypeError as e:
503
+ # Se algum parâmetro não for suportado, remover e tentar novamente
504
+ logger.warning(f"Erro no lançamento completo: {e}")
505
+ minimal_kwargs = {
506
+ 'server_name': "0.0.0.0",
507
+ 'server_port': 7860,
508
+ 'share': False
509
+ }
510
+ demo.launch(**minimal_kwargs)
511
+
512
  except Exception as e:
513
+ logger.error(f"Erro fatal: {e}")
514
+ raise
515
+
516
+ if __name__ == "__main__":
517
+ main()