Update app.py
Browse files
app.py
CHANGED
|
@@ -1,67 +1,364 @@
|
|
| 1 |
-
|
| 2 |
-
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
"modelo": "meta-llama/llama-4-maverick-17b-128e-instruct",
|
| 6 |
-
",
|
| 7 |
-
"tipo_saida": "json",
|
| 8 |
-
"missao": "INICIAR PELO RACIOCÍNIO DO USUÁRIO + CAPTURA BRUTA.\n\nObjetivos:\n- Ajudar a pessoa a organizar, com calma e clareza, o cenário de lesão, violência, acidente ou morte.\n- Capturar fatos básicos, histórico mínimo de vida, contexto do dano e nexo causal percebido.\n- Forçar o usuário a pensar se já tem alguma ideia de valor justo antes de acionar os outros agentes.\n- Detectar dúvida relevante e, se necessário, disparar STOP em vez de JSON.\n\nRegras STOP (fora de escopo):\n- Se a pergunta do usuário não tiver relação com: (a) casos de dano/indenização, (b) as missões/agentes, ou (c) o histórico mínimo do caso atual,\n ENTÃO o modelo NÃO responde ao conteúdo da pergunta.\n- Em vez disso, responde apenas:\n\n STOP: infelizmente não posso responder a essa pergunta,\n pois meu objetivo aqui é apenas ajudar a analisar fatos,\n nexo causal e valor de indenizações em casos de lesão,\n violência, acidente ou morte, seguindo as missões do protocolo.\n\n- Nessa situação, NUNCA retornar JSON; apenas o texto acima (ou uma versão equivalente, breve).\n\nRegras STOP (dúvida crítica):\n- Se a pergunta estiver dentro do contexto do caso, mas houver dúvida alta sobre ponto crítico (nexo causal, gravidade da lesão/morte, valor-base, identidade das partes),\n ENTÃO o agente pode responder diretamente com STOP + até 3 perguntas objetivas para esclarecimento, SEM JSON.\n- Exemplo de formato:\n\n STOP: preciso que você esclareça até 3 pontos antes de continuar:\n\n 1) ...\n 2) ...\n 3) ... (opcional)\n\nCampos de saída (quando NÃO for caso de STOP):\n1. PERGUNTA_NORMALIZADA\n2. CONTEXTO_IDENTIFICADO {\n tipo_caso: \"LESAO_LEVE|GRAVE|GRAVISSIMA|MORTE|OUTRO\",\n foro: \"civel|criminal|trabalhista|outro\"\n }\n3. RESUMO_FATOS_INICIAIS\n4. EXPECTATIVA_VALOR_INICIAL {\n sabe_valor: true/false,\n faixa_sugerida: { min: null, max: null },\n justificativa_intuitiva: \"...\"\n }\n5. NIVEL_CERTEZA_USUARIO (0-10)\n6. DADOS_MINIMOS_NEXO {\n houve_evento: true/false,\n houve_dano: true/false,\n sente_relacao_causa_efeito: true/false\n }\n7. AFETO_RAW { amor: 0.0, medo: 0.0, paixa: 0.0 }\n8. FELICIDADE_LATENTE\n9. SINAIS_DUVIDA { contradicoes: [], lacunas_obvias: [] }\n10. DUVIDA_DETECTADA { true|false }\n11. TESTE_REFLEXAO {\n perguntas: [\n \"O que exatamente aconteceu, em ordem temporal?\",\n \"Se você fosse juiz, que valor consideraria minimamente justo e por quê?\",\n \"Existe algum caso parecido que você conhece? O que aconteceu lá?\"\n ],\n instrucoes_ao_usuario: \"Responda com calma a cada pergunta. O sistema só seguirá quando houver clareza mínima sobre fatos e sua própria expectativa.\"\n }\n12. PROXIMA_ACAO { \"PERGUNTAR_USUARIO\" | \"AVANCAR_FASE_1\" }\n\nRegras gerais:\n- Se DUVIDA_DETECTADA == true em ponto crítico → considerar usar STOP em vez de JSON, conforme regras acima.\n- Se for possível responder com JSON estruturado sem comprometer segurança/claridade, retornar o JSON completo."
|
| 9 |
-
},
|
| 10 |
-
{
|
| 11 |
-
"fase": 1,
|
| 12 |
-
"nome": "HISTORICO_VIDA_E_REDE_AFETIVA",
|
| 13 |
-
"modelo": "meta-llama/llama-4-maverick-17b-128e-instruct",
|
| 14 |
-
",
|
| 15 |
-
"tipo_saida": "json",
|
| 16 |
-
"missao": "MAPEAR VIDA, PAPÉIS E REDE AFETIVA PARA VALOR_DA_VIDA/DIGNIDADE.\n\nObjetivos:\n- Clarificar quem é/era a vítima na sua biografia: trabalho, família, sonhos, vulnerabilidades.\n- Entender rede de dependência e afeto (cônjuge, filhos, pais, etc.).\n- Preparar terreno para valorar a perda/lesão na dimensão existencial.\n\nJSON:\n1. PERFIL_VITIMA { idade, genero, profissao, renda_media, estado_civil }\n2. PAPEL_SOCIAL_CENTRAL { provedor_familiar, cuidador, estudante, aposentado, outro }\n3. DEPENDENTES_DIRETOS { quantidade, tipos: [filhos, pais, conjuges, outros] }\n4. PROJETOS_DE_VIDA { curto_prazo, longo_prazo }\n5. VULNERABILIDADE_PREVIA { pobreza, doenca_preexistente, deficiencia, nenhum }\n6. REDE_AFETIVA { lista_pessoas_chave, grau_dependencia_emocional: 0-10 }\n7. IMPACTO_POTENCIAL_PERDA { descricao_curta, intensidade: 0-10 }\n8. COERENCIA_COM_CONTEXTO_INICIAL { coerente|parcial|incoerente }\n9. LACUNAS_HISTORICO_VIDA [lista]\n10. SUGESTAO_PERGUNTAS_ADICIONAIS_USUARIO [lista]"
|
| 17 |
-
},
|
| 18 |
-
{
|
| 19 |
-
"fase": 2,
|
| 20 |
-
"nome": "FATO_DANO_E_NEXO_CAUSAL",
|
| 21 |
-
"modelo": "meta-llama/llama-4-maverick-17b-128e-instruct",
|
| 22 |
-
",
|
| 23 |
-
"tipo_saida": "json",
|
| 24 |
-
"missao": "CLAREAR FATO, DANO E NEXO CAUSAL DE FORMA ESTRUTURADA.\n\nObjetivos:\n- Organizar, de forma lógica, o que aconteceu, que dano houve e qual nexo alegado.\n- Separar fato objetivo de percepção subjetiva.\n\nJSON:\n1. FATO_GERADOR_LINEAR { linha_do_tempo: [eventos_em_ordem] }\n2. TIPO_EVENTO { acidente_transito, erro_medico, violencia_domestica, crime_intencional, outro }\n3. DANO_CORPORAL_CLASSIFICACAO { LESAO_LEVE|GRAVE|GRAVISSIMA|MORTE|SEM_INFORMACAO }\n4. DANO_CONCRETO_DESCRITO { lesoes, sequelas, morte, dano_estetico }\n5. PROVAS_DISPONIVEIS { laudos_medicos, boletim_ocorrencia, fotos, videos, testemunhas }\n6. NARRATIVA_NEXO_CAUSAL { em_ate_500_caracteres }\n7. GRAU_CONFIANCA_NEXO_DECLARADO (0-10)\n8. AMBIGUIDADES_IDENTIFICADAS { sim|nao, detalhes }\n9. ITENS_QUE_EXIGEM_PERICIA { lista }\n10. COERENCIA_FATO_NEXO { alta|media|baixa }"
|
| 25 |
-
},
|
| 26 |
-
{
|
| 27 |
-
"fase": 3,
|
| 28 |
-
"nome": "CONTEXTO_E_CONSEQUENCIAS_DO_DANO",
|
| 29 |
-
"modelo": "meta-llama/llama-4-maverick-17b-128e-instruct",
|
| 30 |
-
",
|
| 31 |
-
"tipo_saida": "json",
|
| 32 |
-
"missao": "MAPEAR CONTEXTO E CONSEQUÊNCIAS MATERIAIS, MORAIS E EXISTENCIAIS.\n\nJSON:\n1. CONSEQUENCIAS_SAUDE { dor_cronica, limitacao_fisica, dependencia_terceiros, tratamento_longo_prazo }\n2. CONSEQUENCIAS_TRABALHO { dias_afastamento, perda_emprego, rebaixamento_funcao, incapacidade_parcial, incapacidade_total }\n3. CONSEQUENCIAS_FAMILIA { rompimento_relacoes, sobrecarga_cuidador, impacto_filhos }\n4. CONSEQUENCIAS_PSICOLOGICAS { ansiedade, depressao, TEPT, medo_constante }\n5. PERDA_QUALIDADE_VIDA { nota: 0-10, justificativa }\n6. DESCRICAO_DANO_MORAL_SUBJETIVO { humilhacao, medo_de_morrer, perda_dignidade, luto }\n7. SINTONIA_COM_HISTORICO_VIDA { sim|parcial|nao }\n8. PONTOS_FORTES_DANO_MORAL { lista }\n9. PONTOS_FRACOS_DANO_MORAL { lista }"
|
| 33 |
-
},
|
| 34 |
-
{
|
| 35 |
-
"fase": 4,
|
| 36 |
-
"nome": "GRAVIDADE_DANO_E_FAIXA_STJ",
|
| 37 |
-
"modelo": "meta-llama/llama-4-maverick-17b-128e-instruct",
|
| 38 |
-
",
|
| 39 |
-
"tipo_saida": "json",
|
| 40 |
-
"missao": "CONECTAR GRAVIDADE DA LESÃO ÀS FAIXAS JURISPRUDENCIAIS DE DANO MORAL.\n\nJSON:\n1. CLASSIFICACAO_JURIDICA_LESAO { LEVE|GRAVE|GRAVISSIMA|MORTE }\n2. CRITERIOS_USADOS { dias_incapacidade, risco_vida, sequela_permanente, incapacidade_trabalho, deformidade }\n3. FAIXA_REFERENCIA_STJ_SM { min_sm, max_sm, mediana_sm }\n4. FAIXA_REFERENCIA_STJ_RS { min_rs, max_rs, mediana_rs }\n5. AJUSTES_POR_CASO_CONCRETO { fatores_agravantes, fatores_atenuantes }\n6. FAIXA_AJUSTADA_RS { min_rs, max_rs }\n7. NOTA_SOBRE_TETOS_E_PISOS { comentario_curto }"
|
| 41 |
-
},
|
| 42 |
-
{
|
| 43 |
-
"fase": 5,
|
| 44 |
-
"nome": "CENARIOS_VALOR_VIDA",
|
| 45 |
-
"modelo": "meta-llama/llama-4-maverick-17b-128e-instruct",
|
| 46 |
-
",
|
| 47 |
-
"tipo_saida": "json",
|
| 48 |
-
"missao": "GERAR CENÁRIOS DE VALOR (PRINCIPAL/ALTERNATIVO/IMPROVÁVEL) PARA O DANO À VIDA/DIGNIDADE.\n\nJSON (lista de 3 cenários):\n[\n {\n \"ID\": \"PRINCIPAL\",\n \"DESCRICAO\": \"cenário mais provável com base nas provas e na jurisprudência\",\n \"PRIOR\": 0.6,\n \"VALOR_SUGERIDO_RS\": 0,\n \"SUPOSICOES_CHAVE\": [\"nexo_causal_reconhecido\"],\n \"COMPATIBILIDADE_FATOS\": \"alta\",\n \"DANO_MORAL_RS\": { \"min\": 0, \"max\": 0, \"mediano\": 0 },\n \"DANO_MATERIAL_RS\": 0,\n \"VALOR_TOTAL_RS\": { \"min\": 0, \"max\": 0, \"mediano\": 0 }\n },\n {\n \"ID\": \"ALTERNATIVO\",\n \"PRIOR\": 0.3,\n \"...\": \"estrutura_análoga\"\n },\n {\n \"ID\": \"IMPROVAVEL\",\n \"PRIOR\": 0.1,\n \"...\": \"estrutura_análoga\"\n }\n]\n\nRegra: soma dos PRIORES = 1.0."
|
| 49 |
-
},
|
| 50 |
-
{
|
| 51 |
-
"fase": 6,
|
| 52 |
-
"nome": "TESTE_JUSTICA_DO_VALOR",
|
| 53 |
-
"modelo": "meta-llama/llama-4-maverick-17b-128e-instruct",
|
| 54 |
-
",
|
| 55 |
-
"tipo_saida": "json",
|
| 56 |
-
"missao": "EXPLICAR POR QUE O VALOR ESCOLHIDO É JUSTO E POR QUE OUTROS NÃO SÃO.\n\nJSON:\n1. VALOR_RECOMENDADO_RS\n2. CENARIO_BASE { PRINCIPAL|ALTERNATIVO|IMPROVAVEL }\n3. POR_QUE_ESTE_VALOR\n4. POR_QUE_NAO_VALORES_MENORES [lista]\n5. POR_QUE_NAO_VALORES_MAIORES [lista]\n6. TESTE_PROPORCIONALIDADE { passou|falhou, justificativa }\n7. TESTE_ENRIQUECIMENTO_SEM_CAUSA { passou|falhou, justificativa }\n8. TESTE_REPARACAO_MINIMA_DIGNA { passou|falhou, justificativa }\n9. MARGEM_DISCRICIONARIEDADE_RS { min, max }"
|
| 57 |
-
},
|
| 58 |
-
{
|
| 59 |
-
"fase": 7,
|
| 60 |
-
"nome": "RELATORIO_VALOR_VIDA",
|
| 61 |
-
"modelo": "meta-llama/llama-4-maverick-17b-128e-instruct",
|
| 62 |
-
",
|
| 63 |
-
"tipo_saida": "json",
|
| 64 |
-
"missao": "ponto de (STOP) GERAR TEXTO FINAL EXPLICANDO O VALOR DA INDENIZAÇÃO À LUZ DA VIDA, HISTÓRIA E GRAVIDADE DO DANO.\n\nEstrutura mínima:\n1. RESUMO_FATOS_E_NEXO\n2. QUEM_ERA_A_VITIMA_E_O_QUE_PERDEU\n3. GRAVIDADE_DA_LESÃO_E_FAIXA_JURISPRUDENCIAL\n4. CENARIO_ESCOLHIDO_E_VALOR_RECOMENDADO\n5. POR_QUE_ESTE_VALOR\n6. POR_QUE_NAO_MENOS\n7. POR_QUE_NAO_MAIS\n8. OBSERVACOES_SOBRE_DUVIDAS_E_LIMITES_DA_ANALISE"
|
| 65 |
-
}
|
| 66 |
-
]
|
| 67 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# ╔════════════════════════════════════════════════════════════════════════════╗
|
| 2 |
+
# ║ PIPELINE v35: GROQ ONLY + DEBUG LOGS (FIX) ║
|
| 3 |
+
# ║ Usando apenas Groq API (sem Gemini) ║
|
| 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 |
+
from groq import Groq
|
| 13 |
+
|
| 14 |
+
# ==================== 1. CONFIGURAÇÃO ====================
|
| 15 |
+
groq_key = os.getenv("GROQ_API_KEY", "SUA_GROQ_KEY_AQUUI")
|
| 16 |
+
groq_client = Groq(api_key=groq_key)
|
| 17 |
+
|
| 18 |
+
ARQUIVO_CONFIG = "protocolo.json"
|
| 19 |
+
ARQUIVO_HELP = "help.md"
|
| 20 |
+
DELAY_ENTRE_AGENTES = 5 # 3 minutos
|
| 21 |
+
|
| 22 |
+
print("🚀 App inicializada - GROQ ONLY")
|
| 23 |
+
print(f" ✅ Groq: {'OK - API configurada' if groq_key != 'SUA_GROQ_KEY_AQUUI' else '⚠️ usando key placeholder'}")
|
| 24 |
+
|
| 25 |
+
# ==================== 2. UTILIDADES ====================
|
| 26 |
+
def carregar_protocolo():
|
| 27 |
+
try:
|
| 28 |
+
with open(ARQUIVO_CONFIG, "r", encoding="utf-8") as f:
|
| 29 |
+
return f.read()
|
| 30 |
+
except Exception as e:
|
| 31 |
+
print(f"❌ Erro carregar_protocolo: {e}")
|
| 32 |
+
return "[]"
|
| 33 |
+
|
| 34 |
+
def salvar_protocolo(conteudo):
|
| 35 |
+
try:
|
| 36 |
+
json.loads(conteudo)
|
| 37 |
+
with open(ARQUIVO_CONFIG, "w", encoding="utf-8") as f:
|
| 38 |
+
f.write(conteudo)
|
| 39 |
+
print(f"💾 Protocolo salvo: {len(json.loads(conteudo))} agentes")
|
| 40 |
+
return "✅ Protocolo salvo com sucesso"
|
| 41 |
+
except Exception as e:
|
| 42 |
+
print(f"❌ Erro salvar_protocolo: {e}")
|
| 43 |
+
return f"❌ Erro JSON: {str(e)}"
|
| 44 |
+
|
| 45 |
+
def carregar_help():
|
| 46 |
+
try:
|
| 47 |
+
with open(ARQUIVO_HELP, "r", encoding="utf-8") as f:
|
| 48 |
+
return f.read()
|
| 49 |
+
except:
|
| 50 |
+
return "# Help não encontrado\n\nCrie um arquivo help.md na raiz do projeto."
|
| 51 |
+
|
| 52 |
+
def ler_anexo(arquivo):
|
| 53 |
+
if arquivo is None:
|
| 54 |
+
return ""
|
| 55 |
+
try:
|
| 56 |
+
with open(arquivo.name, "r", encoding="utf-8") as f:
|
| 57 |
+
conteudo = f.read()
|
| 58 |
+
print(f"📎 Anexo lido: {os.path.basename(arquivo.name)} ({len(conteudo)} chars)")
|
| 59 |
+
return f"\n\n[ANEXO: {os.path.basename(arquivo.name)}]\n{conteudo}\n[FIM ANEXO]\n"
|
| 60 |
+
except Exception as e:
|
| 61 |
+
print(f"❌ Erro ler_anexo {arquivo.name}: {e}")
|
| 62 |
+
return ""
|
| 63 |
+
|
| 64 |
+
def verificar_stop(texto):
|
| 65 |
+
if not texto:
|
| 66 |
+
return False
|
| 67 |
+
stop_detectado = bool(re.search(r'\bstop\b', str(texto), re.IGNORECASE))
|
| 68 |
+
print(f"🛑 STOP detectado? {stop_detectado} em '{str(texto)[:100]}...'")
|
| 69 |
+
return stop_detectado
|
| 70 |
+
|
| 71 |
+
# ==================== 3. ENGINE DE EXECUÇÃO (GROQ) ====================
|
| 72 |
+
def executar_no(timeline, config):
|
| 73 |
+
print(f"\n🔥 === EXECUTANDO {config['nome']} ===")
|
| 74 |
+
modelo = config.get('modelo', 'meta-llama/llama-4-maverick-17b-128e-instruct')
|
| 75 |
+
print(f" Modelo Groq: {modelo}")
|
| 76 |
+
|
| 77 |
+
try:
|
| 78 |
+
inicio = time.time()
|
| 79 |
+
|
| 80 |
+
# Converte timeline para formato messages do Groq
|
| 81 |
+
messages = []
|
| 82 |
+
|
| 83 |
+
# System message com missão do agente
|
| 84 |
+
messages.append({
|
| 85 |
+
"role": "system",
|
| 86 |
+
"content": f"AGENTE: {config['nome']}\nMISSÃO: {config['missao']}"
|
| 87 |
+
})
|
| 88 |
+
|
| 89 |
+
# Adiciona timeline como contexto
|
| 90 |
+
for msg in timeline:
|
| 91 |
+
role = msg.get('role')
|
| 92 |
+
if role in ['user', 'assistant']:
|
| 93 |
+
content = msg.get('content', '')
|
| 94 |
+
# Se content for dict/list, serializa
|
| 95 |
+
if isinstance(content, (dict, list)):
|
| 96 |
+
content = json.dumps(content, ensure_ascii=False)
|
| 97 |
+
messages.append({
|
| 98 |
+
"role": role,
|
| 99 |
+
"content": str(content)
|
| 100 |
+
})
|
| 101 |
+
|
| 102 |
+
print(f"📤 Groq messages: {len(messages)} mensagens")
|
| 103 |
+
print(f" System: {messages[0]['content'][:200]}...")
|
| 104 |
+
if len(messages) > 1:
|
| 105 |
+
print(f" Last user: {messages[-1]['content'][:200]}...")
|
| 106 |
+
|
| 107 |
+
# Chama Groq API com streaming
|
| 108 |
+
completion = groq_client.chat.completions.create(
|
| 109 |
+
model=modelo,
|
| 110 |
+
messages=messages,
|
| 111 |
+
temperature=1,
|
| 112 |
+
max_completion_tokens=4096,
|
| 113 |
+
top_p=1,
|
| 114 |
+
stream=True,
|
| 115 |
+
stop=None
|
| 116 |
+
)
|
| 117 |
+
|
| 118 |
+
# Coleta resposta em streaming
|
| 119 |
+
out_raw = ""
|
| 120 |
+
for chunk in completion:
|
| 121 |
+
content_chunk = chunk.choices[0].delta.content or ""
|
| 122 |
+
out_raw += content_chunk
|
| 123 |
+
|
| 124 |
+
tempo_exec = time.time() - inicio
|
| 125 |
+
|
| 126 |
+
print(f"📥 OUTPUT GROQ ({len(out_raw)} chars, {tempo_exec:.2f}s):")
|
| 127 |
+
print(out_raw[:1000])
|
| 128 |
+
print("..." if len(out_raw) > 1000 else "")
|
| 129 |
+
|
| 130 |
+
# Parse JSON se necessário
|
| 131 |
+
content = out_raw
|
| 132 |
+
if config.get('tipo_saida') == 'json':
|
| 133 |
+
try:
|
| 134 |
+
# Remove markdown code blocks se presentes
|
| 135 |
+
cleaned = out_raw.strip()
|
| 136 |
+
|
| 137 |
+
content = json.loads(cleaned)
|
| 138 |
+
print("✅ JSON parseado com sucesso")
|
| 139 |
+
except Exception as parse_e:
|
| 140 |
+
print(f"⚠️ Erro parse JSON: {parse_e}")
|
| 141 |
+
print(f" Primeiros 200 chars: {cleaned[:200]}")
|
| 142 |
+
content = out_raw
|
| 143 |
+
|
| 144 |
+
print(f"⏱️ Tempo total: {tempo_exec:.2f}s")
|
| 145 |
+
|
| 146 |
+
return {
|
| 147 |
+
"role": "assistant",
|
| 148 |
+
"agent": config['nome'],
|
| 149 |
+
"content": content,
|
| 150 |
+
"raw": out_raw,
|
| 151 |
+
"tempo": tempo_exec
|
| 152 |
+
}, True
|
| 153 |
+
|
| 154 |
+
except Exception as e:
|
| 155 |
+
print(f"💥 ERRO GROQ: {str(e)}")
|
| 156 |
+
import traceback
|
| 157 |
+
traceback.print_exc()
|
| 158 |
+
return {
|
| 159 |
+
"role": "system",
|
| 160 |
+
"error": str(e),
|
| 161 |
+
"agent": config['nome']
|
| 162 |
+
}, False
|
| 163 |
+
|
| 164 |
+
# ==================== 4. ORQUESTRADOR ====================
|
| 165 |
+
def orquestrador(texto, anexos_list, history, json_config, contexto_objetivo):
|
| 166 |
+
print("\n" + "="*80)
|
| 167 |
+
print("🎬 INICIANDO ORQUESTRADOR - NOVA EXECUÇÃO")
|
| 168 |
+
print(f"📝 Input texto: '{texto[:200]}...' ({len(texto)} chars)")
|
| 169 |
+
print(f"📎 Anexos: {len(anexos_list)} arquivos")
|
| 170 |
+
|
| 171 |
+
if not texto.strip():
|
| 172 |
+
print("⚠️ Texto vazio, abortando")
|
| 173 |
+
yield history, []
|
| 174 |
+
return
|
| 175 |
+
|
| 176 |
+
history = history + [{"role": "user", "content": texto}]
|
| 177 |
+
|
| 178 |
+
try:
|
| 179 |
+
protocolo = json.loads(json_config)
|
| 180 |
+
print(f"🔗 Protocolo: {len(protocolo)} agentes")
|
| 181 |
+
for i, ag in enumerate(protocolo):
|
| 182 |
+
print(f" {i+1}. {ag['nome']} ({ag.get('modelo', 'default')})")
|
| 183 |
+
except Exception as e:
|
| 184 |
+
print(f"💥 Erro JSON config: {e}")
|
| 185 |
+
history.append({"role": "assistant", "content": f"❌ Erro no JSON de Configuração: {str(e)}"})
|
| 186 |
+
yield history, []
|
| 187 |
+
return
|
| 188 |
+
|
| 189 |
+
history.append({"role": "assistant", "content": ""})
|
| 190 |
+
|
| 191 |
+
# Contexto inicial
|
| 192 |
+
contexto_inicial = ""
|
| 193 |
+
if contexto_objetivo and contexto_objetivo.strip():
|
| 194 |
+
contexto_inicial += f"[OBJETIVO DO MODELO]\n{contexto_objetivo.strip()}\n[FIM OBJETIVO]\n\n"
|
| 195 |
+
print(f"🎯 Objetivo adicionado: {len(contexto_objetivo)} chars")
|
| 196 |
+
|
| 197 |
+
if anexos_list:
|
| 198 |
+
for anexo in anexos_list:
|
| 199 |
+
anexo_conteudo = ler_anexo(anexo)
|
| 200 |
+
if anexo_conteudo:
|
| 201 |
+
contexto_inicial += anexo_conteudo
|
| 202 |
+
|
| 203 |
+
full_input = f"{contexto_inicial}{texto}".strip()
|
| 204 |
+
timeline = [{"role": "user", "content": full_input}]
|
| 205 |
+
print(f"🌐 Timeline inicial: {len(full_input)} chars")
|
| 206 |
+
|
| 207 |
+
audit_data = []
|
| 208 |
+
|
| 209 |
+
# Loop pelos agentes
|
| 210 |
+
for idx, cfg in enumerate(protocolo):
|
| 211 |
+
print(f"\n{'='*50}")
|
| 212 |
+
print(f"🚀 FASE {idx+1}/{len(protocolo)}: {cfg['nome']}")
|
| 213 |
+
|
| 214 |
+
history[-1]["content"] = f"⏳ Chamando agente: **{cfg['nome']}**... Aguarde."
|
| 215 |
+
yield history, audit_data
|
| 216 |
+
|
| 217 |
+
print(f"💤 Delay entre agentes: {DELAY_ENTRE_AGENTES}s")
|
| 218 |
+
time.sleep(DELAY_ENTRE_AGENTES)
|
| 219 |
+
|
| 220 |
+
history[-1]["content"] = f"⏳ Agente **{cfg['nome']}** processando..."
|
| 221 |
+
yield history, audit_data
|
| 222 |
+
|
| 223 |
+
res, sucesso = executar_no(timeline, cfg)
|
| 224 |
+
timeline.append(res)
|
| 225 |
+
|
| 226 |
+
audit_entry = {
|
| 227 |
+
"step": idx + 1,
|
| 228 |
+
"agent": cfg['nome'],
|
| 229 |
+
"model": cfg.get('modelo', 'default'),
|
| 230 |
+
"type": cfg.get('tipo_saida', 'texto'),
|
| 231 |
+
"response_preview": str(res.get('content'))[:100] + "...",
|
| 232 |
+
"raw_len": len(res.get('raw', '')),
|
| 233 |
+
"tempo": round(res.get('tempo', 0), 2),
|
| 234 |
+
"sucesso": sucesso,
|
| 235 |
+
"timestamp": datetime.now().strftime('%H:%M:%S')
|
| 236 |
+
}
|
| 237 |
+
audit_data.append(audit_entry)
|
| 238 |
+
print(f"📋 Audit #{audit_entry['step']} salvo")
|
| 239 |
+
|
| 240 |
+
conteudo_resposta = res.get('content', '')
|
| 241 |
+
if verificar_stop(conteudo_resposta):
|
| 242 |
+
print("🛑 STOP detectado - encerrando pipeline")
|
| 243 |
+
history[-1]["content"] = str(conteudo_resposta) if isinstance(conteudo_resposta, str) else json.dumps(conteudo_resposta, ensure_ascii=False, indent=2)
|
| 244 |
+
yield history, audit_data
|
| 245 |
+
return
|
| 246 |
+
|
| 247 |
+
# Se for último agente ou tipo texto, exibe resposta
|
| 248 |
+
if idx == len(protocolo) - 1 or cfg.get('tipo_saida') == 'texto':
|
| 249 |
+
texto_final = str(conteudo_resposta) if isinstance(conteudo_resposta, str) else json.dumps(conteudo_resposta, ensure_ascii=False, indent=2)
|
| 250 |
+
print(f"✨ Exibindo resposta final: {len(texto_final)} chars")
|
| 251 |
+
|
| 252 |
+
# Typewriter effect
|
| 253 |
+
for i in range(0, len(texto_final), 5):
|
| 254 |
+
history[-1]["content"] = texto_final[:i+5]
|
| 255 |
+
yield history, audit_data
|
| 256 |
+
time.sleep(0.05)
|
| 257 |
+
|
| 258 |
+
history[-1]["content"] = texto_final
|
| 259 |
+
yield history, audit_data
|
| 260 |
+
|
| 261 |
+
print("🏁 Pipeline concluída com sucesso")
|
| 262 |
+
print("="*80)
|
| 263 |
+
|
| 264 |
+
# ==================== 5. UI ====================
|
| 265 |
+
def ui_clean():
|
| 266 |
+
config_init = carregar_protocolo()
|
| 267 |
+
help_init = carregar_help()
|
| 268 |
+
|
| 269 |
+
with gr.Blocks(title="AI Forensics - Groq") as app:
|
| 270 |
+
anexos_state = gr.State([])
|
| 271 |
+
|
| 272 |
+
with gr.Tabs():
|
| 273 |
+
with gr.Tab("💬 Chat"):
|
| 274 |
+
gr.Markdown("## Investigador AI (v35 - Groq Only)")
|
| 275 |
+
chatbot = gr.Chatbot(label="Histórico", height=500)
|
| 276 |
+
|
| 277 |
+
with gr.Row():
|
| 278 |
+
txt_in = gr.Textbox(show_label=False, placeholder="Digite sua mensagem...", lines=2, scale=9)
|
| 279 |
+
btn_send = gr.Button("📤 Enviar", variant="primary", scale=1)
|
| 280 |
+
|
| 281 |
+
with gr.Tab("📎 Anexos & Contexto"):
|
| 282 |
+
gr.Markdown("""
|
| 283 |
+
## Anexos e Contexto Factual
|
| 284 |
+
Carregue arquivos que serão enviados **uma vez** antes da primeira operação.
|
| 285 |
+
""")
|
| 286 |
+
objetivo_text = gr.Textbox(
|
| 287 |
+
label="Objetivo do Modelo",
|
| 288 |
+
placeholder="Ex: Você é um analista forense imparcial...",
|
| 289 |
+
lines=5
|
| 290 |
+
)
|
| 291 |
+
gr.Markdown("### Anexos")
|
| 292 |
+
anexos_upload = gr.File(
|
| 293 |
+
file_count="multiple",
|
| 294 |
+
file_types=[".txt", ".md", ".csv", ".json"]
|
| 295 |
+
)
|
| 296 |
+
anexos_display = gr.Textbox(label="Arquivos Carregados", interactive=False, lines=3)
|
| 297 |
+
|
| 298 |
+
def atualizar_anexos(files):
|
| 299 |
+
if not files:
|
| 300 |
+
return [], "Nenhum arquivo carregado"
|
| 301 |
+
nomes = [os.path.basename(f.name) for f in files]
|
| 302 |
+
return files, f"📎 {len(files)} arquivo(s): {', '.join(nomes)}"
|
| 303 |
+
|
| 304 |
+
anexos_upload.change(atualizar_anexos, anexos_upload, [anexos_state, anexos_display])
|
| 305 |
+
|
| 306 |
+
with gr.Tab("⚙️ Protocolo"):
|
| 307 |
+
gr.Markdown("""
|
| 308 |
+
## Edição do Protocolo de Agentes (Groq)
|
| 309 |
+
|
| 310 |
+
**Modelos disponíveis:**
|
| 311 |
+
- `meta-llama/llama-4-maverick-17b-128e-instruct` (padrão)
|
| 312 |
+
- `meta-llama/llama-3.3-70b-versatile`
|
| 313 |
+
- `deepseek-r1-distill-llama-70b`
|
| 314 |
+
- `llama-3.1-70b-versatile`
|
| 315 |
+
|
| 316 |
+
**Exemplo:**
|
| 317 |
+
```
|
| 318 |
+
[
|
| 319 |
+
{
|
| 320 |
+
"nome": "Alien Escrivão",
|
| 321 |
+
"modelo": "meta-llama/llama-4-maverick-17b-128e-instruct",
|
| 322 |
+
"tipo_saida": "json",
|
| 323 |
+
"missao": "Analisar BO de forma imparcial..."
|
| 324 |
+
}
|
| 325 |
+
]
|
| 326 |
+
```
|
| 327 |
+
""")
|
| 328 |
+
|
| 329 |
+
with gr.Row():
|
| 330 |
+
btn_save_proto = gr.Button("💾 Salvar", variant="primary", size="sm")
|
| 331 |
+
btn_reload_proto = gr.Button("🔄 Recarregar", size="sm")
|
| 332 |
+
proto_status = gr.Markdown("")
|
| 333 |
+
code_json = gr.Code(value=config_init, language="json", lines=30)
|
| 334 |
+
|
| 335 |
+
btn_save_proto.click(salvar_protocolo, code_json, proto_status)
|
| 336 |
+
btn_reload_proto.click(lambda: carregar_protocolo(), outputs=code_json)
|
| 337 |
+
|
| 338 |
+
with gr.Tab("🔍 Auditoria"):
|
| 339 |
+
gr.Markdown("## Auditoria da Última Execução")
|
| 340 |
+
audit_display = gr.JSON(label="Dados de Auditoria", value=[])
|
| 341 |
+
|
| 342 |
+
with gr.Tab("❓ Ajuda"):
|
| 343 |
+
help_content = gr.Markdown(help_init)
|
| 344 |
+
btn_reload_help = gr.Button("🔄 Recarregar Help")
|
| 345 |
+
btn_reload_help.click(lambda: carregar_help(), outputs=help_content)
|
| 346 |
+
|
| 347 |
+
# Triggers
|
| 348 |
+
btn_send.click(
|
| 349 |
+
orquestrador,
|
| 350 |
+
[txt_in, anexos_state, chatbot, code_json, objetivo_text],
|
| 351 |
+
[chatbot, audit_display]
|
| 352 |
+
).then(lambda: "", outputs=txt_in)
|
| 353 |
+
|
| 354 |
+
txt_in.submit(
|
| 355 |
+
orquestrador,
|
| 356 |
+
[txt_in, anexos_state, chatbot, code_json, objetivo_text],
|
| 357 |
+
[chatbot, audit_display]
|
| 358 |
+
).then(lambda: "", outputs=txt_in)
|
| 359 |
+
|
| 360 |
+
return app
|
| 361 |
+
|
| 362 |
+
if __name__ == "__main__":
|
| 363 |
+
print("🎉 Lançando app Groq-only...")
|
| 364 |
+
ui_clean().launch(css="footer{display:none!important;}")
|