caarleexx commited on
Commit
9e32e25
·
verified ·
1 Parent(s): ab33b13

Delete app.py

Browse files
Files changed (1) hide show
  1. app.py +0 -836
app.py DELETED
@@ -1,836 +0,0 @@
1
- # -*- coding: utf-8 -*-
2
- """
3
- Pipeline v10 Refatorada e Comentada - Chatbot com Metacognição Pura.
4
-
5
- Este arquivo 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
- Esta versão inclui:
11
- - A lógica completa da pipeline com todos os passos.
12
- - Correções para a compatibilidade com o componente gr.Chatbot.
13
- - Logs de depuração detalhados para as chamadas da API Gemini.
14
- - Comentários extensivos em todo o código para fins didáticos.
15
- """
16
-
17
- # ============================================================================
18
- # 1. IMPORTAÇÕES E CONFIGURAÇÃO INICIAL
19
- # ============================================================================
20
-
21
- # Módulos padrão do Python
22
- import json
23
- import os
24
- import base64
25
- import re
26
- import warnings
27
- from datetime import datetime
28
- from typing import Dict, List, Tuple, Any
29
-
30
- # Bibliotecas de terceiros
31
- import gradio as gr # Para a criação da interface web
32
- import google.generativeai as genai # SDK oficial do Google para a API Gemini
33
-
34
- # Ignora avisos de "FutureWarning" que podem ser gerados por dependências
35
- # da API do Google, mantendo o console mais limpo.
36
- warnings.filterwarnings("ignore", category=FutureWarning, module="google.api_core")
37
-
38
- # --- Configuração da API Gemini ---
39
-
40
- # Carrega a chave da API a partir de uma variável de ambiente chamada "GOOGLE_API_KEY".
41
- # Esta é uma prática de segurança essencial para não expor chaves secretas no código.
42
- API_KEY = os.getenv("GOOGLE_API_KEY")
43
- if not API_KEY:
44
- # Se a chave não for encontrada, levanta um erro claro para o usuário.
45
- raise ValueError("A variável de ambiente GOOGLE_API_KEY não foi configurada.")
46
-
47
- # Configura o SDK do Google com a chave de API fornecida.
48
- genai.configure(api_key=API_KEY)
49
-
50
- # --- Definição dos Modelos ---
51
-
52
- # Define os modelos a serem usados na pipeline.
53
- # O "Counselor" é responsável pela maior parte do raciocínio e geração.
54
- COUNSELOR_MODEL = genai.GenerativeModel("gemini-2.0-flash")
55
- # O "Supervisor" é um modelo focado na verificação final e garantia de qualidade.
56
- SUPERVISOR_MODEL = genai.GenerativeModel("gemini-2.0-flash")
57
-
58
- # Título que será exibido na interface do Gradio.
59
- TITLE = "# 🚀 Pipeline v10 REATORADA E COMENTADA\n**P0-P1 → X1-X2 → P2-P8 (com Metacognição Pura e Verificação)**"
60
-
61
-
62
- # ============================================================================
63
- # 2. PROMPTS CENTRALIZADOS
64
- # ============================================================================
65
- # Centralizar os prompts em um dicionário torna o código mais limpo, fácil de ler
66
- # e de manter. Cada chave corresponde a um passo da pipeline.
67
-
68
- PROMPTS = {
69
- "P0_ALUNO": """
70
- Você é um METACOGNITIVO (pensamento interno, NÃO comunicação).
71
-
72
- TURNO ANTERIOR:
73
- User: {turno_anterior_user}
74
- Assistant: {turno_anterior_assistant}
75
-
76
- NOVA MENSAGEM: {pergunta}
77
- CONTEXTO VAGO: {historico_compacto}
78
-
79
- ---
80
-
81
- Responda EM METACOGNIÇÃO PURA - TELEGRÁFICO
82
- NÃO use frases completas. APENAS essência semântica com conectores mínimos.
83
-
84
- EXEMPLO CERTO: entendeu-sim | pergunta-nova | avança-tópico | não-reformulou
85
-
86
- RETORNE JSON:
87
- {{
88
- "usuario_entendeu": "sim|não",
89
- "evidencias": ["entendeu-pergunta", "pediu-clarificação"],
90
- "usuario_corrigiu": "sim|não",
91
- "correcao_detectada": null|"texto-correção",
92
- "correcao_valida": "sim|não|null",
93
- "o_que_melhorar": null|"explicar-X-melhor",
94
- "decisao": "prosseguir-passo1|reexplicar-passo6|atualizar-resposta-anterior",
95
- "motivo": "texto-curtíssimo"
96
- }}
97
- """,
98
- "P1_TRIAGEM": """
99
- METACOGNIÇÃO - TRIAGEM INICIAL.
100
-
101
- CONTEXTO VAGO: {contexto_vago}
102
- HISTÓRICO RECENTE (últimas 3): {historico_recente}
103
- P0: {p0}
104
- PERGUNTA: {pergunta}
105
-
106
- ---
107
-
108
- CLASSIFIQUE EM TELEGRÁFICO (sem frases).
109
-
110
- RETORNE JSON:
111
- {{
112
- "tipo": "objetiva|factual|subjetiva|aberta",
113
- "sinais": ["tem-resposta-única-verificável", "sem-contexto-pessoal"],
114
- "confianca": "alta|média|baixa",
115
- "decisao": "responder-direto|analisar-profundamente",
116
- "razao": "curtíssima",
117
- "dados_fatuais": ["fato1", "fato2"],
118
- "divergencias_fatuais": ["possível-ambiguidade-1"],
119
- "objetivo_principal": "objetivo-primário-identificado",
120
- "objetivo_secundario": ["objetivo-secundário-1"]
121
- }}
122
- """,
123
- "X1_PERGUNTAS_NECESSARIAS": """
124
- X1-PERGUNTAS CRÍTICAS - TELEGRÁFICO
125
- P1: {p1}
126
- CONTEXTO: {historico_compacto}
127
- PERGUNTA PRINCIPAL: {pergunta}
128
-
129
- ---
130
- Analise as lacunas factuais e subjetivas na pergunta do usuário e no contexto.
131
- Liste as perguntas essenciais que você precisa responder internamente antes de formular a resposta final.
132
-
133
- RETORNE JSON:
134
- {{"perguntas": [
135
- {{"texto": "pergunta-curta-e-essencial", "necessidade": "alta|média|baixa", "relevancia": "alta|média"}}
136
- ]}}
137
- """,
138
- "X2_RESOLVER_PERGUNTAS": """
139
- X2-RESOLUÇÃO INTERNA - TELEGRÁFICO
140
- P1: {p1}
141
- PERGUNTAS CRÍTICAS (X1): {perguntas_x1}
142
- CONTEXTO: {historico_compacto}
143
-
144
- ---
145
- Para cada pergunta crítica levantada no passo anterior, forneça uma resposta curta e direta baseada no seu conhecimento.
146
- Avalie sua confiança e o potencial de conflito ou ambiguidade em cada resposta.
147
-
148
- RETORNE JSON:
149
- {{"respostas": [
150
- {{"pergunta": "texto-original-da-pergunta-x1",
151
- "resposta": "resposta-curta-e-direta",
152
- "confianca": "alta|média|baixa",
153
- "conflito": "alto|médio|baixo",
154
- "razao": "explicação-em-1-2-palavras"}}
155
- ]}}
156
- """,
157
- "P2_CENARIOS": """
158
- METACOGNIÇÃO - GERAÇÃO DE CENÁRIOS.
159
- CONTEXTO VAGO: {historico_compacto}
160
- TRIAGEM P1: {p1}
161
- X1-PERGUNTAS: {x1}
162
- X2-RESPOSTAS: {x2}
163
- PERGUNTA ORIGINAL: {pergunta}
164
-
165
- ---
166
-
167
- 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.
168
-
169
- RETORNE JSON:
170
- {{
171
- "cenarios": {{
172
- "provaveis": [{{"id": "C1", "desc": "cenário-provável-1-comprimido", "contexto-relevante": "descreva-o-contexto"}}]
173
- }},
174
- "total": 1,
175
- "tipo_resposta": "múltipla|unívoca",
176
- "confianca": "alta|média|baixa",
177
- "decisao": "prosseguir|pedir-esclarecimento",
178
- "pergunta_esclarecimento": null|"texto-da-pergunta-para-o-usuario"
179
- }}
180
- """,
181
- "P3_ISOLAR_CENARIOS": """
182
- METACOGNIÇÃO - EXPLORAÇÃO DE CENÁRIO ISOLADO.
183
- CENÁRIO P2: {cenario}
184
-
185
- ---
186
- 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?
187
-
188
- RETORNE JSON:
189
- {{"id": "{cenario_id}",
190
- "resposta_essencia": "conclusão-principal-e-razoes-em-palavras-chave",
191
- "confianca": "alta|média|baixa",
192
- "lacunas": "contexto-ainda-ausente|null"
193
- }}
194
- """,
195
- "P4_CRUZAR_VALIDACOES": """
196
- METACOGNIÇÃO - ABSTRAÇÃO DE CONHECIMENTO.
197
- P1 (Triagem): {p1}
198
- P2 (Cenários): {p2}
199
- P3 (Exploração): {p3}
200
- X2 (Respostas Internas): {x2}
201
-
202
- ---
203
-
204
- 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.
205
-
206
- RETORNE JSON:
207
- {{"principios": [{{"nome": "Custo-Oportunidade", "essencia": "escolher-X-implica-renunciar-Y"}}],
208
- "simbolos": [{{"nome": "Jornada-do-Herói", "essencia": "transformação-ocorre-através-de-desafios"}}],
209
- "principio_central": "nome-do-principio-mais-importante",
210
- "simbolo_dominante": "nome-do-simbolo-mais-relevante"
211
- }}
212
- """,
213
- "P5_LACUNAS_FINAIS": """
214
- METACOGNIÇÃO - ANÁLISE DE INCERTEZA.
215
- P1 (Triagem): {p1}
216
- P4 (Princípios): {p4}
217
-
218
- ---
219
-
220
- 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.
221
-
222
- RETORNE JSON:
223
- {{"analise_cenarios": [{{"cenario": "C1", "certezas": ["certeza1"], "duvidas": ["dúvida1"]}}],
224
- "confianca_global": "alta|média|baixa",
225
- "balanco": "certezas-superam|equilibrado|duvidas-superam",
226
- "decisao": "responder|questionar",
227
- "questionamento": null|"texto-da-pergunta-para-o-usuario-se-a-confianca-for-baixa"
228
- }}
229
- """,
230
- "P6_PONDERAR": """
231
- METACOGNIÇÃO - JULGAMENTO FINAL (JUIZ DA VERDADE).
232
- P2 (Cenários): {p2}
233
- P4 (Princípios): {p4}
234
- P5 (Lacunas): {p5}
235
-
236
- ---
237
-
238
- 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.
239
-
240
- RETORNE JSON:
241
- {{"verdade_principal": "a-conclusao-mais-provavel-e-confiavel",
242
- "nuances_importantes": ["nuance1", "nuance2"],
243
- "confianca_final": "alta|média|baixa",
244
- "decisao": "exibir-resposta-completa|exibir-resposta-com-ressalvas|reprocessar",
245
- "nivel_consciencia": "alto|médio|baixo"
246
- }}
247
- """,
248
- "P7_SINTETIZAR": """
249
- Você é um SINTETIZADOR especialista em transformar METACOGNIÇÃO CRUA em PROSA HUMANIZADA e empática.
250
-
251
- DADOS DO JULGAMENTO (P6): {p6}
252
-
253
- ---
254
-
255
- TAREFA: Converta a análise telegráfica do 'Juiz da Verdade' em uma resposta textual fluida, natural e útil para o usuário.
256
-
257
- INSTRUÇÕES:
258
- 1. Use conectores naturais (ex: "porque", "portanto", "isso significa que").
259
- 2. Expanda abreviações e jargões para uma linguagem clara.
260
- 3. Estruture a resposta em parágrafos lógicos (introdução, desenvolvimento, nuances/conclusão).
261
- 4. Incorpore os princípios e nuances de forma natural na explicação.
262
- 5. Adote um tom de conselheiro: amigável, empático e empoderador.
263
- 6. NÃO invente informações. Baseie-se estritamente nos dados do P6.
264
-
265
- RETORNE A RESPOSTA EM PROSA DENTRO DE UM JSON:
266
- {{"resposta": "Aqui vai o texto fluido, natural e humano..."}}
267
- """,
268
- "P8_VERIFICAR": """
269
- Você é um VERIFICADOR FINAL, um guardião rigoroso da qualidade da resposta.
270
-
271
- RESPOSTA SINTETIZADA (P7):
272
- {resposta_sintetizada}
273
-
274
- ANÁLISE DO JUIZ (P6):
275
- {p6}
276
-
277
- ---
278
-
279
- Realize uma verificação tripla na resposta sintetizada. Seja crítico.
280
-
281
- 1. **VERIFICAÇÃO FACTUAL**: A resposta contém fatos incorretos ou não sustentados pela análise do P6?
282
- 2. **VERIFICAÇÃO LÓGICA**: Existem falácias, saltos de lógica ou contradições? A conclusão segue a linha de raciocínio?
283
- 3. **VERIFICAÇÃO ÉTICA**: A resposta é apropriada, segura e imparcial? Inclui os avisos ou ressalvas necessários?
284
-
285
- RETORNE SEU VEREDITO EM JSON:
286
- {{"verificacao_factual": {{"aprovada": true|false, "problemas": ["descrição do problema se houver"]}},
287
- "verificacao_logica": {{"aprovada": true|false, "problemas": []}},
288
- "verificacao_etica": {{"aprovada": true|false, "problemas": []}},
289
- "todas_aprovadas": true|false,
290
- "decisao": "exibir-resposta-original|corrigir-e-exibir",
291
- "resposta_corrigida": null|"texto da versão corrigida e melhorada da resposta"
292
- }}
293
- """
294
- }
295
-
296
- # ============================================================================
297
- # 3. CLASSES E FUNÇÕES HELPERS (UTILITÁRIOS)
298
- # ============================================================================
299
-
300
- class Logger:
301
- """
302
- Uma classe simples para registrar logs formatados no console.
303
- Ajuda a visualizar o fluxo de execução e a depurar problemas.
304
- """
305
- def __init__(self, verbose: bool = True):
306
- self.verbose = verbose
307
- self.logs = []
308
-
309
- def log(self, msg: str, level: str = "INFO"):
310
- """Registra uma mensagem de log com timestamp, nível e formatação."""
311
- timestamp = datetime.now().strftime("%H:%M:%S")
312
- log_msg = f"[{timestamp}] [{level.upper()}] {msg}"
313
- self.logs.append(log_msg)
314
- if self.verbose:
315
- print(log_msg)
316
- # Imprime uma linha divisória para logs importantes, melhorando a visualização.
317
- if level.upper() in ["TASK", "START", "SUCCESS", "ERROR"]:
318
- print("=" * 70)
319
-
320
- # Instância global do Logger para ser usada em todo o script.
321
- logger = Logger(verbose=True)
322
-
323
- def processar_anexo(arquivo: Any) -> Tuple[str, str]:
324
- """
325
- Processa um arquivo enviado pela interface do Gradio.
326
- Atualmente, a lógica de extração está simplificada, mas a estrutura
327
- permite a implementação de leitores de PDF, imagens, etc.
328
-
329
- Args:
330
- arquivo: O objeto de arquivo vindo do Gradio.
331
-
332
- Returns:
333
- Uma tupla contendo (conteúdo_processado, tipo_do_arquivo).
334
- """
335
- if arquivo is None:
336
- return "", "nenhum"
337
-
338
- # O objeto 'arquivo' do Gradio tem um atributo '.name' que contém o caminho temporário do arquivo.
339
- caminho_arquivo = arquivo.name
340
-
341
- try:
342
- if caminho_arquivo.lower().endswith('.pdf'):
343
- # A lógica real de leitura de PDF (com PyPDF2, por exemplo) iria aqui.
344
- logger.log("Arquivo PDF detectado.", "INFO")
345
- return "[Conteúdo do PDF iria aqui]", "pdf"
346
- elif any(caminho_arquivo.lower().endswith(ext) for ext in ['.png', '.jpg', '.jpeg', '.gif']):
347
- # A lógica real de processamento de imagem (com Pillow, por exemplo) iria aqui.
348
- logger.log("Arquivo de imagem detectado.", "INFO")
349
- return "[Dados da imagem iriam aqui]", "imagem"
350
- return "", "nao_suportado"
351
- except Exception as e:
352
- logger.log(f"Erro ao processar anexo: {e}", "ERROR")
353
- return "", "erro"
354
-
355
- def construir_prompt_com_anexo(pergunta: str, anexo_conteudo: str, tipo_anexo: str) -> str:
356
- """
357
- Adiciona o conteúdo do anexo ao prompt da pergunta inicial para dar contexto ao modelo.
358
-
359
- Args:
360
- pergunta: A pergunta original do usuário.
361
- anexo_conteudo: O conteúdo extraído do anexo.
362
- tipo_anexo: O tipo do anexo ('pdf', 'imagem', etc.).
363
-
364
- Returns:
365
- O prompt final combinado.
366
- """
367
- if not anexo_conteudo or tipo_anexo in ["nenhum", "erro", "nao_suportado"]:
368
- return pergunta
369
- # Formata o prompt de maneira diferente dependendo do tipo de arquivo.
370
- if tipo_anexo == "pdf":
371
- return f"Com base no documento PDF abaixo, responda à pergunta.\n\nDOCUMENTO:\n---\n{anexo_conteudo}\n---\n\nPERGUNTA: {pergunta}"
372
- if tipo_anexo == "imagem":
373
- return f"Com base na imagem anexada, responda à pergunta: {pergunta}"
374
- return pergunta
375
-
376
- def parse_json_ultra_robusto(texto: str) -> Dict:
377
- """
378
- Extrai um objeto JSON de uma string, mesmo que ela contenha texto adicional
379
- ou formatação incorreta (como os marcadores ```json).
380
-
381
- Args:
382
- texto: A string retornada pela API que pode conter um JSON.
383
-
384
- Returns:
385
- Um dicionário Python com o JSON extraído ou um dicionário de erro.
386
- """
387
- if not texto:
388
- return {"erro": "Texto de entrada vazio"}
389
-
390
- # 1. Tenta extrair JSON de blocos de código (```json ... ```), que é comum em modelos de linguagem.
391
- match = re.search(r'```(?:json)?\s*(\{.*?\})\s*```', texto, re.DOTALL)
392
- if match:
393
- json_str = match.group(1)
394
- else:
395
- json_str = texto
396
-
397
- # 2. Tenta carregar a string extraída como JSON.
398
- try:
399
- return json.loads(json_str)
400
- except json.JSONDecodeError:
401
- # 3. Se falhar, tenta um método de "fallback": encontrar o primeiro '{' e o último '}'
402
- # e tentar fazer o parse do conteúdo entre eles. Isso ajuda a limpar lixo no início/fim.
403
- try:
404
- inicio = json_str.find('{')
405
- fim = json_str.rfind('}') + 1
406
- if inicio != -1 and fim != 0:
407
- return json.loads(json_str[inicio:fim])
408
- except json.JSONDecodeError:
409
- logger.log("Falha na extração robusta de JSON.", "WARN")
410
- return {"erro": "parse_falhou", "fallback_text": texto[:500]}
411
-
412
- def chamar_gemini_json(modelo: genai.GenerativeModel, prompt: str, temperatura: float = 0.5, max_tokens: int = 2000) -> Dict:
413
- """
414
- Envia um prompt para o modelo Gemini, solicita uma saída JSON, analisa a resposta
415
- e inclui logs detalhados para depuração.
416
-
417
- Args:
418
- modelo: A instância do modelo Gemini a ser usada.
419
- prompt: O prompt formatado para a tarefa específica.
420
- temperatura: Controla a criatividade da resposta (valores mais altos = mais criativo).
421
- max_tokens: O número máximo de tokens na resposta.
422
-
423
- Returns:
424
- Um dicionário Python com a resposta do modelo ou um dicionário de erro.
425
- """
426
- # Adiciona uma instrução final e explícita ao prompt para garantir que o modelo retorne JSON.
427
- 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.**"
428
-
429
- # === INÍCIO DO LOG DE DEPURAÇÃO (INPUT) ===
430
- # Imprime o prompt exato que está sendo enviado para a API.
431
- # Essencial para depurar o comportamento do modelo.
432
- print("\n" + "="*25 + f" 💬 API INPUT PARA [{modelo.model_name}] " + "="*25)
433
- print(prompt_completo)
434
- print("="*78 + "\n")
435
- logger.log(f"Enviando prompt ({len(prompt_completo)} chars) para {modelo.model_name}", "DEBUG")
436
- # === FIM DO LOG DE DEPURAÇÃO (INPUT) ===
437
-
438
- try:
439
- # Realiza a chamada para a API Gemini.
440
- response = modelo.generate_content(
441
- prompt_completo,
442
- generation_config=genai.types.GenerationConfig(
443
- temperature=temperatura,
444
- max_output_tokens=max_tokens,
445
- )
446
- )
447
-
448
- # Extrai o texto da resposta.
449
- resposta_bruta = response.text or ""
450
-
451
- # === INÍCIO DO LOG DE DEPURAÇÃO (OUTPUT) ===
452
- # Imprime a resposta bruta recebida da API antes de qualquer processamento.
453
- # Crucial para ver o que o modelo realmente retornou.
454
- print("\n" + "="*25 + f" 📥 API RAW OUTPUT DE [{modelo.model_name}] " + "="*25)
455
- print(resposta_bruta)
456
- print("="*78 + "\n")
457
- logger.log(f"Gemini RAW ({len(resposta_bruta)} chars): {resposta_bruta[:400]}...", "DEBUG")
458
- # === FIM DO LOG DE DEPURAÇÃO (OUTPUT) ===
459
-
460
- # Usa o parser robusto para converter a resposta de texto em um dicionário Python.
461
- resultado_json = parse_json_ultra_robusto(resposta_bruta)
462
- return resultado_json
463
-
464
- except Exception as e:
465
- # Captura qualquer erro durante a chamada da API (ex: problemas de conexão, erros de permissão).
466
- logger.log(f"Erro na chamada da API Gemini: {e}", "ERROR")
467
- return {"erro": f"API_ERROR: {str(e)}"}
468
-
469
- def historico_compacto(historico: List[Dict]) -> str:
470
- """
471
- Gera uma string curta com as últimas interações do chat para usar como contexto nos prompts.
472
-
473
- Args:
474
- historico: O histórico de conversa no formato interno.
475
-
476
- Returns:
477
- Uma string resumida da conversa recente.
478
- """
479
- if not historico:
480
- return "Nenhuma conversa anterior."
481
-
482
- # Pega as últimas 4 mensagens, formata e junta em uma única string.
483
- compacto = []
484
- for msg in historico[-4:]:
485
- role = "Usuário" if msg["role"] == "user" else "Assistente"
486
- content = msg["content"].split('\n')[:80] # Pega só a primeira linha, até 80 caracteres.
487
- compacto.append(f"{role}: {content}")
488
-
489
- return "\n".join(compacto)
490
-
491
- def criar_dna() -> Dict:
492
- """
493
- Inicializa a estrutura de dados 'DNA' que armazena o estado e metadados da conversa.
494
-
495
- Returns:
496
- Um dicionário com a estrutura inicial do DNA.
497
- """
498
- return {
499
- "historico_chat": [],
500
- "meta": {"total_turnos": 0}
501
- }
502
-
503
- # ============================================================================
504
- # 4. PASSOS DA PIPELINE (P0-P8, X1-X2)
505
- # ============================================================================
506
- # Cada função representa um passo de raciocínio da pipeline. Elas preparam
507
- # um prompt, chamam a API e retornam o resultado processado.
508
-
509
- def passo_0_aluno(pergunta: str, historico: List[Dict]) -> Dict:
510
- """P0: Analisa a pergunta atual em relação à resposta anterior (metacognição)."""
511
- logger.log("🧠 P0-ALUNO - Analisando feedback do usuário", "TASK")
512
- turno_anterior_user = historico[-2]['content'] if len(historico) >= 2 else 'N/A'
513
- turno_anterior_assistant = historico[-1]['content'] if len(historico) >= 1 else 'N/A'
514
-
515
- prompt = PROMPTS["P0_ALUNO"].format(
516
- turno_anterior_user=turno_anterior_user,
517
- turno_anterior_assistant=turno_anterior_assistant,
518
- pergunta=pergunta,
519
- historico_compacto=historico_compacto(historico)
520
- )
521
- return chamar_gemini_json(COUNSELOR_MODEL, prompt, temperatura=0.3)
522
-
523
- def passo_1_triagem(pergunta: str, p0: Dict, historico: List[Dict]) -> Dict:
524
- """P1: Faz uma triagem inicial da pergunta para classificar tipo e complexidade."""
525
- logger.log("📊 P1-TRIAGEM - Classificando a pergunta", "TASK")
526
- historico_recente_json = json.dumps(historico[-6:], indent=2, ensure_ascii=False)
527
- p0_json = json.dumps(p0, indent=2, ensure_ascii=False)
528
-
529
- prompt = PROMPTS["P1_TRIAGEM"].format(
530
- contexto_vago=historico_compacto(historico),
531
- historico_recente=historico_recente_json,
532
- p0=p0_json,
533
- pergunta=pergunta
534
- )
535
- return chamar_gemini_json(COUNSELOR_MODEL, prompt, temperatura=0.4)
536
-
537
- def passo_x1_perguntas_necessarias(pergunta: str, p1: Dict, historico: List[Dict]) -> Dict:
538
- """X1: Identifica quais perguntas internas precisam ser respondidas para resolver a questão."""
539
- logger.log("❓ X1-PERGUNTAS CRÍTICAS - Identificando lacunas", "TASK")
540
- prompt = PROMPTS["X1_PERGUNTAS_NECESSARIAS"].format(
541
- p1=json.dumps(p1, indent=2),
542
- historico_compacto=historico_compacto(historico),
543
- pergunta=pergunta
544
- )
545
- return chamar_gemini_json(COUNSELOR_MODEL, prompt, max_tokens=1500)
546
-
547
- def passo_x2_resolver_perguntas(p1: Dict, x1: Dict, historico: List[Dict]) -> Dict:
548
- """X2: Responde internamente às perguntas levantadas em X1."""
549
- logger.log("✅ X2-RESOLVER PERGUNTAS - Buscando conhecimento interno", "TASK")
550
- perguntas_x1 = x1.get("perguntas", [])
551
- prompt = PROMPTS["X2_RESOLVER_PERGUNTAS"].format(
552
- p1=json.dumps(p1, indent=2),
553
- perguntas_x1=json.dumps(perguntas_x1, indent=2),
554
- historico_compacto=historico_compacto(historico)
555
- )
556
- return chamar_gemini_json(COUNSELOR_MODEL, prompt, max_tokens=2000)
557
-
558
- def passo_2_cenarios(pergunta: str, p1: Dict, x1: Dict, x2: Dict, historico: List[Dict]) -> Dict:
559
- """P2: Gera diferentes cenários ou perspectivas para a resposta."""
560
- logger.log("🎯 P2-CENÁRIOS - Mapeando possibilidades", "TASK")
561
- prompt = PROMPTS["P2_CENARIOS"].format(
562
- historico_compacto=historico_compacto(historico),
563
- p1=json.dumps(p1, indent=2),
564
- x1=json.dumps(x1, indent=2),
565
- x2=json.dumps(x2, indent=2),
566
- pergunta=pergunta
567
- )
568
- return chamar_gemini_json(COUNSELOR_MODEL, prompt, temperatura=0.6)
569
-
570
- def passo_3_isolar_cenarios(p2: Dict) -> Dict:
571
- """P3: Explora cada cenário gerado em P2 de forma isolada."""
572
- logger.log("🔍 P3-ISOLAR CENÁRIOS - Explorando cada cenário", "TASK")
573
- exploracoes = []
574
- cenarios = p2.get('cenarios', {}).get('provaveis', [])
575
- for c in cenarios[:3]: # Limita a 3 cenários para evitar complexidade e custos excessivos.
576
- prompt = PROMPTS["P3_ISOLAR_CENARIOS"].format(
577
- cenario=json.dumps(c, indent=2),
578
- cenario_id=c.get('id')
579
- )
580
- exploracoes.append(chamar_gemini_json(COUNSELOR_MODEL, prompt, temperatura=0.5))
581
-
582
- return {"exploracoes_isoladas": exploracoes}
583
-
584
- def passo_4_cruzar_validacoes(p1: Dict, p2: Dict, p3: Dict, x2: Dict) -> Dict:
585
- """P4: Abstrai princípios e símbolos a partir da análise dos cenários."""
586
- logger.log("🔗 P4-VALIDAÇÃO CRUZADA - Identificando princípios", "TASK")
587
- prompt = PROMPTS["P4_CRUZAR_VALIDACOES"].format(
588
- p1=json.dumps(p1, indent=2),
589
- p2=json.dumps(p2, indent=2),
590
- p3=json.dumps(p3, indent=2),
591
- x2=json.dumps(x2, indent=2)
592
- )
593
- return chamar_gemini_json(COUNSELOR_MODEL, prompt, temperatura=0.4)
594
-
595
- def passo_5_lacunas_finais(p1: Dict, p4: Dict) -> Dict:
596
- """P5: Realiza uma análise final de certezas vs. dúvidas."""
597
- logger.log("🚨 P5-LACUNAS FINAIS - Avaliando confiança global", "TASK")
598
- prompt = PROMPTS["P5_LACUNAS_FINAIS"].format(
599
- p1=json.dumps(p1, indent=2),
600
- p4=json.dumps(p4, indent=2)
601
- )
602
- return chamar_gemini_json(COUNSELOR_MODEL, prompt, temperatura=0.3)
603
-
604
- def passo_6_ponderar(p2: Dict, p4: Dict, p5: Dict) -> Dict:
605
- """P6: Atua como um 'Juiz da Verdade', ponderando toda a análise para uma decisão final."""
606
- logger.log("⚖️ P6-PONDERAR (JUIZ) - Tomando a decisão final", "TASK")
607
- prompt = PROMPTS["P6_PONDERAR"].format(
608
- p2=json.dumps(p2, indent=2),
609
- p4=json.dumps(p4, indent=2),
610
- p5=json.dumps(p5, indent=2)
611
- )
612
- return chamar_gemini_json(COUNSELOR_MODEL, prompt, temperatura=0.4)
613
-
614
- def passo_7_sintetizar(p6: Dict) -> Dict:
615
- """P7: Converte a análise metacognitiva final em uma resposta em prosa humanizada."""
616
- logger.log("✍️ P7-SINTETIZAR - Gerando prosa humanizada", "TASK")
617
- prompt = PROMPTS["P7_SINTETIZAR"].format(p6=json.dumps(p6, indent=2))
618
- # Usa uma temperatura mais alta para uma resposta mais criativa e fluida.
619
- return chamar_gemini_json(COUNSELOR_MODEL, prompt, temperatura=0.7, max_tokens=2500)
620
-
621
- def passo_8_verificar(p6: Dict, p7: Dict) -> Dict:
622
- """P8: Realiza uma verificação tripla (factual, lógica, ética) na resposta final."""
623
- logger.log("🛡️ P8-VERIFICAR (SUPERVISOR) - Garantindo a qualidade", "TASK")
624
- resposta_sintetizada = p7.get("resposta", "")
625
- prompt = PROMPTS["P8_VERIFICAR"].format(
626
- resposta_sintetizada=resposta_sintetizada,
627
- p6=json.dumps(p6, indent=2)
628
- )
629
- # Usa o modelo Supervisor e uma temperatura baixa para uma verificação mais objetiva.
630
- return chamar_gemini_json(SUPERVISOR_MODEL, prompt, temperatura=0.2)
631
-
632
- # ============================================================================
633
- # 5. ORQUESTRADOR PRINCIPAL
634
- # ============================================================================
635
-
636
- def executar_pipeline(pergunta: str, historico: List[Dict], arquivo_anexo: Any, dna: Dict) -> Tuple[str, List, Dict]:
637
- """
638
- Orquestra a execução de todos os passos da pipeline, desde a análise
639
- inicial até a verificação e entrega da resposta final.
640
-
641
- Args:
642
- pergunta: A pergunta enviada pelo usuário.
643
- historico: O histórico da conversa no formato interno.
644
- arquivo_anexo: O arquivo enviado pelo usuário (se houver).
645
- dna: O dicionário de estado da conversa.
646
-
647
- Returns:
648
- Uma tupla contendo (resposta_final, novo_historico_interno, dna_atualizado).
649
- """
650
- logger.log(f"PIPELINE v10 INICIADA: '{pergunta[:50]}...'", "START")
651
-
652
- if not pergunta or not pergunta.strip():
653
- return "Por favor, digite uma pergunta válida.", historico, dna
654
-
655
- # 1. Processamento de Anexos (se houver).
656
- conteudo_anexo, tipo_anexo = processar_anexo(arquivo_anexo)
657
- pergunta_final = construir_prompt_com_anexo(pergunta, conteudo_anexo, tipo_anexo)
658
-
659
- # Bloco try-except para capturar qualquer erro inesperado durante a complexa execução da pipeline.
660
- try:
661
- # 2. Execução sequencial dos passos da pipeline. O output de um passo serve de input para o próximo.
662
- p0 = passo_0_aluno(pergunta_final, historico)
663
- p1 = passo_1_triagem(pergunta_final, p0, historico)
664
- x1 = passo_x1_perguntas_necessarias(pergunta_final, p1, historico)
665
- x2 = passo_x2_resolver_perguntas(p1, x1, historico)
666
- p2 = passo_2_cenarios(pergunta_final, p1, x1, x2, historico)
667
-
668
- # 3. Ponto de Interrupção: Se o modelo decidir que precisa de mais informações, ele interrompe aqui.
669
- if p2.get("decisao") == "pedir-esclarecimento":
670
- esclarecimento = p2.get("pergunta_esclarecimento", "Poderia fornecer mais detalhes?")
671
- logger.log(f"Pipeline interrompida para pedir esclarecimento: {esclarecimento}", "INFO")
672
- return f"❓ Para te dar uma resposta mais precisa, preciso de um esclarecimento:\n\n> {esclarecimento}", historico, dna
673
-
674
- # 4. Continuação da pipeline com os passos de aprofundamento.
675
- p3 = passo_3_isolar_cenarios(p2)
676
- p4 = passo_4_cruzar_validacoes(p1, p2, p3, x2)
677
- p5 = passo_5_lacunas_finais(p1, p4)
678
-
679
- # 5. Ponto de Interrupção: Se a confiança global for muito baixa, o modelo pode se recusar a responder.
680
- if p5.get("decisao") == "questionar":
681
- questionamento = p5.get("questionamento", "Não tenho informações suficientes para responder.")
682
- logger.log(f"Pipeline interrompida por baixa confiança: {questionamento}", "INFO")
683
- return f"🤔 {questionamento}", historico, dna
684
-
685
- # 6. Passos finais de Julgamento, Geração e Verificação.
686
- p6 = passo_6_ponderar(p2, p4, p5)
687
- p7 = passo_7_sintetizar(p6)
688
- p8 = passo_8_verificar(p6, p7)
689
-
690
- # 7. Seleção da Resposta Final com base na verificação do Supervisor (P8).
691
- if p8.get("todas_aprovadas") and p8.get("decisao") != "corrigir-e-exibir":
692
- resposta_final = p7.get("resposta", "Não foi possível gerar uma resposta.")
693
- else:
694
- # Se a verificação falhou, usa a versão corrigida pelo supervisor.
695
- resposta_final = p8.get("resposta_corrigida", p7.get("resposta", "Ocorreu um erro na verificação final."))
696
- logger.log("Resposta foi corrigida pelo Supervisor (P8).", "WARN")
697
-
698
- except Exception as e:
699
- logger.log(f"Erro crítico na execução da pipeline: {e}", "ERROR")
700
- return f"❌ Ocorreu um erro inesperado durante o processamento: {e}", historico, dna
701
-
702
- # 8. Atualiza o histórico da conversa e o estado (DNA).
703
- novo_historico = historico + [
704
- {"role": "user", "content": pergunta},
705
- {"role": "assistant", "content": resposta_final}
706
- ]
707
- dna["historico_chat"].append({"user": pergunta, "assistant": resposta_final})
708
- dna["meta"]["total_turnos"] += 1
709
-
710
- logger.log("PIPELINE CONCLUÍDA COM SUCESSO", "SUCCESS")
711
- return resposta_final, novo_historico, dna
712
-
713
- # ============================================================================
714
- # 6. INTERFACE COM GRADIO
715
- # ============================================================================
716
-
717
- def converter_historico_de_gradio(historico_gradio: List[List[str]]) -> List[Dict]:
718
- """
719
- Converte o formato de histórico do Chatbot Gradio para o formato interno da pipeline.
720
- - Formato Gradio: [ ["pergunta1", "resposta1"], ["pergunta2", "resposta2"] ]
721
- - Formato Interno: [ {"role": "user", "content": "p1"}, {"role": "assistant", "content": "r1"} ]
722
- """
723
- historico_interno = []
724
- if not historico_gradio:
725
- return historico_interno
726
- for turno in historico_gradio:
727
- pergunta, resposta = turno if turno and turno else "", turno if turno and len(turno) > 1 and turno else ""
728
- if pergunta:
729
- historico_interno.append({"role": "user", "content": pergunta})
730
- if resposta:
731
- historico_interno.append({"role": "assistant", "content": resposta})
732
- return historico_interno
733
-
734
- def converter_historico_para_gradio(historico_interno: List[Dict]) -> List[List[str]]:
735
- """
736
- Converte o formato de histórico interno da pipeline para o formato do Chatbot Gradio.
737
- """
738
- historico_gradio = []
739
- if not historico_interno:
740
- return historico_gradio
741
- # Itera de 2 em 2 para formar os pares [pergunta_usuario, resposta_assistente].
742
- for i in range(0, len(historico_interno), 2):
743
- if i + 1 < len(historico_interno):
744
- pergunta = historico_interno[i]['content']
745
- resposta = historico_interno[i+1]['content']
746
- historico_gradio.append([pergunta, resposta])
747
- return historico_gradio
748
-
749
- def chat_interface(pergunta: str, historico_gradio: List[List[str]], anexo: Any, dna_json_str: str) -> Tuple[List, str, str, None]:
750
- """
751
- Função de callback que conecta a lógica da pipeline com a interface do Gradio.
752
- É chamada toda vez que o usuário envia uma mensagem.
753
-
754
- Args:
755
- pergunta: O texto da caixa de input.
756
- historico_gradio: O estado atual do componente chatbot (formato Gradio).
757
- anexo: O arquivo enviado pelo componente de upload.
758
- dna_json_str: O estado do DNA em formato de string JSON.
759
-
760
- Returns:
761
- Uma tupla com os novos valores para os componentes da interface:
762
- (novo_historico_gradio, texto_input_limpo, novo_dna_json, anexo_limpo)
763
- """
764
- logger.log(f"Nova mensagem recebida: '{pergunta[:80]}...'", "INFO")
765
-
766
- # Carrega o estado do DNA a partir da string JSON. Se falhar, cria um novo.
767
- try:
768
- dna = json.loads(dna_json_str) if dna_json_str and dna_json_str.strip() else criar_dna()
769
- except (json.JSONDecodeError, TypeError):
770
- dna = criar_dna()
771
-
772
- # 1. CONVERTE o histórico do formato Gradio para o nosso formato interno.
773
- historico_interno = converter_historico_de_gradio(historico_gradio)
774
-
775
- # 2. EXECUTA a pipeline com os dados de entrada.
776
- _ , novo_historico_interno, dna_atualizado = executar_pipeline(pergunta, historico_interno, anexo, dna)
777
-
778
- # 3. CONVERTE o histórico resultante de volta para o formato que o Gradio entende.
779
- novo_historico_gradio = converter_historico_para_gradio(novo_historico_interno)
780
-
781
- logger.log("Resposta formatada para Gradio.", "INFO")
782
-
783
- # Retorna os novos estados para atualizar a interface do Gradio.
784
- return novo_historico_gradio, "", json.dumps(dna_atualizado, indent=2, ensure_ascii=False), None
785
-
786
- # --- Bloco de Execução Principal ---
787
- if __name__ == "__main__":
788
- # 'gr.Blocks' é o container principal para criar interfaces complexas com Gradio.
789
- with gr.Blocks(title="Pipeline v10 Refatorada", theme=gr.themes.Soft()) as demo:
790
- # Exibe o título usando Markdown.
791
- gr.Markdown(TITLE)
792
-
793
- # Define o layout da interface em linhas e colunas.
794
- with gr.Row():
795
- with gr.Column(scale=3): # Coluna principal para o chat.
796
- chatbot = gr.Chatbot(label="Chat", height=600, bubble_full_width=False)
797
- with gr.Column(scale=1): # Coluna lateral para informações adicionais.
798
- dna_view = gr.Code(label="DNA (Estado da Conversa)", language="json", interactive=False,
799
- value=json.dumps(criar_dna(), indent=2, ensure_ascii=False))
800
- file_upload = gr.File(label="Anexar PDF ou Imagem", file_types=[".pdf", ".png", ".jpg", ".jpeg"])
801
-
802
- with gr.Row(): # Linha para os controles de input.
803
- input_textbox = gr.Textbox(
804
- label="Digite sua pergunta aqui...",
805
- lines=3,
806
- scale=4, # Ocupa mais espaço na linha.
807
- )
808
- submit_button = gr.Button("🚀 Enviar (v10)", variant="primary", scale=1)
809
-
810
- # Componente oculto para armazenar o estado do DNA entre as chamadas da interface.
811
- # É uma forma de contornar a natureza sem estado do Gradio.
812
- dna_json_hidden = gr.Textbox(value=json.dumps(criar_dna()), visible=False)
813
-
814
- # --- Conexão dos Eventos da Interface com as Funções ---
815
-
816
- # Define o que acontece quando o botão de "Enviar" é clicado.
817
- submit_button.click(
818
- fn=chat_interface, # Função a ser chamada.
819
- inputs=[input_textbox, chatbot, file_upload, dna_json_hidden], # Componentes de entrada.
820
- outputs=[chatbot, input_textbox, dna_json_hidden, file_upload] # Componentes de saída a serem atualizados.
821
- )
822
- # Permite que o usuário envie a mensagem apertando "Enter" na caixa de texto.
823
- input_textbox.submit(
824
- fn=chat_interface,
825
- inputs=[input_textbox, chatbot, file_upload, dna_json_hidden],
826
- outputs=[chatbot, input_textbox, dna_json_hidden, file_upload]
827
- )
828
- # Sincroniza o visualizador de DNA com o estado oculto sempre que ele mudar.
829
- dna_json_hidden.change(
830
- fn=lambda x: x, # Uma função simples que apenas repassa o valor.
831
- inputs=[dna_json_hidden],
832
- outputs=[dna_view]
833
- )
834
-
835
- # Inicia a aplicação web do Gradio.
836
- demo.launch(server_name="0.0.0.0", server_port=7860, share=False)