Spaces:
Sleeping
Sleeping
File size: 8,380 Bytes
121fc9e 5558e3e 6050f7a 1e45ac8 37f3bb4 121fc9e 6050f7a 121fc9e 6050f7a 5558e3e 6050f7a 121fc9e 37f3bb4 1e45ac8 37f3bb4 1e45ac8 37f3bb4 1e45ac8 37f3bb4 1e45ac8 37f3bb4 1e45ac8 37f3bb4 1e45ac8 37f3bb4 1e45ac8 5558e3e 121fc9e 957e316 5558e3e 9962ed4 957e316 121fc9e e0164c8 bf0034d 121fc9e 957e316 121fc9e 37f3bb4 6050f7a 5558e3e 121fc9e e0164c8 6050f7a 121fc9e 6050f7a 957e316 6050f7a 121fc9e 6050f7a e0164c8 121fc9e 5558e3e 9962ed4 e0164c8 6050f7a e0164c8 121fc9e e0164c8 6050f7a 121fc9e 957e316 1e45ac8 37f3bb4 1e45ac8 957e316 1e45ac8 957e316 1e45ac8 121fc9e bf0034d |
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 |
import gradio as gr
import torch
from sentence_transformers import SentenceTransformer, util
# Usaremos AutoTokenizer e AutoModelForCausalLM para o novo modelo
from transformers import AutoTokenizer, AutoModelForCausalLM
from pypdf import PdfReader
import os
import re # MÓDULO ADICIONADO para Expressões Regulares
# --- 1. Carregamento dos Modelos ---
# Modelo de recuperação não muda, ele é excelente para essa tarefa.
print("Carregando o modelo de recuperação (Sentence Transformer)...")
retriever_model = SentenceTransformer('all-MiniLM-L6-v2')
# Carregando o modelo de geração DeepSeek
print("Carregando o modelo de geração (DeepSeek)...")
# Nota: "trust_remote_code=True" é necessário para carregar a arquitetura do DeepSeek
generator_tokenizer = AutoTokenizer.from_pretrained(
'deepseek-ai/deepseek-coder-1.3b-instruct',
trust_remote_code=True
)
generator_model = AutoModelForCausalLM.from_pretrained(
'deepseek-ai/deepseek-coder-1.3b-instruct',
trust_remote_code=True
)
print("Modelos carregados com sucesso!")
# --- 2. Função para Processar Arquivos Enviados (COM CHUNKING ESTRUTURADO) ---
def process_files(files):
if not files:
return None, "Por favor, envie um ou mais arquivos."
knowledge_text = ""
for file in files:
file_path = file.name
if file_path.endswith(".pdf"):
try:
reader = PdfReader(file_path)
for page in reader.pages:
page_text = page.extract_text()
if page_text:
# Adiciona um espaço extra entre as páginas para garantir a separação
knowledge_text += page_text + "\n\n"
except Exception as e:
return None, f"Erro ao ler o arquivo PDF {os.path.basename(file_path)}: {e}"
elif file_path.endswith(".txt"):
try:
with open(file_path, 'r', encoding='utf-8') as f:
knowledge_text += f.read() + "\n\n"
except Exception as e:
return None, f"Erro ao ler o arquivo TXT {os.path.basename(file_path)}: {e}"
if not knowledge_text.strip():
return None, "Não foi possível extrair texto dos arquivos fornecidos."
# MUDANÇA PRINCIPAL: Extrator Estruturado usando Regex
# Este padrão de regex divide o texto ANTES de uma linha que parece um cabeçalho de seção
# (Ex: "1. CLIENTE", "8. PADRÕES"). Isso mantém a seção inteira em um único chunk.
# O `(?m)` ativa o modo multiline, fazendo `^` corresponder ao início de cada linha.
chunk_pattern = r"(?m)(^\d+\..*)"
# Divide o texto em chunks usando o padrão e remove os vazios
text_chunks = [chunk.strip() for chunk in re.split(chunk_pattern, knowledge_text) if chunk.strip()]
# Reagrupa o cabeçalho com seu conteúdo
structured_chunks = []
i = 0
while i < len(text_chunks):
if re.match(chunk_pattern, text_chunks[i]) and i + 1 < len(text_chunks):
# Junta o cabeçalho (ex: "1. CLIENTE") com o conteúdo seguinte
structured_chunks.append(text_chunks[i] + "\n" + text_chunks[i+1])
i += 2
else:
# Adiciona conteúdo que não corresponde a um cabeçalho
structured_chunks.append(text_chunks[i])
i += 1
if not structured_chunks:
return None, "O texto extraído não continha blocos de texto válidos para processamento."
print(f"Processando {len(structured_chunks)} chunks estruturados dos arquivos...")
knowledge_base_embeddings = retriever_model.encode(structured_chunks, convert_to_tensor=True, show_progress_bar=True)
print("Base de conhecimento criada a partir dos arquivos.")
return (structured_chunks, knowledge_base_embeddings), f"✅ Sucesso! {len(files)} arquivo(s) processado(s), gerando {len(structured_chunks)} chunks estruturados."
# --- 3. A Função Principal do RAG (sem alterações) ---
def answer_question(question, knowledge_state):
if not question:
return "Por favor, insira uma pergunta."
if not knowledge_state or not knowledge_state[0] or knowledge_state[1] is None:
return "⚠️ A base de conhecimento está vazia. Por favor, processe alguns arquivos primeiro."
knowledge_base, knowledge_base_embeddings = knowledge_state
# Etapa de Recuperação
question_embedding = retriever_model.encode(question, convert_to_tensor=True)
cosine_scores = util.cos_sim(question_embedding, knowledge_base_embeddings)
# Aumentado para 7 para mais contexto
top_k = min(7, len(knowledge_base))
top_results = torch.topk(cosine_scores, k=top_k, dim=-1)
retrieved_context = "\n---\n".join([knowledge_base[i] for i in top_results.indices[0]])
if not retrieved_context.strip():
return "Não foi possível encontrar um contexto relevante nos documentos para responder a esta pergunta."
print(f"\n--- Nova Pergunta de Auditoria ---")
print(f"Pergunta: {question}")
print(f"Contexto Recuperado (Top {top_k}):\n{retrieved_context}")
# Prompt com regras explícitas de extração de entidades
prompt = f"""### Instruction:
Você é um assistente de IA especialista em extrair informações de documentos técnicos. Analise o 'Contexto' para responder à 'Pergunta' seguindo estas regras rigorosamente:
**Regras de Extração:**
1. **Use APENAS a informação do 'Contexto'.** Não adicione informações externas.
2. **Para perguntas sobre 'cliente':** Procure por linhas que comecem com "Cliente:", "Contratante:", ou um nome de empresa claro. Ignore frases genéricas sobre escopo ou lotes.
3. **Para perguntas sobre 'instrumento':** Procure por linhas que comecem com "Instrumento:", "Descrição:", "Objeto:", "TAG:", ou um modelo específico. Ignore descrições de métodos de calibração.
4. **Para perguntas sobre 'título' ou 'resumo':** Resuma os dados principais, como o tipo de documento (ex: Certificado de Calibração), o nome do cliente e o instrumento calibrado.
**Contexto:**
{retrieved_context}
**Pergunta:**
{question}
### Response:
"""
input_ids = generator_tokenizer(prompt, return_tensors="pt").input_ids
input_length = input_ids.shape[1]
# Ajuste nos parâmetros de geração
outputs = generator_model.generate(
input_ids,
# Aumentado para permitir respostas mais detalhadas
max_new_tokens=350,
do_sample=False,
eos_token_id=generator_tokenizer.eos_token_id,
pad_token_id=generator_tokenizer.eos_token_id
)
# Decodificação correta para modelos Causal LM
generated_tokens = outputs[0, input_length:]
answer = generator_tokenizer.decode(generated_tokens, skip_special_tokens=True)
return answer
# --- 4. Interface Gráfica (sem alterações na estrutura) ---
with gr.Blocks(theme=gr.themes.Soft()) as interface:
knowledge_state = gr.State()
gr.Markdown(
"""
# 🤖 RAG - Auditor de Documentos (v9 - Chunking Estruturado)
**1. Carregue seus arquivos**: Envie um ou mais certificados ou documentos nos formatos `.pdf` ou `.txt`.
**2. Processe os arquivos**: Clique no botão para criar a base de conhecimento.
**3. Faça perguntas**: Após o processamento, faça perguntas sobre o conteúdo dos documentos.
"""
)
with gr.Row():
with gr.Column(scale=1):
file_uploader = gr.File(label="Carregar Certificados (.pdf, .txt)", file_count="multiple", file_types=[".pdf", ".txt"])
process_button = gr.Button("Processar Arquivos", variant="primary")
status_box = gr.Textbox(label="Status do Processamento", interactive=False)
with gr.Column(scale=2):
question_box = gr.Textbox(label="Faça sua pergunta aqui", placeholder="Ex: Qual o resultado da calibração do instrumento PI-101?")
submit_button = gr.Button("Obter Resposta", variant="primary")
answer_box = gr.Textbox(label="Resposta Baseada nos Documentos", interactive=False, lines=5)
process_button.click(fn=process_files, inputs=[file_uploader], outputs=[knowledge_state, status_box])
submit_button.click(fn=answer_question, inputs=[question_box, knowledge_state], outputs=[answer_box])
# --- 5. Lançamento do App ---
if __name__ == "__main__":
interface.launch()
|