Update app.py
Browse files
app.py
CHANGED
@@ -9,19 +9,48 @@ from nltk.tokenize import sent_tokenize
|
|
9 |
import fitz # PyMuPDF
|
10 |
import logging
|
11 |
from tqdm import tqdm
|
|
|
12 |
|
13 |
# Configurar logging
|
14 |
logging.basicConfig(level=logging.INFO)
|
15 |
logger = logging.getLogger(__name__)
|
16 |
|
17 |
-
|
18 |
-
|
19 |
-
|
20 |
-
|
21 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
22 |
|
23 |
class PDFQuestionAnswering:
|
24 |
def __init__(self):
|
|
|
|
|
|
|
|
|
25 |
# Usar modelo multilíngue mais avançado
|
26 |
self.model_name = "deepset/roberta-base-squad2"
|
27 |
try:
|
@@ -36,12 +65,45 @@ class PDFQuestionAnswering:
|
|
36 |
logger.error(f"Erro ao carregar o modelo: {e}")
|
37 |
raise
|
38 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
39 |
def extract_text_from_pdf(self, pdf_file: str) -> Tuple[str, Dict[int, str]]:
|
40 |
"""
|
41 |
-
Extrai texto do PDF
|
42 |
-
Retorna o texto completo e um dicionário mapeando números de página para texto
|
43 |
"""
|
44 |
try:
|
|
|
45 |
doc = fitz.open(pdf_file)
|
46 |
full_text = ""
|
47 |
page_text = {}
|
@@ -52,55 +114,75 @@ class PDFQuestionAnswering:
|
|
52 |
page_text[page_num] = text
|
53 |
full_text += text + "\n"
|
54 |
|
|
|
55 |
return full_text, page_text
|
|
|
56 |
except Exception as e:
|
57 |
-
logger.
|
58 |
-
|
59 |
-
|
60 |
-
|
61 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
62 |
|
63 |
def preprocess_text(self, text: str) -> str:
|
64 |
"""
|
65 |
Pré-processa o texto removendo caracteres especiais e formatação indesejada
|
66 |
"""
|
67 |
-
|
68 |
-
|
69 |
-
|
70 |
-
|
71 |
-
|
72 |
-
|
73 |
-
|
74 |
-
|
75 |
-
|
76 |
-
|
77 |
-
|
78 |
-
"""
|
79 |
-
sentences = sent_tokenize(text)
|
80 |
-
chunks = []
|
81 |
-
current_chunk = ""
|
82 |
-
|
83 |
-
for sentence in sentences:
|
84 |
-
if len(current_chunk) + len(sentence) < max_length:
|
85 |
-
current_chunk += sentence + " "
|
86 |
-
else:
|
87 |
-
chunks.append(current_chunk.strip())
|
88 |
-
current_chunk = sentence + " "
|
89 |
-
|
90 |
-
if current_chunk:
|
91 |
-
chunks.append(current_chunk.strip())
|
92 |
-
|
93 |
-
return chunks
|
94 |
|
95 |
def get_best_answer(self, question: str, chunks: List[str]) -> Dict:
|
96 |
"""
|
97 |
Obtém a melhor resposta considerando todos os chunks de texto
|
98 |
"""
|
99 |
try:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
100 |
answers = []
|
101 |
for chunk in chunks:
|
102 |
-
|
103 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
104 |
|
105 |
# Ordenar por score
|
106 |
best_answer = max(answers, key=lambda x: x['score'])
|
@@ -112,16 +194,36 @@ class PDFQuestionAnswering:
|
|
112 |
}
|
113 |
except Exception as e:
|
114 |
logger.error(f"Erro ao processar resposta: {e}")
|
115 |
-
return {
|
|
|
|
|
|
|
|
|
116 |
|
117 |
def answer_question(self, pdf_file: gr.File, question: str) -> Dict:
|
118 |
"""
|
119 |
Processa o PDF e responde à pergunta
|
120 |
"""
|
121 |
try:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
122 |
# Extrair texto do PDF
|
123 |
full_text, page_text = self.extract_text_from_pdf(pdf_file.name)
|
124 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
125 |
# Pré-processar texto
|
126 |
processed_text = self.preprocess_text(full_text)
|
127 |
|
@@ -202,4 +304,5 @@ def create_interface():
|
|
202 |
if __name__ == "__main__":
|
203 |
# Criar e iniciar a interface
|
204 |
demo = create_interface()
|
205 |
-
|
|
|
|
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:
|
|
|
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 = {}
|
|
|
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'])
|
|
|
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 |
|
|
|
304 |
if __name__ == "__main__":
|
305 |
# Criar e iniciar a interface
|
306 |
demo = create_interface()
|
307 |
+
# Desabilitar SSR para evitar problemas
|
308 |
+
demo.launch(share=False, debug=True, ssr=False)
|