caarleexx commited on
Commit
7673602
·
verified ·
1 Parent(s): e72f8e1

Delete app.py

Browse files
Files changed (1) hide show
  1. app.py +0 -644
app.py DELETED
@@ -1,644 +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 a capacidade de pausar the pipeline quando o modelo tem uma
11
- dúvida, perguntar ao usuário e, em seguida, retomar a execução do ponto de
12
- parada usando o esclarecimento fornecido.
13
- """
14
-
15
- # ============================================================================
16
- # 1. IMPORTAÇÕES E CONFIGURAÇÃO INICIAL
17
- # ============================================================================
18
- import json
19
- import os
20
- import base64
21
- import re
22
- import warnings
23
- from datetime import datetime
24
- from typing import Dict, List, Tuple, Any
25
-
26
- import gradio as gr
27
- import google.generativeai as genai
28
-
29
- warnings.filterwarnings("ignore", category=FutureWarning, module="google.api_core")
30
-
31
- API_KEY = os.getenv("GOOGLE_API_KEY")
32
- if not API_KEY:
33
- raise ValueError("A variável de ambiente GOOGLE_API_KEY não foi configurada.")
34
-
35
- genai.configure(api_key=API_KEY)
36
-
37
- COUNSELOR_MODEL = genai.GenerativeModel("gemini-2.0-flash")
38
- SUPERVISOR_MODEL = genai.GenerativeModel("gemini-2.0-flash")
39
-
40
- TITLE = "# 🚀 Pipeline v10 | PAUSA & RETOMADA\n**Sistema com gerenciamento de estado para interrupção e continuação do raciocínio.**"
41
-
42
- # ============================================================================
43
- # 2. PROMPTS CENTRALIZADOS
44
- # ============================================================================
45
- PROMPTS = {
46
- "P0_ALUNO": """
47
- Você é um METACOGNITIVO (pensamento interno, NÃO comunicação).
48
-
49
- TURNO ANTERIOR:
50
- User: {turno_anterior_user}
51
- Assistant: {turno_anterior_assistant}
52
-
53
- NOVA MENSAGEM: {pergunta}
54
- CONTEXTO VAGO: {historico_compacto}
55
-
56
- ---
57
-
58
- Responda EM METACOGNIÇÃO PURA - TELEGRÁFICO
59
- NÃO use frases completas. APENAS essência semântica com conectores mínimos.
60
-
61
- EXEMPLO CERTO: entendeu-sim | pergunta-nova | avança-tópico | não-reformulou
62
-
63
- RETORNE JSON:
64
- {{
65
- "usuario_entendeu": "sim|não",
66
- "evidencias": ["entendeu-pergunta", "pediu-clarificação"],
67
- "usuario_corrigiu": "sim|não",
68
- "correcao_detectada": null|"texto-correção",
69
- "correcao_valida": "sim|não|null",
70
- "o_que_melhorar": null|"explicar-X-melhor",
71
- "decisao": "prosseguir-passo1|reexplicar-passo6|atualizar-resposta-anterior",
72
- "motivo": "texto-curtíssimo"
73
- }}
74
- """,
75
- "P1_TRIAGEM": """
76
- METACOGNIÇÃO - TRIAGEM INICIAL.
77
-
78
- CONTEXTO VAGO: {contexto_vago}
79
- HISTÓRICO RECENTE (últimas 3): {historico_recente}
80
- P0: {p0}
81
- PERGUNTA: {pergunta}
82
-
83
- ---
84
-
85
- CLASSIFIQUE EM TELEGRÁFICO (sem frases).
86
-
87
- RETORNE JSON:
88
- {{
89
- "tipo": "objetiva|factual|subjetiva|aberta",
90
- "sinais": ["tem-resposta-única-verificável", "sem-contexto-pessoal"],
91
- "confianca": "alta|média|baixa",
92
- "decisao": "responder-direto|analisar-profundamente",
93
- "razao": "curtíssima",
94
- "dados_fatuais": ["fato1", "fato2"],
95
- "divergencias_fatuais": ["possível-ambiguidade-1"],
96
- "objetivo_principal": "objetivo-primário-identificado",
97
- "objetivo_secundario": ["objetivo-secundário-1"]
98
- }}
99
- """,
100
- "X1_PERGUNTAS_NECESSARIAS": """
101
- X1-PERGUNTAS CRÍTICAS - TELEGRÁFICO
102
- P1: {p1}
103
- CONTEXTO: {historico_compacto}
104
- PERGUNTA PRINCIPAL: {pergunta}
105
-
106
- ---
107
- Analise as lacunas factuais e subjetivas na pergunta do usuário e no contexto.
108
- Liste as perguntas essenciais que você precisa responder internamente antes de formular a resposta final.
109
-
110
- RETORNE JSON:
111
- {{"perguntas": [
112
- {{"texto": "pergunta-curta-e-essencial", "necessidade": "alta|média|baixa", "relevancia": "alta|média"}}
113
- ]}}
114
- """,
115
- "X2_RESOLVER_PERGUNTAS": """
116
- X2-RESOLUÇÃO INTERNA - TELEGRÁFICO
117
- P1: {p1}
118
- PERGUNTAS CRÍTICAS (X1): {perguntas_x1}
119
- CONTEXTO: {historico_compacto}
120
-
121
- ---
122
- Para cada pergunta crítica levantada no passo anterior, forneça uma resposta curta e direta baseada no seu conhecimento.
123
- Avalie sua confiança e o potencial de conflito ou ambiguidade em cada resposta.
124
-
125
- RETORNE JSON:
126
- {{"respostas": [
127
- {{"pergunta": "texto-original-da-pergunta-x1",
128
- "resposta": "resposta-curta-e-direta",
129
- "confianca": "alta|média|baixa",
130
- "conflito": "alto|médio|baixo",
131
- "razao": "explicação-em-1-2-palavras"}}
132
- ]}}
133
- """,
134
- "P2_CENARIOS": """
135
- METACOGNIÇÃO - GERAÇÃO DE CENÁRIOS.
136
- CONTEXTO VAGO: {historico_compacto}
137
- TRIAGEM P1: {p1}
138
- X1-PERGUNTAS: {x1}
139
- X2-RESPOSTAS: {x2}
140
- PERGUNTA ORIGINAL: {pergunta}
141
-
142
- ---
143
-
144
- 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.
145
-
146
- RETORNE JSON:
147
- {{
148
- "cenarios": {{
149
- "provaveis": [{{"id": "C1", "desc": "cenário-provável-1-comprimido", "contexto-relevante": "descreva-o-contexto"}}]
150
- }},
151
- "total": 1,
152
- "tipo_resposta": "múltipla|unívoca",
153
- "confianca": "alta|m��dia|baixa",
154
- "decisao": "prosseguir|pedir-esclarecimento",
155
- "pergunta_esclarecimento": null|"texto-da-pergunta-para-o-usuario"
156
- }}
157
- """,
158
- "P3_ISOLAR_CENARIOS": """
159
- METACOGNIÇÃO - EXPLORAÇÃO DE CENÁRIO ISOLADO.
160
- CENÁRIO P2: {cenario}
161
-
162
- ---
163
- 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?
164
-
165
- RETORNE JSON:
166
- {{"id": "{cenario_id}",
167
- "resposta_essencia": "conclusão-principal-e-razoes-em-palavras-chave",
168
- "confianca": "alta|média|baixa",
169
- "lacunas": "contexto-ainda-ausente|null"
170
- }}
171
- """,
172
- "P4_CRUZAR_VALIDACOES": """
173
- METACOGNIÇÃO - ABSTRAÇÃO DE CONHECIMENTO.
174
- P1 (Triagem): {p1}
175
- P2 (Cenários): {p2}
176
- P3 (Exploração): {p3}
177
- X2 (Respostas Internas): {x2}
178
-
179
- ---
180
-
181
- 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.
182
-
183
- RETORNE JSON:
184
- {{"principios": [{{"nome": "Custo-Oportunidade", "essencia": "escolher-X-implica-renunciar-Y"}}],
185
- "simbolos": [{{"nome": "Jornada-do-Herói", "essencia": "transformação-ocorre-através-de-desafios"}}],
186
- "principio_central": "nome-do-principio-mais-importante",
187
- "simbolo_dominante": "nome-do-simbolo-mais-relevante"
188
- }}
189
- """,
190
- "P5_LACUNAS_FINAIS": """
191
- METACOGNIÇÃO - ANÁLISE DE INCERTEZA.
192
- P1 (Triagem): {p1}
193
- P4 (Princípios): {p4}
194
-
195
- ---
196
-
197
- 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.
198
-
199
- RETORNE JSON:
200
- {{"analise_cenarios": [{{"cenario": "C1", "certezas": ["certeza1"], "duvidas": ["dúvida1"]}}],
201
- "confianca_global": "alta|média|baixa",
202
- "balanco": "certezas-superam|equilibrado|duvidas-superam",
203
- "decisao": "responder|questionar",
204
- "questionamento": null|"texto-da-pergunta-para-o-usuario-se-a-confianca-for-baixa"
205
- }}
206
- """,
207
- "P6_PONDERAR": """
208
- METACOGNIÇÃO - JULGAMENTO FINAL (JUIZ DA VERDADE).
209
- P2 (Cenários): {p2}
210
- P4 (Princípios): {p4}
211
- P5 (Lacunas): {p5}
212
-
213
- ---
214
-
215
- 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.
216
-
217
- RETORNE JSON:
218
- {{"verdade_principal": "a-conclusao-mais-provavel-e-confiavel",
219
- "nuances_importantes": ["nuance1", "nuance2"],
220
- "confianca_final": "alta|média|baixa",
221
- "decisao": "exibir-resposta-completa|exibir-resposta-com-ressalvas|reprocessar",
222
- "nivel_consciencia": "alto|médio|baixo"
223
- }}
224
- """,
225
- "P7_SINTETIZAR": """
226
- Você é um SINTETIZADOR especialista em transformar METACOGNIÇÃO CRUA em PROSA HUMANIZADA e empática.
227
-
228
- DADOS DO JULGAMENTO (P6): {p6}
229
-
230
- ---
231
-
232
- TAREFA: Converta a análise telegráfica do 'Juiz da Verdade' em uma resposta textual fluida, natural e útil para o usuário.
233
-
234
- INSTRUÇÕES:
235
- 1. Use conectores naturais (ex: "porque", "portanto", "isso significa que").
236
- 2. Expanda abreviações e jargões para uma linguagem clara.
237
- 3. Estruture a resposta em parágrafos lógicos (introdução, desenvolvimento, nuances/conclusão).
238
- 4. Incorpore os princípios e nuances de forma natural na explicação.
239
- 5. Adote um tom de conselheiro: amigável, empático e empoderador.
240
- 6. NÃO invente informações. Baseie-se estritamente nos dados do P6.
241
-
242
- RETORNE A RESPOSTA EM PROSA DENTRO DE UM JSON:
243
- {{"resposta": "Aqui vai o texto fluido, natural e humano..."}}
244
- """,
245
- "P8_VERIFICAR": """
246
- Você é um VERIFICADOR FINAL, um guardião rigoroso da qualidade da resposta.
247
-
248
- RESPOSTA SINTETIZADA (P7):
249
- {resposta_sintetizada}
250
-
251
- ANÁLISE DO JUIZ (P6):
252
- {p6}
253
-
254
- ---
255
-
256
- Realize uma verificação tripla na resposta sintetizada. Seja crítico.
257
-
258
- 1. **VERIFICAÇÃO FACTUAL**: A resposta contém fatos incorretos ou não sustentados pela análise do P6?
259
- 2. **VERIFICAÇÃO LÓGICA**: Existem falácias, saltos de lógica ou contradições? A conclusão segue a linha de raciocínio?
260
- 3. **VERIFICAÇÃO ÉTICA**: A resposta é apropriada, segura e imparcial? Inclui os avisos ou ressalvas necessários?
261
-
262
- RETORNE SEU VEREDITO EM JSON:
263
- {{"verificacao_factual": {{"aprovada": true|false, "problemas": ["descrição do problema se houver"]}},
264
- "verificacao_logica": {{"aprovada": true|false, "problemas": []}},
265
- "verificacao_etica": {{"aprovada": true|false, "problemas": []}},
266
- "todas_aprovadas": true|false,
267
- "decisao": "exibir-resposta-original|corrigir-e-exibir",
268
- "resposta_corrigida": null|"texto da versão corrigida e melhorada da resposta"
269
- }}
270
- """
271
- }
272
-
273
-
274
- # ============================================================================
275
- # 3. CLASSES E FUNÇÕES HELPERS
276
- # ============================================================================
277
-
278
- class Logger:
279
- def __init__(self, verbose: bool = True):
280
- self.verbose = verbose
281
-
282
- def log(self, msg: str, level: str = "INFO"):
283
- log_msg = f"[{datetime.now().strftime('%H:%M:%S')}] [{level.upper()}] {msg}"
284
- if self.verbose:
285
- print(log_msg)
286
- if level.upper() in ["TASK", "START", "SUCCESS", "ERROR", "WARN"]:
287
- print("=" * 70)
288
-
289
- logger = Logger(verbose=True)
290
-
291
-
292
- def processar_anexo(arquivo: Any) -> Tuple[str, str]:
293
- if arquivo is None:
294
- return "", "nenhum"
295
- caminho_arquivo = arquivo.name
296
- try:
297
- if caminho_arquivo.lower().endswith('.pdf'):
298
- return "[Conteúdo do PDF iria aqui]", "pdf"
299
- elif any(caminho_arquivo.lower().endswith(ext) for ext in ['.png', '.jpg', '.jpeg', '.gif']):
300
- return "[Dados da imagem iriam aqui]", "imagem"
301
- return "", "nao_suportado"
302
- except Exception as e:
303
- logger.log(f"Erro ao processar anexo: {e}", "ERROR")
304
- return "", "erro"
305
-
306
-
307
- def construir_prompt_com_anexo(pergunta: str, anexo_conteudo: str, tipo_anexo: str) -> str:
308
- if not anexo_conteudo or tipo_anexo in ["nenhum", "erro", "nao_suportado"]:
309
- return pergunta
310
- if tipo_anexo == "pdf":
311
- return f"Com base no documento PDF, responda: {pergunta}\nDOCUMENTO:\n{anexo_conteudo}"
312
- if tipo_anexo == "imagem":
313
- return f"Com base na imagem, responda: {pergunta}"
314
- return pergunta
315
-
316
-
317
- def parse_json_ultra_robusto(texto: str) -> Dict:
318
- if not texto:
319
- return {"erro": "Texto de entrada vazio"}
320
- try:
321
- match = re.search(r'```(?:json)?\s*(\{.*?\})\s*```', texto, re.DOTALL)
322
- if match:
323
- json_str = match.group(1)
324
- else:
325
- json_str = texto
326
- return json.loads(json_str)
327
- except json.JSONDecodeError:
328
- try:
329
- inicio = texto.find('{')
330
- fim = texto.rfind('}') + 1
331
- if inicio != -1 and fim != 0:
332
- return json.loads(texto[inicio:fim])
333
- except json.JSONDecodeError:
334
- return {"erro": "parse_falhou", "fallback_text": texto[:500]}
335
-
336
-
337
- def chamar_gemini_json(modelo: genai.GenerativeModel, prompt: str, temperatura: float = 0.5, max_tokens: int = 2000) -> Dict:
338
- prompt_completo = f"{prompt}\n\n---\n\n**INSTRUÇÃO OBRIGATÓRIA: Sua resposta DEVE ser um único e válido objeto JSON.**"
339
-
340
- print("\n" + "="*25 + f" 💬 API INPUT PARA [{modelo.model_name}] " + "="*25)
341
- print(prompt_completo)
342
- print("="*78 + "\n")
343
- logger.log(f"Enviando prompt ({len(prompt_completo)} chars) para {modelo.model_name}", "DEBUG")
344
-
345
- try:
346
- response = modelo.generate_content(
347
- prompt_completo,
348
- generation_config=genai.types.GenerationConfig(
349
- temperature=temperatura,
350
- max_output_tokens=max_tokens,
351
- )
352
- )
353
- resposta_bruta = response.text or ""
354
-
355
- print("\n" + "="*25 + f" 📥 API RAW OUTPUT DE [{modelo.model_name}] " + "="*25)
356
- print(resposta_bruta)
357
- print("="*78 + "\n")
358
- logger.log(f"Gemini RAW ({len(resposta_bruta)} chars): {resposta_bruta[:400]}...", "DEBUG")
359
-
360
- return parse_json_ultra_robusto(resposta_bruta)
361
-
362
- except Exception as e:
363
- logger.log(f"Erro na chamada da API Gemini: {e}", "ERROR")
364
- return {"erro": f"API_ERROR: {str(e)}"}
365
-
366
-
367
- def historico_compacto(historico: List[Dict]) -> str:
368
- if not historico:
369
- return "Nenhuma conversa anterior."
370
- compacto = [f"{'User' if m['role'] == 'user' else 'Assistant'}: {m['content'][:80]}" for m in historico[-4:]]
371
- return "\n".join(compacto)
372
-
373
-
374
- def criar_dna() -> Dict:
375
- return {
376
- "historico_chat": [],
377
- "meta": {"total_turnos": 0},
378
- "pipeline_state": {
379
- "status": "completed",
380
- "paused_at_step": None,
381
- "saved_data": {}
382
- }
383
- }
384
-
385
- # ============================================================================
386
- # 4. PASSOS DA PIPELINE (P0-P8, X1-X2)
387
- # ============================================================================
388
- def passo_0_aluno(pergunta: str, historico: List[Dict]) -> Dict:
389
- logger.log("🧠 P0-ALUNO", "TASK")
390
- # Simulação para simplificar a demonstração da lógica principal
391
- return {"p0_data": True}
392
-
393
- def passo_1_triagem(pergunta: str, p0: Dict, historico: List[Dict]) -> Dict:
394
- logger.log("📊 P1-TRIAGEM", "TASK")
395
- return {"p1_data": True}
396
-
397
- def passo_x1_perguntas_necessarias(pergunta: str, p1: Dict, historico: List[Dict]) -> Dict:
398
- logger.log("❓ X1-PERGUNTAS", "TASK")
399
- return {"x1_data": True}
400
-
401
- def passo_x2_resolver_perguntas(p1: Dict, x1: Dict, historico: List[Dict]) -> Dict:
402
- logger.log("✅ X2-RESOLVER", "TASK")
403
- return {"x2_data": True}
404
-
405
- def passo_2_cenarios(pergunta: str, p1: Dict, x1: Dict, x2: Dict, historico: List[Dict]) -> Dict:
406
- logger.log("🎯 P2-CENÁRIOS", "TASK")
407
- # Simulação: Para perguntas sobre "dúvida", ele pausa.
408
- if "dúvida" in pergunta.lower():
409
- logger.log("P2 encontrou ambiguidade. Decidindo pedir esclarecimento.", "INFO")
410
- return {"decisao": "pedir-esclarecimento", "pergunta_esclarecimento": "Você pode esclarecer o ponto X sobre a sua dúvida?"}
411
- return {"decisao": "prosseguir", "p2_data": True}
412
-
413
- def passo_3_isolar_cenarios(p2: Dict) -> Dict:
414
- logger.log("🔍 P3-ISOLAR", "TASK")
415
- return {"p3_data": True}
416
-
417
- def passo_4_cruzar_validacoes(p1: Dict, p2: Dict, p3: Dict, x2: Dict) -> Dict:
418
- logger.log("🔗 P4-VALIDAÇÃO", "TASK")
419
- # Simulação para o P5
420
- if p2.get("p2_data"):
421
- return {"principio_central": "Certeza"}
422
- return {"principio_central": "Incerteza"}
423
-
424
- def passo_5_lacunas_finais(p1: Dict, p4: Dict) -> Dict:
425
- logger.log("🚨 P5-LACUNAS FINAIS", "TASK")
426
- # Simulação de interrupção
427
- if p4.get("principio_central") == "Incerteza":
428
- return {"decisao": "questionar", "questionamento": "A incerteza é muito alta. Preciso de mais dados sobre Y. Você pode fornecer?"}
429
- return {"decisao": "responder", "p5_data": True}
430
-
431
- def passo_6_ponderar(p2: Dict, p4: Dict, p5: Dict) -> Dict:
432
- logger.log("⚖️ P6-PONDERAR", "TASK")
433
- return {"p6_data": True}
434
-
435
- def passo_7_sintetizar(p6: Dict) -> Dict:
436
- logger.log("✍️ P7-SINTETIZAR", "TASK")
437
- return {"resposta": "Esta é a resposta final, gerada com sucesso."}
438
-
439
- def passo_8_verificar(p6: Dict, p7: Dict) -> Dict:
440
- logger.log("🛡️ P8-VERIFICAR", "TASK")
441
- return {"todas_aprovadas": True, "resposta_corrigida": None}
442
-
443
- # ============================================================================
444
- # 5. ORQUESTRADOR PRINCIPAL
445
- # ============================================================================
446
-
447
- def iniciar_nova_pipeline(pergunta: str, historico: List[Dict], anexo: Any, dna: Dict) -> Tuple[str, List, Dict]:
448
- logger.log(f"INICIANDO NOVA PIPELINE: '{pergunta[:50]}...'", "START")
449
-
450
- conteudo_anexo, tipo_anexo = processar_anexo(anexo)
451
- pergunta_final = construir_prompt_com_anexo(pergunta, conteudo_anexo, tipo_anexo)
452
-
453
- p0 = passo_0_aluno(pergunta_final, historico)
454
- p1 = passo_1_triagem(pergunta_final, p0, historico)
455
- x1 = passo_x1_perguntas_necessarias(pergunta_final, p1, historico)
456
- x2 = passo_x2_resolver_perguntas(p1, x1, historico)
457
- p2 = passo_2_cenarios(pergunta_final, p1, x1, x2, historico)
458
-
459
- if p2.get("decisao") == "pedir-esclarecimento":
460
- logger.log("INTERRUPÇÃO no P2. Salvando estado no DNA.", "WARN")
461
- dna['pipeline_state'] = {
462
- "status": "paused",
463
- "paused_at_step": "P2",
464
- "saved_data": {
465
- 'p0': p0, 'p1': p1, 'x1': x1, 'x2': x2,
466
- 'pergunta_original': pergunta,
467
- 'historico_original': historico
468
- }
469
- }
470
- esclarecimento = p2.get("pergunta_esclarecimento", "Poderia esclarecer?")
471
- return f"❓ DÚVIDA: {esclarecimento}", historico, dna
472
-
473
- p3 = passo_3_isolar_cenarios(p2)
474
- p4 = passo_4_cruzar_validacoes(p1, p2, p3, x2)
475
- p5 = passo_5_lacunas_finais(p1, p4)
476
-
477
- if p5.get("decisao") == "questionar":
478
- logger.log("INTERRUPÇÃO no P5. Salvando estado no DNA.", "WARN")
479
- dna['pipeline_state'] = {
480
- "status": "paused",
481
- "paused_at_step": "P5",
482
- "saved_data": {
483
- 'p0': p0, 'p1': p1, 'x1': x1, 'x2': x2, 'p2': p2, 'p3': p3, 'p4': p4,
484
- 'pergunta_original': pergunta,
485
- 'historico_original': historico
486
- }
487
- }
488
- questionamento = p5.get("questionamento", "Preciso de mais informações.")
489
- return f"🤔 PERGUNTA: {questionamento}", historico, dna
490
-
491
- p6 = passo_6_ponderar(p2, p4, p5)
492
- p7 = passo_7_sintetizar(p6)
493
- p8 = passo_8_verificar(p6, p7)
494
-
495
- resposta_final = p8.get("resposta_corrigida") or p7.get("resposta", "Não foi possível gerar uma resposta.")
496
-
497
- novo_historico = historico + [{"role": "user", "content": pergunta}, {"role": "assistant", "content": resposta_final}]
498
- dna["historico_chat"].append({"user": pergunta, "assistant": resposta_final})
499
- dna["meta"]["total_turnos"] += 1
500
-
501
- logger.log("PIPELINE (NOVA) CONCLUÍDA COM SUCESSO", "SUCCESS")
502
- return resposta_final, novo_historico, dna
503
-
504
-
505
- def resumir_pipeline(esclarecimento_usuario: str, dna: Dict) -> Tuple[str, List, Dict]:
506
- logger.log(f"RESUMINDO PIPELINE com esclarecimento: '{esclarecimento_usuario[:50]}...'", "START")
507
-
508
- estado_salvo = dna['pipeline_state']['saved_data']
509
- ponto_parada = dna['pipeline_state']['paused_at_step']
510
- historico = estado_salvo['historico_original']
511
- pergunta_original = estado_salvo['pergunta_original']
512
-
513
- pergunta_atualizada = f"{pergunta_original}\n\n---ESCLARECIMENTO DO USUÁRIO---\n{esclarecimento_usuario}"
514
-
515
- dna['pipeline_state'] = criar_dna()['pipeline_state']
516
-
517
- try:
518
- if ponto_parada == 'P2':
519
- logger.log("Retomando a partir do passo P3...", "INFO")
520
- p1, x1, x2 = estado_salvo['p1'], estado_salvo['x1'], estado_salvo['x2']
521
- p2_novo = passo_2_cenarios(pergunta_atualizada, p1, x1, x2, historico)
522
-
523
- p3 = passo_3_isolar_cenarios(p2_novo)
524
- p4 = passo_4_cruzar_validacoes(p1, p2_novo, p3, x2)
525
- p5 = passo_5_lacunas_finais(p1, p4)
526
- p6 = passo_6_ponderar(p2_novo, p4, p5)
527
-
528
- elif ponto_parada == 'P5':
529
- logger.log("Retomando a partir do passo P6...", "INFO")
530
- p1, p2, p4 = estado_salvo['p1'], estado_salvo['p2'], estado_salvo['p4']
531
- p5_novo = passo_5_lacunas_finais(p1, p4)
532
- p6 = passo_6_ponderar(p2, p4, p5_novo)
533
-
534
- else:
535
- raise ValueError(f"Ponto de parada desconhecido: {ponto_parada}")
536
-
537
- p7 = passo_7_sintetizar(p6)
538
- p8 = passo_8_verificar(p6, p7)
539
-
540
- resposta_final = p8.get("resposta_corrigida") or p7.get("resposta", "Não foi possível gerar uma resposta.")
541
-
542
- except Exception as e:
543
- logger.log(f"Erro ao resumir a pipeline: {e}", "ERROR")
544
- return f"Ocorreu um erro ao processar seu esclarecimento: {e}", historico, dna
545
-
546
- novo_historico = historico + [{"role": "user", "content": pergunta_original}, {"role": "assistant", "content": resposta_final}]
547
- dna["historico_chat"].append({"user": pergunta_original, "assistant": resposta_final})
548
- dna["meta"]["total_turnos"] += 1
549
-
550
- logger.log("PIPELINE (RESUMIDA) CONCLUÍDA COM SUCESSO", "SUCCESS")
551
- return resposta_final, novo_historico, dna
552
-
553
-
554
- def executar_pipeline(pergunta: str, historico: List[Dict], anexo: Any, dna: Dict) -> Tuple[str, List, Dict]:
555
- if 'pipeline_state' not in dna or 'status' not in dna['pipeline_state']:
556
- dna['pipeline_state'] = criar_dna()['pipeline_state']
557
-
558
- if dna['pipeline_state']['status'] == 'paused':
559
- return resumir_pipeline(pergunta, dna)
560
- else:
561
- return iniciar_nova_pipeline(pergunta, historico, anexo, dna)
562
-
563
- # ============================================================================
564
- # 6. INTERFACE COM GRADIO
565
- # ============================================================================
566
- def converter_historico_de_gradio(historico_gradio: List[List[str]]) -> List[Dict]:
567
- historico_interno = []
568
- if not historico_gradio:
569
- return historico_interno
570
- for turno in historico_gradio:
571
- pergunta = turno[0] if turno and turno[0] is not None else ""
572
- resposta = turno[1] if turno and len(turno) > 1 and turno[1] is not None else ""
573
- if pergunta:
574
- historico_interno.append({"role": "user", "content": pergunta})
575
- if resposta:
576
- historico_interno.append({"role": "assistant", "content": resposta})
577
- return historico_interno
578
-
579
- def converter_historico_para_gradio(historico_interno: List[Dict]) -> List[List[str]]:
580
- historico_gradio = []
581
- if not historico_interno:
582
- return historico_gradio
583
- for i in range(0, len(historico_interno), 2):
584
- if i + 1 < len(historico_interno):
585
- pergunta = historico_interno[i]['content']
586
- resposta = historico_interno[i+1]['content']
587
- historico_gradio.append([pergunta, resposta])
588
- return historico_gradio
589
-
590
- def chat_interface(pergunta: str, historico_gradio: List[List[str]], anexo: Any, dna_json_str: str) -> Tuple[List, str, str, None]:
591
- try:
592
- dna = json.loads(dna_json_str) if dna_json_str and dna_json_str.strip() else criar_dna()
593
- except (json.JSONDecodeError, TypeError):
594
- dna = criar_dna()
595
-
596
- historico_interno = converter_historico_de_gradio(historico_gradio)
597
-
598
- resposta, novo_historico_interno, dna_atualizado = executar_pipeline(pergunta, historico_interno, anexo, dna)
599
-
600
- novo_historico_gradio = converter_historico_para_gradio(novo_historico_interno)
601
-
602
- return novo_historico_gradio, "", json.dumps(dna_atualizado, indent=2, ensure_ascii=False), None
603
-
604
- # --- Bloco de Execução Principal ---
605
- if __name__ == "__main__":
606
- with gr.Blocks(title="Pipeline v10 com Pausa/Retomada", theme=gr.themes.Soft()) as demo:
607
- gr.Markdown(TITLE)
608
-
609
- with gr.Row():
610
- with gr.Column(scale=3):
611
- chatbot = gr.Chatbot(label="Chat", height=600, bubble_full_width=False)
612
- with gr.Column(scale=1):
613
- dna_view = gr.Code(label="DNA (Estado da Conversa)", language="json", interactive=False,
614
- value=json.dumps(criar_dna(), indent=2, ensure_ascii=False))
615
- file_upload = gr.File(label="Anexar Arquivo")
616
-
617
- with gr.Row():
618
- input_textbox = gr.Textbox(
619
- label="Digite sua pergunta ou esclarecimento aqui...",
620
- placeholder="Ex: Qual a melhor estratégia para... / Sobre o ponto X, quero dizer que...",
621
- lines=3,
622
- scale=4,
623
- )
624
- submit_button = gr.Button("🚀 Enviar", variant="primary", scale=1)
625
-
626
- dna_json_hidden = gr.Textbox(value=json.dumps(criar_dna()), visible=False)
627
-
628
- submit_button.click(
629
- fn=chat_interface,
630
- inputs=[input_textbox, chatbot, file_upload, dna_json_hidden],
631
- outputs=[chatbot, input_textbox, dna_json_hidden, file_upload]
632
- )
633
- input_textbox.submit(
634
- fn=chat_interface,
635
- inputs=[input_textbox, chatbot, file_upload, dna_json_hidden],
636
- outputs=[chatbot, input_textbox, dna_json_hidden, file_upload]
637
- )
638
- dna_json_hidden.change(
639
- fn=lambda x: x,
640
- inputs=[dna_json_hidden],
641
- outputs=[dna_view]
642
- )
643
-
644
- demo.launch(server_name="0.0.0.0", server_port=7860, share=False)