Spaces:
Running
Running
File size: 15,886 Bytes
e998be0 ec4a946 e998be0 95baff4 9c70697 95baff4 4360e04 e998be0 a2074bf 0d322e4 a2074bf 95baff4 e998be0 7ceeb49 ec4a946 e998be0 7ceeb49 e998be0 7ceeb49 e998be0 7ceeb49 e998be0 7ceeb49 e998be0 4360e04 e998be0 4360e04 9c70697 4360e04 e998be0 95baff4 4360e04 9c70697 0d322e4 95baff4 32d9fa7 95baff4 e998be0 4360e04 32d9fa7 4360e04 32d9fa7 e998be0 95baff4 e998be0 95baff4 0d322e4 95baff4 e998be0 95baff4 e998be0 4360e04 577fcf1 64597df 53b56be 64597df 53b56be 64597df fc7ec2a a2074bf 2b4bebf eef235b fc7ec2a 5991a4f b4139b2 8ee16a5 fc7ec2a 0d322e4 ce1d11e 64597df 0e7ecc2 33f14fa 0e7ecc2 33f14fa 0e7ecc2 33f14fa 4360e04 e998be0 0e7ecc2 e998be0 0e7ecc2 e998be0 4360e04 a68e0ce 4360e04 e998be0 64597df 0e7ecc2 33f14fa 64597df e998be0 64597df e998be0 64597df e998be0 64597df 0e7ecc2 e998be0 64597df e998be0 4360e04 252b190 4360e04 7f95e3b 4360e04 252b190 4360e04 e998be0 64597df a68e0ce 0e7ecc2 24fc9d0 0e7ecc2 a68e0ce 64597df e998be0 64597df 24fc9d0 64597df e998be0 64597df 7638942 24fc9d0 e998be0 64597df e998be0 4360e04 a68e0ce e998be0 4360e04 9c70697 4360e04 252b190 4360e04 e998be0 252b190 577fcf1 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 |
import gradio as gr
import pymupdf # PyMuPDF
import pandas as pd
from pydantic_ai import Agent
from pydantic_ai.settings import ModelSettings
from pydantic import BaseModel
from typing import List, Optional
import google.generativeai as genai
import openai
import os
from dotenv import load_dotenv
import io
import json
import re
# Padrões globais de regex para extração de referências
REFERENCE_PATTERNS = [
# Padrão 0: Referências numeradas com autores múltiplos (formato: Número. Autores. Título. Journal info (ano).)
r'^\d+\.\s*([A-Z][A-Za-z\s,&.-]+?(?:\s&\s[A-Z][A-Za-z\s,&.-]+?)*)\.\s*([^.]+?)\.\s*([^.]+?)\s+(\d+),?\s*([^(]*?)\s*\((\d{4})\)',
# Padrão 1: Autor(es). (Ano). Título. Journal/Editora.
r'^([A-Z][A-Za-z\s,&.-]+?)\.\s*\((\d{4}[a-z]?)\)\.\s*([^.]+?)\.\s*([^.]+?)\.?\s*$',
# Padrão 2: Referências numeradas [1] Autor... ano Título. Journal doi:...
r'^\[\d+\]\s*([A-Z][A-Za-z\s,&.-]+?)\s+(\d{4})\s+([^.]+?)\.\s*([^.]+?)(?:\s+doi:([^\s.]+))?\.?\s*$',
# Padrão 3: Autor, A. (Ano). Título. Journal.
r'^([A-Z][A-Za-z\s,&.-]+?)\s+\((\d{4}[a-z]?)\)[.,]\s*([^.]+?)[.,]\s*([^.]+?)\.?\s*$',
# Padrão 4: Autor et al. (Ano) Título. Journal
r'^([A-Z][A-Za-z\s,&.-]*?et\s+al\.?)\s*\((\d{4}[a-z]?)\)[.,]?\s*([^.]+?)[.,]\s*([^.]+?)\.?\s*$',
# Padrão 5: Sobrenome, Nome (Ano). Título. Journal.
r'^([A-Z][a-z]+,\s*[A-Z][A-Za-z\s,&.-]*?)\.\s*\((\d{4}[a-z]?)\)\.\s*([^.]+?)\.\s*([^.]+?)\.?\s*$',
# Padrão 6: Múltiplos autores com &
r'^([A-Z][A-Za-z\s,&.-]+?&[A-Za-z\s,&.-]+?)\.\s*\((\d{4}[a-z]?)\)\.\s*([^.]+?)\.\s*([^.]+?)\.?\s*$',
# Padrão 7: Referências numeradas [número] Autor: Título, Editora (ano)
r'^\[\d+\]\s*([A-Z][A-Za-z\s,&.-]+?):\s*([^,]+?),\s*([^(]+?)\s*\((\d{4})\)',
# Padrão 8: Referências numeradas com DOI opcional
r"""
^ # início de linha (após possível marcador de ordem)
(?:\d+\.\s*)? # número da referência (opcional), seguido de ponto e espaço
(?P<autores> # grupo 'autores'
[^\.]+? # tudo antes do primeiro ponto final (não guloso)
)\.\s+
(?P<titulo> # grupo 'titulo'
[^\n\.]+ # até o próximo ponto final ou quebra de linha
)\.
\s*
(?P<journal> # grupo 'journal'
[^\n;]+ # até o próximo ponto e vírgula (ou quebra de linha)
)
[;,]?\s*
(?P<ano> # grupo 'ano'
\d{4} # 4 dígitos (ano)
)
(?:;[^\n]*?)? # volume, issue, páginas (opcional, não capturado)
(?:\n+ # nova linha(s), captura DOI opcional
(?P<doi> https?://doi\.org/[^\s]+ )
)? # DOI pode estar na linha de baixo ou ausente
"""
]
class Reference(BaseModel):
authors: List[str]
title: str
journal: Optional[str] = None
year: Optional[int] = None
volume: Optional[str] = None
pages: Optional[str] = None
doi: Optional[str] = None
class ReferencesResponse(BaseModel):
references: List[Reference]
def extract_pdf_text(pdf_file):
"""Extrai texto e metadados básicos do PDF"""
try:
# Abrir o PDF com PyMuPDF
doc = pymupdf.open(stream=pdf_file, filetype="pdf")
# Extrair texto de todas as páginas
full_text = ""
for page_num in range(len(doc)):
page = doc.load_page(page_num)
full_text += page.get_text() + "\n"
# Extrair metadados básicos
metadata_dict = doc.metadata
metadata = {
"num_pages": len(doc),
"title": metadata_dict.get('title', 'Não disponível') if metadata_dict.get('title') else 'Não disponível',
"author": metadata_dict.get('author', 'Não disponível') if metadata_dict.get('author') else 'Não disponível',
"subject": metadata_dict.get('subject', 'Não disponível') if metadata_dict.get('subject') else 'Não disponível',
"creator": metadata_dict.get('creator', 'Não disponível') if metadata_dict.get('creator') else 'Não disponível'
}
# Fechar o documento
doc.close()
return full_text, metadata
except Exception as e:
return None, {"error": f"Erro ao processar PDF: {str(e)}"}
def extract_references_with_llm(text, model_name):
"""Usa Pydantic AI com diferentes modelos para extrair e estruturar referências"""
try:
# Determinar se é modelo Google ou OpenAI
if model_name.startswith('gemini'):
# Configurar a API key do Google
genai.configure(api_key=os.getenv("GEMINI_API_KEY"))
api_key = os.getenv("GEMINI_API_KEY")
else:
# Usar OpenAI
api_key = os.getenv("OPENAI_API_KEY")
if not api_key:
return [{"error": f"Chave da API não encontrada para o modelo {model_name}"}]
# Criar o agente Pydantic AI
agent = Agent(
model_name,
model_settings=ModelSettings(
timeout=30,
),
output_type=ReferencesResponse,
system_prompt="""
Você é um especialista em análise de artigos científicos.
Sua tarefa é identificar e extrair APENAS a seção de referências bibliográficas do texto fornecido.
Para cada referência encontrada, extraia:
- authors: lista completa de autores
- title: título completo do trabalho
- journal: nome da revista/conferência/editora
- year: ano de publicação
- volume: volume (se disponível)
- pages: páginas (se disponível)
- doi: DOI (se disponível)
Seja preciso e extraia referências completas.
"""
)
# Ajustar limite de texto baseado no modelo
if model_name.startswith('gemini'):
limited_text = text[:1500000] # Gemini tem limite maior
else:
limited_text = text[:500000] # OpenAI tem limite menor
# Executar o agente
result = agent.run_sync(f"Extraia as referências bibliográficas do seguinte texto de artigo científico:\n\n{limited_text}")
# Converter para lista de dicionários para compatibilidade com DataFrame
references_list = []
for ref in result.output.references:
references_list.append({
"authors": ", ".join(ref.authors) if ref.authors else "",
"title": ref.title,
"journal": ref.journal or "",
"year": ref.year or "",
"volume": ref.volume or "",
"pages": ref.pages or "",
"doi": ref.doi or ""
})
return references_list
except Exception as e:
return [{"error": f"Erro ao processar com LLM ({model_name}): {str(e)}"}]
def extract_references_with_regex(text):
"""Extrai referências usando expressões regulares em todo o texto"""
try:
references = []
# Processar cada padrão
for pattern_index, pattern in enumerate(REFERENCE_PATTERNS):
reflist = re.findall(pattern, text, re.MULTILINE | re.UNICODE | re.DOTALL| re.VERBOSE)
if reflist:
for ref_match in reflist:
groups = ref_match
if len(groups) >= 4:
authors = groups[0].strip()
# Para o padrão numerado especial (6 grupos)
if len(groups) == 6:
title = groups[1].strip()
journal = groups[2].strip()
volume = groups[3].strip()
pages = groups[4].strip()
year = groups[5].strip()
# Para o padrão 7 (formato [número] Autor: Título, Editora (ano))
elif pattern_index == 7:
title = groups[1].strip()
journal = groups[2].strip()
year = groups[3].strip()
volume = ""
else:
# Para outros padrões (4 grupos)
year = groups[1].strip()
title = groups[2].strip()
journal = groups[3].strip()
volume = ""
# Extrair DOI se presente
doi_match = re.search(r'doi[:\s]*([^\s,]+)', journal, re.IGNORECASE)
doi = doi_match.group(1) if doi_match else ""
# Extrair volume e páginas (se não foram extraídos pelo padrão especial)
if len(groups) != 6:
vol_pages_match = re.search(r'(\d+)\s*\(?\d*\)?\s*[,:]\s*(\d+[-–]\d+)', journal)
volume = vol_pages_match.group(1) if vol_pages_match else ""
pages = vol_pages_match.group(2) if vol_pages_match else ""
else:
# Para o padrão numerado, extrair páginas do journal
pages_match = re.search(r'(\d+[-–]\d+)', journal)
pages = pages_match.group(1) if pages_match else ""
# Limpar campos
authors = re.sub(r'\s+', ' ', authors)
title = re.sub(r'\s+', ' ', title)
journal = re.sub(r'\s+', ' ', journal)
reference = {
"authors": authors,
"title": title,
"journal": journal,
"year": year,
"volume": volume,
"pages": pages,
"doi": doi
}
references.append(reference)
return references
except Exception as e:
return [{"error": f"Erro na extração por regex: {str(e)}"}]
def create_plain_text(text, regex_references):
"""Retorna o texto extraído como texto simples"""
try:
return text
except Exception as e:
return f"Erro ao processar texto: {str(e)}"
def process_pdf(pdf_file, model_name):
"""Função principal que processa o PDF e retorna resultados"""
if pdf_file is None:
return {"error": "Nenhum arquivo enviado"}, pd.DataFrame(), pd.DataFrame(), "❌ Nenhum arquivo enviado", "Nenhum texto para exibir"
# Extrair texto do PDF
text, metadata = extract_pdf_text(pdf_file)
if text is None:
return metadata, pd.DataFrame(), pd.DataFrame(), "❌ Erro ao processar PDF", "Erro ao extrair texto"
# Adicionar modelo selecionado aos metadados
metadata["modelo_usado"] = model_name
metadata["caracteres_extraidos"] = len(text)
metadata["palavras_aproximadas"] = len(text.split())
# Extrair referências com LLM
llm_references = extract_references_with_llm(text, model_name)
# Extrair referências com Regex
regex_references = extract_references_with_regex(text)
# Criar texto simples
plain_text = create_plain_text(text, regex_references)
# Converter para DataFrames
if llm_references and not any("error" in ref for ref in llm_references):
llm_df = pd.DataFrame(llm_references)
else:
llm_df = pd.DataFrame({"Erro": ["Não foi possível extrair referências com LLM"]})
if regex_references and not any("error" in ref for ref in regex_references):
regex_df = pd.DataFrame(regex_references)
else:
regex_df = pd.DataFrame({"Erro": ["Não foi possível extrair referências com Regex"]})
# Criar status
llm_count = len(llm_references) if llm_references and not any("error" in ref for ref in llm_references) else 0
regex_count = len(regex_references) if regex_references and not any("error" in ref for ref in regex_references) else 0
status = f"📊 **Resultados da Extração:**\n- LLM ({model_name}): {llm_count} referências\n- Regex: {regex_count} referências"
return metadata, llm_df, regex_df, status, plain_text
def create_interface():
"""Cria a interface Gradio"""
with gr.Blocks(title="Extrator de Referências") as interface:
gr.Markdown("# 📚 Extrator de Referências de Artigos Científicos")
gr.Markdown("Faça upload de um PDF de artigo científico para extrair automaticamente a lista de referências usando IA e expressões regulares.")
with gr.Row():
with gr.Column():
pdf_input = gr.File(
label="📄 Upload do PDF",
file_types=[".pdf"],
type="binary"
)
with gr.Column():
model_dropdown = gr.Dropdown(
choices=[
"gemini-2.5-flash-lite",
"gemini-2.5-pro",
"gemini-2.5-flash",
"gpt-4o",
"gpt-o3-mini",
"gpt-4.1"
],
value="gemini-2.5-flash-lite",
label="🤖 Modelo de IA",
info="Selecione o modelo para extrair as referências"
)
extract_btn = gr.Button("🔍 Extrair Referências", variant="primary")
with gr.Row():
with gr.Column():
metadata_output = gr.JSON(label="📋 Metadados do Artigo")
with gr.Column():
extracted_text_output = gr.Textbox(
label="📄 Texto Extraído",
lines=20,
max_lines=20,
buttons=['copy'],
interactive=False
)
with gr.Row():
with gr.Column():
llm_references_output = gr.Dataframe(
label="🤖 Referências Extraídas por IA",
row_count=(10,'dynamic'),
buttons=['copy', 'fullscreen'],
wrap=True
)
with gr.Column():
regex_references_output = gr.Dataframe(
label="🔍 Referências Extraídas por Regex",
row_count=(10,'dynamic'),
buttons=['copy', 'fullscreen'],
wrap=True
)
status_output = gr.Markdown(label="📊 Status da Extração")
extract_btn.click(
process_pdf,
inputs=[pdf_input, model_dropdown],
outputs=[metadata_output, llm_references_output, regex_references_output, status_output, extracted_text_output]
)
return interface
def main():
load_dotenv() # Carrega variáveis de ambiente do arquivo .env
# Verificar se as chaves das APIs estão configuradas
google_key = os.getenv("GEMINI_API_KEY")
openai_key = os.getenv("OPENAI_API_KEY")
if not google_key and not openai_key:
print("⚠️ AVISO: Nenhuma chave de API encontrada!")
print("Configure pelo menos uma das seguintes no arquivo .env:")
print("- GEMINI_API_KEY=sua_chave_do_google")
print("- OPENAI_API_KEY=sua_chave_da_openai")
elif not google_key:
print("ℹ️ Apenas OpenAI configurado. Modelos Gemini não funcionarão.")
elif not openai_key:
print("ℹ️ Apenas Google configurado. Modelos OpenAI não funcionarão.")
interface = create_interface()
interface.launch(share=False)
if __name__ == "__main__":
main()
|