DHEIVER commited on
Commit
be1ab03
·
verified ·
1 Parent(s): 37f3bb4

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +108 -83
app.py CHANGED
@@ -1,20 +1,16 @@
1
  import gradio as gr
2
  import torch
3
  from sentence_transformers import SentenceTransformer, util
4
- # Usaremos AutoTokenizer e AutoModelForCausalLM para o novo modelo
5
  from transformers import AutoTokenizer, AutoModelForCausalLM
6
  from pypdf import PdfReader
7
  import os
8
- import re # MÓDULO ADICIONADO para Expressões Regulares
9
 
10
  # --- 1. Carregamento dos Modelos ---
11
- # Modelo de recuperação não muda, ele é excelente para essa tarefa.
12
- print("Carregando o modelo de recuperação (Sentence Transformer)...")
13
  retriever_model = SentenceTransformer('all-MiniLM-L6-v2')
14
 
15
- # Carregando o modelo de geração DeepSeek
16
- print("Carregando o modelo de geração (DeepSeek)...")
17
- # Nota: "trust_remote_code=True" é necessário para carregar a arquitetura do DeepSeek
18
  generator_tokenizer = AutoTokenizer.from_pretrained(
19
  'deepseek-ai/deepseek-coder-1.3b-instruct',
20
  trust_remote_code=True
@@ -25,12 +21,14 @@ generator_model = AutoModelForCausalLM.from_pretrained(
25
  )
26
  print("Modelos carregados com sucesso!")
27
 
28
- # --- 2. Função para Processar Arquivos Enviados (COM CHUNKING ESTRUTURADO) ---
29
  def process_files(files):
30
  if not files:
31
- return None, "Por favor, envie um ou mais arquivos."
 
32
  knowledge_text = ""
33
  for file in files:
 
34
  file_path = file.name
35
  if file_path.endswith(".pdf"):
36
  try:
@@ -38,138 +36,165 @@ def process_files(files):
38
  for page in reader.pages:
39
  page_text = page.extract_text()
40
  if page_text:
41
- # Adiciona um espaço extra entre as páginas para garantir a separação
42
  knowledge_text += page_text + "\n\n"
43
  except Exception as e:
44
- return None, f"Erro ao ler o arquivo PDF {os.path.basename(file_path)}: {e}"
45
  elif file_path.endswith(".txt"):
46
  try:
47
  with open(file_path, 'r', encoding='utf-8') as f:
48
  knowledge_text += f.read() + "\n\n"
49
  except Exception as e:
50
- return None, f"Erro ao ler o arquivo TXT {os.path.basename(file_path)}: {e}"
51
 
52
  if not knowledge_text.strip():
53
- return None, "Não foi possível extrair texto dos arquivos fornecidos."
54
 
55
- # MUDANÇA PRINCIPAL: Extrator Estruturado usando Regex
56
- # Este padrão de regex divide o texto ANTES de uma linha que parece um cabeçalho de seção
57
- # (Ex: "1. CLIENTE", "8. PADRÕES"). Isso mantém a seção inteira em um único chunk.
58
- # O `(?m)` ativa o modo multiline, fazendo `^` corresponder ao início de cada linha.
59
  chunk_pattern = r"(?m)(^\d+\..*)"
60
-
61
- # Divide o texto em chunks usando o padrão e remove os vazios
62
  text_chunks = [chunk.strip() for chunk in re.split(chunk_pattern, knowledge_text) if chunk.strip()]
63
 
64
- # Reagrupa o cabeçalho com seu conteúdo
65
  structured_chunks = []
66
  i = 0
67
  while i < len(text_chunks):
68
  if re.match(chunk_pattern, text_chunks[i]) and i + 1 < len(text_chunks):
69
- # Junta o cabeçalho (ex: "1. CLIENTE") com o conteúdo seguinte
70
  structured_chunks.append(text_chunks[i] + "\n" + text_chunks[i+1])
71
  i += 2
72
  else:
73
- # Adiciona conteúdo que não corresponde a um cabeçalho
74
  structured_chunks.append(text_chunks[i])
75
  i += 1
76
 
77
  if not structured_chunks:
78
  return None, "O texto extraído não continha blocos de texto válidos para processamento."
79
 
80
- print(f"Processando {len(structured_chunks)} chunks estruturados dos arquivos...")
81
  knowledge_base_embeddings = retriever_model.encode(structured_chunks, convert_to_tensor=True, show_progress_bar=True)
82
- print("Base de conhecimento criada a partir dos arquivos.")
83
 
84
- return (structured_chunks, knowledge_base_embeddings), f"✅ Sucesso! {len(files)} arquivo(s) processado(s), gerando {len(structured_chunks)} chunks estruturados."
85
 
86
 
87
- # --- 3. A Função Principal do RAG (sem alterações) ---
88
- def answer_question(question, knowledge_state):
89
- if not question:
90
- return "Por favor, insira uma pergunta."
91
- if not knowledge_state or not knowledge_state[0] or knowledge_state[1] is None:
92
- return "⚠️ A base de conhecimento está vazia. Por favor, processe alguns arquivos primeiro."
93
-
94
  knowledge_base, knowledge_base_embeddings = knowledge_state
95
-
96
- # Etapa de Recuperação
97
  question_embedding = retriever_model.encode(question, convert_to_tensor=True)
98
  cosine_scores = util.cos_sim(question_embedding, knowledge_base_embeddings)
99
- # Aumentado para 7 para mais contexto
100
- top_k = min(7, len(knowledge_base))
101
  top_results = torch.topk(cosine_scores, k=top_k, dim=-1)
102
  retrieved_context = "\n---\n".join([knowledge_base[i] for i in top_results.indices[0]])
103
 
104
- if not retrieved_context.strip():
105
- return "Não foi possível encontrar um contexto relevante nos documentos para responder a esta pergunta."
 
 
 
 
 
 
 
 
 
 
 
 
 
 
106
 
107
- print(f"\n--- Nova Pergunta de Auditoria ---")
108
- print(f"Pergunta: {question}")
109
- print(f"Contexto Recuperado (Top {top_k}):\n{retrieved_context}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
110
 
111
- # Prompt com regras explícitas de extração de entidades
112
- prompt = f"""### Instruction:
113
- Você é um assistente de IA especialista em extrair informações de documentos técnicos. Analise o 'Contexto' para responder à 'Pergunta' seguindo estas regras rigorosamente:
114
 
115
- **Regras de Extração:**
116
- 1. **Use APENAS a informação do 'Contexto'.** Não adicione informações externas.
117
- 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.
118
- 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.
119
- 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.
120
 
121
- **Contexto:**
122
- {retrieved_context}
 
 
123
 
124
- **Pergunta:**
125
- {question}
 
126
 
127
- ### Response:
 
128
  """
129
 
130
- input_ids = generator_tokenizer(prompt, return_tensors="pt").input_ids
131
- input_length = input_ids.shape[1]
132
-
133
- # Ajuste nos parâmetros de geração
134
- outputs = generator_model.generate(
135
- input_ids,
136
- # Aumentado para permitir respostas mais detalhadas
137
- max_new_tokens=350,
138
- do_sample=False,
139
- eos_token_id=generator_tokenizer.eos_token_id,
140
- pad_token_id=generator_tokenizer.eos_token_id
141
- )
142
 
143
- # Decodificação correta para modelos Causal LM
144
- generated_tokens = outputs[0, input_length:]
145
- answer = generator_tokenizer.decode(generated_tokens, skip_special_tokens=True)
146
-
147
- return answer
148
 
149
- # --- 4. Interface Gráfica (sem alterações na estrutura) ---
150
  with gr.Blocks(theme=gr.themes.Soft()) as interface:
151
  knowledge_state = gr.State()
152
  gr.Markdown(
153
  """
154
- # 🤖 RAG - Auditor de Documentos (v9 - Chunking Estruturado)
155
- **1. Carregue seus arquivos**: Envie um ou mais certificados ou documentos nos formatos `.pdf` ou `.txt`.
156
- **2. Processe os arquivos**: Clique no botão para criar a base de conhecimento.
157
- **3. Faça perguntas**: Após o processamento, faça perguntas sobre o conteúdo dos documentos.
158
  """
159
  )
 
160
  with gr.Row():
161
  with gr.Column(scale=1):
162
- file_uploader = gr.File(label="Carregar Certificados (.pdf, .txt)", file_count="multiple", file_types=[".pdf", ".txt"])
163
- process_button = gr.Button("Processar Arquivos", variant="primary")
164
  status_box = gr.Textbox(label="Status do Processamento", interactive=False)
 
165
  with gr.Column(scale=2):
166
- question_box = gr.Textbox(label="Faça sua pergunta aqui", placeholder="Ex: Qual o resultado da calibração do instrumento PI-101?")
167
- submit_button = gr.Button("Obter Resposta", variant="primary")
168
- answer_box = gr.Textbox(label="Resposta Baseada nos Documentos", interactive=False, lines=5)
 
 
 
 
 
169
 
170
  process_button.click(fn=process_files, inputs=[file_uploader], outputs=[knowledge_state, status_box])
171
- submit_button.click(fn=answer_question, inputs=[question_box, knowledge_state], outputs=[answer_box])
172
 
173
- # --- 5. Lançamento do App ---
174
  if __name__ == "__main__":
175
  interface.launch()
 
1
  import gradio as gr
2
  import torch
3
  from sentence_transformers import SentenceTransformer, util
 
4
  from transformers import AutoTokenizer, AutoModelForCausalLM
5
  from pypdf import PdfReader
6
  import os
7
+ import re
8
 
9
  # --- 1. Carregamento dos Modelos ---
10
+ print("A carregar o modelo de recuperação (Sentence Transformer)...")
 
11
  retriever_model = SentenceTransformer('all-MiniLM-L6-v2')
12
 
13
+ print("A carregar o modelo de geração (DeepSeek)...")
 
 
14
  generator_tokenizer = AutoTokenizer.from_pretrained(
15
  'deepseek-ai/deepseek-coder-1.3b-instruct',
16
  trust_remote_code=True
 
21
  )
22
  print("Modelos carregados com sucesso!")
23
 
24
+ # --- 2. Função de Processamento de Ficheiros (Chunking Estruturado) ---
25
  def process_files(files):
26
  if not files:
27
+ return None, "Por favor, envie um ou mais ficheiros."
28
+
29
  knowledge_text = ""
30
  for file in files:
31
+ # ... (código de extração de texto de PDF/TXT permanece o mesmo) ...
32
  file_path = file.name
33
  if file_path.endswith(".pdf"):
34
  try:
 
36
  for page in reader.pages:
37
  page_text = page.extract_text()
38
  if page_text:
 
39
  knowledge_text += page_text + "\n\n"
40
  except Exception as e:
41
+ return None, f"Erro ao ler o ficheiro PDF {os.path.basename(file_path)}: {e}"
42
  elif file_path.endswith(".txt"):
43
  try:
44
  with open(file_path, 'r', encoding='utf-8') as f:
45
  knowledge_text += f.read() + "\n\n"
46
  except Exception as e:
47
+ return None, f"Erro ao ler o ficheiro TXT {os.path.basename(file_path)}: {e}"
48
 
49
  if not knowledge_text.strip():
50
+ return None, "Não foi possível extrair texto dos ficheiros fornecidos."
51
 
 
 
 
 
52
  chunk_pattern = r"(?m)(^\d+\..*)"
 
 
53
  text_chunks = [chunk.strip() for chunk in re.split(chunk_pattern, knowledge_text) if chunk.strip()]
54
 
 
55
  structured_chunks = []
56
  i = 0
57
  while i < len(text_chunks):
58
  if re.match(chunk_pattern, text_chunks[i]) and i + 1 < len(text_chunks):
 
59
  structured_chunks.append(text_chunks[i] + "\n" + text_chunks[i+1])
60
  i += 2
61
  else:
 
62
  structured_chunks.append(text_chunks[i])
63
  i += 1
64
 
65
  if not structured_chunks:
66
  return None, "O texto extraído não continha blocos de texto válidos para processamento."
67
 
68
+ print(f"A processar {len(structured_chunks)} chunks estruturados dos ficheiros...")
69
  knowledge_base_embeddings = retriever_model.encode(structured_chunks, convert_to_tensor=True, show_progress_bar=True)
70
+ print("Base de conhecimento criada a partir dos ficheiros.")
71
 
72
+ return (structured_chunks, knowledge_base_embeddings), f"✅ Sucesso! {len(files)} ficheiro(s) processado(s), gerando {len(structured_chunks)} chunks estruturados."
73
 
74
 
75
+ # --- 3. A FERRAMENTA PRINCIPAL DO AGENTE: find_info ---
76
+ def find_info(question, knowledge_state):
77
+ """Esta função atua como a 'ferramenta de busca' do agente. Ela não dá a resposta final,
78
+ apenas extrai a informação bruta pedida."""
 
 
 
79
  knowledge_base, knowledge_base_embeddings = knowledge_state
80
+
 
81
  question_embedding = retriever_model.encode(question, convert_to_tensor=True)
82
  cosine_scores = util.cos_sim(question_embedding, knowledge_base_embeddings)
83
+ top_k = min(5, len(knowledge_base)) # Reduzido para ser mais focado
 
84
  top_results = torch.topk(cosine_scores, k=top_k, dim=-1)
85
  retrieved_context = "\n---\n".join([knowledge_base[i] for i in top_results.indices[0]])
86
 
87
+ prompt = f"### Instruction:\nExtraia a informação exata para responder à pergunta com base no contexto. Seja direto.\n\nContexto:\n{retrieved_context}\n\nPergunta:\n{question}\n\n### Response:"
88
+
89
+ input_ids = generator_tokenizer(prompt, return_tensors="pt").input_ids
90
+ outputs = generator_model.generate(input_ids, max_new_tokens=100, do_sample=False, pad_token_id=generator_tokenizer.eos_token_id)
91
+ answer = generator_tokenizer.decode(outputs[0, input_ids.shape[1]:], skip_special_tokens=True)
92
+ return answer.strip()
93
+
94
+
95
+ # --- 4. O CÉREBRO DO AGENTE: evaluate_document ---
96
+ def evaluate_document(task, knowledge_state):
97
+ """Esta é a função principal do agente. Ela orquestra as chamadas à ferramenta 'find_info'
98
+ para construir um relatório de avaliação completo."""
99
+ if not task:
100
+ return "Por favor, forneça uma tarefa de avaliação.", ""
101
+ if not knowledge_state or not knowledge_state[0] or knowledge_state[1] is None:
102
+ return "⚠️ A base de conhecimento está vazia. Por favor, processe alguns ficheiros primeiro.", ""
103
 
104
+ thought_process = "Iniciando a avaliação do documento...\n"
105
+
106
+ # Passo 1: Identificação
107
+ thought_process += "Passo 1: A identificar o cliente e o instrumento...\n"
108
+ client_info = find_info("Qual o nome do cliente ou contratante?", knowledge_state)
109
+ instrument_info = find_info("Qual é a descrição, TAG ou modelo do instrumento calibrado?", knowledge_state)
110
+ thought_process += f" - Cliente Encontrado: {client_info}\n"
111
+ thought_process += f" - Instrumento Encontrado: {instrument_info}\n\n"
112
+
113
+ # Passo 2: Resultado e Incerteza
114
+ thought_process += "Passo 2: A verificar o resultado da calibração e a incerteza...\n"
115
+ result_info = find_info("Qual foi o resultado final da calibração (ex: Aprovado, Reprovado)?", knowledge_state)
116
+ uncertainty_info = find_info("Qual é a incerteza de medição reportada?", knowledge_state)
117
+ thought_process += f" - Resultado: {result_info}\n"
118
+ thought_process += f" - Incerteza: {uncertainty_info}\n\n"
119
+
120
+ # Passo 3: Validade
121
+ thought_process += "Passo 3: A verificar as datas...\n"
122
+ calibration_date = find_info("Qual foi a data em que a calibração foi realizada?", knowledge_state)
123
+ due_date = find_info("Qual a data de vencimento ou próxima calibração?", knowledge_state)
124
+ thought_process += f" - Data da Calibração: {calibration_date}\n"
125
+ thought_process += f" - Data de Vencimento: {due_date}\n\n"
126
+
127
+ # Passo 4: Geração do Relatório Final
128
+ thought_process += "Passo 4: A compilar o relatório final...\n"
129
+ final_prompt = f"""### Instruction:
130
+ Você é um auditor de metrologia a escrever um relatório de avaliação. Com base nos 'Dados Recolhidos' abaixo, escreva um parecer técnico conciso e estruturado.
131
+
132
+ **Dados Recolhidos:**
133
+ - Cliente: {client_info}
134
+ - Instrumento: {instrument_info}
135
+ - Resultado da Calibração: {result_info}
136
+ - Incerteza de Medição: {uncertainty_info}
137
+ - Data da Execução: {calibration_date}
138
+ - Próxima Calibração: {due_date}
139
 
140
+ ### Response:
141
+ **Relatório de Avaliação do Certificado**
 
142
 
143
+ **1. Identificação:**
144
+ - **Cliente:** [Preencha com o cliente]
145
+ - **Instrumento:** [Preencha com o instrumento]
 
 
146
 
147
+ **2. Análise Técnica:**
148
+ - **Resultado:** [Preencha com o resultado]
149
+ - **Incerteza:** [Preencha com a incerteza]
150
+ - **Conformidade:** [Comente brevemente se o resultado 'Aprovado' é consistente com os dados]
151
 
152
+ **3. Validade:**
153
+ - **Data da Calibração:** [Preencha com a data]
154
+ - **Validade:** [Preencha com a data de vencimento]
155
 
156
+ **4. Parecer Final:**
157
+ - [Escreva uma frase de conclusão sobre a validade e aceitabilidade do certificado com base nos dados.]
158
  """
159
 
160
+ input_ids = generator_tokenizer(final_prompt, return_tensors="pt").input_ids
161
+ outputs = generator_model.generate(input_ids, max_new_tokens=400, do_sample=False, pad_token_id=generator_tokenizer.eos_token_id)
162
+ final_report = generator_tokenizer.decode(outputs[0, input_ids.shape[1]:], skip_special_tokens=True)
 
 
 
 
 
 
 
 
 
163
 
164
+ thought_process += "Avaliação concluída."
165
+ return thought_process, final_report
 
 
 
166
 
167
+ # --- 5. Interface Gráfica (Atualizada para o Agente) ---
168
  with gr.Blocks(theme=gr.themes.Soft()) as interface:
169
  knowledge_state = gr.State()
170
  gr.Markdown(
171
  """
172
+ # 🤖 Agente de Avaliação de Documentos de Metrologia
173
+ **1. Carregue um documento**: Envie um certificado de calibração em `.pdf` ou `.txt`.
174
+ **2. Processe o documento**: Clique no botão para criar a base de conhecimento.
175
+ **3. Inicie a Avaliação**: uma tarefa ao agente (ex: "Avalie este certificado") e clique em "Iniciar Avaliação".
176
  """
177
  )
178
+
179
  with gr.Row():
180
  with gr.Column(scale=1):
181
+ file_uploader = gr.File(label="Carregar Documento (.pdf, .txt)", file_count="multiple", file_types=[".pdf", ".txt"])
182
+ process_button = gr.Button("Processar Documento", variant="primary")
183
  status_box = gr.Textbox(label="Status do Processamento", interactive=False)
184
+
185
  with gr.Column(scale=2):
186
+ task_box = gr.Textbox(label="Tarefa de Avaliação", placeholder='Ex: Avalie este certificado de calibração.')
187
+ submit_button = gr.Button("Iniciar Avaliação", variant="primary")
188
+
189
+ with gr.Row():
190
+ with gr.Column():
191
+ thought_box = gr.Textbox(label="Passos do Agente", interactive=False, lines=15)
192
+ with gr.Column():
193
+ report_box = gr.Textbox(label="Relatório Final de Avaliação", interactive=False, lines=15)
194
 
195
  process_button.click(fn=process_files, inputs=[file_uploader], outputs=[knowledge_state, status_box])
196
+ submit_button.click(fn=evaluate_document, inputs=[task_box, knowledge_state], outputs=[thought_box, report_box])
197
 
198
+ # --- 6. Lançamento do App ---
199
  if __name__ == "__main__":
200
  interface.launch()