| |
| |
| |
| |
|
|
| import os |
| import json |
| import time |
| from datetime import datetime |
| import gradio as gr |
| import google.generativeai as genai |
|
|
| |
|
|
| api_key = os.getenv("GOOGLE_API_KEY", "SUA_API_KEY_AQUI") |
| if api_key and api_key != "SUA_API_KEY_AQUI": |
| genai.configure(api_key=api_key) |
|
|
| model_flash = genai.GenerativeModel("gemini-flash-latest") |
| model_pro = genai.GenerativeModel("gemini-pro-latest") |
|
|
| ARQUIVO_CONFIG = "protocolo_epistemico_forense.json" |
| MAX_ITERACOES = 3 |
|
|
| |
|
|
| def carregar_protocolo(): |
| """Carrega configuração do protocolo""" |
| try: |
| with open(ARQUIVO_CONFIG, "r", encoding="utf-8") as f: |
| conteudo = f.read() |
| |
| json.loads(conteudo) |
| return conteudo |
| except FileNotFoundError: |
| print(f"⚠️ Arquivo {ARQUIVO_CONFIG} não encontrado. Criando protocolo padrão...") |
| return criar_protocolo_padrao() |
| except json.JSONDecodeError as e: |
| print(f"⚠️ JSON inválido: {e}") |
| return criar_protocolo_padrao() |
| except Exception as e: |
| print(f"⚠️ Erro ao carregar protocolo: {e}") |
| return "[]" |
|
|
| def criar_protocolo_padrao(): |
| """Cria protocolo padrão se não existir""" |
| protocolo_minimo = [ |
| {"fase": 0, "nome": "ESTADO_INICIAL", "modelo": "flash", "tipo_saida": "json", |
| "missao": "Registre o input inicial em JSON com: PERGUNTA_NORMALIZADA, CONTEXTO_IDENTIFICADO, TIPO_CASO, PARTES_ENVOLVIDAS, INCERTEZA_INICIAL (0-1), EVIDENCIAS_DISPONIVEIS."}, |
|
|
| {"fase": 1, "nome": "MAPEAMENTO_OBJETIVO", "modelo": "flash", "tipo_saida": "json", |
| "missao": "Analise o estado inicial e retorne JSON com: OBJETIVO_CLARIFICADO, TIPO_RESPOSTA_ESPERADA, CRITERIOS_VALIDACAO (3-5), PRESSUPOSTOS_IMPLICITOS, PERGUNTAS_DERIVADAS, METODOLOGIA_SUGERIDA."}, |
|
|
| {"fase": 2, "nome": "INVENTARIO_EPISTEMICO", "modelo": "flash", "tipo_saida": "json", |
| "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."}, |
|
|
| {"fase": 3, "nome": "GERACAO_CENARIOS", "modelo": "flash", "tipo_saida": "json", |
| "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."}, |
|
|
| {"fase": 4, "nome": "ANALISE_CONTRAFACTUAL", "modelo": "flash", "tipo_saida": "json", |
| "missao": "Teste 'E SE?' para cada suposição. Retorne JSON: VARIAVEIS_CRITICAS, MAPA_CAUSAL, PONTOS_INFLEXAO (que se falsos invertem conclusões), TESTES_RECOMENDADOS."}, |
|
|
| {"fase": 5, "nome": "CADEIA_INVESTIGATIVA", "modelo": "flash", "tipo_saida": "json", |
| "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."}, |
|
|
| {"fase": 6, "nome": "COLETA_ATUALIZACAO_BAYESIANA", "modelo": "pro", "tipo_saida": "json", |
| "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."}, |
|
|
| {"fase": 7, "nome": "TESTE_CRUCIALIDADE", "modelo": "pro", "tipo_saida": "json", |
| "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)."}, |
|
|
| {"fase": 8, "nome": "GERACAO_RESPOSTA", "modelo": "pro", "tipo_saida": "json", |
| "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."}, |
|
|
| {"fase": 9, "nome": "RELATORIO_FINAL", "modelo": "pro", "tipo_saida": "texto", |
| "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)"} |
| ] |
|
|
| protocolo_json = json.dumps(protocolo_minimo, ensure_ascii=False, indent=2) |
|
|
| try: |
| with open(ARQUIVO_CONFIG, "w", encoding="utf-8") as f: |
| f.write(protocolo_json) |
| print(f"✅ Protocolo padrão criado: {ARQUIVO_CONFIG}") |
| except Exception as e: |
| print(f"⚠️ Não foi possível salvar protocolo: {e}") |
|
|
| return protocolo_json |
|
|
| def salvar_protocolo(conteudo): |
| """Salva e valida JSON do protocolo""" |
| try: |
| protocolo = json.loads(conteudo) |
|
|
| |
| if not isinstance(protocolo, list): |
| return "❌ Erro: Protocolo deve ser um array JSON" |
|
|
| fases = {f.get('fase') for f in protocolo if isinstance(f, dict) and 'fase' in f} |
| if len(fases) < 10: |
| return f"⚠️ Aviso: Protocolo tem apenas {len(fases)} fases (esperado: 10)" |
|
|
| with open(ARQUIVO_CONFIG, "w", encoding="utf-8") as f: |
| f.write(conteudo) |
|
|
| return f"✅ Protocolo salvo com sucesso ({len(fases)} fases)" |
| except json.JSONDecodeError as e: |
| return f"❌ Erro no JSON: {str(e)}" |
| except Exception as e: |
| return f"❌ Erro: {str(e)}" |
|
|
| def ler_anexo(arquivo): |
| """Lê arquivo anexado""" |
| if arquivo is None: |
| return "" |
| try: |
| with open(arquivo.name, "r", encoding="utf-8") as f: |
| conteudo = f.read() |
| return f"\n\n--- ANEXO: {os.path.basename(arquivo.name)} ---\n{conteudo}\n--- FIM ANEXO ---\n" |
| except Exception as e: |
| return f"\n[ERRO ao ler anexo: {e}]\n" |
|
|
| |
|
|
| def executar_fase(timeline, config, fase_atual, total_fases): |
| """Executa uma fase do protocolo""" |
|
|
| |
| modelo = model_pro if config.get("modelo") == "pro" else model_flash |
|
|
| |
| contexto = json.dumps(timeline, ensure_ascii=False, indent=2) |
|
|
| |
| prompt = f"""--- TIMELINE COMPLETA --- |
| {contexto} |
| |
| --- FASE ATUAL --- |
| AGENTE: {config['nome']} |
| FASE: {fase_atual}/{total_fases} |
| TIPO_SAIDA: {config['tipo_saida']} |
| |
| --- MISSÃO --- |
| {config['missao']} |
| |
| --- INSTRUÇÕES --- |
| 1. Analise TODA a timeline acima |
| 2. Execute sua missão com rigor |
| 3. {"Retorne APENAS JSON válido" if config['tipo_saida'] == 'json' else "Retorne texto em Markdown"} |
| 4. Seja epistemicamente honesto sobre incertezas |
| """ |
|
|
| log = f"\n🔸 Fase {fase_atual}: {config['nome']}" |
|
|
| try: |
| inicio = time.time() |
|
|
| |
| max_tentativas = 2 |
| for tentativa in range(max_tentativas): |
| try: |
| resp = modelo.generate_content( |
| prompt, |
| generation_config={ |
| "temperature": 0.3 if config['tipo_saida'] == 'json' else 0.7, |
| "max_output_tokens": 8000 |
| } |
| ) |
| output_raw = resp.text |
| break |
| except Exception as e: |
| if tentativa == max_tentativas - 1: |
| raise e |
| time.sleep(2) |
|
|
| tempo = time.time() - inicio |
|
|
| |
| if config['tipo_saida'] == 'json': |
| |
| output_limpo = output_raw.strip() |
| output_limpo = output_limpo.replace('```json', '').replace('```', '') |
| content = json.loads(output_limpo) |
| else: |
| content = output_raw |
|
|
| log += f" ✓ ({tempo:.1f}s)" |
|
|
| return { |
| "role": "assistant", |
| "agent": config['nome'], |
| "fase": config['fase'], |
| "content": content |
| }, log, True |
|
|
| except Exception as e: |
| log += f" ✗ ERRO: {str(e)[:100]}" |
| return { |
| "role": "system", |
| "agent": config['nome'], |
| "fase": config['fase'], |
| "error": str(e) |
| }, log, False |
|
|
| |
|
|
| def orquestrador(texto, arquivo, history, json_config): |
| """Orquestra execução do protocolo de 10 fases com loop iterativo""" |
|
|
| |
| anexo = ler_anexo(arquivo) |
| full_input = f"{texto}\n{anexo}".strip() |
|
|
| if not full_input: |
| yield history, {}, "⚠️ Nenhum input fornecido." |
| return |
|
|
| |
| history = history + [[texto + (" 📎" if arquivo else ""), None]] |
|
|
| try: |
| protocolo = json.loads(json_config) |
|
|
| |
| fases_disponiveis = {f['fase'] for f in protocolo if isinstance(f, dict) and 'fase' in f} |
| if 7 not in fases_disponiveis: |
| history[-1][1] = "❌ **Erro: Fase 7 não encontrada no protocolo**\n\nO protocolo deve ter 10 fases (0-9)." |
| yield history, {}, "Erro: Fase 7 ausente" |
| return |
|
|
| except Exception as e: |
| history[-1][1] = f"❌ **Erro no JSON de Configuração**\n\n```\n{str(e)}\n```" |
| yield history, {}, f"Erro JSON: {e}" |
| return |
|
|
| |
| timeline = [{ |
| "role": "user", |
| "content": full_input, |
| "timestamp": datetime.now().isoformat() |
| }] |
|
|
| logs = f"🚀 INÍCIO: {datetime.now().strftime('%H:%M:%S')}\n" |
| logs += f"📋 Protocolo: {len(protocolo)} fases\n" |
| logs += f"🔄 Max iterações: {MAX_ITERACOES}\n" |
| logs += "=" * 60 + "\n" |
|
|
| history[-1][1] = "⏳ **Iniciando Protocolo Epistêmico...**\n\nFase 0/10: Estado Inicial" |
| yield history, timeline, logs |
|
|
| |
| fases_sequenciais = [f for f in protocolo if f['fase'] < 7] |
|
|
| for cfg in fases_sequenciais: |
| fase_num = cfg['fase'] |
| history[-1][1] = f"⚙️ **Fase {fase_num}/10: {cfg['nome']}**\n\nProcessando..." |
| yield history, timeline, logs |
|
|
| resultado, log_add, sucesso = executar_fase(timeline, cfg, fase_num, 10) |
| timeline.append(resultado) |
| logs += log_add + "\n" |
|
|
| if not sucesso: |
| history[-1][1] = f"❌ **Erro na Fase {fase_num}**\n\n{resultado.get('error', 'Erro desconhecido')}" |
| yield history, timeline, logs |
| return |
|
|
| yield history, timeline, logs |
|
|
| |
| iteracao = 0 |
| fase_7_passou = False |
|
|
| |
| cfg_fase7 = None |
| for f in protocolo: |
| if f.get('fase') == 7: |
| cfg_fase7 = f |
| break |
|
|
| if cfg_fase7 is None: |
| history[-1][1] = "❌ **Erro: Fase 7 (TESTE_CRUCIALIDADE) não encontrada**" |
| yield history, timeline, logs |
| return |
|
|
| while iteracao < MAX_ITERACOES and not fase_7_passou: |
| iteracao += 1 |
| logs += f"\n{'='*60}\n🔄 ITERAÇÃO {iteracao}/{MAX_ITERACOES}\n{'='*60}\n" |
|
|
| |
| if iteracao > 1: |
| history[-1][1] = f"🔄 **Iteração {iteracao}**: Refinando cenários (Fases 3-6)..." |
| yield history, timeline, logs |
|
|
| fases_refinamento = [f for f in protocolo if 3 <= f['fase'] < 7] |
| for cfg in fases_refinamento: |
| resultado, log_add, sucesso = executar_fase(timeline, cfg, cfg['fase'], 10) |
| timeline.append(resultado) |
| logs += log_add + "\n" |
|
|
| if not sucesso: |
| history[-1][1] = f"❌ **Erro no refinamento**" |
| yield history, timeline, logs |
| return |
|
|
| yield history, timeline, logs |
|
|
| |
| history[-1][1] = f"🧪 **Fase 7/10: Teste de Crucialidade** (Iteração {iteracao})\n\nValidando..." |
| yield history, timeline, logs |
|
|
| resultado, log_add, sucesso = executar_fase(timeline, cfg_fase7, 7, 10) |
| timeline.append(resultado) |
| logs += log_add + "\n" |
|
|
| if not sucesso: |
| history[-1][1] = f"❌ **Erro na Fase 7**" |
| yield history, timeline, logs |
| return |
|
|
| |
| teste_resultado = resultado.get('content', {}) |
| fase_7_passou = teste_resultado.get('PASSOU', False) |
| score = teste_resultado.get('SCORE_CRUCIALIDADE', 0) |
|
|
| if fase_7_passou: |
| logs += f"✅ TESTE PASSOU (score: {score:.3f})\n" |
| history[-1][1] = f"✅ **Validação Aprovada!**\n\nScore: {score:.3f}" |
| else: |
| logs += f"⚠️ TESTE FALHOU (score: {score:.3f})\n" |
| fragilidades = teste_resultado.get('FRAGILIDADES_IDENTIFICADAS', []) |
| history[-1][1] = f"⚠️ **Validação Reprovada** (Score: {score:.3f})\n\nFragilidades:\n" + "\n".join(f"- {f}" for f in fragilidades[:5]) |
|
|
| yield history, timeline, logs |
|
|
| if not fase_7_passou: |
| logs += f"\n⚠️ Limite de iterações atingido ({MAX_ITERACOES}). Prosseguindo com melhor resultado.\n" |
|
|
| |
| fases_finais = [f for f in protocolo if f['fase'] >= 8] |
|
|
| for cfg in fases_finais: |
| fase_num = cfg['fase'] |
| history[-1][1] = f"📝 **Fase {fase_num}/10: {cfg['nome']}**\n\nGerando..." |
| yield history, timeline, logs |
|
|
| resultado, log_add, sucesso = executar_fase(timeline, cfg, fase_num, 10) |
| timeline.append(resultado) |
| logs += log_add + "\n" |
|
|
| if not sucesso: |
| history[-1][1] = f"❌ **Erro na Fase {fase_num}**" |
| yield history, timeline, logs |
| return |
|
|
| |
| if cfg['fase'] == 9 and cfg['tipo_saida'] == 'texto': |
| final_response = resultado.get('content', 'Erro ao gerar relatório') |
| history[-1][1] = final_response |
|
|
| yield history, timeline, logs |
|
|
| |
| logs += "\n" + "=" * 60 + "\n" |
| logs += f"✅ CONCLUÍDO: {datetime.now().strftime('%H:%M:%S')}\n" |
| logs += f"📊 Total de fases executadas: {len([t for t in timeline if t.get('fase') is not None])}\n" |
| logs += f"🔄 Iterações necessárias: {iteracao}\n" |
|
|
| yield history, timeline, logs |
|
|
| |
|
|
| def criar_ui(): |
| """Cria interface Gradio""" |
|
|
| css = """ |
| footer {display: none !important;} |
| .contain {border: none !important;} |
| """ |
|
|
| config_inicial = carregar_protocolo() |
|
|
| with gr.Blocks( |
| title="🔬 Investigador Epistêmico", |
| css=css, |
| theme=gr.themes.Soft() |
| ) as app: |
|
|
| gr.Markdown(""" |
| # 🔬 Investigador Epistêmico |
| ### Protocolo Causal de 10 Fases | Resolução do Paradoxo de Ménon |
| """) |
|
|
| with gr.Tabs(): |
|
|
| |
| with gr.Tab("💬 Investigação"): |
| chatbot = gr.Chatbot( |
| label="", |
| show_label=False, |
| height=650, |
| show_copy_button=True, |
| render_markdown=True, |
| type="tuples" |
| ) |
|
|
| with gr.Row(): |
| with gr.Column(scale=10): |
| txt_input = gr.Textbox( |
| show_label=False, |
| placeholder="Descreva o caso, pergunta ou situação a investigar...", |
| lines=2, |
| max_lines=8, |
| container=False |
| ) |
|
|
| with gr.Column(scale=1, min_width=60): |
| file_input = gr.UploadButton( |
| "📎", |
| file_types=[".txt", ".md", ".csv", ".json", ".pdf"], |
| size="sm" |
| ) |
|
|
| with gr.Column(scale=1, min_width=100): |
| btn_enviar = gr.Button("🚀 Investigar", variant="primary", size="sm") |
|
|
| file_status = gr.Markdown("", visible=True) |
| file_input.upload( |
| lambda x: f"📎 **Anexo:** {os.path.basename(x.name)}", |
| file_input, |
| file_status |
| ) |
|
|
| |
| with gr.Tab("🕵️ Depuração"): |
| gr.Markdown("### Timeline Completa (DNA da Investigação)") |
| output_timeline = gr.JSON(label="Timeline") |
|
|
| gr.Markdown("### Logs do Sistema") |
| output_logs = gr.Textbox(label="Logs", lines=25, max_lines=50) |
|
|
| |
| with gr.Tab("⚙️ Configuração"): |
| gr.Markdown(""" |
| ### Protocolo Epistêmico (JSON) |
| |
| **Estrutura de cada fase:** |
| - `fase`: Número da fase (0-9) |
| - `nome`: Identificador da fase |
| - `modelo`: "flash" ou "pro" |
| - `tipo_saida`: "json" ou "texto" |
| - `missao`: Instruções detalhadas |
| |
| **Fluxo:** 0→1→2→3→4→5→6→7(teste)→[volta 3 se falhar]→8→9 |
| """) |
|
|
| with gr.Row(): |
| btn_salvar = gr.Button("💾 Salvar Protocolo", variant="primary") |
| label_salvar = gr.Label(show_label=False) |
|
|
| code_protocolo = gr.Code( |
| value=config_inicial, |
| language="json", |
| label="protocolo_epistemico_forense.json", |
| lines=30 |
| ) |
|
|
| btn_salvar.click(salvar_protocolo, code_protocolo, label_salvar) |
|
|
| |
| triggers = [btn_enviar.click, txt_input.submit] |
|
|
| for trigger in triggers: |
| trigger( |
| orquestrador, |
| inputs=[txt_input, file_input, chatbot, code_protocolo], |
| outputs=[chatbot, output_timeline, output_logs] |
| ).then( |
| lambda: (None, ""), |
| outputs=[txt_input, file_status] |
| ) |
|
|
| return app |
|
|
| |
|
|
| if __name__ == "__main__": |
| print("=" * 80) |
| print("🔬 INVESTIGADOR EPISTÊMICO - Protocolo Causal v28") |
| print("=" * 80) |
| print(f"📋 Arquivo de configuração: {ARQUIVO_CONFIG}") |
| print(f"🔄 Máximo de iterações: {MAX_ITERACOES}") |
| print("=" * 80) |
|
|
| app = criar_ui() |
| app.launch( |
| server_name="0.0.0.0", |
| server_port=7860, |
| share=False, |
| show_error=True |
| ) |
|
|