caarleexx commited on
Commit
a30f7c6
·
verified ·
1 Parent(s): c1baccb

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +363 -66
app.py CHANGED
@@ -1,67 +1,364 @@
1
- [
2
- {
3
- "fase": 0,
4
- "nome": "CONTEXTO_INICIAL_VALOR_VIDA",
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;}")