caarleexx commited on
Commit
cad0242
·
verified ·
1 Parent(s): a643b29

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +179 -420
app.py CHANGED
@@ -1,501 +1,260 @@
1
  # ╔════════════════════════════════════════════════════════════════════════════╗
2
- # ║ PIPELINE EPISTÊMICO v28: Protocolo Causal de 10 Fases
3
- # ║ Resolução do Paradoxo de Ménon aplicado à investigação forense
4
  # ╚════════════════════════════════════════════════════════════════════════════╝
5
 
6
  import os
7
  import json
 
8
  import time
9
  from datetime import datetime
10
  import gradio as gr
11
  import google.generativeai as genai
 
12
 
13
  # ==================== 1. CONFIGURAÇÃO ====================
14
-
15
  api_key = os.getenv("GOOGLE_API_KEY", "SUA_API_KEY_AQUI")
16
- if api_key and api_key != "SUA_API_KEY_AQUI":
17
- genai.configure(api_key=api_key)
18
 
19
  model_flash = genai.GenerativeModel("gemini-flash-latest")
20
- model_pro = genai.GenerativeModel("gemini-pro-latest")
21
 
 
22
  ARQUIVO_CONFIG = "protocolo_epistemico_forense.json"
23
- MAX_ITERACOES = 3 # Máximo de voltas Fase 7 → Fase 3
24
 
25
  # ==================== 2. UTILIDADES ====================
26
 
27
  def carregar_protocolo():
28
- """Carrega configuração do protocolo"""
29
- try:
30
- with open(ARQUIVO_CONFIG, "r", encoding="utf-8") as f:
31
- conteudo = f.read()
32
- # Validar se é JSON válido
33
- json.loads(conteudo)
34
- return conteudo
35
- except FileNotFoundError:
36
- print(f"⚠️ Arquivo {ARQUIVO_CONFIG} não encontrado. Criando protocolo padrão...")
37
- return criar_protocolo_padrao()
38
- except json.JSONDecodeError as e:
39
- print(f"⚠️ JSON inválido: {e}")
40
- return criar_protocolo_padrao()
41
- except Exception as e:
42
- print(f"⚠️ Erro ao carregar protocolo: {e}")
43
- return "[]"
44
-
45
- def criar_protocolo_padrao():
46
- """Cria protocolo padrão se não existir"""
47
- protocolo_minimo = [
48
- {"fase": 0, "nome": "ESTADO_INICIAL", "modelo": "flash", "tipo_saida": "json",
49
- "missao": "Registre o input inicial em JSON com: PERGUNTA_NORMALIZADA, CONTEXTO_IDENTIFICADO, TIPO_CASO, PARTES_ENVOLVIDAS, INCERTEZA_INICIAL (0-1), EVIDENCIAS_DISPONIVEIS."},
50
-
51
- {"fase": 1, "nome": "MAPEAMENTO_OBJETIVO", "modelo": "flash", "tipo_saida": "json",
52
- "missao": "Analise o estado inicial e retorne JSON com: OBJETIVO_CLARIFICADO, TIPO_RESPOSTA_ESPERADA, CRITERIOS_VALIDACAO (3-5), PRESSUPOSTOS_IMPLICITOS, PERGUNTAS_DERIVADAS, METODOLOGIA_SUGERIDA."},
53
-
54
- {"fase": 2, "nome": "INVENTARIO_EPISTEMICO", "modelo": "flash", "tipo_saida": "json",
55
- "missao": "Mapeie conhecimento em 4 categorias JSON: CONHECIMENTO_CERTO (P≈1.0), CONHECIMENTO_PROVAVEL (P≈0.7-0.9), CONHECIMENTO_INCERTO (P≈0.3-0.6), DESCONHECIMENTO_TOTAL, PERCENTUAL_COBERTURA (0-100), GAPS_CRITICOS."},
56
-
57
- {"fase": 3, "nome": "GERACAO_CENARIOS", "modelo": "flash", "tipo_saida": "json",
58
- "missao": "Gere array de 3+ cenários em JSON. Cada um com: ID (C1, C2...), DESCRICAO, PROBABILIDADE_PRIOR (soma=1.0), SUPOSICOES, COMPATIBILIDADE, EVIDENCIAS_SUPORTE, EVIDENCIAS_CONTRA, PLAUSIBILIDADE_SCORE."},
59
-
60
- {"fase": 4, "nome": "ANALISE_CONTRAFACTUAL", "modelo": "flash", "tipo_saida": "json",
61
- "missao": "Teste 'E SE?' para cada suposição. Retorne JSON: VARIAVEIS_CRITICAS, MAPA_CAUSAL, PONTOS_INFLEXAO (que se falsos invertem conclusões), TESTES_RECOMENDADOS."},
62
-
63
- {"fase": 5, "nome": "CADEIA_INVESTIGATIVA", "modelo": "flash", "tipo_saida": "json",
64
- "missao": "Construa cadeia de perguntas priorizadas. JSON: CADEIA_PERGUNTAS (array ordenado com id, pergunta, justificativa, reducao_entropia_estimada, metodo_busca, proximos_passos, prioridade), ARVORE_DECISAO, EVIDENCIAS_NECESSARIAS."},
65
-
66
- {"fase": 6, "nome": "COLETA_ATUALIZACAO_BAYESIANA", "modelo": "pro", "tipo_saida": "json",
67
- "missao": "Aplique P(C|E) = P(E|C) × P(C) / P(E). JSON: EVIDENCIAS_ANALISADAS (com likelihoods), CENARIOS_ATUALIZADOS (prior→posterior), GANHO_INFORMACAO (KL divergence), CONVERGENCIA (bool), CENARIO_DOMINANTE."},
68
-
69
- {"fase": 7, "nome": "TESTE_CRUCIALIDADE", "modelo": "pro", "tipo_saida": "json",
70
- "missao": "Valide contra 7 critérios (cada 0.0-1.0): CONSISTENCIA_INTERNA, COMPATIBILIDADE_EVIDENCIAS, EXPLICACOES_ALTERNATIVAS, FALSIFICABILIDADE, PARSIMONIA, ESCOPO, PREDICOES_VERIFICAVEIS. JSON: TESTES (dict), SCORE_CRUCIALIDADE (média), LIMIAR_CONFIANCA (0.70), PASSOU (bool), ACAO (PROSSEGUIR ou VOLTAR_FASE_3), FRAGILIDADES_IDENTIFICADAS (array)."},
71
-
72
- {"fase": 8, "nome": "GERACAO_RESPOSTA", "modelo": "pro", "tipo_saida": "json",
73
- "missao": "Sintetize em JSON: AFIRMACAO_PRINCIPAL, NIVEL_CONFIANCA (0-1), EVIDENCIAS_SUPORTE, SUPOSICOES_CRITICAS, CENARIOS_ALTERNATIVOS (P>0.1), PROXIMOS_PASSOS, FRONTEIRAS_CONHECIMENTO, CALIBRACAO_EPISTEMICA (honest/overconfident/underconfident), QUALIFICADORES."},
74
-
75
- {"fase": 9, "nome": "RELATORIO_FINAL", "modelo": "pro", "tipo_saida": "texto",
76
- "missao": "Escreva relatório completo em Markdown com estrutura: # RELATÓRIO DE ANÁLISE EPISTÊMICA\n\n## SUMÁRIO EXECUTIVO\n(Caso, Conclusão, Confiança, Calibração)\n\n## 1. ESTADO INICIAL\n## 2. OBJETIVO\n## 3. INVENTÁRIO DE CONHECIMENTO\n(Certo, Provável, Incerto, Gaps)\n\n## 4. CENÁRIOS CONSIDERADOS\n## 5. ANÁLISE BAYESIANA\n(Tabela Prior→Posterior)\n\n## 6. VALIDAÇÃO\n(Tabela 7 critérios)\n\n## 7. CONCLUSÃO E RECOMENDAÇÕES\n(Afirmação, Evidências-chave, Suposições críticas, Próximos passos)\n\n## 8. FRONTEIRAS DO CONHECIMENTO\n## 9. CALIBRAÇÃO EPISTÊMICA\n## 10. METADADOS\n(Fases, Iterações, Timestamp)"}
77
- ]
78
-
79
- protocolo_json = json.dumps(protocolo_minimo, ensure_ascii=False, indent=2)
80
-
81
  try:
82
- with open(ARQUIVO_CONFIG, "w", encoding="utf-8") as f:
83
- f.write(protocolo_json)
84
- print(f"✅ Protocolo padrão criado: {ARQUIVO_CONFIG}")
85
- except Exception as e:
86
- print(f"⚠️ Não foi possível salvar protocolo: {e}")
87
-
88
- return protocolo_json
89
 
90
  def salvar_protocolo(conteudo):
91
- """Salva e valida JSON do protocolo"""
92
  try:
93
- protocolo = json.loads(conteudo)
94
-
95
- # Validar estrutura
96
- if not isinstance(protocolo, list):
97
- return " Erro: Protocolo deve ser um array JSON"
98
-
99
- fases = {f.get('fase') for f in protocolo if isinstance(f, dict) and 'fase' in f}
100
- if len(fases) < 10:
101
- return f"⚠️ Aviso: Protocolo tem apenas {len(fases)} fases (esperado: 10)"
102
-
103
- with open(ARQUIVO_CONFIG, "w", encoding="utf-8") as f:
104
- f.write(conteudo)
105
-
106
- return f" Protocolo salvo com sucesso ({len(fases)} fases)"
107
- except json.JSONDecodeError as e:
108
- return f"❌ Erro no JSON: {str(e)}"
109
- except Exception as e:
110
- return f"❌ Erro: {str(e)}"
111
-
112
- def ler_anexo(arquivo):
113
- """Lê arquivo anexado"""
114
- if arquivo is None:
115
- return ""
 
 
 
 
 
 
 
 
116
  try:
117
- with open(arquivo.name, "r", encoding="utf-8") as f:
118
- conteudo = f.read()
119
- return f"\n\n--- ANEXO: {os.path.basename(arquivo.name)} ---\n{conteudo}\n--- FIM ANEXO ---\n"
120
- except Exception as e:
121
- return f"\n[ERRO ao ler anexo: {e}]\n"
122
 
123
- # ==================== 3. ENGINE DE EXECUÇÃO ====================
124
 
125
- def executar_fase(timeline, config, fase_atual, total_fases):
126
- """Executa uma fase do protocolo"""
127
 
128
- # Selecionar modelo
 
129
  modelo = model_pro if config.get("modelo") == "pro" else model_flash
130
-
131
- # Construir contexto com toda a timeline
132
- contexto = json.dumps(timeline, ensure_ascii=False, indent=2)
133
-
134
- # Prompt estruturado
135
- prompt = f"""--- TIMELINE COMPLETA ---
136
- {contexto}
137
-
138
- --- FASE ATUAL ---
139
- AGENTE: {config['nome']}
140
- FASE: {fase_atual}/{total_fases}
141
- TIPO_SAIDA: {config['tipo_saida']}
142
-
143
- --- MISSÃO ---
144
- {config['missao']}
145
-
146
- --- INSTRUÇÕES ---
147
- 1. Analise TODA a timeline acima
148
- 2. Execute sua missão com rigor
149
- 3. {"Retorne APENAS JSON válido" if config['tipo_saida'] == 'json' else "Retorne texto em Markdown"}
150
- 4. Seja epistemicamente honesto sobre incertezas
151
- """
152
-
153
- log = f"\n🔸 Fase {fase_atual}: {config['nome']}"
154
-
155
  try:
156
  inicio = time.time()
157
-
158
- # Executar com retry
159
- max_tentativas = 2
160
- for tentativa in range(max_tentativas):
161
- try:
162
- resp = modelo.generate_content(
163
- prompt,
164
- generation_config={
165
- "temperature": 0.3 if config['tipo_saida'] == 'json' else 0.7,
166
- "max_output_tokens": 8000
167
- }
168
- )
169
- output_raw = resp.text
170
- break
171
- except Exception as e:
172
- if tentativa == max_tentativas - 1:
173
- raise e
174
- time.sleep(2)
175
-
176
  tempo = time.time() - inicio
177
-
178
- # Processar output
179
- if config['tipo_saida'] == 'json':
180
- # Limpar markdown
181
- output_limpo = output_raw.strip()
182
- output_limpo = output_limpo.replace('```json', '').replace('```', '')
183
- content = json.loads(output_limpo)
184
- else:
185
- content = output_raw
186
-
187
- log += f" ✓ ({tempo:.1f}s)"
188
-
189
- return {
190
- "role": "assistant",
191
- "agent": config['nome'],
192
- "fase": config['fase'],
193
- "content": content
194
- }, log, True
195
-
196
  except Exception as e:
197
- log += f" ERRO: {str(e)[:100]}"
198
- return {
199
- "role": "system",
200
- "agent": config['nome'],
201
- "fase": config['fase'],
202
- "error": str(e)
203
- }, log, False
204
 
205
- # ==================== 4. ORQUESTRADOR COM LOOP ITERATIVO ====================
206
 
207
  def orquestrador(texto, arquivo, history, json_config):
208
- """Orquestra execução do protocolo de 10 fases com loop iterativo"""
209
-
210
- # 1. Validar input
211
- anexo = ler_anexo(arquivo)
212
- full_input = f"{texto}\n{anexo}".strip()
213
-
214
- if not full_input:
215
- yield history, {}, "⚠️ Nenhum input fornecido."
216
  return
217
 
218
  # 2. Setup
219
  history = history + [[texto + (" 📎" if arquivo else ""), None]]
220
-
221
- try:
222
- protocolo = json.loads(json_config)
223
-
224
- # Validar que protocolo tem todas as fases necessárias
225
- fases_disponiveis = {f['fase'] for f in protocolo if isinstance(f, dict) and 'fase' in f}
226
- if 7 not in fases_disponiveis:
227
- history[-1][1] = "❌ **Erro: Fase 7 não encontrada no protocolo**\n\nO protocolo deve ter 10 fases (0-9)."
228
- yield history, {}, "Erro: Fase 7 ausente"
229
- return
230
-
231
- except Exception as e:
232
- history[-1][1] = f"❌ **Erro no JSON de Configuração**\n\n```\n{str(e)}\n```"
233
- yield history, {}, f"Erro JSON: {e}"
234
  return
235
 
236
- # 3. Inicializar timeline
237
- timeline = [{
238
- "role": "user",
239
- "content": full_input,
240
- "timestamp": datetime.now().isoformat()
241
- }]
242
-
243
- logs = f"🚀 INÍCIO: {datetime.now().strftime('%H:%M:%S')}\n"
244
- logs += f"📋 Protocolo: {len(protocolo)} fases\n"
245
- logs += f"🔄 Max iterações: {MAX_ITERACOES}\n"
246
- logs += "=" * 60 + "\n"
247
-
248
- history[-1][1] = "⏳ **Iniciando Protocolo Epistêmico...**\n\nFase 0/10: Estado Inicial"
249
  yield history, timeline, logs
250
 
251
- # 4. Executar fases 0-6 (sequenciais)
252
- fases_sequenciais = [f for f in protocolo if f['fase'] < 7]
253
-
254
- for cfg in fases_sequenciais:
255
- fase_num = cfg['fase']
256
- history[-1][1] = f"⚙️ **Fase {fase_num}/10: {cfg['nome']}**\n\nProcessando..."
 
 
 
 
 
 
 
 
 
 
 
 
 
257
  yield history, timeline, logs
258
-
259
- resultado, log_add, sucesso = executar_fase(timeline, cfg, fase_num, 10)
260
- timeline.append(resultado)
261
- logs += log_add + "\n"
262
-
263
- if not sucesso:
264
- history[-1][1] = f" **Erro na Fase {fase_num}**\n\n{resultado.get('error', 'Erro desconhecido')}"
265
- yield history, timeline, logs
266
- return
267
-
268
  yield history, timeline, logs
269
 
270
- # 5. Loop iterativo: Fases 7 (teste) ↔ Fases 3-6 (refinamento)
271
- iteracao = 0
272
- fase_7_passou = False
273
-
274
- # Buscar configuração da fase 7 com segurança
275
- cfg_fase7 = None
276
- for f in protocolo:
277
- if f.get('fase') == 7:
278
- cfg_fase7 = f
279
- break
280
 
281
- if cfg_fase7 is None:
282
- history[-1][1] = "❌ **Erro: Fase 7 (TESTE_CRUCIALIDADE) não encontrada**"
 
 
 
 
 
 
 
 
 
 
283
  yield history, timeline, logs
284
- return
285
-
286
- while iteracao < MAX_ITERACOES and not fase_7_passou:
287
- iteracao += 1
288
- logs += f"\n{'='*60}\n🔄 ITERAÇÃO {iteracao}/{MAX_ITERACOES}\n{'='*60}\n"
289
-
290
- # Se não é primeira vez, re-executar fases 3-6
291
- if iteracao > 1:
292
- history[-1][1] = f"🔄 **Iteração {iteracao}**: Refinando cenários (Fases 3-6)..."
293
- yield history, timeline, logs
294
-
295
- fases_refinamento = [f for f in protocolo if 3 <= f['fase'] < 7]
296
- for cfg in fases_refinamento:
297
- resultado, log_add, sucesso = executar_fase(timeline, cfg, cfg['fase'], 10)
298
- timeline.append(resultado)
299
- logs += log_add + "\n"
300
-
301
- if not sucesso:
302
- history[-1][1] = f"❌ **Erro no refinamento**"
303
- yield history, timeline, logs
304
- return
305
-
306
- yield history, timeline, logs
307
-
308
- # Executar Fase 7: TESTE_CRUCIALIDADE
309
- history[-1][1] = f"🧪 **Fase 7/10: Teste de Crucialidade** (Iteração {iteracao})\n\nValidando..."
310
- yield history, timeline, logs
311
-
312
- resultado, log_add, sucesso = executar_fase(timeline, cfg_fase7, 7, 10)
313
- timeline.append(resultado)
314
  logs += log_add + "\n"
315
-
316
- if not sucesso:
317
- history[-1][1] = f"❌ **Erro na Fase 7**"
318
- yield history, timeline, logs
319
- return
320
-
321
- # Verificar se passou no teste
322
- teste_resultado = resultado.get('content', {})
323
- fase_7_passou = teste_resultado.get('PASSOU', False)
324
- score = teste_resultado.get('SCORE_CRUCIALIDADE', 0)
325
-
326
- if fase_7_passou:
327
- logs += f"✅ TESTE PASSOU (score: {score:.3f})\n"
328
- history[-1][1] = f"✅ **Validação Aprovada!**\n\nScore: {score:.3f}"
329
- else:
330
- logs += f"⚠️ TESTE FALHOU (score: {score:.3f})\n"
331
- fragilidades = teste_resultado.get('FRAGILIDADES_IDENTIFICADAS', [])
332
- history[-1][1] = f"⚠️ **Validação Reprovada** (Score: {score:.3f})\n\nFragilidades:\n" + "\n".join(f"- {f}" for f in fragilidades[:5])
333
-
334
- yield history, timeline, logs
335
-
336
- if not fase_7_passou:
337
- logs += f"\n⚠️ Limite de iterações atingido ({MAX_ITERACOES}). Prosseguindo com melhor resultado.\n"
338
-
339
- # 6. Fases finais 8-9 (sempre executam)
340
- fases_finais = [f for f in protocolo if f['fase'] >= 8]
341
-
342
- for cfg in fases_finais:
343
- fase_num = cfg['fase']
344
- history[-1][1] = f"📝 **Fase {fase_num}/10: {cfg['nome']}**\n\nGerando..."
345
- yield history, timeline, logs
346
-
347
- resultado, log_add, sucesso = executar_fase(timeline, cfg, fase_num, 10)
348
- timeline.append(resultado)
349
- logs += log_add + "\n"
350
-
351
- if not sucesso:
352
- history[-1][1] = f"❌ **Erro na Fase {fase_num}**"
353
- yield history, timeline, logs
354
- return
355
-
356
- # Se for a fase final (9 - RELATORIO_FINAL), mostrar resultado
357
- if cfg['fase'] == 9 and cfg['tipo_saida'] == 'texto':
358
- final_response = resultado.get('content', 'Erro ao gerar relatório')
359
- history[-1][1] = final_response
360
-
361
  yield history, timeline, logs
362
 
363
- # 7. Finalizar
364
- logs += "\n" + "=" * 60 + "\n"
365
- logs += f"✅ CONCLUÍDO: {datetime.now().strftime('%H:%M:%S')}\n"
366
- logs += f"📊 Total de fases executadas: {len([t for t in timeline if t.get('fase') is not None])}\n"
367
- logs += f"🔄 Iterações necessárias: {iteracao}\n"
368
-
369
  yield history, timeline, logs
370
 
371
- # ==================== 5. UI COM GRADIO ====================
372
-
373
- def criar_ui():
374
- """Cria interface Gradio"""
375
 
 
376
  css = """
377
- footer {display: none !important;}
378
  .contain {border: none !important;}
379
  """
 
 
380
 
381
- config_inicial = carregar_protocolo()
382
-
383
- with gr.Blocks(
384
- title="🔬 Investigador Epistêmico",
385
- css=css,
386
- theme=gr.themes.Soft()
387
- ) as app:
388
-
389
- gr.Markdown("""
390
- # 🔬 Investigador Epistêmico
391
- ### Protocolo Causal de 10 Fases | Resolução do Paradoxo de Ménon
392
- """)
393
-
394
  with gr.Tabs():
395
-
396
- # === ABA 1: INVESTIGAÇÃO ===
397
- with gr.Tab("💬 Investigação"):
398
  chatbot = gr.Chatbot(
399
- label="",
400
- show_label=False,
401
- height=650,
402
  show_copy_button=True,
403
- render_markdown=True,
404
- type="tuples" # Explicitamente definido
405
  )
406
-
407
  with gr.Row():
408
  with gr.Column(scale=10):
409
- txt_input = gr.Textbox(
410
- show_label=False,
411
- placeholder="Descreva o caso, pergunta ou situação a investigar...",
412
- lines=2,
413
- max_lines=8,
414
  container=False
415
  )
416
-
417
- with gr.Column(scale=1, min_width=60):
418
- file_input = gr.UploadButton(
419
- "📎",
420
- file_types=[".txt", ".md", ".csv", ".json", ".pdf"],
421
  size="sm"
422
  )
423
-
424
- with gr.Column(scale=1, min_width=100):
425
- btn_enviar = gr.Button("🚀 Investigar", variant="primary", size="sm")
426
-
427
  file_status = gr.Markdown("", visible=True)
428
- file_input.upload(
429
- lambda x: f"📎 **Anexo:** {os.path.basename(x.name)}",
430
- file_input,
431
- file_status
432
- )
433
 
434
- # === ABA 2: DEPURAÇÃO ===
435
  with gr.Tab("🕵️ Depuração"):
436
- gr.Markdown("### Timeline Completa (DNA da Investigação)")
437
- output_timeline = gr.JSON(label="Timeline")
438
-
439
- gr.Markdown("### Logs do Sistema")
440
- output_logs = gr.Textbox(label="Logs", lines=25, max_lines=50)
441
-
442
- # === ABA 3: CONFIGURAÇÃO ===
443
- with gr.Tab("⚙️ Configuração"):
444
- gr.Markdown("""
445
- ### Protocolo Epistêmico (JSON)
446
-
447
- **Estrutura de cada fase:**
448
- - `fase`: Número da fase (0-9)
449
- - `nome`: Identificador da fase
450
- - `modelo`: "flash" ou "pro"
451
- - `tipo_saida`: "json" ou "texto"
452
- - `missao`: Instruções detalhadas
453
-
454
- **Fluxo:** 0→1→2→3→4→5→6→7(teste)→[volta 3 se falhar]→8→9
455
- """)
456
-
457
  with gr.Row():
458
- btn_salvar = gr.Button("💾 Salvar Protocolo", variant="primary")
459
- label_salvar = gr.Label(show_label=False)
460
-
461
- code_protocolo = gr.Code(
462
- value=config_inicial,
463
- language="json",
464
- label="protocolo_epistemico_forense.json",
465
- lines=30
466
- )
467
-
468
- btn_salvar.click(salvar_protocolo, code_protocolo, label_salvar)
469
-
470
- # === EVENTOS ===
471
- triggers = [btn_enviar.click, txt_input.submit]
472
 
473
- for trigger in triggers:
474
- trigger(
 
 
 
 
 
 
 
 
 
 
 
 
475
  orquestrador,
476
- inputs=[txt_input, file_input, chatbot, code_protocolo],
477
- outputs=[chatbot, output_timeline, output_logs]
478
  ).then(
479
- lambda: (None, ""),
480
- outputs=[txt_input, file_status]
481
  )
482
 
483
  return app
484
 
485
- # ==================== 6. MAIN ====================
486
-
487
  if __name__ == "__main__":
488
- print("=" * 80)
489
- print("🔬 INVESTIGADOR EPISTÊMICO - Protocolo Causal v28")
490
- print("=" * 80)
491
- print(f"📋 Arquivo de configuração: {ARQUIVO_CONFIG}")
492
- print(f"🔄 Máximo de iterações: {MAX_ITERACOES}")
493
- print("=" * 80)
494
-
495
- app = criar_ui()
496
- app.launch(
497
- server_name="0.0.0.0",
498
- server_port=7860,
499
- share=False,
500
- show_error=True
501
- )
 
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
 
6
  import os
7
  import json
8
+ import re
9
  import time
10
  from datetime import datetime
11
  import gradio as gr
12
  import google.generativeai as genai
13
+ # import PyMuPDF as fitz # <-- BIBLIOTECA NECESSÁRIA
14
 
15
  # ==================== 1. CONFIGURAÇÃO ====================
 
16
  api_key = os.getenv("GOOGLE_API_KEY", "SUA_API_KEY_AQUI")
17
+ if api_key: genai.configure(api_key=api_key)
 
18
 
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 ====================
26
 
27
  def carregar_protocolo():
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
28
  try:
29
+ with open(ARQUIVO_CONFIG, "r", encoding="utf-8") as f: return f.read()
30
+ except: return "[]"
 
 
 
 
 
31
 
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:
66
+ with open(filename, "r", encoding="utf-8") as f:
67
+ return [f.read()], anexo_info
68
+ except: return [], ""
 
 
69
 
 
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
+
86
+ prompt = f"--- INPUT PARA O AGENTE ---\n{input_para_prompt}\n----------------\nAGENTE: {config['nome']}\nMISSÃO: {config['missao']}"
87
+
88
+ log = f"\n🔸 {config['nome']}..."
 
 
 
 
 
 
 
 
 
 
 
 
89
  try:
90
  inicio = time.time()
91
+ resp = modelo.generate_content(prompt)
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
106
+ fragmentos, anexo_info = ler_anexo_e_fragmentar(arquivo)
107
+
108
+ if not texto and not fragmentos:
109
+ yield history, {}, "Sem input."
 
 
 
110
  return
111
 
112
  # 2. Setup
113
  history = history + [[texto + (" 📎" if arquivo else ""), None]]
114
+ try: protocolo = json.loads(json_config)
115
+ except:
116
+ history[-1][1] = "❌ Erro no JSON de Configuração."
117
+ yield history, {}, "Erro JSON"
 
 
 
 
 
 
 
 
 
 
118
  return
119
 
120
+ # A TIMELINE começa com o input do usuário (a pergunta/instrução)
121
+ timeline = [{"role": "user", "content": texto}]
122
+ logs = f"🚀 START: {datetime.now().strftime('%H:%M:%S')}\n"
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
+
170
+ res, log_add, raw = executar_no(timeline, cfg)
171
+ timeline.append(res)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
172
  logs += log_add + "\n"
173
+
174
+ if cfg['tipo_saida'] == 'texto':
175
+ final_response = res['content']
176
+ history[-1][1] = final_response
177
+
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
178
  yield history, timeline, logs
179
 
180
+ logs += "✅ FIM. (Análise Epistêmica Concluída)"
 
 
 
 
 
181
  yield history, timeline, logs
182
 
183
+ # ==================== 5. UI LIMPA (v27) ====================
 
 
 
184
 
185
+ def ui_clean():
186
  css = """
187
+ footer {display: none !important;}
188
  .contain {border: none !important;}
189
  """
190
+
191
+ config_init = carregar_protocolo()
192
 
193
+ with gr.Blocks(title="Protocolo Epistêmico Forense", css=css, theme=gr.themes.Soft()) as app:
194
+
 
 
 
 
 
 
 
 
 
 
 
195
  with gr.Tabs():
196
+
197
+ # === ABA 1: CHAT (LIMPO) ===
198
+ with gr.Tab("💬 Investigador"):
199
  chatbot = gr.Chatbot(
200
+ label="",
201
+ show_label=False,
202
+ height=600,
203
  show_copy_button=True,
204
+ render_markdown=True
 
205
  )
206
+
207
  with gr.Row():
208
  with gr.Column(scale=10):
209
+ txt_in = gr.Textbox(
210
+ show_label=False,
211
+ placeholder="Descreva o caso ou instrução...",
212
+ lines=1,
213
+ max_lines=5,
214
  container=False
215
  )
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):
223
+ btn_send = gr.Button("Enviar", variant="primary", size="sm")
224
+
225
+ # Feedback visual sutil do arquivo
226
  file_status = gr.Markdown("", visible=True)
227
+ file_in.upload(lambda x: f"📎 Anexo: {os.path.basename(x.name)}", file_in, file_status)
 
 
 
 
228
 
229
+ # === ABA 2: DEPURAÇÃO (ESCONDIDO) ===
230
  with gr.Tab("🕵️ Depuração"):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
231
  with gr.Row():
232
+ out_dna = gr.JSON(label="DNA (Timeline)")
233
+ out_logs = gr.Textbox(label="Logs do Sistema", lines=20)
 
 
 
 
 
 
 
 
 
 
 
 
234
 
235
+ # === ABA 3: CONFIG (TÉCNICO) ===
236
+ with gr.Tab("⚙️ Config"):
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 ===
245
+ triggers = [btn_send.click, txt_in.submit]
246
+
247
+ for trig in triggers:
248
+ trig(
249
  orquestrador,
250
+ inputs=[txt_in, file_in, chatbot, code_json],
251
+ outputs=[chatbot, out_dna, out_logs]
252
  ).then(
253
+ lambda: (None, ""),
254
+ outputs=[txt_in, file_status]
255
  )
256
 
257
  return app
258
 
 
 
259
  if __name__ == "__main__":
260
+ ui_clean().launch()