caarleexx commited on
Commit
8f6402d
·
verified ·
1 Parent(s): 01d589f

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +237 -280
app.py CHANGED
@@ -6,6 +6,7 @@ from typing import Dict, List, Tuple, Optional
6
  import gradio as gr
7
  import google.generativeai as genai
8
  import warnings
 
9
  warnings.filterwarnings("ignore", category=FutureWarning, module="google.api_core")
10
 
11
  # ============================================================================
@@ -15,363 +16,319 @@ API_KEY = os.getenv("GOOGLE_API_KEY", "sua-chave-aqui")
15
  genai.configure(api_key=API_KEY)
16
  model = genai.GenerativeModel("gemini-2.0-flash")
17
 
18
- TITLE = "# 🚀 Pipeline Híbrida v9\n**Lógica app-7.py + Estrutura 9 Passos**"
19
 
20
  # ============================================================================
21
- # LOGGER
22
  # ============================================================================
23
  class Logger:
24
  def __init__(self, verbose=True):
25
  self.verbose = verbose
26
  self.logs = []
27
-
28
  def log(self, msg: str, level="INFO"):
29
  timestamp = datetime.now().strftime("%H:%M:%S")
30
  log_msg = f"[{timestamp}] [{level}] {msg}"
31
  self.logs.append(log_msg)
32
- if self.verbose:
33
- print(log_msg)
34
 
35
  logger = Logger(verbose=True)
36
 
37
  # ============================================================================
38
- # HELPERS GLOBAIS (app-7.py style)
39
  # ============================================================================
40
  def processar_anexo(arquivo) -> Tuple[str, str]:
41
- if arquivo is None:
42
- logger.log("Nenhum anexo fornecido.", "DEBUG")
43
- return "", "nenhum"
44
  try:
45
  caminho = str(arquivo)
46
  if caminho.lower().endswith('.pdf'):
47
- logger.log(f"Processando PDF: {caminho}", "DEBUG")
48
  try:
49
  import PyPDF2
50
  with open(caminho, 'rb') as f:
51
  leitor = PyPDF2.PdfReader(f)
52
  texto = "".join(pagina.extract_text() + "\n" for pagina in leitor.pages[:3])
53
- logger.log("PDF processado com sucesso.", "DEBUG")
54
  return texto[:2000], "pdf"
55
- except Exception as e:
56
- logger.log(f"Falha ao processar PDF: {str(e)}", "WARNING")
57
- return f"[PDF: {os.path.basename(caminho)}]", "pdf"
58
- elif any(caminho.lower().endswith(ext) for ext in ['.png', '.jpg', '.jpeg', '.gif']):
59
- logger.log(f"Processando imagem: {caminho}", "DEBUG")
60
  with open(caminho, 'rb') as f:
61
- conteudo_b64 = base64.b64encode(f.read()).decode()[:500]
62
- logger.log("Imagem processada para base64.", "DEBUG")
63
- return conteudo_b64, "imagem"
64
- else:
65
- logger.log(f"Tipo de anexo não suportado: {caminho}", "WARNING")
66
- return "", "nao_suportado"
67
- except Exception as e:
68
- logger.log(f"Erro ao processar anexo: {str(e)}", "ERROR")
69
- return "", "erro"
70
 
71
  def construir_prompt_com_anexo(pergunta: str, anexo_conteudo: str, tipo_anexo: str) -> str:
72
- if not anexo_conteudo or tipo_anexo == "nenhum":
73
- return pergunta
74
- if tipo_anexo == "pdf":
75
- prompt = f"""DOCUMENTO PDF:
76
- {anexo_conteudo}
77
-
78
  PERGUNTA: {pergunta}"""
79
- logger.log("Construindo prompt com conteúdo PDF.", "DEBUG")
80
- return prompt
81
- prompt = f"""IMAGEM FORNECIDA
82
-
83
  PERGUNTA: {pergunta}"""
84
- logger.log("Construindo prompt com conteúdo de imagem.", "DEBUG")
85
- return prompt
86
 
87
- def parse_json_seguro(texto: str) -> Dict:
 
 
 
 
88
  try:
89
- if not texto:
90
- logger.log("Texto vazio recebido para parse de JSON.", "WARNING")
91
- return {"erro": "texto_vazio"}
92
- import re
93
- match = re.search(r'\{.*\}', texto, re.DOTALL)
94
- if match:
95
- return json.loads(match.group(0).strip())
96
- inicio, fim = texto.find('{'), texto.rfind('}') + 1
97
- if inicio >= 0 and fim > inicio:
98
- return json.loads(texto[inicio:fim])
99
- logger.log("JSON não encontrado no texto.", "WARNING")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
100
  except Exception as e:
101
- logger.log(f"Falha ao parsear JSON: {e}", "ERROR")
102
- return {"erro": "parse_falhou"}
 
 
 
 
 
 
103
 
104
- def chamar_gemini_json(prompt_base: str, temperatura: float = 0.5, max_tokens: int = 1500) -> Dict:
105
- prompt = f"{prompt_base}\n\nRETORNE APENAS JSON VÁLIDO. SEM TEXTO EXTRA."
106
- logger.log(f"Enviando prompt ao modelo Gemini (temperatura={temperatura}, max_tokens={max_tokens})", "DEBUG")
 
107
  try:
108
  response = model.generate_content(prompt,
109
  generation_config=genai.types.GenerationConfig(
110
- temperature=temperatura, max_output_tokens=max_tokens))
111
- logger.log("Resposta recebida do Gemini.", "DEBUG")
112
- return parse_json_seguro(response.text or "")
 
113
  except Exception as e:
114
- logger.log(f"Erro na chamada da API Gemini: {str(e)}", "ERROR")
115
  return {"erro": str(e)}
116
 
117
- def historico_texto(historico: List) -> str:
118
- if not historico:
119
- return "Primeira interação"
120
- texto = ""
121
- for msg in historico[-3:]:
122
- if isinstance(msg, dict) and 'content' in msg:
123
- texto += f"U: {msg['content'][:80]}...\n"
124
- elif isinstance(msg, (list, tuple)) and len(msg) >= 2:
125
- texto += f"U: {msg[0][:80]}...\n"
126
- return texto[:800]
127
-
128
- def criar_dna_vazio() -> Dict:
129
- logger.log("Inicializando objeto DNA vazio.", "DEBUG")
130
  return {
131
  "historico_chat": [],
132
- "historico_passos": {
133
- "passo0": [], "passo1": [], "passo2": [], "passo3": [],
134
- "passo4": [], "passo5": [], "passo6": [], "passo7": [], "passo8": []
135
- },
136
- "memoria_contextual": {"resumo": "", "temas": []},
137
- "metadados": {"total_turnos": 0}
138
  }
139
 
140
  # ============================================================================
141
- # PASSOS HÍBRIDOS (base app-7.py + estrutura v9)
142
  # ============================================================================
143
-
144
- def passo_0_aluno(pergunta: str, historico: List, dna: Dict) -> Dict:
145
- logger.log("🧠 Executando P0: ALUNO - Feedback anterior", "TASK")
146
- hist = historico_texto(historico)
147
- prompt = f"""METACOGNIÇÃO PURA - P0-ALUNO (TELEGRÁFICO)
148
- TURNO ANTERIOR: {hist}
149
- NOVA MENSAGEM: {pergunta}
150
-
151
- USUARIO-entendeu sim/nao | pergunta-nova avanca-topico |
152
- usuario-corrigiu sim/nao | correcao-detectada texto-null
153
-
154
- {{"tipo_relacao": "continuação|correção|dúvida|nova",
155
- "peso_historico": "alto|médio|baixo",
156
- "decisao": "prosseguirpasso1|reexplicar"}}"""
157
- return chamar_gemini_json(prompt, temperatura=0.5)
158
-
159
- def passo_1_triagem_epistemica(pergunta: str, p0: Dict, historico: List, dna: Dict) -> Dict:
160
- logger.log("📊 Executando P1: TRIAGEM", "TASK")
161
- contextovago = dna.get("memoria_contextual", {}).get("resumo", "")
162
- historicorecente = historico[-3:]
163
- prompt = f"""METACOGNIÇÃO - P1-TRIAGEM (TELEGRÁFICO)
164
- CONTEXTO VAGO: {contextovago}
165
- HISTÓRICO RECENTE: {json.dumps(historicorecente[-3:], ensure_ascii=False)}
166
  PERGUNTA: {pergunta}
167
 
168
- tipo objetiva/factual/subjetiva | sinais tem-resposta-unica |
169
- confianca alta/media/baixa | decisao responderdireto/analisarprofundamente
170
 
171
- {{"tipo": "objetiva|factual|subjetiva",
172
- "sinais": ["tem-resposta-unica"],
173
- "confianca": "alta|media|baixa",
174
- "decisao": "responderdireto|analisarprofundamente",
175
- "razao": "breve"}}"""
176
- return chamar_gemini_json(prompt, temperatura=0.5)
177
-
178
- def passo_2_gerador_cenarios(pergunta: str, p1: Dict, historico: List, dna: Dict) -> Dict:
179
- logger.log("🎯 Executando P2: CENÁRIOS", "TASK")
180
- contextovago = dna.get("memoria_contextual", {}).get("resumo", "")
181
- prompt = f"""METACOGNIÇÃO - P2-CENÁRIOS (TELEGRÁFICO)
182
- CONTEXTO VAGO: {contextovago}
183
- TRIAGEM P1: {json.dumps(p1, ensure_ascii=False)}
184
  PERGUNTA: {pergunta}
185
 
186
- cenarios: desc cenrio-1-comprimido | razao resposta-muda |
187
- prob maisprovavel/menosprovavel | total 2-3
 
 
188
 
189
- {{"cenarios": {{"provaveis": [{{"id": "C1", "desc": "curta", "validadores": []}}],
190
- "improvaveis": [{{"id": "C2", "desc": "curta"}}],
191
- "total": 2}},
192
- "decisao": "prosseguir|pediresclarecimento",
193
- "pergunta": "null"}}"""
194
- return chamar_gemini_json(prompt, temperatura=0.6)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
195
 
196
- def passo_3_explorador_puro(p2: Dict) -> Dict:
197
- logger.log("🔍 Executando P3: EXPLORADOR PURO", "TASK")
198
  exploracoes = []
199
- cenarios = p2.get('cenarios', {})
200
  for tipo in ['provaveis', 'improvaveis']:
201
- for cenario in cenarios.get(tipo, [])[:2]:
202
- prompt = f"""P3-EXPLORADOR PURO: {cenario.get('id')} ISOLADO
203
- NÃO compare cenários. Solução direta: {cenario.get('desc', '')}
204
-
205
- {{"cenario_id": "{cenario.get('id')}", "solucao": "",
206
- "convergentes": [], "divergentes": [], "balanco": "positivo|negativo"}}"""
207
  exploracoes.append(chamar_gemini_json(prompt))
208
- return {"exploracoes": exploracoes}
209
 
210
- def passo_4_validador_cruzado(p1: Dict, p2: Dict, p3: Dict) -> Dict:
211
- logger.log("✅ Executando P4: VALIDADOR CRUZADO", "TASK")
212
- prompt = f"""P4-VALIDADOR: Cruze P1+P2+P3
213
- {{"confiabilidades": [{{"fato": "", "conf_verdade": "alto"}}],
214
- "ambiguidades_validadas": []}}"""
215
  return chamar_gemini_json(prompt)
216
 
217
- def passo_5_detector_lacunas(p1: Dict, p4: Dict) -> Dict:
218
- logger.log("🚨 Executando P5: DETECTOR LACUNAS", "TASK")
219
- prompt = f"""P5-LACUNAS: CRÍTICAS|IMPORTANTES|OPCIONAIS
220
- {{"lacunas": [{{"desc": "", "tipo": "CRÍTICA"}}],
221
- "decisao": "PERGUNTAR|PROSSEGUIR",
222
- "pergunta_lacuna": "null"}}"""
223
  return chamar_gemini_json(prompt)
224
 
225
- def passo_6_metacognicao_ponderativa(p2: Dict, p4: Dict, p5: Dict) -> Dict:
226
- logger.log("⚖️ Executando P6: METACOGNIÇÃO PONDERATIVA", "TASK")
227
- prompt = f"""P6-PONDERATIVA: Escolha cenário final
228
- {{"cenario_final": "C1", "confianca": "alto", "justificativa": ""}}"""
229
- return chamar_gemini_json(prompt, temperatura=0.6)
230
-
231
- def passo_7_sintetizador_refinado(p6: Dict) -> Dict:
232
- logger.log("✍️ Executando P7: SINTETIZADOR", "TASK")
233
- prompt = f"""SINTETIZADOR v1.8 - PROSA HUMANIZADA
234
- P6: {json.dumps(p6, ensure_ascii=False)}
235
-
236
- Transforme METACOGNIÇÃO em PROSA:
237
- 1. Conectores naturais (porque, portanto)
238
- 2. Expanda abreviações
239
- 3. Parágrafos estruturados
240
-
241
- {{"resposta_final": "TEXTO FLUIDO NATURAL",
242
- "tom": "assertivo|cauteloso"}}"""
243
- return chamar_gemini_json(prompt, temperatura=0.8, max_tokens=2500)
244
-
245
- def passo_8_validador_final(p7: Dict) -> Dict:
246
- logger.log("🔍 Executando P8: VALIDADOR FINAL", "TASK")
247
- resposta = p7.get('resposta_final', '')
248
- prompt = f"""P8-VERIFICADOR FINAL
249
- RESPOSTA: {resposta[:1500]}
250
-
251
- VERIFICAÇÃO TRIPLA:
252
- 1. FACTUAL aprovada true/false
253
- 2. LÓGICA aprovada true/false
254
- 3. ÉTICA aprovada true/false
255
-
256
- {{"verificacao_factual": {{"aprovada": true, "problemas": []}},
257
- "verificacao_logica": {{"aprovada": true}},
258
- "verificacao_etica": {{"aprovada": true}},
259
- "todas_aprovadas": true,
260
- "resposta_final": "texto validado"}}"""
261
- return chamar_gemini_json(prompt, temperatura=0.4)
262
 
263
  # ============================================================================
264
- # ORQUESTADOR PRINCIPAL HÍBRIDO
265
  # ============================================================================
266
  def processar_pipeline(pergunta: str, historico: List, arquivo_anexo=None, dna=None) -> Tuple[str, List, Dict]:
267
- if dna is None or not isinstance(dna, dict) or "historico_chat" not in dna:
268
- logger.log("DNA inválido ou inexistente, inicializando novo DNA.", "WARNING")
269
- dna = criar_dna_vazio()
270
 
271
- logger.log(f"🚀 Iniciando pipeline híbrida para: {pergunta[:60]}", "INPUT")
272
- if not pergunta.strip():
273
- logger.log("Pergunta vazia ou inválida detectada.", "WARNING")
274
- return "Digite uma pergunta válida.", historico, dna
275
 
276
- anexo_conteudo, tipo_anexo = processar_anexo(arquivo_anexo)
277
- pergunta_com_anexo = construir_prompt_com_anexo(pergunta, anexo_conteudo, tipo_anexo)
278
 
279
  try:
280
- p0 = passo_0_aluno(pergunta_com_anexo, historico, dna)
281
- dna["historico_passos"]["passo0"].append(p0)
282
-
283
- p1 = passo_1_triagem_epistemica(pergunta_com_anexo, p0, historico, dna)
284
- dna["historico_passos"]["passo1"].append(p1)
285
-
286
- p2 = passo_2_gerador_cenarios(pergunta_com_anexo, p1, historico, dna)
287
- dna["historico_passos"]["passo2"].append(p2)
288
-
289
- if p2.get("decisao") == "pediresclarecimento":
290
- logger.log("Interrupção em P2: pedido de esclarecimento pelo usuário.", "INFO")
291
- return p2.get("pergunta", "Preciso de mais informações."), historico, dna
292
-
293
- p3 = passo_3_explorador_puro(p2)
294
- dna["historico_passos"]["passo3"].append(p3)
295
-
296
- p4 = passo_4_validador_cruzado(p1, p2, p3)
297
- dna["historico_passos"]["passo4"].append(p4)
298
-
299
- p5 = passo_5_detector_lacunas(p1, p4)
300
- dna["historico_passos"]["passo5"].append(p5)
301
-
302
- if p5.get("decisao") == "PERGUNTAR":
303
- logger.log("Interrupção em P5: lacuna crítica detectada, solicitando pergunta de esclarecimento.", "INFO")
304
- return p5.get("pergunta_lacuna", "Por favor forneça mais detalhes."), historico, dna
305
-
306
- p6 = passo_6_metacognicao_ponderativa(p2, p4, p5)
307
- dna["historico_passos"]["passo6"].append(p6)
308
-
309
- p7 = passo_7_sintetizador_refinado(p6)
310
- dna["historico_passos"]["passo7"].append(p7)
311
-
312
- p8 = passo_8_validador_final(p7)
313
- dna["historico_passos"]["passo8"].append(p8)
314
-
315
- resposta_final = p8.get("resposta_final", "Erro na validação final.")
316
- disclaimer = p8.get("disclaimer", "")
317
-
318
- if disclaimer:
319
- resposta_final += f"\n\n⚠️ {disclaimer}"
320
-
321
- novo_historico = historico + [{"role": "user", "content": pergunta},
322
- {"role": "assistant", "content": resposta_final}]
323
-
324
- dna["historico_chat"].append({"user": pergunta, "assistant": resposta_final})
325
- dna["metadados"]["total_turnos"] += 1
326
-
327
- logger.log("Pipeline concluída com sucesso.", "SUCCESS")
328
- return resposta_final, novo_historico, dna
329
-
330
  except Exception as e:
331
- logger.log(f"Erro inesperado na pipeline: {str(e)}", "ERROR")
332
  return f"Erro: {str(e)}", historico, dna
333
 
334
  # ============================================================================
335
- # INTERFACE GRADIO
336
  # ============================================================================
337
- def chat_interface(mensagem: str, historico: List, arquivo_anexo=None, dna_json: str = "{}") -> Tuple[List, str, str, None]:
338
- if not mensagem.strip():
339
- logger.log("Mensagem vazia recebida na interface.", "WARNING")
340
- return historico, "", dna_json, None
341
- try:
342
- dna = json.loads(dna_json) if dna_json else criar_dna_vazio()
343
- except Exception as e:
344
- logger.log(f"Falha ao carregar DNA do estado: {str(e)}. Criando novo DNA.", "WARNING")
345
- dna = criar_dna_vazio()
346
-
347
- hist_tuples = []
348
- for msg in historico:
349
- if isinstance(msg, dict) and 'content' in msg:
350
- hist_tuples.append(msg['content'])
351
- elif isinstance(msg, (list, tuple)) and len(msg) >= 2:
352
- hist_tuples.append(msg[0])
353
-
354
- resposta, novo_hist, dna_atualizado = processar_pipeline(mensagem, hist_tuples, arquivo_anexo, dna)
355
- return novo_hist, "", json.dumps(dna_atualizado, indent=2, ensure_ascii=False), None
356
 
357
- # ============================================================================
358
- # MAIN
359
- # ============================================================================
360
  if __name__ == "__main__":
361
- with gr.Blocks(title="Pipeline Híbrida v9", theme=gr.themes.Soft()) as demo:
362
- gr.Markdown(f"{TITLE}\n\n**🧬 Lógica app-7.py + Estrutura 9 Passos v9**")
 
363
  with gr.Row():
364
  with gr.Column(scale=2):
365
- chatbot = gr.Chatbot(label="🤖 Pipeline Híbrida v9", height=550, type="messages")
366
- dna_display = gr.Code(label="🧬 DNA", language="json", lines=15)
367
  with gr.Column(scale=1):
368
- entrada = gr.Textbox(label="📝 Pergunta", lines=3, placeholder="Descreva o caso...", scale=2)
369
- anexo = gr.File(label="📎 Anexo (PDF/PNG/JPG)", file_types=[".pdf", ".png", ".jpg", ".jpeg"], scale=1)
370
- btn = gr.Button("🚀 ANALISAR", variant="primary")
371
-
372
- btn.click(chat_interface, inputs=[entrada, chatbot, anexo, dna_display], outputs=[chatbot, entrada, dna_display, anexo])
373
- entrada.submit(chat_interface, inputs=[entrada, chatbot, anexo, dna_display], outputs=[chatbot, entrada, dna_display, anexo])
374
- gr.Markdown("**⚠️ Aviso legal**: ferramenta auxiliar. Consulte profissional qualificado.")
375
-
376
- logger.log("🚀 Pipeline Híbrida v9 iniciada e aguardando interações.", "INFO")
377
- demo.launch(server_name="0.0.0.0", server_port=7860, share=False)
 
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
  # ============================================================================
 
16
  genai.configure(api_key=API_KEY)
17
  model = genai.GenerativeModel("gemini-2.0-flash")
18
 
19
+ TITLE = "# 🚀 Pipeline v10\n**P0-P1 X1-X2 P2-P8**"
20
 
21
  # ============================================================================
22
+ # LOGGER MELHORADO
23
  # ============================================================================
24
  class Logger:
25
  def __init__(self, verbose=True):
26
  self.verbose = verbose
27
  self.logs = []
 
28
  def log(self, msg: str, level="INFO"):
29
  timestamp = datetime.now().strftime("%H:%M:%S")
30
  log_msg = f"[{timestamp}] [{level}] {msg}"
31
  self.logs.append(log_msg)
32
+ if self.verbose: print(log_msg)
 
33
 
34
  logger = Logger(verbose=True)
35
 
36
  # ============================================================================
37
+ # HELPERS ULTRA ROBUSTOS
38
  # ============================================================================
39
  def processar_anexo(arquivo) -> Tuple[str, str]:
40
+ if arquivo is None: return "", "nenhum"
 
 
41
  try:
42
  caminho = str(arquivo)
43
  if caminho.lower().endswith('.pdf'):
 
44
  try:
45
  import PyPDF2
46
  with open(caminho, 'rb') as f:
47
  leitor = PyPDF2.PdfReader(f)
48
  texto = "".join(pagina.extract_text() + "\n" for pagina in leitor.pages[:3])
 
49
  return texto[:2000], "pdf"
50
+ except: return f"[PDF detectado]", "pdf"
51
+ elif any(caminho.lower().endswith(ext) for ext in ['.png','.jpg','.jpeg','.gif']):
 
 
 
52
  with open(caminho, 'rb') as f:
53
+ return base64.b64encode(f.read()).decode()[:500], "imagem"
54
+ return "", "nao_suportado"
55
+ except: return "", "erro"
 
 
 
 
 
 
56
 
57
  def construir_prompt_com_anexo(pergunta: str, anexo_conteudo: str, tipo_anexo: str) -> str:
58
+ if not anexo_conteudo or tipo_anexo == "nenhum": return pergunta
59
+ if tipo_anexo == "pdf": return f"""DOCUMENTO:
60
+ {anexo_conteudo[:1800]}
61
+ ---
 
 
62
  PERGUNTA: {pergunta}"""
63
+ return f"""ANEXO VISUAL:
 
 
 
64
  PERGUNTA: {pergunta}"""
 
 
65
 
66
+ def parse_json_ultra_robusto(texto: str) -> Dict:
67
+ """Extrai QUALQUER JSON de texto bagunçado"""
68
+ logger.log(f"Parse: {len(texto)} chars", "DEBUG")
69
+ if not texto: return {"erro": "vazio"}
70
+
71
  try:
72
+ # Regex múltiplos JSONs
73
+ matches = re.findall(r'\{[^{}]*(?:\{[^{}]*\}[^{}]*)*\}', texto, re.DOTALL)
74
+ for match in matches:
75
+ try: return json.loads(match)
76
+ except: continue
77
+
78
+ # JSON balanceado primeiro
79
+ inicio = texto.find('{')
80
+ if inicio != -1:
81
+ count, i = 1, inicio + 1
82
+ while i < len(texto) and count > 0:
83
+ if texto[i] == '{': count += 1
84
+ elif texto[i] == '}': count -= 1
85
+ i += 1
86
+ if count == 0: return json.loads(texto[inicio:i])
87
+
88
+ # Fallback último JSON
89
+ fim = texto.rfind('}')
90
+ if fim != -1:
91
+ count, i = 1, fim - 1
92
+ while i >= 0 and count > 0:
93
+ if texto[i] == '}': count += 1
94
+ elif texto[i] == '{': count -= 1
95
+ i -= 1
96
+ if count == 0: return json.loads(texto[i+1:fim+1])
97
+
98
  except Exception as e:
99
+ logger.log(f"Parse falhou: {str(e)[:100]}", "ERROR")
100
+
101
+ return {"erro": "parse_falhou", "fallback": texto[:200]}
102
+
103
+ def chamar_gemini_json(prompt_base: str, temperatura=0.4, max_tokens=1500) -> Dict:
104
+ prompt = f"""{prompt_base}
105
+
106
+ ---
107
 
108
+ **JSON PURO OBRIGATÓRIO**
109
+ SEM TEXTO EXTRA. APENAS:
110
+ {{"chave": "valor"}}"""
111
+
112
  try:
113
  response = model.generate_content(prompt,
114
  generation_config=genai.types.GenerationConfig(
115
+ temperature=temperatura,
116
+ max_output_tokens=max_tokens))
117
+ logger.log(f"Gemini OK: {len(response.text)} chars", "DEBUG")
118
+ return parse_json_ultra_robusto(response.text or "")
119
  except Exception as e:
120
+ logger.log(f"API erro: {str(e)}", "ERROR")
121
  return {"erro": str(e)}
122
 
123
+ def historico_compacto(historico: List) -> str:
124
+ if not historico: return "PRIMEIRO"
125
+ return "\n".join([str(msg).split("\n")[0][:80] for msg in historico[-2:]])
126
+
127
+ def criar_dna() -> Dict:
 
 
 
 
 
 
 
 
128
  return {
129
  "historico_chat": [],
130
+ "historico_passos": {f"passo{i}": [] for i in range(10)},
131
+ "memoria": {"resumo": ""},
132
+ "meta": {"turnos": 0}
 
 
 
133
  }
134
 
135
  # ============================================================================
136
+ # PASSOS P0-P1 (ANTIGOS)
137
  # ============================================================================
138
+ def passo_0_aluno(pergunta: str, historico: List) -> Dict:
139
+ logger.log("🧠 P0-ALUNO", "TASK")
140
+ prompt = f"""P0-ALUNO TELEGRÁFICO
141
+ HISTÓRICO: {historico_compacto(historico)}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
142
  PERGUNTA: {pergunta}
143
 
144
+ {{"relacao": "continua|nova|corrige", "peso_hist": "alto|medio|baixo"}}"""
145
+ return chamar_gemini_json(prompt)
146
 
147
+ def passo_1_triagem(pergunta: str, p0: Dict, historico: List) -> Dict:
148
+ logger.log("📊 P1-TRIAGEM", "TASK")
149
+ prompt = f"""P1-TRIAGEM TELEGRÁFICO
150
+ P0: {json.dumps(p0)}
 
 
 
 
 
 
 
 
 
151
  PERGUNTA: {pergunta}
152
 
153
+ {{"tipo": "factual|subjetiva|aberta",
154
+ "confianca": "alta|media|baixa",
155
+ "fatos": ["fato1", "fato2"]}}"""
156
+ return chamar_gemini_json(prompt)
157
 
158
+ # ============================================================================
159
+ # NOVOS PASSOS X1-X2
160
+ # ============================================================================
161
+ def passo_x1_perguntas_necessarias(pergunta: str, p1: Dict, historico: List) -> Dict:
162
+ """X1: Quais perguntas precisamos responder antes dos cenários?"""
163
+ logger.log("❓ X1-PERGUNTAS NECESSÁRIAS", "TASK")
164
+ prompt = f"""X1-PERGUNTAS CRÍTICAS TELEGRÁFICO
165
+ P1: {json.dumps(p1)}
166
+ CONTEXTO: {historico_compacto(historico)}
167
+ PERGUNTA PRINCIPAL: {pergunta}
168
+
169
+ LACUNAS FACTUAIS/SUBJETIVAS:
170
+ {{"perguntas": [
171
+ {{"texto": "pergunta curta", "necessidade": "alta|media|baixa", "relevancia": "alta|media"}}
172
+ ]}}"""
173
+ return chamar_gemini_json(prompt)
174
+
175
+ def passo_x2_resolver_perguntas(pergunta: str, p1: Dict, x1: Dict, historico: List) -> Dict:
176
+ """X2: Responde as perguntas levantadas com confiança/conflito"""
177
+ logger.log("✅ X2-RESOLVER PERGUNTAS", "TASK")
178
+ perguntas = x1.get("perguntas", [])
179
+ prompt = f"""X2-RESOLUÇÃO TELEGRÁFICO
180
+ P1: {json.dumps(p1)}
181
+ PERGUNTAS X1: {json.dumps(perguntas)}
182
+ CONTEXTO: {historico_compacto(historico)}
183
+
184
+ PARA CADA PERGUNTA:
185
+ {{"respostas": [
186
+ {{"pergunta": "texto original",
187
+ "resposta": "curta direta",
188
+ "confianca": "alta|media|baixa",
189
+ "conflito": "alto|medio|baixo",
190
+ "razao": "1-2 palavras"}
191
+ ]}}"""
192
+ return chamar_gemini_json(prompt, max_tokens=2000)
193
+
194
+ # ============================================================================
195
+ # PASSOS P2-P8 (MELHORADOS COM X1-X2)
196
+ # ============================================================================
197
+ def passo_2_cenarios(pergunta: str, p1: Dict, x1: Dict, x2: Dict, historico: List) -> Dict:
198
+ logger.log("🎯 P2-CENÁRIOS (com X1-X2)", "TASK")
199
+ prompt = f"""P2-CENÁRIOS TELEGRÁFICO
200
+ P1: {json.dumps(p1)}
201
+ X1-PERGUNTAS: {json.dumps(x1)}
202
+ X2-RESPOSTAS: {json.dumps(x2)}
203
+ PERGUNTA: {pergunta}
204
+
205
+ {{"cenarios": {{"provaveis": [{{"id": "C1", "desc": "curto"}}],
206
+ "improvaveis": [{{"id": "C2", "desc": "curto"}}]},
207
+ "total": 2}}"""
208
+ return chamar_gemini_json(prompt)
209
 
210
+ def passo_3_isolar_cenarios(p2: Dict) -> Dict:
211
+ logger.log("🔍 P3-ISOLAR", "TASK")
212
  exploracoes = []
 
213
  for tipo in ['provaveis', 'improvaveis']:
214
+ for c in p2.get('cenarios', {}).get(tipo, [])[:2]:
215
+ prompt = f"""P3-ISOLAR: {c.get('id')}
216
+ {{"id": "{c.get('id')}", "solucao": "direta", "balanco": "positivo|negativo"}}"""
 
 
 
217
  exploracoes.append(chamar_gemini_json(prompt))
218
+ return {"isolados": exploracoes}
219
 
220
+ def passo_4_cruzar_validacoes(p1: Dict, p2: Dict, p3: Dict, x2: Dict) -> Dict:
221
+ logger.log("✅ P4-VALIDAR", "TASK")
222
+ prompt = f"""P4-VALIDAR TELEGRÁFICO
223
+ {{"confiancas": [{{"fato": "X", "origem": "X1|X2|P3", "nivel": "alta|baixa"}}]}}"""
 
224
  return chamar_gemini_json(prompt)
225
 
226
+ def passo_5_lacunas_finais(p1: Dict, p4: Dict) -> Dict:
227
+ logger.log("🚨 P5-LACUNAS FINAIS", "TASK")
228
+ prompt = f"""P5-LACUNAS RESTANTES
229
+ {{"lacunas": [{{"texto": "curto", "critica": "sim|nao"}}],
230
+ "decisao": "continuar|parar"}}"""
 
231
  return chamar_gemini_json(prompt)
232
 
233
+ def passo_6_ponderar(p2: Dict, p4: Dict, p5: Dict) -> Dict:
234
+ logger.log("⚖️ P6-PONDERAR", "TASK")
235
+ prompt = f"""P6-DECISÃO FINAL
236
+ {{"vencedor": "C1", "peso": "0.8", "razao": "curta"}}"""
237
+ return chamar_gemini_json(prompt)
238
+
239
+ def passo_7_sintetizar(p6: Dict) -> Dict:
240
+ logger.log("✍️ P7-SINTETIZAR", "TASK")
241
+ prompt = f"""P7-PROSA NATURAL
242
+ P6: {json.dumps(p6)}
243
+
244
+ {{"resposta": "texto fluido completo"}}"""
245
+ return chamar_gemini_json(prompt, temperatura=0.7, max_tokens=2500)
246
+
247
+ def passo_8_verificar(p7: Dict) -> Dict:
248
+ logger.log("🔍 P8-FINAL", "TASK")
249
+ prompt = f"""P8-VERIFICAÇÃO
250
+ {{"factual": "ok", "logica": "ok", "etica": "ok",
251
+ "final": "texto aprovado"}}"""
252
+ return chamar_gemini_json(prompt)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
253
 
254
  # ============================================================================
255
+ # ORQUESTADOR PRINCIPAL v10
256
  # ============================================================================
257
  def processar_pipeline(pergunta: str, historico: List, arquivo_anexo=None, dna=None) -> Tuple[str, List, Dict]:
258
+ if dna is None or not isinstance(dna, dict):
259
+ dna = criar_dna()
 
260
 
261
+ logger.log(f"🚀 v10: {pergunta[:50]}", "START")
262
+ if not pergunta.strip(): return "Pergunta inválida", historico, dna
 
 
263
 
264
+ anexo_c, tipo_a = processar_anexo(arquivo_anexo)
265
+ pergunta_final = construir_prompt_com_anexo(pergunta, anexo_c, tipo_a)
266
 
267
  try:
268
+ # P0-P1
269
+ p0 = passo_0_aluno(pergunta_final, historico)
270
+ p1 = passo_1_triagem(pergunta_final, p0, historico)
271
+
272
+ # NOVOS X1-X2
273
+ x1 = passo_x1_perguntas_necessarias(pergunta_final, p1, historico)
274
+ x2 = passo_x2_resolver_perguntas(pergunta_final, p1, x1, historico)
275
+
276
+ # P2 usa X1-X2
277
+ p2 = passo_2_cenarios(pergunta_final, p1, x1, x2, historico)
278
+ if p2.get("decisao") == "parar":
279
+ return x1.get("perguntas", ["Esclareça"]), historico, dna
280
+
281
+ # P3-P8 fluxo normal
282
+ p3 = passo_3_isolar_cenarios(p2)
283
+ p4 = passo_4_cruzar_validacoes(p1, p2, p3, x2)
284
+ p5 = passo_5_lacunas_finais(p1, p4)
285
+
286
+ if p5.get("decisao") == "parar":
287
+ return "Lacuna crítica", historico, dna
288
+
289
+ p6 = passo_6_ponderar(p2, p4, p5)
290
+ p7 = passo_7_sintetizar(p6)
291
+ p8 = passo_8_verificar(p7)
292
+
293
+ resposta = p8.get("final", p7.get("resposta", "Erro"))
294
+ novo_hist = historico + [{"role": "user", "content": pergunta},
295
+ {"role": "assistant", "content": resposta}]
296
+
297
+ dna["historico_chat"].append({"u": pergunta, "a": resposta})
298
+ dna["meta"]["turnos"] += 1
299
+
300
+ return resposta, novo_hist, dna
301
+
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
302
  except Exception as e:
303
+ logger.log(f"ERRO v10: {str(e)}", "ERROR")
304
  return f"Erro: {str(e)}", historico, dna
305
 
306
  # ============================================================================
307
+ # INTERFACE
308
  # ============================================================================
309
+ def chat_interface(msg: str, hist: List, anexo=None, dna_json: str="{}") -> Tuple[List, str, str, None]:
310
+ try: dna = json.loads(dna_json) if dna_json else {}
311
+ except: dna = {}
312
+
313
+ resp, novo_hist, dna_new = processar_pipeline(msg, hist, anexo, dna)
314
+ return novo_hist, "", json.dumps(dna_new, indent=2), None
 
 
 
 
 
 
 
 
 
 
 
 
 
315
 
 
 
 
316
  if __name__ == "__main__":
317
+ with gr.Blocks(title="Pipeline v10", theme=gr.themes.Soft()) as demo:
318
+ gr.Markdown(TITLE + "\n**X1-Perguntas X2-Resolução Cenários Aprofundados**")
319
+
320
  with gr.Row():
321
  with gr.Column(scale=2):
322
+ chat = gr.Chatbot(height=550, type="messages")
323
+ dna_view = gr.Code(label="DNA", language="json", lines=12)
324
  with gr.Column(scale=1):
325
+ input_txt = gr.Textbox(label="Pergunta", lines=3, scale=2)
326
+ file_up = gr.File(label="Anexo", file_types=[".pdf",".png",".jpg"])
327
+ btn_go = gr.Button("🚀 v10", variant="primary")
328
+
329
+ btn_go.click(chat_interface, [input_txt, chat, file_up, dna_view],
330
+ [chat, input_txt, dna_view, file_up])
331
+ input_txt.submit(chat_interface, [input_txt, chat, file_up, dna_view],
332
+ [chat, input_txt, dna_view, file_up])
333
+
334
+ demo.launch(server_name="0.0.0.0", server_port=7860)