caarleexx commited on
Commit
cdd457f
·
verified ·
1 Parent(s): a25df5c

Upload ai_studio_code (2).py

Browse files
Files changed (1) hide show
  1. ai_studio_code (2).py +705 -0
ai_studio_code (2).py ADDED
@@ -0,0 +1,705 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ Pipeline v10 Refatorada - Chatbot com Metacognição Pura e Verificação.
4
+
5
+ Esta aplicação implementa um chatbot avançado utilizando a API Google Gemini.
6
+ A arquitetura se baseia em uma pipeline de múltiplos passos (P0-P8, X1-X2)
7
+ que analisa, raciocina, gera cenários e verifica as respostas antes de
8
+ entregá-las ao usuário.
9
+
10
+ Principais características:
11
+ - Orquestração de múltiplos modelos (Counselor e Supervisor).
12
+ - Passos de metacognição para análise interna do problema.
13
+ - Geração e avaliação de múltiplos cenários de resposta.
14
+ - Verificação final de fatos, lógica e ética.
15
+ - Estrutura robusta para parsing de JSON e tratamento de anexos.
16
+ """
17
+ import json
18
+ import os
19
+ import base64
20
+ import re
21
+ import warnings
22
+ from datetime import datetime
23
+ from typing import Dict, List, Tuple, Any
24
+
25
+ import gradio as gr
26
+ import google.generativeai as genai
27
+ # Ignora avisos futuros de dependências da API do Google
28
+ warnings.filterwarnings("ignore", category=FutureWarning, module="google.api_core")
29
+
30
+ # ============================================================================
31
+ # 1. CONFIGURAÇÃO E INICIALIZAÇÃO
32
+ # ============================================================================
33
+
34
+ # Carrega a chave da API a partir de variáveis de ambiente.
35
+ # É uma prática de segurança recomendada para não expor chaves no código.
36
+ API_KEY = os.getenv("GOOGLE_API_KEY")
37
+ if not API_KEY:
38
+ raise ValueError("A variável de ambiente GOOGLE_API_KEY não foi configurada.")
39
+
40
+ genai.configure(api_key=API_KEY)
41
+
42
+ # Modelos utilizados na pipeline:
43
+ # - Counselor: Responsável pela maior parte da análise e raciocínio.
44
+ # - Supervisor: Responsável pela verificação final de qualidade.
45
+ COUNSELOR_MODEL = genai.GenerativeModel("gemini-1.5-flash")
46
+ SUPERVISOR_MODEL = genai.GenerativeModel("gemini-1.5-flash")
47
+
48
+ TITLE = "# 🚀 Pipeline v10 REATORADA\n**P0-P1 → X1-X2 → P2-P8 (com Metacognição Pura e Verificação)**"
49
+
50
+ # ============================================================================
51
+ # 2. PROMPTS CENTRALIZADOS
52
+ # ============================================================================
53
+
54
+ PROMPTS = {
55
+ "P0_ALUNO": """
56
+ Você é um METACOGNITIVO (pensamento interno, NÃO comunicação).
57
+
58
+ TURNO ANTERIOR:
59
+ User: {turno_anterior_user}
60
+ Assistant: {turno_anterior_assistant}
61
+
62
+ NOVA MENSAGEM: {pergunta}
63
+ CONTEXTO VAGO: {historico_compacto}
64
+
65
+ ---
66
+
67
+ Responda EM METACOGNIÇÃO PURA - TELEGRÁFICO
68
+ NÃO use frases completas. APENAS essência semântica com conectores mínimos.
69
+
70
+ EXEMPLO CERTO: entendeu-sim | pergunta-nova | avança-tópico | não-reformulou
71
+
72
+ RETORNE JSON:
73
+ {{
74
+ "usuario_entendeu": "sim|não",
75
+ "evidencias": ["entendeu-pergunta", "pediu-clarificação"],
76
+ "usuario_corrigiu": "sim|não",
77
+ "correcao_detectada": null|"texto-correção",
78
+ "correcao_valida": "sim|não|null",
79
+ "o_que_melhorar": null|"explicar-X-melhor",
80
+ "decisao": "prosseguir-passo1|reexplicar-passo6|atualizar-resposta-anterior",
81
+ "motivo": "texto-curtíssimo"
82
+ }}
83
+ """,
84
+ "P1_TRIAGEM": """
85
+ METACOGNIÇÃO - TRIAGEM INICIAL.
86
+
87
+ CONTEXTO VAGO: {contexto_vago}
88
+ HISTÓRICO RECENTE (últimas 3): {historico_recente}
89
+ P0: {p0}
90
+ PERGUNTA: {pergunta}
91
+
92
+ ---
93
+
94
+ CLASSIFIQUE EM TELEGRÁFICO (sem frases).
95
+
96
+ RETORNE JSON:
97
+ {{
98
+ "tipo": "objetiva|factual|subjetiva|aberta",
99
+ "sinais": ["tem-resposta-única-verificável", "sem-contexto-pessoal"],
100
+ "confianca": "alta|média|baixa",
101
+ "decisao": "responder-direto|analisar-profundamente",
102
+ "razao": "curtíssima",
103
+ "dados_fatuais": ["fato1", "fato2"],
104
+ "divergencias_fatuais": ["possível-ambiguidade-1"],
105
+ "objetivo_principal": "objetivo-primário-identificado",
106
+ "objetivo_secundario": ["objetivo-secundário-1"]
107
+ }}
108
+ """,
109
+ "X1_PERGUNTAS_NECESSARIAS": """
110
+ X1-PERGUNTAS CRÍTICAS - TELEGRÁFICO
111
+ P1: {p1}
112
+ CONTEXTO: {historico_compacto}
113
+ PERGUNTA PRINCIPAL: {pergunta}
114
+
115
+ ---
116
+ Analise as lacunas factuais e subjetivas na pergunta do usuário e no contexto.
117
+ Liste as perguntas essenciais que você precisa responder internamente antes de formular a resposta final.
118
+
119
+ RETORNE JSON:
120
+ {{"perguntas": [
121
+ {{"texto": "pergunta-curta-e-essencial", "necessidade": "alta|média|baixa", "relevancia": "alta|média"}}
122
+ ]}}
123
+ """,
124
+ "X2_RESOLVER_PERGUNTAS": """
125
+ X2-RESOLUÇÃO INTERNA - TELEGRÁFICO
126
+ P1: {p1}
127
+ PERGUNTAS CRÍTICAS (X1): {perguntas_x1}
128
+ CONTEXTO: {historico_compacto}
129
+
130
+ ---
131
+ Para cada pergunta crítica levantada no passo anterior, forneça uma resposta curta e direta baseada no seu conhecimento.
132
+ Avalie sua confiança e o potencial de conflito ou ambiguidade em cada resposta.
133
+
134
+ RETORNE JSON:
135
+ {{"respostas": [
136
+ {{"pergunta": "texto-original-da-pergunta-x1",
137
+ "resposta": "resposta-curta-e-direta",
138
+ "confianca": "alta|média|baixa",
139
+ "conflito": "alto|médio|baixo",
140
+ "razao": "explicação-em-1-2-palavras"}}
141
+ ]}}
142
+ """,
143
+ "P2_CENARIOS": """
144
+ METACOGNIÇÃO - GERAÇÃO DE CENÁRIOS.
145
+ CONTEXTO VAGO: {historico_compacto}
146
+ TRIAGEM P1: {p1}
147
+ X1-PERGUNTAS: {x1}
148
+ X2-RESPOSTAS: {x2}
149
+ PERGUNTA ORIGINAL: {pergunta}
150
+
151
+ ---
152
+
153
+ Mapeie cenários possíveis onde a resposta à pergunta original mudaria significativamente. Pense nas diferentes perspectivas, contextos ou premissas que alteram a conclusão. Use formato telegráfico.
154
+
155
+ RETORNE JSON:
156
+ {{
157
+ "cenarios": {{
158
+ "provaveis": [{{"id": "C1", "desc": "cenário-provável-1-comprimido", "contexto-relevante": "descreva-o-contexto"}}]
159
+ }},
160
+ "total": 1,
161
+ "tipo_resposta": "múltipla|unívoca",
162
+ "confianca": "alta|média|baixa",
163
+ "decisao": "prosseguir|pedir-esclarecimento",
164
+ "pergunta_esclarecimento": null|"texto-da-pergunta-para-o-usuario"
165
+ }}
166
+ """,
167
+ "P3_ISOLAR_CENARIOS": """
168
+ METACOGNIÇÃO - EXPLORAÇÃO DE CENÁRIO ISOLADO.
169
+ CENÁRIO P2: {cenario}
170
+
171
+ ---
172
+ Para este cenário específico, defina a essência da resposta em formato telegráfico. Qual seria a conclusão principal e quais as lacunas de informação restantes?
173
+
174
+ RETORNE JSON:
175
+ {{"id": "{cenario_id}",
176
+ "resposta_essencia": "conclusão-principal-e-razoes-em-palavras-chave",
177
+ "confianca": "alta|média|baixa",
178
+ "lacunas": "contexto-ainda-ausente|null"
179
+ }}
180
+ """,
181
+ "P4_CRUZAR_VALIDACOES": """
182
+ METACOGNIÇÃO - ABSTRAÇÃO DE CONHECIMENTO.
183
+ P1 (Triagem): {p1}
184
+ P2 (Cenários): {p2}
185
+ P3 (Exploração): {p3}
186
+ X2 (Respostas Internas): {x2}
187
+
188
+ ---
189
+
190
+ Identifique os princípios fundamentais, teorias ou símbolos arquetípicos que sustentam as respostas nos cenários explorados. Abstraia o conhecimento para um nível mais alto. Use formato telegráfico.
191
+
192
+ RETORNE JSON:
193
+ {{"principios": [{{"nome": "Custo-Oportunidade", "essencia": "escolher-X-implica-renunciar-Y"}}],
194
+ "simbolos": [{{"nome": "Jornada-do-Herói", "essencia": "transformação-ocorre-através-de-desafios"}}],
195
+ "principio_central": "nome-do-principio-mais-importante",
196
+ "simbolo_dominante": "nome-do-simbolo-mais-relevante"
197
+ }}
198
+ """,
199
+ "P5_LACUNAS_FINAIS": """
200
+ METACOGNIÇÃO - ANÁLISE DE INCERTEZA.
201
+ P1 (Triagem): {p1}
202
+ P4 (Princípios): {p4}
203
+
204
+ ---
205
+
206
+ Avalie o balanço entre certezas e dúvidas com base em toda a análise feita até agora. A informação disponível é suficiente para dar uma resposta confiante? Use formato telegráfico.
207
+
208
+ RETORNE JSON:
209
+ {{"analise_cenarios": [{{"cenario": "C1", "certezas": ["certeza1"], "duvidas": ["dúvida1"]}}],
210
+ "confianca_global": "alta|média|baixa",
211
+ "balanco": "certezas-superam|equilibrado|duvidas-superam",
212
+ "decisao": "responder|questionar",
213
+ "questionamento": null|"texto-da-pergunta-para-o-usuario-se-a-confianca-for-baixa"
214
+ }}
215
+ """,
216
+ "P6_PONDERAR": """
217
+ METACOGNIÇÃO - JULGAMENTO FINAL (JUIZ DA VERDADE).
218
+ P2 (Cenários): {p2}
219
+ P4 (Princípios): {p4}
220
+ P5 (Lacunas): {p5}
221
+
222
+ ---
223
+
224
+ Aja como um árbitro socrático. Com base em toda a metacognição, valide as "verdades" encontradas e decida o nível de consciência sobre a complexidade da resposta. Use formato telegráfico.
225
+
226
+ RETORNE JSON:
227
+ {{"verdade_principal": "a-conclusao-mais-provavel-e-confiavel",
228
+ "nuances_importantes": ["nuance1", "nuance2"],
229
+ "confianca_final": "alta|média|baixa",
230
+ "decisao": "exibir-resposta-completa|exibir-resposta-com-ressalvas|reprocessar",
231
+ "nivel_consciencia": "alto|médio|baixo"
232
+ }}
233
+ """,
234
+ "P7_SINTETIZAR": """
235
+ Você é um SINTETIZADOR especialista em transformar METACOGNIÇÃO CRUA em PROSA HUMANIZADA e empática.
236
+
237
+ DADOS DO JULGAMENTO (P6): {p6}
238
+
239
+ ---
240
+
241
+ TAREFA: Converta a análise telegráfica do 'Juiz da Verdade' em uma resposta textual fluida, natural e útil para o usuário.
242
+
243
+ INSTRUÇÕES:
244
+ 1. Use conectores naturais (ex: "porque", "portanto", "isso significa que").
245
+ 2. Expanda abreviações e jargões para uma linguagem clara.
246
+ 3. Estruture a resposta em parágrafos lógicos (introdução, desenvolvimento, nuances/conclusão).
247
+ 4. Incorpore os princípios e nuances de forma natural na explicação.
248
+ 5. Adote um tom de conselheiro: amigável, empático e empoderador.
249
+ 6. NÃO invente informações. Baseie-se estritamente nos dados do P6.
250
+
251
+ RETORNE A RESPOSTA EM PROSA DENTRO DE UM JSON:
252
+ {{"resposta": "Aqui vai o texto fluido, natural e humano..."}}
253
+ """,
254
+ "P8_VERIFICAR": """
255
+ Você é um VERIFICADOR FINAL, um guardião rigoroso da qualidade da resposta.
256
+
257
+ RESPOSTA SINTETIZADA (P7):
258
+ {resposta_sintetizada}
259
+
260
+ ANÁLISE DO JUIZ (P6):
261
+ {p6}
262
+
263
+ ---
264
+
265
+ Realize uma verificação tripla na resposta sintetizada. Seja crítico.
266
+
267
+ 1. **VERIFICAÇÃO FACTUAL**: A resposta contém fatos incorretos ou não sustentados pela análise do P6?
268
+ 2. **VERIFICAÇÃO LÓGICA**: Existem falácias, saltos de lógica ou contradições? A conclusão segue a linha de raciocínio?
269
+ 3. **VERIFICAÇÃO ÉTICA**: A resposta é apropriada, segura e imparcial? Inclui os avisos ou ressalvas necessários?
270
+
271
+ RETORNE SEU VEREDITO EM JSON:
272
+ {{"verificacao_factual": {{"aprovada": true|false, "problemas": ["descrição do problema se houver"]}},
273
+ "verificacao_logica": {{"aprovada": true|false, "problemas": []}},
274
+ "verificacao_etica": {{"aprovada": true|false, "problemas": []}},
275
+ "todas_aprovadas": true|false,
276
+ "decisao": "exibir-resposta-original|corrigir-e-exibir",
277
+ "resposta_corrigida": null|"texto da versão corrigida e melhorada da resposta"
278
+ }}
279
+ """
280
+ }
281
+
282
+ # ============================================================================
283
+ # 3. CLASSES E FUNÇÕES HELPERS
284
+ # ============================================================================
285
+
286
+ class Logger:
287
+ """Classe simples para registrar logs formatados no console."""
288
+ def __init__(self, verbose: bool = True):
289
+ self.verbose = verbose
290
+ self.logs = []
291
+
292
+ def log(self, msg: str, level: str = "INFO"):
293
+ """Registra uma mensagem de log com timestamp e nível."""
294
+ timestamp = datetime.now().strftime("%H:%M:%S")
295
+ log_msg = f"[{timestamp}] [{level.upper()}] {msg}"
296
+ self.logs.append(log_msg)
297
+ if self.verbose:
298
+ print(log_msg)
299
+ if level.upper() in ["TASK", "START", "SUCCESS", "ERROR"]:
300
+ print("=" * 70)
301
+
302
+ logger = Logger(verbose=True)
303
+
304
+ def processar_anexo(arquivo: Any) -> Tuple[str, str]:
305
+ """
306
+ Processa um arquivo enviado, extraindo texto de PDFs ou representando imagens.
307
+ Retorna o conteúdo processado e o tipo de arquivo.
308
+ """
309
+ if arquivo is None:
310
+ return "", "nenhum"
311
+
312
+ caminho_arquivo = arquivo.name # Em Gradio, .name contém o path temporário
313
+
314
+ try:
315
+ if caminho_arquivo.lower().endswith('.pdf'):
316
+ try:
317
+ import PyPDF2
318
+ with open(caminho_arquivo, 'rb') as f:
319
+ leitor = PyPDF2.PdfReader(f)
320
+ # Extrai texto das 3 primeiras páginas para manter o prompt conciso
321
+ texto = "".join(page.extract_text() + "\n" for page in leitor.pages[:3])
322
+ return texto[:3000], "pdf" # Limita o tamanho do texto
323
+ except ImportError:
324
+ logger.log("PyPDF2 não instalado. PDF não pode ser lido.", "WARN")
325
+ return "[ERRO: PyPDF2 não instalado para ler PDF]", "erro"
326
+ except Exception as e:
327
+ logger.log(f"Falha ao ler PDF: {e}", "ERROR")
328
+ return f"[PDF detectado, mas falha na leitura: {e}]", "pdf"
329
+
330
+ elif any(caminho_arquivo.lower().endswith(ext) for ext in ['.png', '.jpg', '.jpeg', '.gif']):
331
+ with open(caminho_arquivo, 'rb') as f:
332
+ encoded_string = base64.b64encode(f.read()).decode()
333
+ return encoded_string[:1000], "imagem" # Retorna uma parte da string base64
334
+
335
+ return "", "nao_suportado"
336
+ except Exception as e:
337
+ logger.log(f"Erro inesperado em processar_anexo: {e}", "ERROR")
338
+ return "", "erro"
339
+
340
+ def construir_prompt_com_anexo(pergunta: str, anexo_conteudo: str, tipo_anexo: str) -> str:
341
+ """Adiciona o conteúdo do anexo ao prompt da pergunta inicial."""
342
+ if not anexo_conteudo or tipo_anexo in ["nenhum", "erro", "nao_suportado"]:
343
+ return pergunta
344
+ if tipo_anexo == "pdf":
345
+ return f"Com base no documento PDF abaixo, responda à pergunta.\n\nDOCUMENTO:\n---\n{anexo_conteudo}\n---\n\nPERGUNTA: {pergunta}"
346
+ if tipo_anexo == "imagem":
347
+ return f"Com base na imagem anexada, responda à pergunta: {pergunta}" # Modelo deve ser capaz de lidar com imagem + texto
348
+ return pergunta
349
+
350
+ def parse_json_ultra_robusto(texto: str) -> Dict:
351
+ """
352
+ Extrai um objeto JSON de uma string, mesmo que esteja mal formatada ou
353
+ contenha texto adicional (ex: ```json ... ```).
354
+ """
355
+ if not texto:
356
+ return {"erro": "Texto de entrada vazio"}
357
+
358
+ # 1. Tenta extrair JSON de blocos de código (```json ... ```)
359
+ match = re.search(r'```(?:json)?\s*(\{.*?\})\s*```', texto, re.DOTALL)
360
+ if match:
361
+ json_str = match.group(1)
362
+ try:
363
+ return json.loads(json_str)
364
+ except json.JSONDecodeError:
365
+ texto = json_str # Prossiga com a string extraída
366
+
367
+ # 2. Tenta encontrar o primeiro JSON completo e balanceado
368
+ try:
369
+ inicio = texto.find('{')
370
+ if inicio != -1:
371
+ fim, contador = -1, 0
372
+ for i in range(inicio, len(texto)):
373
+ if texto[i] == '{':
374
+ contador += 1
375
+ elif texto[i] == '}':
376
+ contador -= 1
377
+ if contador == 0:
378
+ fim = i + 1
379
+ break
380
+ if fim != -1:
381
+ return json.loads(texto[inicio:fim])
382
+ except (json.JSONDecodeError, IndexError):
383
+ pass # Ignora erros e tenta o próximo método
384
+
385
+ logger.log("Falha na extração robusta de JSON. Retornando fallback.", "WARN")
386
+ return {"erro": "parse_falhou", "fallback_text": texto[:500]}
387
+
388
+ def chamar_gemini_json(modelo: genai.GenerativeModel, prompt: str, temperatura: float = 0.5, max_tokens: int = 2000) -> Dict:
389
+ """
390
+ Envia um prompt para o modelo Gemini, força uma saída JSON e a analisa.
391
+ Retorna um dicionário, seja o JSON bem-sucedido ou um objeto de erro.
392
+ """
393
+ prompt_completo = f"{prompt}\n\n---\n\n**INSTRUÇÃO OBRIGATÓRIA: Sua resposta DEVE ser um único e válido objeto JSON. Não inclua texto antes ou depois do JSON.**"
394
+
395
+ logger.log(f"Enviando prompt ({len(prompt_completo)} chars) para {modelo.model_name}", "DEBUG")
396
+
397
+ try:
398
+ response = modelo.generate_content(
399
+ prompt_completo,
400
+ generation_config=genai.types.GenerationConfig(
401
+ temperature=temperatura,
402
+ max_output_tokens=max_tokens,
403
+ response_mime_type="application/json" # Força saída JSON se o modelo suportar
404
+ )
405
+ )
406
+
407
+ resposta_bruta = response.text or ""
408
+ logger.log(f"Gemini RAW ({len(resposta_bruta)} chars): {resposta_bruta[:400]}...", "DEBUG")
409
+
410
+ resultado_json = parse_json_ultra_robusto(resposta_bruta)
411
+ return resultado_json
412
+
413
+ except Exception as e:
414
+ logger.log(f"Erro na chamada da API Gemini: {e}", "ERROR")
415
+ return {"erro": f"API_ERROR: {str(e)}"}
416
+
417
+ def historico_compacto(historico: List[Dict]) -> str:
418
+ """Gera uma string curta com as últimas interações do chat."""
419
+ if not historico:
420
+ return "Nenhuma conversa anterior."
421
+
422
+ compacto = []
423
+ for msg in historico[-4:]: # Pega as últimas 4 mensagens
424
+ role = "Usuário" if msg["role"] == "user" else "Assistente"
425
+ content = msg["content"].split('\n')[0][:80] # Primeira linha, até 80 caracteres
426
+ compacto.append(f"{role}: {content}")
427
+
428
+ return "\n".join(compacto)
429
+
430
+ def criar_dna() -> Dict:
431
+ """Inicializa a estrutura de dados 'DNA' que persiste o estado da conversa."""
432
+ return {
433
+ "historico_chat": [],
434
+ "meta": {"total_turnos": 0}
435
+ }
436
+
437
+ # ============================================================================
438
+ # 4. PASSOS DA PIPELINE (P0-P8, X1-X2)
439
+ # ============================================================================
440
+
441
+ def passo_0_aluno(pergunta: str, historico: List[Dict]) -> Dict:
442
+ """P0: Analisa a pergunta atual em relação à resposta anterior (metacognição)."""
443
+ logger.log("🧠 P0-ALUNO - Analisando feedback do usuário", "TASK")
444
+ turno_anterior_user = historico[-2]['content'] if len(historico) >= 2 else 'N/A'
445
+ turno_anterior_assistant = historico[-1]['content'] if len(historico) >= 1 else 'N/A'
446
+
447
+ prompt = PROMPTS["P0_ALUNO"].format(
448
+ turno_anterior_user=turno_anterior_user,
449
+ turno_anterior_assistant=turno_anterior_assistant,
450
+ pergunta=pergunta,
451
+ historico_compacto=historico_compacto(historico)
452
+ )
453
+ return chamar_gemini_json(COUNSELOR_MODEL, prompt, temperatura=0.3)
454
+
455
+ def passo_1_triagem(pergunta: str, p0: Dict, historico: List[Dict]) -> Dict:
456
+ """P1: Faz uma triagem inicial da pergunta para classificar tipo e complexidade."""
457
+ logger.log("📊 P1-TRIAGEM - Classificando a pergunta", "TASK")
458
+ historico_recente_json = json.dumps(historico[-6:], indent=2, ensure_ascii=False)
459
+ p0_json = json.dumps(p0, indent=2, ensure_ascii=False)
460
+
461
+ prompt = PROMPTS["P1_TRIAGEM"].format(
462
+ contexto_vago=historico_compacto(historico),
463
+ historico_recente=historico_recente_json,
464
+ p0=p0_json,
465
+ pergunta=pergunta
466
+ )
467
+ return chamar_gemini_json(COUNSELOR_MODEL, prompt, temperatura=0.4)
468
+
469
+ def passo_x1_perguntas_necessarias(pergunta: str, p1: Dict, historico: List[Dict]) -> Dict:
470
+ """X1: Identifica quais perguntas internas precisam ser respondidas para resolver a questão."""
471
+ logger.log("❓ X1-PERGUNTAS CRÍTICAS - Identificando lacunas", "TASK")
472
+ prompt = PROMPTS["X1_PERGUNTAS_NECESSARIAS"].format(
473
+ p1=json.dumps(p1, indent=2),
474
+ historico_compacto=historico_compacto(historico),
475
+ pergunta=pergunta
476
+ )
477
+ return chamar_gemini_json(COUNSELOR_MODEL, prompt, max_tokens=1500)
478
+
479
+ def passo_x2_resolver_perguntas(p1: Dict, x1: Dict, historico: List[Dict]) -> Dict:
480
+ """X2: Responde internamente às perguntas levantadas em X1."""
481
+ logger.log("✅ X2-RESOLVER PERGUNTAS - Buscando conhecimento interno", "TASK")
482
+ perguntas_x1 = x1.get("perguntas", [])
483
+ prompt = PROMPTS["X2_RESOLVER_PERGUNTAS"].format(
484
+ p1=json.dumps(p1, indent=2),
485
+ perguntas_x1=json.dumps(perguntas_x1, indent=2),
486
+ historico_compacto=historico_compacto(historico)
487
+ )
488
+ return chamar_gemini_json(COUNSELOR_MODEL, prompt, max_tokens=2000)
489
+
490
+ def passo_2_cenarios(pergunta: str, p1: Dict, x1: Dict, x2: Dict, historico: List[Dict]) -> Dict:
491
+ """P2: Gera diferentes cenários ou perspectivas para a resposta."""
492
+ logger.log("🎯 P2-CENÁRIOS - Mapeando possibilidades", "TASK")
493
+ prompt = PROMPTS["P2_CENARIOS"].format(
494
+ historico_compacto=historico_compacto(historico),
495
+ p1=json.dumps(p1, indent=2),
496
+ x1=json.dumps(x1, indent=2),
497
+ x2=json.dumps(x2, indent=2),
498
+ pergunta=pergunta
499
+ )
500
+ return chamar_gemini_json(COUNSELOR_MODEL, prompt, temperatura=0.6)
501
+
502
+ def passo_3_isolar_cenarios(p2: Dict) -> Dict:
503
+ """P3: Explora cada cenário gerado em P2 de forma isolada."""
504
+ logger.log("🔍 P3-ISOLAR CENÁRIOS - Explorando cada cenário", "TASK")
505
+ exploracoes = []
506
+ cenarios = p2.get('cenarios', {}).get('provaveis', [])
507
+ for c in cenarios[:3]: # Limita a 3 cenários para evitar complexidade excessiva
508
+ prompt = PROMPTS["P3_ISOLAR_CENARIOS"].format(
509
+ cenario=json.dumps(c, indent=2),
510
+ cenario_id=c.get('id')
511
+ )
512
+ exploracoes.append(chamar_gemini_json(COUNSELOR_MODEL, prompt, temperatura=0.5))
513
+
514
+ return {"exploracoes_isoladas": exploracoes}
515
+
516
+ def passo_4_cruzar_validacoes(p1: Dict, p2: Dict, p3: Dict, x2: Dict) -> Dict:
517
+ """P4: Abstrai princípios e símbolos a partir da análise dos cenários."""
518
+ logger.log("🔗 P4-VALIDAÇÃO CRUZADA - Identificando princípios", "TASK")
519
+ prompt = PROMPTS["P4_CRUZAR_VALIDACOES"].format(
520
+ p1=json.dumps(p1, indent=2),
521
+ p2=json.dumps(p2, indent=2),
522
+ p3=json.dumps(p3, indent=2),
523
+ x2=json.dumps(x2, indent=2)
524
+ )
525
+ return chamar_gemini_json(COUNSELOR_MODEL, prompt, temperatura=0.4)
526
+
527
+ def passo_5_lacunas_finais(p1: Dict, p4: Dict) -> Dict:
528
+ """P5: Realiza uma análise final de certezas vs. dúvidas."""
529
+ logger.log("🚨 P5-LACUNAS FINAIS - Avaliando confiança global", "TASK")
530
+ prompt = PROMPTS["P5_LACUNAS_FINAIS"].format(
531
+ p1=json.dumps(p1, indent=2),
532
+ p4=json.dumps(p4, indent=2)
533
+ )
534
+ return chamar_gemini_json(COUNSELOR_MODEL, prompt, temperatura=0.3)
535
+
536
+ def passo_6_ponderar(p2: Dict, p4: Dict, p5: Dict) -> Dict:
537
+ """P6: Atua como um 'Juiz da Verdade', ponderando toda a análise para uma decisão final."""
538
+ logger.log("⚖️ P6-PONDERAR (JUIZ) - Tomando a decisão final", "TASK")
539
+ prompt = PROMPTS["P6_PONDERAR"].format(
540
+ p2=json.dumps(p2, indent=2),
541
+ p4=json.dumps(p4, indent=2),
542
+ p5=json.dumps(p5, indent=2)
543
+ )
544
+ return chamar_gemini_json(COUNSELOR_MODEL, prompt, temperatura=0.4)
545
+
546
+ def passo_7_sintetizar(p6: Dict) -> Dict:
547
+ """P7: Converte a análise metacognitiva final em uma resposta em prosa humanizada."""
548
+ logger.log("✍️ P7-SINTETIZAR - Gerando prosa humanizada", "TASK")
549
+ prompt = PROMPTS["P7_SINTETIZAR"].format(p6=json.dumps(p6, indent=2))
550
+ # Usa uma temperatura mais alta para uma resposta mais criativa e fluida
551
+ return chamar_gemini_json(COUNSELOR_MODEL, prompt, temperatura=0.7, max_tokens=2500)
552
+
553
+ def passo_8_verificar(p6: Dict, p7: Dict) -> Dict:
554
+ """P8: Realiza uma verificação tripla (factual, lógica, ética) na resposta final."""
555
+ logger.log("🛡️ P8-VERIFICAR (SUPERVISOR) - Garantindo a qualidade", "TASK")
556
+ resposta_sintetizada = p7.get("resposta", "")
557
+ prompt = PROMPTS["P8_VERIFICAR"].format(
558
+ resposta_sintetizada=resposta_sintetizada,
559
+ p6=json.dumps(p6, indent=2)
560
+ )
561
+ return chamar_gemini_json(SUPERVISOR_MODEL, prompt, temperatura=0.2)
562
+
563
+ # ============================================================================
564
+ # 5. ORQUESTRADOR PRINCIPAL
565
+ # ============================================================================
566
+
567
+ def executar_pipeline(pergunta: str, historico: List[Dict], arquivo_anexo: Any, dna: Dict) -> Tuple[str, List, Dict]:
568
+ """
569
+ Orquestra a execução de todos os passos da pipeline, desde a análise
570
+ inicial até a verificação e entrega da resposta final.
571
+ """
572
+ logger.log(f"PIPELINE v10 INICIADA: '{pergunta[:50]}...'", "START")
573
+
574
+ if not pergunta or not pergunta.strip():
575
+ return "Por favor, digite uma pergunta válida.", historico, dna
576
+
577
+ # 1. Processamento de Anexos
578
+ conteudo_anexo, tipo_anexo = processar_anexo(arquivo_anexo)
579
+ pergunta_final = construir_prompt_com_anexo(pergunta, conteudo_anexo, tipo_anexo)
580
+
581
+ try:
582
+ # 2. Pipeline de Metacognição e Análise
583
+ p0 = passo_0_aluno(pergunta_final, historico)
584
+ p1 = passo_1_triagem(pergunta_final, p0, historico)
585
+ x1 = passo_x1_perguntas_necessarias(pergunta_final, p1, historico)
586
+ x2 = passo_x2_resolver_perguntas(p1, x1, historico)
587
+ p2 = passo_2_cenarios(pergunta_final, p1, x1, x2, historico)
588
+
589
+ # 3. Ponto de Interrupção: Esclarecimento
590
+ if p2.get("decisao") == "pedir-esclarecimento":
591
+ esclarecimento = p2.get("pergunta_esclarecimento", "Poderia fornecer mais detalhes?")
592
+ logger.log(f"Pipeline interrompida para pedir esclarecimento: {esclarecimento}", "INFO")
593
+ return f"❓ Para te dar uma resposta mais precisa, preciso de um esclarecimento:\n\n> {esclarecimento}", historico, dna
594
+
595
+ # 4. Aprofundamento e Síntese
596
+ p3 = passo_3_isolar_cenarios(p2)
597
+ p4 = passo_4_cruzar_validacoes(p1, p2, p3, x2)
598
+ p5 = passo_5_lacunas_finais(p1, p4)
599
+
600
+ # 5. Ponto de Interrupção: Confiança Baixa
601
+ if p5.get("decisao") == "questionar":
602
+ questionamento = p5.get("questionamento", "Não tenho informações suficientes para responder.")
603
+ logger.log(f"Pipeline interrompida por baixa confiança: {questionamento}", "INFO")
604
+ return f"🤔 {questionamento}", historico, dna
605
+
606
+ # 6. Julgamento, Geração e Verificação Final
607
+ p6 = passo_6_ponderar(p2, p4, p5)
608
+ p7 = passo_7_sintetizar(p6)
609
+ p8 = passo_8_verificar(p6, p7) # Corrigido para passar p6 e p7
610
+
611
+ # 7. Seleção da Resposta Final
612
+ if p8.get("todas_aprovadas") and p8.get("decisao") != "corrigir-e-exibir":
613
+ resposta_final = p7.get("resposta", "Não foi possível gerar uma resposta.")
614
+ else:
615
+ resposta_final = p8.get("resposta_corrigida", p7.get("resposta", "Ocorreu um erro na verificação final."))
616
+ logger.log("Resposta foi corrigida pelo Supervisor (P8).", "WARN")
617
+
618
+ except Exception as e:
619
+ logger.log(f"Erro crítico na execução da pipeline: {e}", "ERROR")
620
+ return f"❌ Ocorreu um erro inesperado durante o processamento: {e}", historico, dna
621
+
622
+ # 8. Atualização do Histórico e DNA
623
+ novo_historico = historico + [
624
+ {"role": "user", "content": pergunta},
625
+ {"role": "assistant", "content": resposta_final}
626
+ ]
627
+ dna["historico_chat"].append({"user": pergunta, "assistant": resposta_final})
628
+ dna["meta"]["total_turnos"] += 1
629
+
630
+ logger.log("PIPELINE CONCLUÍDA COM SUCESSO", "SUCCESS")
631
+ return resposta_final, novo_historico, dna
632
+
633
+ # ============================================================================
634
+ # 6. INTERFACE COM GRADIO
635
+ # ============================================================================
636
+
637
+ def chat_interface(pergunta: str, historico: List[Dict], anexo: Any, dna_json_str: str) -> Tuple[List, str, str, None]:
638
+ """Função de callback para a interface do Gradio."""
639
+ logger.log(f"Nova mensagem recebida: '{pergunta[:80]}...'", "INFO")
640
+
641
+ try:
642
+ dna = json.loads(dna_json_str) if dna_json_str and dna_json_str.strip() else criar_dna()
643
+ except (json.JSONDecodeError, TypeError):
644
+ logger.log("DNA inválido ou vazio, inicializando um novo.", "WARN")
645
+ dna = criar_dna()
646
+
647
+ if not isinstance(dna, dict) or "meta" not in dna:
648
+ logger.log("Estrutura do DNA corrompida, reinicializando.", "WARN")
649
+ dna = criar_dna()
650
+
651
+ resposta, novo_historico, dna_atualizado = executar_pipeline(pergunta, historico, anexo, dna)
652
+
653
+ logger.log(f"Resposta enviada ({len(resposta)} chars).", "INFO")
654
+
655
+ # Retorna os valores para atualizar os componentes do Gradio:
656
+ # 1. chatbot (novo histórico)
657
+ # 2. textbox de input (limpo)
658
+ # 3. code viewer do DNA (atualizado)
659
+ # 4. file uploader (limpo)
660
+ return novo_historico, "", json.dumps(dna_atualizado, indent=2, ensure_ascii=False), None
661
+
662
+ if __name__ == "__main__":
663
+ with gr.Blocks(title="Pipeline v10 Refatorada", theme=gr.themes.Soft()) as demo:
664
+ gr.Markdown(TITLE)
665
+
666
+ with gr.Row():
667
+ with gr.Column(scale=3):
668
+ chatbot = gr.Chatbot(label="Chat", height=600, bubble_full_width=False)
669
+ dna_state = gr.State(value=criar_dna()) # Usar gr.State para o histórico completo
670
+ with gr.Column(scale=1):
671
+ dna_view = gr.Code(label="DNA (Estado da Conversa)", language="json", interactive=False,
672
+ value=json.dumps(criar_dna(), indent=2, ensure_ascii=False))
673
+ file_upload = gr.File(label="Anexar PDF ou Imagem", file_types=[".pdf", ".png", ".jpg", ".jpeg"])
674
+
675
+ with gr.Row():
676
+ input_textbox = gr.Textbox(
677
+ label="Digite sua pergunta aqui...",
678
+ placeholder="Ex: Qual a melhor estratégia para aprender uma nova habilidade?",
679
+ lines=3,
680
+ scale=4,
681
+ )
682
+ submit_button = gr.Button("🚀 Enviar (v10)", variant="primary", scale=1)
683
+
684
+ # Oculta o estado do DNA em formato JSON string para passar entre chamadas
685
+ dna_json_hidden = gr.Textbox(value=json.dumps(criar_dna()), visible=False)
686
+
687
+ # Ações da Interface
688
+ submit_button.click(
689
+ fn=chat_interface,
690
+ inputs=[input_textbox, chatbot, file_upload, dna_json_hidden],
691
+ outputs=[chatbot, input_textbox, dna_json_hidden, file_upload]
692
+ )
693
+ input_textbox.submit(
694
+ fn=chat_interface,
695
+ inputs=[input_textbox, chatbot, file_upload, dna_json_hidden],
696
+ outputs=[chatbot, input_textbox, dna_json_hidden, file_upload]
697
+ )
698
+ # Sincroniza o DNA JSON oculto com o visualizador
699
+ dna_json_hidden.change(
700
+ fn=lambda x: x,
701
+ inputs=[dna_json_hidden],
702
+ outputs=[dna_view]
703
+ )
704
+
705
+ demo.launch(server_name="0.0.0.0", server_port=7860, share=False)