caarleexx commited on
Commit
9dd6be3
·
verified ·
1 Parent(s): e9fbabf

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +207 -554
app.py CHANGED
@@ -1,626 +1,279 @@
1
  import json
2
  import os
3
  import base64
 
 
4
  from datetime import datetime
5
- from typing import Dict, List, Tuple, Optional
 
6
  import gradio as gr
7
  import google.generativeai as genai
8
- import warnings
9
- import re
10
- warnings.filterwarnings("ignore", category=FutureWarning, module="google.api_core")
11
 
12
  # ============================================================================
13
  # CONFIGURAÇÃO
14
  # ============================================================================
15
- API_KEY = os.getenv("GOOGLE_API_KEY", "sua-chave-aqui")
 
 
 
 
 
 
 
 
 
 
 
16
  genai.configure(api_key=API_KEY)
17
- counselor_model = genai.GenerativeModel("gemini-2.0-flash")
18
- supervisor_model = genai.GenerativeModel("gemini-2.0-flash")
19
 
20
- TITLE = "# 🚀 Pipeline v10 ATUALIZADA\n**P0-P1 X1-X2 → P2-P8 (com Metacognição Pura)**"
21
 
22
  # ============================================================================
23
- # LOGGER MELHORADO
24
  # ============================================================================
25
- class Logger:
26
- def __init__(self, verbose=True):
27
- self.verbose = verbose
28
- self.logs = []
29
- def log(self, msg: str, level="INFO"):
30
- timestamp = datetime.now().strftime("%H:%M:%S")
31
- log_msg = f"[{timestamp}] [{level}] {msg}"
32
- self.logs.append(log_msg)
33
- if self.verbose: print(log_msg)
34
- print("="*70)
35
-
36
- logger = Logger(verbose=True)
37
 
38
  # ============================================================================
39
- # HELPERS ULTRA ROBUSTOS (DEPURAÇÃO)
40
  # ============================================================================
41
  def processar_anexo(arquivo) -> Tuple[str, str]:
42
- if arquivo is None: return "", "nenhum"
43
- try:
44
- caminho = str(arquivo)
45
- if caminho.lower().endswith('.pdf'):
46
- try:
47
- import PyPDF2
48
- with open(caminho, 'rb') as f:
49
- leitor = PyPDF2.PdfReader(f)
50
- texto = "".join(pagina.extract_text() + "\n" for pagina in leitor.pages[:3])
51
- return texto[:2000], "pdf"
52
- except: return f"[PDF detectado]", "pdf"
53
- elif any(caminho.lower().endswith(ext) for ext in ['.png','.jpg','.jpeg','.gif']):
54
- with open(caminho, 'rb') as f:
55
- return base64.b64encode(f.read()).decode()[:500], "imagem"
56
- return "", "nao_suportado"
57
- except Exception as e:
58
- logger.log(f"Erro processar_anexo: {str(e)}", "ERROR")
59
- return "", "erro"
60
-
61
- def construir_prompt_com_anexo(pergunta: str, anexo_conteudo: str, tipo_anexo: str) -> str:
62
- if not anexo_conteudo or tipo_anexo == "nenhum": return pergunta
63
- if tipo_anexo == "pdf": return f"""DOCUMENTO:
64
- {anexo_conteudo[:1800]}
65
- ---
66
- PERGUNTA: {pergunta}"""
67
- return f"""ANEXO VISUAL:
68
- PERGUNTA: {pergunta}"""
69
-
70
- def parse_json_ultra_robusto(texto: str) -> Dict:
71
- """Extrai QUALQUER JSON de texto bagunçado"""
72
- logger.log(f"Parse: {len(texto)} chars", "DEBUG")
73
- print(f"DEBUG PARSE INPUT: {texto[:300]}...")
74
- if not texto: return {"erro": "vazio"}
75
-
76
- try:
77
- # Regex múltiplos JSONs
78
- matches = re.findall(r'\{[^{}]*(?:\{[^{}]*\}[^{}]*)*\}', texto, re.DOTALL)
79
- for match in matches:
80
- try:
81
- resultado = json.loads(match)
82
- logger.log(f"Parse OK via regex: {json.dumps(resultado, indent=2)[:200]}", "SUCCESS")
83
- return resultado
84
- except: continue
85
-
86
- # JSON balanceado primeiro
87
- inicio = texto.find('{')
88
- if inicio != -1:
89
- count, i = 1, inicio + 1
90
- while i < len(texto) and count > 0:
91
- if texto[i] == '{': count += 1
92
- elif texto[i] == '}': count -= 1
93
- i += 1
94
- if count == 0:
95
- resultado = json.loads(texto[inicio:i])
96
- logger.log(f"Parse OK balanceado", "SUCCESS")
97
- return resultado
98
-
99
- # Fallback último JSON
100
- fim = texto.rfind('}')
101
- if fim != -1:
102
- count, i = 1, fim - 1
103
- while i >= 0 and count > 0:
104
- if texto[i] == '}': count += 1
105
- elif texto[i] == '{': count -= 1
106
- i -= 1
107
- if count == 0:
108
- resultado = json.loads(texto[i+1:fim+1])
109
- logger.log(f"Parse OK fallback", "SUCCESS")
110
- return resultado
111
 
112
- except Exception as e:
113
- logger.log(f"Parse falhou: {str(e)}", "ERROR")
114
-
115
- return {"erro": "parse_falhou", "fallback": texto[:200]}
116
-
117
- def chamar_gemini_json(modelo, prompt_base: str, temperatura=0.4, max_tokens=1500) -> Dict:
118
- prompt = f"""{prompt_base}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
119
 
120
  ---
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
121
 
122
- **JSON PURO OBRIGATÓRIO**
123
- SEM TEXTO EXTRA. APENAS:
124
- {{"chave": "valor"}}"""
125
-
126
- logger.log(f"Enviando prompt ({len(prompt)} chars): {prompt[:300]}...", "DEBUG")
127
- print(f"DEBUG PROMPT GEMINI:\n{prompt}")
128
-
129
  try:
130
- response = modelo.generate_content(prompt,
131
- generation_config=genai.types.GenerationConfig(
132
- temperature=temperatura,
133
- max_output_tokens=max_tokens))
134
-
135
- resposta_bruta = response.text or ""
136
- logger.log(f"Gemini RAW ({len(resposta_bruta)} chars): {resposta_bruta[:500]}...", "DEBUG")
137
- print(f"DEBUG - RESPOSTA BRUTA GEMINI:\n{resposta_bruta}")
138
-
139
- resultado = parse_json_ultra_robusto(resposta_bruta)
140
- logger.log(f"Gemini PARSE OK: {json.dumps(resultado, indent=2)[:300]}", "SUCCESS")
141
- return resultado
142
-
143
- except Exception as e:
144
- logger.log(f"API erro: {str(e)}", "ERROR")
145
- print(f"ERRO GEMINI: {str(e)}")
146
- return {"erro": str(e)}
147
 
148
  def historico_compacto(historico: List) -> str:
149
- if not historico: return "PRIMEIRO"
150
- return "\n".join([str(msg).split("\n")[0][:80] for msg in historico[-2:]])
151
 
152
  def criar_dna() -> Dict:
153
- return {
154
- "historico_chat": [],
155
- "historico_passos": {f"passo{i}": [] for i in range(10)},
156
- "memoria": {"resumo": ""},
157
- "meta": {"total_turnos": 0, "ultimo_turno_limpeza": 0}
158
- }
159
 
160
  # ============================================================================
161
- # PASSOS P0-P1 (COPIADOS DA VERSÃO ANTIGA - METACOGNIÇÃO PURA)
162
  # ============================================================================
163
- def passo_0_aluno(pergunta: str, historico: List) -> Dict:
164
- logger.log("🧠 P0-ALUNO - METACOGNIÇÃO PURA", "TASK")
165
- print("="*70)
166
- print("PASSO 0 ALUNO analisando feedback...")
167
-
168
- turno_anterior = historico[-1] if len(historico) > 0 else None
169
- memoria_contextual = "NA" # Pode ser expandido depois
170
-
171
- prompt = f"""Você é um METACOGNITIVO (pensamento interno, NÃO comunicação).
172
-
173
- TURNO ANTERIOR:
174
- User: {turno_anterior['content'] if turno_anterior else 'NA'}
175
- Assistant: {historico[-2]['content'] if len(historico) >= 2 else 'NA'}
176
 
177
- NOVA MENSAGEM: {pergunta}
178
- CONTEXTO VAGO: {historico_compacto(historico)}
 
 
179
 
180
- ---
 
 
181
 
182
- RESponda EM METACOGNIÇÃO PURA - TELEGRÁFICO
183
- NÃO use frases completas. APENAS essência semântica com conectores mínimos.
184
-
185
- EXEMPLO CERTO: entendeu-sim | pergunta-nova | avança-tópico | não-reformulou
186
-
187
- RETORNE JSON:
188
- {{
189
- "usuario_entendeu": "sim|não",
190
- "evidencias": ["entendeu-pergunta", "pediu-clarificação"],
191
- "usuario_corrigiu": "sim|não",
192
- "correcao_detectada": null|"texto-correção",
193
- "correcao_valida": "sim|não|null",
194
- "o_que_melhorar": null|"explicar-X-melhor",
195
- "decisao": "prosseguir-passo1|reexplicar-passo6|atualizar-resposta-anterior",
196
- "motivo": "texto-curtíssimo"
197
- }}"""
198
-
199
- return chamar_gemini_json(counselor_model, prompt, temperatura=0.5)
200
 
201
- def passo_1_triagem(pergunta: str, p0: Dict, historico: List) -> Dict:
202
- logger.log("📊 P1-TRIAGEM - METACOGNIÇÃO PURA", "TASK")
203
- print("="*70)
204
- print("PASSO 1 TRIAGEM...")
205
-
206
- contexto_vago = historico_compacto(historico)
207
- historico_recente = [{"user": msg["content"], "assistant": historico[i+1]["content"] if i+1 < len(historico) else "NA"}
208
- for i, msg in enumerate(historico[-4:]) if msg["role"] == "user"]
209
-
210
- prompt = f"""METACOGNIÇÃO - TRIAGEM INICIAL.
211
 
212
- CONTEXTO VAGO: {contexto_vago}
213
- HISTÓRICO RECENTE (últimas 3): {json.dumps(historico_recente[-3:], indent=2, ensure_ascii=False)}
214
- P0: {json.dumps(p0, indent=2, ensure_ascii=False)}
215
  PERGUNTA: {pergunta}
216
 
217
- ---
218
-
219
- CLASSIFIQUE EM TELEGRÁFICO (sem frases).
220
-
221
- RETORNE JSON:
222
- {{
223
- "tipo": "objetiva|factual|subjetiva|aberta",
224
- "sinais": ["tem-resposta-única-verificável", "sem-contexto-pessoal"],
225
- "confianca": "alta|média|baixa",
226
- "decisao": "responder-direto|analisar-profundamente",
227
- "razao": "curtíssima",
228
- "dados_fatuais": ["fato1", "fato2"],
229
- "divergencias_fatuais": ["possível-ambiguidade-1"],
230
- "objetivo_principal": "objetivo-primário-identificado",
231
- "objetivo_secundario": ["objetivo-secundário-1"]
232
- }}"""
233
-
234
- return chamar_gemini_json(counselor_model, prompt, temperatura=0.5)
235
 
236
- # ============================================================================
237
- # NOVOS PASSOS X1-X2 (MELHORADOS)
238
- # ============================================================================
239
- def passo_x1_perguntas_necessarias(pergunta: str, p1: Dict, historico: List) -> Dict:
240
- """X1: Quais perguntas precisamos responder antes dos cenários?"""
241
- logger.log("❓ X1-PERGUNTAS NECESSÁRIAS", "TASK")
242
- print("="*70)
243
 
244
- prompt = f"""X1-PERGUNTAS CRÍTICAS - TELEGRÁFICO
245
- P1: {json.dumps(p1, indent=2)}
246
  CONTEXTO: {historico_compacto(historico)}
247
- PERGUNTA PRINCIPAL: {pergunta}
248
-
249
- LACUNAS FACTUAIS/SUBJETIVAS:
250
 
251
- {{"perguntas": [
252
- {{"texto": "pergunta-curta", "necessidade": "alta|média|baixa", "relevancia": "alta|média"}}
253
- ]}}"""
254
-
255
- return chamar_gemini_json(counselor_model, prompt, max_tokens=2000)
256
 
257
- def passo_x2_resolver_perguntas(pergunta: str, p1: Dict, x1: Dict, historico: List) -> Dict:
258
- """X2: Responde as perguntas levantadas com confiança/conflito"""
259
- logger.log("✅ X2-RESOLVER PERGUNTAS", "TASK")
260
- print("="*70)
261
-
262
- perguntas = x1.get("perguntas", [])
263
- prompt = f"""X2-RESOLUÇÃO - TELEGRÁFICO
264
- P1: {json.dumps(p1, indent=2)}
265
- PERGUNTAS X1: {json.dumps(perguntas, indent=2)}
266
- CONTEXTO: {historico_compacto(historico)}
267
 
268
- PARA CADA PERGUNTA:
269
- {{"respostas": [
270
- {{"pergunta": "texto-original",
271
- "resposta": "curta-direta",
272
- "confianca": "alta|média|baixa",
273
- "conflito": "alto|médio|baixo",
274
- "razao": "1-2-palavras"}}
275
- ]}}"""
276
-
277
- return chamar_gemini_json(counselor_model, prompt, max_tokens=2000)
278
 
279
- # ============================================================================
280
- # PASSOS P2-P8 (COPIADOS E ADAPTADOS DA VERSÃO ANTIGA)
281
- # ============================================================================
282
- def passo_2_cenarios(pergunta: str, p1: Dict, x1: Dict, x2: Dict, historico: List) -> Dict:
283
- logger.log("🎯 P2-CENÁRIOS (com X1-X2)", "TASK")
284
- print("="*70)
285
- print("PASSO 2 CENÁRIOS...")
286
-
287
- prompt = f"""METACOGNIÇÃO - CENÁRIOS.
288
- CONTEXTO VAGO: {historico_compacto(historico)}
289
- TRIAGEM P1: {json.dumps(p1, indent=2)}
290
- X1-PERGUNTAS: {json.dumps(x1, indent=2)}
291
- X2-RESPOSTAS: {json.dumps(x2, indent=2)}
292
  PERGUNTA: {pergunta}
293
 
294
- ---
295
-
296
- MAPEIE CENÁRIOS ONDE RESPOSTA MUDA - TELEGRÁFICO
297
-
298
- RETORNE JSON:
299
- {{"cenarios": {{"provaveis": [{{"id": "C1", "desc": "cenário-1-comprimido", "contexto-relevante": "X"}}],
300
- "improvaveis": [{{"id": "C2", "desc": "cenário-2-comprimido"}}]}},
301
- "total": 2,
302
- "tipo_resposta": "múltipla|unívoca",
303
- "confianca": "alta|média|baixa",
304
- "decisao": "prosseguir|pedir-esclarecimento",
305
- "pergunta_esclarecimento": null|"texto-pergunta"
306
- }}"""
307
-
308
- resultado = chamar_gemini_json(counselor_model, prompt, temperatura=0.6)
309
- logger.log(f"Cenários: {resultado.get('cenarios', {}).get('total', 0)}", "INFO")
310
- return resultado
311
-
312
- def passo_3_isolar_cenarios(p2: Dict) -> Dict:
313
- logger.log("🔍 P3-ISOLAR CENÁRIOS", "TASK")
314
- print("="*70)
315
- print("PASSO 3 EXPLORAÇÃO...")
316
-
317
- exploracoes = []
318
- for tipo in ['provaveis', 'improvaveis']:
319
- for c in p2.get('cenarios', {}).get(tipo, [])[:2]:
320
- prompt = f"""METACOGNIÇÃO - EXPLORAÇÃO.
321
- CENÁRIO P2: {json.dumps(c, indent=2)}
322
-
323
- PARA ESTE CENÁRIO, RESPONDA ESSÊNCIA - TELEGRÁFICO
324
-
325
- {{"id": "{c.get('id')}",
326
- "resposta_essencia": "palavra-chave-razoes",
327
- "confianca": "alta|média|baixa",
328
- "lacunas": "contexto-ausente|null"
329
- }}"""
330
- exploracoes.append(chamar_gemini_json(counselor_model, prompt, temperatura=0.6))
331
-
332
- logger.log(f"P3: {len(exploracoes)} explorações isoladas", "INFO")
333
- return {"isolados": exploracoes}
334
-
335
- def passo_4_cruzar_validacoes(p1: Dict, p2: Dict, p3: Dict, x2: Dict) -> Dict:
336
- logger.log("✅ P4-VALIDAR (Princípios/Símbolos)", "TASK")
337
- print("="*70)
338
- print("PASSO 4 PRINCÍPIOS...")
339
-
340
- prompt = f"""METACOGNIÇÃO - CONHECIMENTO FUNDAMENTAL.
341
- P1: {json.dumps(p1, indent=2)}
342
- P2: {json.dumps(p2, indent=2)}
343
- P3: {json.dumps(p3, indent=2)}
344
- X2: {json.dumps(x2, indent=2)}
345
-
346
- ---
347
-
348
- IDENTIFIQUE PRINCÍPIOS E SÍMBOLOS - TELEGRÁFICO
349
-
350
- RETORNE JSON:
351
- {{"principios": [{{"nome": "Custo-Oportunidade", "essencia": "escolher-X-renunciar-Y"}}],
352
- "simbolos": [{{"nome": "Jornada-Herói", "essencia": "transformação-iminente"}}],
353
- "principio_central": "nome",
354
- "simbolo_dominante": "nome"
355
- }}"""
356
-
357
- return chamar_gemini_json(counselor_model, prompt, temperatura=0.6)
358
-
359
- def passo_5_lacunas_finais(p1: Dict, p4: Dict) -> Dict:
360
- logger.log("🚨 P5-LACUNAS FINAIS (Metacognição)", "TASK")
361
- print("="*70)
362
- print("PASSO 5 METACOGNIÇÃO...")
363
-
364
- prompt = f"""METACOGNIÇÃO - CERTEZAS vs DÚVIDAS.
365
- P1: {json.dumps(p1, indent=2)}
366
- P4: {json.dumps(p4, indent=2)}
367
-
368
- ---
369
-
370
- AVALIE CERTEZAS vs DÚVIDAS - TELEGRÁFICO
371
-
372
- RETORNE JSON:
373
- {{"analise_cenarios": [{{"cenario": "C1", "certezas": ["certeza1"], "duvidas": ["dúvida1"]}}],
374
- "confianca_global": "alta|média|baixa",
375
- "balanco": "certezas-superam|equilibrado",
376
- "decisao": "responder|questionar",
377
- "questionamento": null|"texto"
378
- }}"""
379
-
380
- return chamar_gemini_json(counselor_model, prompt, temperatura=0.5)
381
-
382
- def passo_6_ponderar(p2: Dict, p4: Dict, p5: Dict) -> Dict:
383
- logger.log("⚖️ P6-PONDERAR (Juiz da Verdade)", "TASK")
384
- print("="*70)
385
- print("PASSO 6 JUIZ DA VERDADE...")
386
-
387
- prompt = f"""METACOGNIÇÃO - ARBITRAGEM SOCRÁTICA.
388
- P2: {json.dumps(p2, indent=2)}
389
- P4: {json.dumps(p4, indent=2)}
390
- P5: {json.dumps(p5, indent=2)}
391
-
392
- ---
393
-
394
- VALIDE VERDADES SOCRÁTICAS - TELEGRÁFICO
395
-
396
- RETORNE JSON:
397
- {{"verdade1_nao_sei": "validada-true|false",
398
- "evidencias": "P3-respostas-P4-principios",
399
- "confianca": "alta|média|baixa",
400
- "decisao": "exibir-resposta|exibir-falhas|reprocessar",
401
- "nivel_consciencia": "alto|médio|baixo"
402
- }}"""
403
-
404
- return chamar_gemini_json(counselor_model, prompt, temperatura=0.5)
405
-
406
- def passo_7_sintetizar(p6: Dict) -> Dict:
407
- logger.log("✍️ P7-SINTETIZAR (Prosa Humanizada)", "TASK")
408
- print("="*70)
409
- print("SINTETIZADOR - Transformando metacognição em prosa...")
410
-
411
- prompt = f"""Você é um SINTETIZADOR - transforma METACOGNIÇÃO CRUA em PROSA HUMANIZADA.
412
-
413
- JUIZ P6: {json.dumps(p6, indent=2)}
414
-
415
- ---
416
-
417
- TAREFA: TRANSFORMAR METACOGNIÇÃO → PROSA HUMANIZADA
418
-
419
- INSTRUÇÕES:
420
- 1. Adicione conectores naturais (porque, portanto, isso significa)
421
- 2. Expanda abreviações (curva-aprendizado → curva de aprendizado)
422
- 3. Humanize números (3-6m → 3 a 6 meses)
423
- 4. Estruture em parágrafos (introdução-desenvolvimento-nuances)
424
- 5. Integre princípios naturalmente
425
- 6. Tom: Amigo, empático, empoderador
426
-
427
- GENRE RESPOSTA FINAL (texto fluido, natural, humano):"""
428
-
429
- resposta_raw = chamar_gemini_json(counselor_model, prompt, temperatura=0.8, max_tokens=2500)
430
- logger.log(f"P7: Resposta gerada ({len(resposta_raw.get('resposta', ''))} chars)", "INFO")
431
- return resposta_raw
432
-
433
- def passo_8_verificar(p7: Dict) -> Dict:
434
- logger.log("🔍 P8-FINAL (Verificador)", "TASK")
435
- print("="*70)
436
- print("PASSO 8 VERIFICAÇÃO FINAL...")
437
-
438
- resposta_sintetizada = p7.get("resposta", "")
439
-
440
- prompt = f"""Você é um VERIFICADOR FINAL - guardião de qualidade.
441
-
442
- RESPOSTA SINTETIZADA: {resposta_sintetizada}
443
- ANÁLISE P6: {json.dumps(p7, indent=2)}
444
-
445
- ---
446
-
447
- VERIFICAÇÃO TRIPLA (devolver JSON):
448
- 1. VERIFICAÇÃO FACTUAL: Fatos incorretos?
449
- 2. VERIFICAÇÃO LÓGICA: Falácias? Saltos injustificados?
450
- 3. VERIFICAÇÃO ÉTICA: Apropriada? Disclaimers?
451
-
452
- RETORNE JSON:
453
- {{"verificacao_factual": {{"aprovada": "true|false", "problemas": []}},
454
- "verificacao_logica": {{"aprovada": "true|false", "problemas": []}},
455
- "verificacao_etica": {{"aprovada": "true|false", "problemas": []}},
456
- "todas_aprovadas": "true|false",
457
- "decisao": "exibir-resposta|corrigir|reexibir",
458
- "resposta_corrigida": null|"versão-corrigida"
459
- }}"""
460
-
461
- return chamar_gemini_json(supervisor_model, prompt, temperatura=0.5)
462
 
463
  # ============================================================================
464
- # ORQUESTADOR PRINCIPAL v10 (SEM TRY-EXCEPT PARA DEPURAÇÃO)
465
  # ============================================================================
466
- #PIPELINE PRINCIPAL
467
- def processar_pipeline(pergunta: str, historico: List, arquivo_anexo=None, dna=None) -> tuple:
468
- if dna is None or not isinstance(dna, dict):
469
- dna = criar_dna()
470
- logger.log("🧬 DNA inicializado")
471
-
472
- logger.log(f"🚀 PIPELINE v10: {pergunta[:60]}...")
473
-
474
- if not pergunta.strip():
475
- return "Digite uma pergunta válida", historico, dna
476
-
477
- # Processa anexo
478
- conteudo_anexo, tipo_anexo = processar_anexo(arquivo_anexo)
479
- pergunta_completa = prompt_com_anexo(pergunta, conteudo_anexo, tipo_anexo)
480
-
481
-
482
- # PASSOS 0-2
483
- try:
484
- p0 = passo_0_aluno(pergunta_completa, historico)
485
- p1 = passo_1_triagem(pergunta_completa, p0, historico)
486
- x1 = passo_x1_perguntas(pergunta_completa, p1, historico)
487
- x2 = passo_x2_respostas(pergunta_completa, p1, x1)
488
-
489
- # CENÁRIOS
490
- p2 = passo_2_cenarios(pergunta_completa, p1, x1, x2)
491
- p3 = passo_3_isolar(p2)
492
- p4 = passo_4_validar(p1, p2, p3)
493
-
494
-
495
- # LACUNAS
496
- p5 = passo_5_lacunas(p1, p4)
497
- if p5.get("decisao") == "INTERROMPIDO":
498
- esclarecimento = p5.get("pergunta_clarificacao", "Preciso de mais informações")
499
- return f"❓ **ESCLARECIMENTO NECESSÁRIO**:\n{esclarecimento}", historico, dna
500
-
501
- # DECISÃO FINAL
502
- p6 = passo_6_decidir(p2, p4, p5)
503
- p7 = passo_7_sintetizar(p6)
504
-
505
- # FORMATA RESPOSTA FINAL
506
- resposta_final = formatar_resposta_para_chat(p7)
507
-
508
- # NOVO HISTÓRICO
509
- novo_historico = historico + [
510
- {"role": "user", "content": pergunta},
511
- {"role": "assistant", "content": resposta_final}
512
- ]
513
-
514
- # ATUALIZA DNA
515
- dna["historico_chat"].append({"user": pergunta, "assistant": resposta_final})
516
- dna["metadados"]["total_turnos"] += 1
517
-
518
- logger.log("✅ PIPELINE CONCLUÍDA")
519
- return resposta_final, novo_historico, dna
520
-
521
- except Exception as e:
522
- erro_msg = f"❌ Erro na pipeline: {str(e)}"
523
- logger.log(f"ERRO: {e}", "ERROR")
524
- return erro_msg, historico, dna
525
-
526
 
527
- def processar_pipeline2(pergunta: str, historico: List, arquivo_anexo=None, dna=None) -> Tuple[str, List, Dict]:
528
- if dna is None or not isinstance(dna, dict):
529
- dna = criar_dna()
530
-
531
- logger.log(f"🚀 v10 ATUALIZADA: {pergunta[:50]}", "START")
532
- print("="*70)
533
- print("PIPELINE PRINCIPAL v10 ATUALIZADA")
534
- print("="*70)
535
-
536
- if not pergunta.strip():
537
- return "Pergunta inválida", historico, dna
538
-
539
- anexo_c, tipo_a = processar_anexo(arquivo_anexo)
540
- pergunta_final = construir_prompt_com_anexo(pergunta, anexo_c, tipo_a)
541
 
542
- # P0-P1 (Metacognição Pura)
543
- p0 = passo_0_aluno(pergunta_final, historico)
544
- logger.log(f"P0 Decisão: {p0.get('decisao', 'NA')}", "INFO")
545
-
546
- p1 = passo_1_triagem(pergunta_final, p0, historico)
547
- logger.log(f"P1 Tipo: {p1.get('tipo', 'NA')} | Decisão: {p1.get('decisao', 'NA')}", "INFO")
548
 
549
- # X1-X2 (Perguntas Críticas)
550
- x1 = passo_x1_perguntas_necessarias(pergunta_final, p1, historico)
551
- x2 = passo_x2_resolver_perguntas(pergunta_final, p1, x1, historico)
 
 
 
 
 
 
 
 
552
 
553
- # P2-P8 (Fluxo Principal com Metacognição)
554
- p2 = passo_2_cenarios(pergunta_final, p1, x1, x2, historico)
555
- if p2.get("decisao") == "pedir-esclarecimento":
556
- esclarecimento = p2.get("pergunta_esclarecimento", "Esclareça")
557
- return esclarecimento, historico, dna
558
 
559
- p3 = passo_3_isolar_cenarios(p2)
560
- p4 = passo_4_cruzar_validacoes(p1, p2, p3, x2)
561
- p5 = passo_5_lacunas_finais(p1, p4)
562
 
563
- if p5.get("decisao") == "questionar":
564
- questionamento = p5.get("questionamento", "Lacuna crítica")
565
- return questionamento, historico, dna
566
 
567
- p6 = passo_6_ponderar(p2, p4, p5)
568
- p7 = passo_7_sintetizar(p6)
569
- p8 = passo_8_verificar(p7)
570
 
571
- # Decisão Final P8
572
- if p8.get("todas_aprovadas") == "true" or p8.get("decisao") == "exibir-resposta":
573
- resposta = p7.get("resposta", "Resposta gerada")
574
  else:
575
- resposta = p8.get("resposta_corrigida", p7.get("resposta", "Erro na verificação"))
576
-
577
- novo_hist = historico + [{"role": "user", "content": pergunta},
578
- {"role": "assistant", "content": resposta}]
579
-
580
- dna["historico_chat"].append({"u": pergunta, "a": resposta})
581
- dna["meta"]["total_turnos"] += 1
 
 
582
 
583
- logger.log(" PIPELINE CONCLUÍDA", "SUCCESS")
584
- print("="*70)
585
- print("RESPOSTA FINAL:")
586
- print(resposta)
587
- print("="*70)
588
 
589
  return resposta, novo_hist, dna
590
 
591
  # ============================================================================
592
- # INTERFACE (DEPURAÇÃO)
593
  # ============================================================================
594
- def chat_interface(msg: str, hist: List, anexo=None, dna_json: str="{}") -> Tuple[List, str, str, None]:
595
- print(f"\n🎯 NOVA MENSAGEM: {msg[:100]}...")
596
- try:
597
- dna = json.loads(dna_json) if dna_json else {}
598
- except Exception as e:
599
- print(f"Erro DNA: {e}")
600
- dna = {}
601
 
 
602
  resp, novo_hist, dna_new = processar_pipeline(msg, hist, anexo, dna)
603
- print(f"✅ RESPOSTA ENVIADA: {len(resp)} chars")
604
  return novo_hist, "", json.dumps(dna_new, indent=2), None
605
 
606
-
607
  if __name__ == "__main__":
608
- with gr.Blocks(title="Pipeline v10 ATUALIZADA", theme=gr.themes.Soft()) as demo:
609
- gr.Markdown(TITLE + "\n**Metacognição Pura + X1-X2 + Verificação Final**")
610
 
611
  with gr.Row():
612
- with gr.Column(scale=2):
613
- chat = gr.Chatbot(height=550, type="messages")
614
- dna_view = gr.Code(label="DNA", language="json", lines=12)
615
- with gr.Column(scale=1):
616
- input_txt = gr.Textbox(label="Pergunta", lines=3, scale=2)
617
- file_up = gr.File(label="Anexo", file_types=[".pdf",".png",".jpg"])
618
- btn_go = gr.Button("🚀 v10 ATUALIZADA", variant="primary")
619
-
620
- btn_go.click(chat_interface, [input_txt, chat, file_up, dna_view],
621
- [chat, input_txt, dna_view, file_up])
622
-
623
- input_txt.submit(chat_interface, [input_txt, chat, file_up, dna_view],
624
- [chat, input_txt, dna_view, file_up])
625
-
626
- demo.launch(server_name="0.0.0.0", server_port=7860, share=False)
 
1
  import json
2
  import os
3
  import base64
4
+ import re
5
+ import warnings
6
  from datetime import datetime
7
+ from typing import Dict, List, Tuple
8
+
9
  import gradio as gr
10
  import google.generativeai as genai
 
 
 
11
 
12
  # ============================================================================
13
  # CONFIGURAÇÃO
14
  # ============================================================================
15
+ # Filtra avisos do gRPC para limpar o console
16
+ warnings.filterwarnings("ignore", category=FutureWarning, module="google.api_core")
17
+
18
+ # API KEY
19
+ API_KEY = os.getenv("GOOGLE_API_KEY", "")
20
+ if not API_KEY:
21
+ print("❌ ERRO CRÍTICO: GOOGLE_API_KEY não encontrada nas variáveis de ambiente.")
22
+ print("Defina com: export GOOGLE_API_KEY='sua_chave'")
23
+ # Não paramos o script, mas vai dar erro na chamada
24
+ else:
25
+ print(f"✅ API Key carregada (termina em ...{API_KEY[-4:]})")
26
+
27
  genai.configure(api_key=API_KEY)
28
+ model = genai.GenerativeModel("gemini-1.5-flash")
 
29
 
30
+ TITLE = "# 🛠️ Anise v10.2 DEBUG MODE\n**Console mostra SAÍDA BRUTA**"
31
 
32
  # ============================================================================
33
+ # SISTEMA DE LOGS & DEBUG
34
  # ============================================================================
35
+ def debug_print(titulo: str, conteudo: any):
36
+ """Imprime no console com formatação visível para debug"""
37
+ print(f"\n{'='*60}")
38
+ print(f"🐛 DEBUG: {titulo}")
39
+ print(f"{'-'*60}")
40
+ if isinstance(conteudo, (dict, list)):
41
+ print(json.dumps(conteudo, indent=2, ensure_ascii=False))
42
+ else:
43
+ print(str(conteudo))
44
+ print(f"{'='*60}\n")
 
 
45
 
46
  # ============================================================================
47
+ # HELPERS (SEM TRY/EXCEPT PARA DEBUG)
48
  # ============================================================================
49
  def processar_anexo(arquivo) -> Tuple[str, str]:
50
+ if arquivo is None:
51
+ return "", "nenhum"
52
+
53
+ caminho = str(arquivo)
54
+ print(f"📂 Processando arquivo: {caminho}")
55
+
56
+ if caminho.lower().endswith('.pdf'):
57
+ import PyPDF2 # Se falhar aqui, queremos ver o erro de importação
58
+ with open(caminho, 'rb') as f:
59
+ leitor = PyPDF2.PdfReader(f)
60
+ texto = "".join(pagina.extract_text() + "\n" for pagina in leitor.pages[:5])
61
+ print(f"📄 PDF extraído: {len(texto)} caracteres")
62
+ return texto[:5000], "pdf"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
63
 
64
+ elif any(caminho.lower().endswith(ext) for ext in ['.png','.jpg','.jpeg','.gif','.webp']):
65
+ with open(caminho, 'rb') as f:
66
+ encoded = base64.b64encode(f.read()).decode()
67
+ print(f"🖼️ Imagem codificada: {len(encoded)} bytes")
68
+ return encoded, "imagem"
69
+
70
+ return "", "nao_suportado"
71
+
72
+ def limpar_json_raw(texto: str) -> str:
73
+ """Limpa markdown ```json ... ``` para tentar parsear"""
74
+ texto = re.sub(r'^```json\s*', '', texto, flags=re.MULTILINE)
75
+ texto = re.sub(r'^```\s*', '', texto, flags=re.MULTILINE)
76
+ texto = re.sub(r'```$', '', texto, flags=re.MULTILINE)
77
+ return texto.strip()
78
+
79
+ def chamar_gemini_json(prompt_base: str, etapa: str, temperatura=0.2, max_tokens=2000) -> Dict:
80
+ """
81
+ Chama o Gemini e imprime o RAW OUTPUT.
82
+ Se falhar o JSON, explode o erro para análise.
83
+ """
84
+ full_prompt = f"""{prompt_base}
85
 
86
  ---
87
+ **INSTRUÇÃO DE SISTEMA OBRIGATÓRIA:**
88
+ 1. Responda APENAS com um JSON válido.
89
+ 2. NÃO use blocos de código markdown (```json).
90
+ 3. NÃO escreva texto antes ou depois do JSON.
91
+ """
92
+
93
+ # 1. Chamada API
94
+ print(f"📡 Enviando requisição para {etapa}...")
95
+ response = model.generate_content(
96
+ full_prompt,
97
+ generation_config=genai.types.GenerationConfig(
98
+ temperature=temperatura,
99
+ max_output_tokens=max_tokens,
100
+ # response_mime_type="application/json" # Opcional: força modo JSON estrito do 1.5
101
+ )
102
+ )
103
+
104
+ raw_text = response.text
105
+
106
+ # 2. PRINT DA SAÍDA BRUTA (O que você pediu)
107
+ print(f"\n🛑 SAÍDA BRUTA GEMINI [{etapa}]:")
108
+ print(f">>> INICIO RAW <<<\n{raw_text}\n>>> FIM RAW <<<")
109
+
110
+ # 3. Tentativa de Parse (sem try/except silencioso)
111
+ texto_limpo = limpar_json_raw(raw_text)
112
+
113
+ if not texto_limpo:
114
+ raise ValueError(f"[{etapa}] Gemini retornou texto vazio!")
115
 
 
 
 
 
 
 
 
116
  try:
117
+ dados_json = json.loads(texto_limpo)
118
+ return dados_json
119
+ except json.JSONDecodeError as e:
120
+ print(f"❌ FALHA NO PARSE JSON [{etapa}]")
121
+ print(f"Erro: {e}")
122
+ print("Tentando corrigir manualmente strings...")
123
+ # Fallback simples se for apenas texto solto (opcional)
124
+ raise e # Relança o erro para parar o script e ver o traceback
 
 
 
 
 
 
 
 
 
125
 
126
  def historico_compacto(historico: List) -> str:
127
+ if not historico: return "Nenhum."
128
+ return "\n".join([f"{m['role']}: {str(m['content'])[:100]}..." for m in historico[-4:]])
129
 
130
  def criar_dna() -> Dict:
131
+ return {"historico": [], "meta": {"turnos": 0}}
 
 
 
 
 
132
 
133
  # ============================================================================
134
+ # PIPELINE - PASSOS (COM LOGS EXPLÍCITOS)
135
  # ============================================================================
 
 
 
 
 
 
 
 
 
 
 
 
 
136
 
137
+ def passo_0_aluno(pergunta: str, historico: List) -> Dict:
138
+ prompt = f"""ETAPA: P0-INTENÇÃO
139
+ HISTÓRICO: {historico_compacto(historico)}
140
+ PERGUNTA: {pergunta}
141
 
142
+ Analise a intenção do usuário.
143
+ JSON: {{"relacao": "continua|nova", "intent": "resumo"}}"""
144
+ return chamar_gemini_json(prompt, "P0")
145
 
146
+ def passo_1_triagem(pergunta: str, p0: Dict) -> Dict:
147
+ prompt = f"""ETAPA: P1-TRIAGEM
148
+ P0: {json.dumps(p0)}
149
+ PERGUNTA: {pergunta}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
150
 
151
+ Classifique a pergunta.
152
+ JSON: {{"tipo": "factual|analitica", "complexidade": "alta|baixa"}}"""
153
+ return chamar_gemini_json(prompt, "P1")
 
 
 
 
 
 
 
154
 
155
+ def passo_x1_lacunas(pergunta: str, p1: Dict) -> Dict:
156
+ prompt = f"""ETAPA: X1-LACUNAS
157
+ P1: {json.dumps(p1)}
158
  PERGUNTA: {pergunta}
159
 
160
+ Quais perguntas o assistente deve fazer a si mesmo para responder isso perfeitamente?
161
+ JSON: {{"perguntas_internas": ["pergunta1", "pergunta2"]}}"""
162
+ return chamar_gemini_json(prompt, "X1")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
163
 
164
+ def passo_x2_resolver(x1: Dict, historico: List) -> Dict:
165
+ perguntas = x1.get("perguntas_internas", [])
166
+ if not perguntas: return {"respostas": []}
 
 
 
 
167
 
168
+ prompt = f"""ETAPA: X2-RESOLUÇÃO
169
+ PERGUNTAS: {json.dumps(perguntas)}
170
  CONTEXTO: {historico_compacto(historico)}
 
 
 
171
 
172
+ Responda as perguntas internas.
173
+ JSON: {{"respostas": [{{"p": "pergunta", "r": "resposta", "confianca": "alta|baixa"}}]}}"""
174
+ return chamar_gemini_json(prompt, "X2")
 
 
175
 
176
+ def passo_2_cenarios(pergunta: str, x2: Dict) -> Dict:
177
+ prompt = f"""ETAPA: P2-CENÁRIOS
178
+ DADOS X2: {json.dumps(x2)}
179
+ PERGUNTA ORIGINAL: {pergunta}
 
 
 
 
 
 
180
 
181
+ Crie cenários de resposta ou decida parar se faltar informação crítica.
182
+ JSON: {{"decisao": "continuar|parar", "cenarios": ["C1: ...", "C2: ..."], "motivo_parada": "..."}}"""
183
+ return chamar_gemini_json(prompt, "P2")
 
 
 
 
 
 
 
184
 
185
+ def passo_7_sintese(p2: Dict, pergunta: str) -> Dict:
186
+ prompt = f"""ETAPA: P7-FINAL
187
+ CENÁRIOS: {json.dumps(p2)}
 
 
 
 
 
 
 
 
 
 
188
  PERGUNTA: {pergunta}
189
 
190
+ Escreva a resposta final para o usuário.
191
+ JSON: {{"resposta_final": "texto aqui"}}"""
192
+ return chamar_gemini_json(prompt, "P7", temperatura=0.7)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
193
 
194
  # ============================================================================
195
+ # ORQUESTADOR (SEM REDE DE SEGURANÇA)
196
  # ============================================================================
197
+ def processar_pipeline(pergunta: str, historico: List, arquivo_anexo=None, dna=None) -> Tuple[str, List, Dict]:
198
+ # Debug inicial
199
+ debug_print("INICIO PIPELINE", f"Pergunta: {pergunta}\nAnexo: {arquivo_anexo}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
200
 
201
+ if dna is None: dna = criar_dna()
 
 
 
 
 
 
 
 
 
 
 
 
 
202
 
203
+ # 1. Processar Anexo
204
+ conteudo_anexo, tipo_anexo = processar_anexo(arquivo_anexo)
 
 
 
 
205
 
206
+ # 2. Montar Prompt com Contexto Visual/Documental
207
+ if tipo_anexo == "pdf":
208
+ prompt_final = f"CONTEXTO DO PDF:\n{conteudo_anexo}\n---\nPERGUNTA: {pergunta}"
209
+ elif tipo_anexo == "imagem":
210
+ prompt_final = f"[IMAGEM BASE64: {conteudo_anexo}]\nAnalise a imagem.\nPERGUNTA: {pergunta}"
211
+ else:
212
+ prompt_final = pergunta
213
+
214
+ # 3. Execução Linear (Se falhar, o erro aparece no console)
215
+ p0 = passo_0_aluno(prompt_final, historico)
216
+ debug_print("P0 Resultado", p0)
217
 
218
+ p1 = passo_1_triagem(prompt_final, p0)
219
+ debug_print("P1 Resultado", p1)
 
 
 
220
 
221
+ x1 = passo_x1_lacunas(prompt_final, p1)
222
+ debug_print("X1 Resultado", x1)
 
223
 
224
+ x2 = passo_x2_resolver(x1, historico)
225
+ debug_print("X2 Resultado", x2)
 
226
 
227
+ p2 = passo_2_cenarios(prompt_final, x2)
228
+ debug_print("P2 Resultado", p2)
 
229
 
230
+ # Lógica de Parada
231
+ if p2.get("decisao") == "parar":
232
+ resposta = f"⚠️ Não consigo responder com certeza.\nMotivo: {p2.get('motivo_parada')}"
233
  else:
234
+ p7 = passo_7_sintese(p2, prompt_final)
235
+ debug_print("P7 Resultado", p7)
236
+ resposta = p7.get("resposta_final", "Erro na síntese P7")
237
+
238
+ # Atualiza Histórico
239
+ novo_hist = historico + [
240
+ {"role": "user", "content": pergunta},
241
+ {"role": "assistant", "content": resposta}
242
+ ]
243
 
244
+ dna["historico"].append({"turn": dna["meta"]["turnos"], "p": pergunta, "r": resposta[:20]})
245
+ dna["meta"]["turnos"] += 1
 
 
 
246
 
247
  return resposta, novo_hist, dna
248
 
249
  # ============================================================================
250
+ # INTERFACE
251
  # ============================================================================
252
+ def chat_interface(msg, hist, anexo, dna_json):
253
+ # Converte string JSON do DNA de volta para dict
254
+ dna = json.loads(dna_json) if dna_json else {}
255
+ if hist is None: hist = []
 
 
 
256
 
257
+ # Chama o pipeline (Erros vão aparecer no console do servidor)
258
  resp, novo_hist, dna_new = processar_pipeline(msg, hist, anexo, dna)
259
+
260
  return novo_hist, "", json.dumps(dna_new, indent=2), None
261
 
 
262
  if __name__ == "__main__":
263
+ with gr.Blocks(title="Anise Debug", theme=gr.themes.Base()) as demo:
264
+ gr.Markdown(TITLE)
265
 
266
  with gr.Row():
267
+ chat = gr.Chatbot(height=500, type="messages", label="Chat")
268
+ dna_box = gr.Code(label="DNA (JSON State)", language="json")
269
+
270
+ with gr.Row():
271
+ txt_in = gr.Textbox(label="Pergunta", scale=2)
272
+ file_in = gr.File(label="Anexo")
273
+ btn = gr.Button("Enviar", variant="primary")
274
+
275
+ btn.click(chat_interface, [txt_in, chat, file_in, dna_box], [chat, txt_in, dna_box, file_in])
276
+ txt_in.submit(chat_interface, [txt_in, chat, file_in, dna_box], [chat, txt_in, dna_box, file_in])
277
+
278
+ print("🚀 Servidor Iniciado! Verifique este console para logs.")
279
+ demo.launch(server_name="0.0.0.0", server_port=7860)