caarleexx commited on
Commit
ffdd9b4
·
verified ·
1 Parent(s): 30be354

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +47 -58
app.py CHANGED
@@ -1,5 +1,5 @@
1
  # ╔════════════════════════════════════════════════════════════════════════════╗
2
- # ║ PIPELINE v28: ANÁLISE EPISTÊMICA (COM LÓGICA DE FRAGMENTAÇÃO)
3
  # ║ Layout: Chat (Aba 1) | Debug (Aba 2) | Config (Aba 3) ║
4
  # ╚════════════════════════════════════════════════════════════════════════════╝
5
 
@@ -10,7 +10,7 @@ import time
10
  from datetime import datetime
11
  import gradio as gr
12
  import google.generativeai as genai
13
- ##mport PyMuPDF as fitz # <-- BIBLIOTECA NECESSÁRIA
14
 
15
  # ==================== 1. CONFIGURAÇÃO ====================
16
  api_key = os.getenv("GOOGLE_API_KEY", "SUA_API_KEY_AQUI")
@@ -19,7 +19,7 @@ if api_key: genai.configure(api_key=api_key)
19
  model_flash = genai.GenerativeModel("gemini-flash-latest")
20
  model_pro = genai.GenerativeModel("gemini-pro-latest")
21
 
22
- # **ATUALIZAÇÃO: Usando o novo nome de arquivo de configuração**
23
  ARQUIVO_CONFIG = "protocolo_epistemico_forense.json"
24
 
25
  # ==================== 2. UTILIDADES ====================
@@ -32,34 +32,48 @@ def carregar_protocolo():
32
  def salvar_protocolo(conteudo):
33
  try:
34
  json.loads(conteudo)
35
- with open(ARQUIVO_CONFIG, "w", encoding="utf-8") as f: f.write(conteudo)
36
- # O nome do arquivo salvo DEVE ser o ARQUIVO_CONFIG, mas o Gradio usa o label, vamos manter o nome.
37
- # with open("protocolo.json", "w", encoding="utf-8") as f: f.write(conteudo)
38
  return "✅ Salvo"
39
  except: return "❌ Erro JSON"
40
 
41
- # **NOVA FUNÇÃO: Prepara fragmentos de PDF (Placeholder para a lógica de fragmentação)**
42
- def ler_anexo_e_fragmentar(arquivo):
43
- """Lê o anexo. Se for PDF, divide em fragmentos de 5 páginas.
44
  Se for TXT/Outro, retorna o texto completo em uma lista de 1 fragmento."""
45
  if arquivo is None: return [], ""
46
  filename = arquivo.name
47
  anexo_info = f"\n\n[ANEXO SISTEMA: {os.path.basename(filename)}]\n"
48
 
49
- # Adicionando suporte para PDF (necessita de biblioteca externa)
50
  if filename.lower().endswith(".pdf"):
51
- # ⚠️ PLACEHOLDER: A lógica real de divisão de PDF (ex: usando PyMuPDF) vai aqui ⚠️
52
- print(f"DEBUG: Arquivo PDF detectado. Simulando fragmentação de 5 páginas.")
53
 
54
- # O retorno é uma lista de fragmentos de texto (simulados)
55
- # Se fosse um agente de transcrição, o loop chamaria o LLM aqui.
56
- # Aqui, simulamos o texto extraído da divisão em fragmentos.
57
- return [
58
- "FRAGMENTO 1 (Pgs 1-5): Regras de Contrato: O preço base é R$100.000,00. O prazo de entrega é 180 dias.",
59
- "FRAGMENTO 2 (Pgs 6-10): Termos de Aditivo: Um aditivo posterior (data X) modificou o preço para R$120.000,00 e o prazo para 210 dias.",
60
- "FRAGMENTO 3 (Pgs 11-15): Finalização: O cliente alega que o preço final acordado era R$110.000,00 e que houve falha na entrega de evidências.",
61
- "FRAGMENTO 4 (Pgs 16-20): Conclusão: O sistema de auditoria não conseguiu rastrear a evidência E1."
62
- ], anexo_info
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
63
 
64
  # Para arquivos não PDF, lê o conteúdo como um único fragmento.
65
  try:
@@ -70,16 +84,13 @@ def ler_anexo_e_fragmentar(arquivo):
70
 
71
  # ==================== 3. ENGINE DE EXECUÇÃO ====================
72
 
73
- # **MODIFICADA para aceitar um fragmento de texto como input se for o Passo 0**
74
  def executar_no(timeline, config, fragmento_input=None):
75
  modelo = model_pro if config.get("modelo") == "pro" else model_flash
76
 
77
- # Se fragmento_input for fornecido (i.e., estamos no Passo 0-LOOP), o LLM recebe o fragmento
78
  if fragmento_input is not None:
79
- # Prompt para o LLM processar o fragmento. O contexto da timeline é ignorado aqui.
80
  input_para_prompt = fragmento_input
81
  else:
82
- # Prompt padrão para os passos sequenciais, que usam a TIMELINE completa como contexto
83
  contexto = json.dumps(timeline, ensure_ascii=False, indent=2)
84
  input_para_prompt = contexto
85
 
@@ -92,14 +103,13 @@ def executar_no(timeline, config, fragmento_input=None):
92
  out = resp.text
93
  tempo = time.time() - inicio
94
 
95
- # O Passo 0 pode retornar texto, mas os outros passos JSON precisam de parsing
96
  content = json.loads(out.strip().replace('```json','').replace('```','')) if config['tipo_saida']=='json' and fragmento_input is None else out
97
  log += f" (OK - {tempo:.2f}s)"
98
  return {"role": "assistant", "agent": config['nome'], "content": content}, log, out
99
  except Exception as e:
100
  return {"role": "system", "error": str(e)}, f" (ERRO: {e})", str(e)
101
 
102
- # ==================== 4. ORQUESTRADOR (LÓGICA DO LOOP) ====================
103
 
104
  def orquestrador(texto, arquivo, history, json_config):
105
  # 1. Input Check e Fragmentação
@@ -123,47 +133,26 @@ def orquestrador(texto, arquivo, history, json_config):
123
  history[-1][1] = "⏳ Iniciando análise..."
124
  yield history, timeline, logs
125
 
126
- # 3. Execução: Loop e Sequência
127
 
128
- # 3a. PASSO 0: LÓGICA DE LOOP/FRAGEMENTAÇÃO (NOVA LÓGICA)
129
- # O protocolo epistêmico começa no ESTADO_INICIAL (Fase 0)
130
- if protocolo and 'fase' in protocolo[0] and protocolo[0]['fase'] == 0 and len(fragmentos) > 1:
131
-
132
- # O Agente 0 será executado UMA ÚNICA VEZ, mas com o input pré-processado/concatenado
133
- # A instrução aqui é: Chamar o LLM (ESTADO_INICIAL) para CADA fragmento, mas isso não faz sentido
134
- # para o objetivo do ESTADO_INICIAL, que precisa do input COMPLETO.
135
-
136
- # ADAPTAÇÃO: Vamos concatenar os fragmentos ANTES de chamar o ESTADO_INICIAL (Agente 0)
137
- # O Agente 0 (ESTADO_INICIAL) recebe o texto concatenado no seu input inicial.
138
-
139
  concatenated_input = anexo_info + "\n\n" + "\n\n".join(fragmentos)
140
  full_input_to_pass = f"{texto}\n{concatenated_input}".strip()
141
 
142
  # O full_input_to_pass torna-se o 'content' do primeiro item da timeline
143
  timeline[0]['content'] = full_input_to_pass
144
- history[-1][1] = "✅ Fragmentos concatenados. Iniciando FASE 0..."
145
  yield history, timeline, logs
146
 
147
-
148
- elif fragmentos:
149
- # Caso o input seja um arquivo não-PDF (1 fragmento), adicionamos ao full_input
150
- full_input_to_pass = f"{texto}\n{anexo_info}{fragmentos[0]}".strip()
151
- timeline[0]['content'] = full_input_to_pass
152
- history[-1][1] = "✅ Anexo lido. Iniciando FASE 0..."
153
- yield history, timeline, logs
154
 
155
 
156
- # 3b. PASSOS SEGUINTES: EXECUÇÃO SEQUENCIAL (Lógica original, mas aprimorada)
157
  final_response = ""
158
  for cfg in protocolo:
159
 
160
- # Lógica de Controle de Iteração (Baseada no TESTE_CRUCIALIDADE, Passo 7)
161
- if cfg['nome'] == 'TESTE_CRUCIALIDADE':
162
- # ⚠️ Aqui deveria haver a lógica de 'Se PASSOU=false, voltar para FASE 3',
163
- # mas o orquestrador não suporta iterar fases passadas sem reescrita total.
164
- # Executamos o teste, mas ignoramos a ação 'VOLTAR_FASE_3' no loop simples.
165
- pass
166
-
167
  history[-1][1] = f"⚙️ FASE {cfg.get('fase', '?')}: {cfg['nome']} trabalhando..."
168
  yield history, timeline, logs
169
 
@@ -216,7 +205,7 @@ def ui_clean():
216
  with gr.Column(scale=1, min_width=50):
217
  file_in = gr.UploadButton(
218
  "📎",
219
- file_types=[".txt", ".md", ".csv", ".json", ".pdf"], # Adicionado suporte a PDF
220
  size="sm"
221
  )
222
  with gr.Column(scale=1, min_width=80):
@@ -237,8 +226,8 @@ def ui_clean():
237
  with gr.Row():
238
  btn_save = gr.Button("Salvar Config")
239
  lbl_save = gr.Label(show_label=False)
240
- # **ATUALIZAÇÃO: Label para refletir o novo protocolo**
241
- code_json = gr.Code(value=config_init, language="json", label=ARQUIVO_CONFIG)
242
  btn_save.click(salvar_protocolo, code_json, lbl_save)
243
 
244
  # === TRIGGERS ===
 
1
  # ╔════════════════════════════════════════════════════════════════════════════╗
2
+ # ║ PIPELINE v29: ANÁLISE EPISTÊMICA (COM LÓGICA REAL PYPDF)
3
  # ║ Layout: Chat (Aba 1) | Debug (Aba 2) | Config (Aba 3) ║
4
  # ╚════════════════════════════════════════════════════════════════════════════╝
5
 
 
10
  from datetime import datetime
11
  import gradio as gr
12
  import google.generativeai as genai
13
+ import pypdf # <--- BIBLIOTECA NECESSÁRIA AGORA
14
 
15
  # ==================== 1. CONFIGURAÇÃO ====================
16
  api_key = os.getenv("GOOGLE_API_KEY", "SUA_API_KEY_AQUI")
 
19
  model_flash = genai.GenerativeModel("gemini-flash-latest")
20
  model_pro = genai.GenerativeModel("gemini-pro-latest")
21
 
22
+ # Usando o nome do protocolo de análise epistêmica
23
  ARQUIVO_CONFIG = "protocolo_epistemico_forense.json"
24
 
25
  # ==================== 2. UTILIDADES ====================
 
32
  def salvar_protocolo(conteudo):
33
  try:
34
  json.loads(conteudo)
35
+ # Assumindo que o Gradio salva como 'protocolo.json'
36
+ with open("protocolo.json", "w", encoding="utf-8") as f: f.write(conteudo)
 
37
  return "✅ Salvo"
38
  except: return "❌ Erro JSON"
39
 
40
+ # **FUNÇÃO REAL: Lógica de Fragmentação de PDF com pypdf**
41
+ def ler_anexo_e_fragmentar(arquivo, paginas_por_fragmento=5):
42
+ """Lê o anexo. Se for PDF, usa pypdf para dividir em fragmentos de X páginas.
43
  Se for TXT/Outro, retorna o texto completo em uma lista de 1 fragmento."""
44
  if arquivo is None: return [], ""
45
  filename = arquivo.name
46
  anexo_info = f"\n\n[ANEXO SISTEMA: {os.path.basename(filename)}]\n"
47
 
48
+ # Lógica REAL para PDF
49
  if filename.lower().endswith(".pdf"):
50
+ print(f"DEBUG: Arquivo PDF detectado. Iniciando fragmentação de {paginas_por_fragmento} páginas.")
 
51
 
52
+ fragments = []
53
+ try:
54
+ reader = pypdf.PdfReader(filename)
55
+ total_pages = len(reader.pages)
56
+
57
+ for i in range(0, total_pages, paginas_por_fragmento):
58
+ fragment_text = []
59
+ start_page = i
60
+ end_page = min(i + paginas_por_fragmento, total_pages)
61
+
62
+ for j in range(start_page, end_page):
63
+ # pypdf usa índice 0, então página N é reader.pages[N-1]
64
+ try:
65
+ fragment_text.append(reader.pages[j].extract_text())
66
+ except Exception as e:
67
+ fragment_text.append(f"[ERRO DE EXTRAÇÃO NA PÁGINA {j+1}: {e}]")
68
+
69
+ fragment_header = f"FRAGMENTO (Pgs {start_page+1}-{end_page} / Total {total_pages}):\n"
70
+ fragments.append(fragment_header + "\n".join(fragment_text))
71
+
72
+ return fragments, anexo_info
73
+
74
+ except Exception as e:
75
+ # Em caso de erro na leitura/extração do PDF
76
+ return [f"ERRO CRÍTICO NA LEITURA DE PDF: {e}"], anexo_info
77
 
78
  # Para arquivos não PDF, lê o conteúdo como um único fragmento.
79
  try:
 
84
 
85
  # ==================== 3. ENGINE DE EXECUÇÃO ====================
86
 
87
+ # Funções inalteradas, pois a lógica de loop foi centralizada no ORQUESTRADOR
88
  def executar_no(timeline, config, fragmento_input=None):
89
  modelo = model_pro if config.get("modelo") == "pro" else model_flash
90
 
 
91
  if fragmento_input is not None:
 
92
  input_para_prompt = fragmento_input
93
  else:
 
94
  contexto = json.dumps(timeline, ensure_ascii=False, indent=2)
95
  input_para_prompt = contexto
96
 
 
103
  out = resp.text
104
  tempo = time.time() - inicio
105
 
 
106
  content = json.loads(out.strip().replace('```json','').replace('```','')) if config['tipo_saida']=='json' and fragmento_input is None else out
107
  log += f" (OK - {tempo:.2f}s)"
108
  return {"role": "assistant", "agent": config['nome'], "content": content}, log, out
109
  except Exception as e:
110
  return {"role": "system", "error": str(e)}, f" (ERRO: {e})", str(e)
111
 
112
+ # ==================== 4. ORQUESTRADOR (LÓGICA DO LOOP/FRAGMENTAÇÃO) ====================
113
 
114
  def orquestrador(texto, arquivo, history, json_config):
115
  # 1. Input Check e Fragmentação
 
133
  history[-1][1] = "⏳ Iniciando análise..."
134
  yield history, timeline, logs
135
 
136
+ # 3. Lógica de Pré-processamento/Conciliação de Fragmentos
137
 
138
+ if len(fragmentos) > 0:
 
 
 
 
 
 
 
 
 
 
139
  concatenated_input = anexo_info + "\n\n" + "\n\n".join(fragmentos)
140
  full_input_to_pass = f"{texto}\n{concatenated_input}".strip()
141
 
142
  # O full_input_to_pass torna-se o 'content' do primeiro item da timeline
143
  timeline[0]['content'] = full_input_to_pass
144
+ history[-1][1] = "✅ Pré-processamento: Anexo lido e concatenado. Iniciando FASE 0..."
145
  yield history, timeline, logs
146
 
147
+ else:
148
+ # Se não há anexo ou só a pergunta
149
+ pass
 
 
 
 
150
 
151
 
152
+ # 4. PASSOS SEGUINTES: EXECUÇÃO SEQUENCIAL (Protocolo Epistêmico)
153
  final_response = ""
154
  for cfg in protocolo:
155
 
 
 
 
 
 
 
 
156
  history[-1][1] = f"⚙️ FASE {cfg.get('fase', '?')}: {cfg['nome']} trabalhando..."
157
  yield history, timeline, logs
158
 
 
205
  with gr.Column(scale=1, min_width=50):
206
  file_in = gr.UploadButton(
207
  "📎",
208
+ file_types=[".txt", ".md", ".csv", ".json", ".pdf"],
209
  size="sm"
210
  )
211
  with gr.Column(scale=1, min_width=80):
 
226
  with gr.Row():
227
  btn_save = gr.Button("Salvar Config")
228
  lbl_save = gr.Label(show_label=False)
229
+ # Label para refletir o novo protocolo
230
+ code_json = gr.Code(value=config_init, language="json", label="protocolo.json")
231
  btn_save.click(salvar_protocolo, code_json, lbl_save)
232
 
233
  # === TRIGGERS ===