caarleexx commited on
Commit
8bb13c5
·
verified ·
1 Parent(s): f417479

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +313 -239
app.py CHANGED
@@ -1,25 +1,14 @@
 
1
  # -*- coding: utf-8 -*-
2
  """
3
- Pipeline v10 - VERSÃO FINAL COM NOVA CHAMADA DE API.
4
-
5
- Este script implementa a nova forma de chamada da API Gemini, utilizando
6
- genai.Client e generate_content_stream, conforme solicitado.
7
 
8
  PRINCIPAIS ALTERAÇÕES:
9
- - NOVA CHAMADA DE API: A função `chamar_gemini_json` foi totalmente
10
- refatorada para usar `genai.Client` e `generate_content_stream`.
11
- Ela agora coleta os 'chunks' do stream para montar a resposta JSON completa.
12
- - MODELO PRO: Utiliza o 'gemini-1.5-pro-latest' para todas as operações.
13
- - FERRAMENTAS ATIVADAS: A Pesquisa Google (`GoogleSearch`) foi habilitada
14
- como uma ferramenta para o modelo, permitindo que ele fundamente melhor
15
- suas análises internas.
16
- - ROBUSTEZ E FUNCIONALIDADES ANTERIORES: Todas as lógicas de sanitização,
17
- bypass, pausa/retomada e tratamento de erros foram mantidas.
18
  """
19
-
20
- # ============================================================================
21
- # 1. IMPORTAÇÕES E CONFIGURAÇÃO INICIAL
22
- # ============================================================================
23
  import json
24
  import os
25
  import re
@@ -28,341 +17,426 @@ from datetime import datetime
28
  from typing import Dict, List, Tuple, Any
29
 
30
  import gradio as gr
31
-
32
-
33
-
34
  from google import genai
35
  from google.genai import types
36
 
 
 
 
37
  warnings.filterwarnings("ignore", category=FutureWarning, module="google.api_core")
38
 
39
- # --- Configuração da API ---
40
  API_KEY = os.getenv("GOOGLE_API_KEY")
41
  if not API_KEY:
42
  raise ValueError("A variável de ambiente GOOGLE_API_KEY não foi configurada.")
43
 
44
- # ATUALIZADO: Inicializa o Cliente em vez dos modelos.
45
- # A chave de API é passada diretamente aqui
46
- # Only run this block for Gemini Developer API
47
  CLIENT = genai.Client(api_key=API_KEY)
 
 
 
48
 
49
- # --- Definição dos Modelos ---
50
- # ATUALIZADO: Agora são apenas strings com os nomes dos modelos.
51
- COUNSELOR_MODEL_NAME = "gemini-flash-latest"
52
- SUPERVISOR_MODEL_NAME = "gemini-flash-latest" # Pode ser diferente se desejado
53
 
54
- # --- Título da Interface ---
55
- TITLE = "# 🚀 Pipeline v10 | Nova API do Gemini Pro\n**Utilizando `Client`, `stream` e `tools` para raciocínio aprimorado.**"
56
 
57
- # ============================================================================
58
- # 2. PROMPTS CENTRALIZADOS
59
- # ============================================================================
60
- # (Os prompts permanecem os mesmos da versão anterior, sem alterações)
61
- PROMPTS = {
62
- "P1_TRIAGEM": """
63
- METACOGNIÇÃO - TRIAGEM INICIAL.
64
- PERGUNTA: {pergunta}
65
- ---
66
- Analise a pergunta e classifique-a.
67
- - tipo: 'factual', 'objetiva_tecnica', 'subjetiva_complexa'.
68
- - confianca: 'extrema', 'alta', 'media', 'baixa', 'insuficiente'.
69
- RETORNE JSON: {{"tipo": "...", "confianca": "...", "decisao": "responder_direto|analisar_profundamente"}}
70
- """,
71
- "GERAR_RESPOSTA_DIRETA": """
72
- TAREFA: Resposta Direta (Bypass).
73
- PERGUNTA: "{pergunta}"
74
- ---
75
- Forneça uma resposta direta, precisa e concisa.
76
- RETORNE JSON: {{"resposta_direta": "Sua resposta concisa aqui."}}
77
- """,
78
- "JUSTIFICAR_BYPASS": """
79
- METACOGNIÇÃO - JUSTIFICATIVA DE BYPASS.
80
- ANÁLISE (P1): {p1}
81
- ---
82
- Justifique por que a pipeline de raciocínio profundo foi pulada.
83
- RETORNE JSON: {{"justificativa_bypass": {{"motivo": "...", "acao_tomada": "...", "proximo_passo": "..."}}}}
84
- """,
85
- "P2_CENARIOS": """
86
- METACOGNIÇÃO - GERAÇÃO DE CENÁRIOS.
87
- ANÁLISE (P1): {p1}, PERGUNTA: {pergunta}
88
- ---
89
- Gere o cenário mais provável e um alternativo improvável.
90
- RETORNE JSON: {{"cenarios": {{"provaveis": [], "improvaveis": []}}, "decisao": "prosseguir"}}
91
- """,
92
- "P4_CRUZAR_VALIDACOES": """
93
- METACOGNIÇÃO - ABSTRAÇÃO.
94
- ANÁLISES ANTERIORES: {p1}, {p2}, {p3}
95
- ---
96
- Identifique o princípio fundamental da discussão.
97
- RETORNE JSON: {{"principio_central": "..."}}
98
- """,
99
- "P5_LACUNAS_FINAIS": """
100
- METACOGNIÇÃO - ANÁLISE DE INCERTEZA.
101
- PRINCÍPIO CENTRAL (P4): {p4}, PERGUNTA: {pergunta}
102
- ---
103
- Identifique a principal lacuna de informação e formule uma pergunta clara para o usuário.
104
- RETORNE JSON: {{"pontos_de_incerteza": [], "decisao_interna": "questionar", "pergunta_chave_para_usuario": "..."}}
105
- """,
106
- "P7_SINTETIZAR": """
107
- SINTETIZADOR.
108
- DADOS (P6): {p6}
109
- ---
110
- Converta a análise técnica em uma resposta final coesa.
111
- RETORNE JSON: {{"resposta": "..."}}
112
- """,
113
- "P8_VERIFICAR": """
114
- VERIFICADOR FINAL.
115
- RESPOSTA: {resposta_a_verificar}
116
- ---
117
- Realize uma verificação tripla (factual, lógica, clareza) e corrija se necessário.
118
- RETORNE JSON: {{"todas_aprovadas": true|false, "resposta_corrigida": null}}
119
- """
120
- }
121
  # ============================================================================
122
  # 3. CLASSES E FUNÇÕES HELPERS
123
  # ============================================================================
124
-
125
  class Logger:
 
126
  def __init__(self, verbose: bool = True): self.verbose = verbose
127
  def log(self, msg: str, level: str = "INFO"):
128
  log_msg = f"[{datetime.now().strftime('%H:%M:%S')}] [{level.upper()}] {msg}"; print(log_msg)
129
  if level.upper() in ["TASK", "START", "SUCCESS", "ERROR", "WARN"]: print("=" * 70)
130
-
131
  logger = Logger(verbose=True)
132
 
133
  def sanitizar_texto(texto: str) -> str:
 
134
  if not isinstance(texto, str): return ""
135
  texto_limpo = re.sub(r'[\x00-\x08\x0b\x0c\x0e-\x1f]', '', texto)
136
  texto_limpo = re.sub(r'\s{2,}', ' ', texto_limpo).replace('\\n', '\n')
137
  return texto_limpo.strip()
138
 
139
- # ATUALIZADO: Função totalmente refatorada para usar a nova chamada de API.
140
- # ============================================================================
141
- # CORREÇÃO DA FUNÇÃO DE CHAMADA DA API (google-genai v1.0+)
142
- # ===========================================================================
143
-
144
- # ============================================================================
145
- # FUNÇÃO CORRIGIDA: REMOVIDO response_mime_type PARA COMPATIBILIDADE COM TOOLS
146
- # ============================================================================
147
-
148
- def chamar_gemini_json(model_name: str, prompt: str, temperatura: float = 0.7, max_tokens: int = 12000) -> Dict:
149
- # 1. Ajuste de Prompt
150
- # Modelos "Thinking" funcionam melhor quando explicamos o que queremos,
151
- # mas mantemos a proibição de Markdown no output final para facilitar o regex.
152
- prompt_completo = f"{prompt}\n\n---\n\n**INSTRUÇÃO CRÍTICA: Ao final do seu raciocínio, sua resposta FINAL deve ser estritamente um único objeto JSON válido. Não use blocos de código markdown"
153
-
154
- print(f"\n{'='*25} 💬 API INPUT PARA [{model_name}] {'='*25}\n{prompt_completo[:300]}...\n{'='*78}\n")
155
 
156
  try:
157
- # 2. Configurar o Thinking
158
- # include_thoughts=True fará com que o pensamento apareça no output (ajuda a debugar),
159
- # mas o seu regex filtrará para pegar só o JSON depois.
160
- thinking_config = types.thinkingConfig(
161
- #include_thoughts=True
162
- thinkingBudget: -1
 
163
  )
164
 
165
- # 3. Ferramentas (Google Search)
166
- tools = [
167
- types.Tool(google_search=types.GoogleSearch()),
168
- ]
169
-
170
- # 4. Configuração da Geração
171
- # ATENÇÃO: Modelos "Thinking" exigem temperatura maior ou igual a 0.7 para serem criativos no raciocínio.
172
- # removemos 'response_mime_type' pois conflita com Tools e Thinking juntos.
173
  generate_content_config = types.GenerateContentConfig(
174
- temperature=0.7, # Thinking requer > 0
175
  max_output_tokens=max_tokens,
176
- thinking_config=thinking_config,
177
  tools=tools
178
  )
179
 
180
- # 5. Configurar Conteúdo
181
- contents = [
182
- types.Content(
183
- role="user",
184
- parts=[types.Part.from_text(text=prompt_completo)],
185
- ),
186
- ]
187
-
188
- # 6. Chamada API
189
  stream = CLIENT.models.generate_content_stream(
190
- model=model_name,
191
- contents=contents,
192
- config=generate_content_config,
193
  )
 
 
 
 
 
 
194
 
195
- # 7. Agregar Resposta
196
- resposta_bruta = ""
197
- for chunk in stream:
198
- # Captura partes de texto e partes de pensamento (se retornadas como texto no stream)
199
- if chunk.text:
200
- resposta_bruta += chunk.text
201
- # Modelos Thinking as vezes retornam "executable_code", garantimos pegar apenas texto
202
-
203
- print(f"\n{'='*25} 📥 API RAW OUTPUT (Thinking + JSON) {'='*25}\n{resposta_bruta[:500]}... [conteudo longo] ...\n{'='*78}\n")
204
-
205
- # 8. Sanitização e Extração
206
- # Como o modelo vai "pensar" primeiro, o texto vai começar com o raciocínio e terminar com o JSON.
207
- # O Regex aqui é CRUCIAL.
208
- match = re.search(r'(\{.*\})', resposta_bruta, re.DOTALL)
209
-
210
  if match:
211
- json_str = match.group(0)
212
- return json.loads(json_str)
213
  else:
214
- # Fallback: Tenta limpar chars estranhos caso o regex falhe
215
- logger.log("Regex primário falhou, tentando limpeza forçada...", "WARN")
216
- resposta_sanitizada = sanitizar_texto(resposta_bruta)
217
- match_retry = re.search(r'(\{.*\})', resposta_sanitizada, re.DOTALL)
218
- if match_retry:
219
- return json.loads(match_retry.group(0))
220
-
221
- return {"erro": "JSON_NOT_FOUND", "raw": resposta_bruta[:500], "detalhes": "O modelo pensou mas não entregou o JSON no formato esperado."}
222
-
223
  except Exception as e:
224
- logger.log(f"Falha na chamada da API com Thinking: {e}", "ERROR")
225
  return {"erro": "API_CALL_FAILED", "detalhes": str(e)}
226
 
 
 
 
 
 
 
 
 
227
 
228
-
229
-
 
 
 
 
 
 
 
 
230
  def criar_dna() -> Dict:
231
- return { "historico_chat": [], "meta": {"total_turnos": 0}, "pipeline_state": { "status": "completed", "paused_at_step": None, "saved_data": {} } }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
232
 
233
  # ============================================================================
234
- # 4. PASSOS DA PIPELINE
235
  # ============================================================================
236
- # ATUALIZADO: As chamadas agora passam o NOME do modelo.
237
- def passo_1_triagem(pergunta: str) -> Dict:
 
 
238
  logger.log("📊 PASSO 1: TRIAGEM INICIAL", "TASK")
239
- return chamar_gemini_json(COUNSELOR_MODEL_NAME, PROMPTS["P1_TRIAGEM"].format(pergunta=pergunta))
240
- def passo_gerar_resposta_direta(pergunta: str) -> Dict:
 
 
241
  logger.log("⚡ FAST PATH: GERANDO RESPOSTA DIRETA", "TASK")
242
- return chamar_gemini_json(COUNSELOR_MODEL_NAME, PROMPTS["GERAR_RESPOSTA_DIRETA"].format(pergunta=pergunta))
 
 
 
 
243
  def passo_justificar_bypass(p1: Dict) -> Dict:
244
  logger.log("⚡ FAST PATH: GERANDO JUSTIFICATIVA", "TASK")
245
  return chamar_gemini_json(COUNSELOR_MODEL_NAME, PROMPTS["JUSTIFICAR_BYPASS"].format(p1=json.dumps(p1)))
246
- def passo_2_cenarios(pergunta: str, p1: Dict) -> Dict:
 
247
  logger.log("🧠 PASSO 2: GERAÇÃO DE CENÁRIOS", "TASK")
248
- return chamar_gemini_json(COUNSELOR_MODEL_NAME, PROMPTS["P2_CENARIOS"].format(p1=json.dumps(p1), pergunta=pergunta))
249
- def passo_4_cruzar_validacoes(p1: Dict, p2: Dict, p3: Dict) -> Dict:
 
 
 
 
 
250
  logger.log("🧠 PASSO 4: IDENTIFICAÇÃO DO PRINCÍPIO CENTRAL", "TASK")
251
- return chamar_gemini_json(COUNSELOR_MODEL_NAME, PROMPTS["P4_CRUZAR_VALIDACOES"].format(p1=json.dumps(p1), p2=json.dumps(p2), p3=json.dumps(p3)))
252
- def passo_5_lacunas_finais(pergunta: str, p4: Dict) -> Dict:
 
 
253
  logger.log("🧠 PASSO 5: ANÁLISE DE INCERTEZA", "TASK")
254
- return chamar_gemini_json(COUNSELOR_MODEL_NAME, PROMPTS["P5_LACUNAS_FINAIS"].format(p4=json.dumps(p4), pergunta=pergunta))
255
- def passo_8_verificar(resposta_a_verificar: str) -> Dict:
 
 
256
  logger.log("✅ PASSO 8: VERIFICAÇÃO FINAL", "TASK")
257
- return chamar_gemini_json(SUPERVISOR_MODEL_NAME, PROMPTS["P8_VERIFICAR"].format(resposta_a_verificar=resposta_a_verificar))
 
 
 
 
258
  def passo_3_isolar_cenarios(p2: Dict) -> Dict: return {"simulado": True}
259
  def passo_6_ponderar(p2: Dict, p4: Dict, p5: Dict) -> Dict: return {"simulado": True}
260
- def passo_7_sintetizar(p6: Dict) -> Dict: return {"resposta": "Resposta vinda da pipeline completa."}
261
-
262
- # ============================================================================
263
- # 5. ORQUESTRADOR PRINCIPAL
264
- # ============================================================================
265
 
266
- def iniciar_nova_pipeline(pergunta_original: str, historico: List[Dict], anexo: Any, dna: Dict) -> Tuple[str, List, Dict]:
 
267
  pergunta = sanitizar_texto(pergunta_original)
268
- logger.log(f"INICIANDO NOVA PIPELINE (Input Sanitizado): '{pergunta[:70]}...'", "START")
269
 
270
- p1 = passo_1_triagem(pergunta)
 
271
  if "erro" in p1: return f"Erro na Triagem: {p1.get('detalhes', 'Não foi possível classificar a pergunta.')}", historico, dna
272
 
 
273
  if p1.get("decisao") == "responder_direto":
274
- logger.log("DECISÃO: Tomar o Caminho Rápido (Bypass).", "INFO")
275
- resposta_direta_data = passo_gerar_resposta_direta(pergunta)
 
276
  justificativa_data = passo_justificar_bypass(p1)
277
 
278
- if "erro" in resposta_direta_data or "erro" in justificativa_data: return "Erro ao gerar a resposta direta.", historico, dna
 
 
 
 
279
 
280
  resposta_direta = resposta_direta_data.get("resposta_direta", "Não foi possível gerar a resposta.")
281
  justificativa = justificativa_data.get("justificativa_bypass", {})
282
- verificacao = passo_8_verificar(resposta_direta)
283
- resposta_final = verificacao.get("resposta_corrigida") or resposta_direta
284
 
285
- justificativa_texto = f"**JUSTIFICATIVA DE RESPOSTA DIRETA:**\n- **Motivo:** {justificativa.get('motivo', 'N/A')}\n- **Ação:** {justificativa.get('acao_tomada', 'N/A')}\n\n---\n"
 
 
286
  resposta_formatada = justificativa_texto + resposta_final
287
 
288
  novo_historico = historico + [{"role": "user", "content": pergunta_original}, {"role": "assistant", "content": resposta_formatada}]
289
- logger.log("PIPELINE (FAST PATH) CONCLUÍDA", "SUCCESS")
 
 
 
 
290
  return "PIPELINE_COMPLETED", novo_historico, dna
291
- else:
292
- logger.log("DECISÃO: Tomar o Caminho Completo de Análise Profunda.", "INFO")
293
- p2 = passo_2_cenarios(pergunta, p1)
 
 
 
294
  p3 = passo_3_isolar_cenarios(p2)
295
- p4 = passo_4_cruzar_validacoes(p1, p2, p3)
296
- p5 = passo_5_lacunas_finais(pergunta, p4)
297
 
298
  if p5.get("decisao_interna") == "questionar":
299
- logger.log("INTERRUPÇÃO no P5. Salvando estado no DNA.", "WARN")
 
 
 
 
300
  dna['pipeline_state'] = {"status": "paused", "paused_at_step": "P5", "saved_data": {'p1':p1, 'p2':p2, 'p3':p3, 'p4':p4, 'pergunta_original': pergunta, 'historico_original': historico}}
301
  pergunta_do_bot = p5.get('pergunta_chave_para_usuario', 'Preciso de mais informações.')
302
  historico_atualizado = historico + [{"role": "user", "content": pergunta_original}, {"role": "assistant", "content": pergunta_do_bot}]
303
  return "PIPELINE_PAUSED", historico_atualizado, dna
304
-
 
305
  p6 = passo_6_ponderar(p2, p4, p5)
306
  p7 = passo_7_sintetizar(p6)
307
- p8 = passo_8_verificar(p7.get("resposta", ""))
 
 
 
308
  resposta_final = p8.get("resposta_corrigida") or p7.get("resposta", "Não foi possível gerar a resposta.")
309
  novo_historico = historico + [{"role": "user", "content": pergunta_original}, {"role": "assistant", "content": resposta_final}]
310
- logger.log("PIPELINE (COMPLETA) CONCLUÍDA", "SUCCESS")
 
 
 
 
 
311
  return "PIPELINE_COMPLETED", novo_historico, dna
312
 
313
- def resumir_pipeline(esclarecimento_usuario_original: str, dna: Dict) -> Tuple[str, List, Dict]:
314
- # ... (lógica de resumir pipeline, que pode ser reativada e adaptada se necessário) ...
315
- logger.log("Lógica de retomada (resumir pipeline) executada.", "INFO")
316
- resposta = "A lógica de retomada foi acionada. O fluxo completo precisa ser re-implementado."
317
- dna['pipeline_state']['status'] = 'completed'
318
- return "PIPELINE_COMPLETED", dna['pipeline_state']['saved_data']['historico_original'] + [{"role": "user", "content": esclarecimento_usuario_original}, {"role": "assistant", "content": resposta}], dna
319
 
320
- def executar_pipeline(pergunta: str, historico: List[Dict], anexo: Any, dna: Dict) -> Tuple[str, List, Dict]:
 
 
321
  try:
 
322
  if 'pipeline_state' not in dna: dna.update(criar_dna())
 
 
323
  if dna['pipeline_state']['status'] == 'paused':
324
  return resumir_pipeline(pergunta, dna)
325
- return iniciar_nova_pipeline(pergunta, historico, anexo, dna)
 
 
 
 
 
326
  except Exception as e:
 
327
  logger.log(f"Erro catastrófico no orquestrador: {e}", "ERROR")
328
  resposta_erro = f"Ocorreu um erro inesperado na pipeline: {e}"
 
 
 
 
 
 
329
  return "PIPELINE_ERROR", historico + [{"role": "user", "content": pergunta}, {"role": "assistant", "content": resposta_erro}], dna
330
 
 
331
  # ============================================================================
332
- # 6. INTERFACE COM GRADIO
333
  # ============================================================================
334
- def chat_interface(pergunta: str, historico_gradio: List[List[str]], anexo: Any, dna_json_str: str) -> Tuple[List, str, str, None]:
335
- # ... (lógica da interface sem alterações) ...
 
 
336
  try:
337
  dna = json.loads(dna_json_str) if dna_json_str and dna_json_str.strip() else criar_dna()
338
  except:
339
  dna = criar_dna()
340
 
341
- historico_interno = [item for turno in historico_gradio for item in ({"role": "user", "content": turno[0]}, {"role": "assistant", "content": turno[1]}) if turno and item['content']]
342
- _ , novo_historico_para_exibir, dna_atualizado = executar_pipeline(pergunta, historico_interno, anexo, dna)
 
 
 
 
 
 
 
 
 
 
 
343
 
 
344
  novo_historico_gradio = []
345
  for i in range(0, len(novo_historico_para_exibir), 2):
346
- if i + 1 < len(novo_historico_para_exibir):
347
- novo_historico_gradio.append([novo_historico_para_exibir[i]['content'], novo_historico_para_exibir[i+1]['content']])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
348
 
349
- return novo_historico_gradio, "", json.dumps(dna_atualizado, indent=2, ensure_ascii=False), None
350
 
351
  if __name__ == "__main__":
 
 
 
 
 
352
  with gr.Blocks(title="Pipeline v10 - Raciocínio Adaptativo", theme=gr.themes.Soft()) as demo:
353
- # ... (layout do Gradio sem alterações) ...
 
 
 
 
 
 
 
 
 
354
  gr.Markdown(TITLE)
355
- with gr.Row():
356
- with gr.Column(scale=3):
357
- chatbot = gr.Chatbot(label="Chat", height=600, bubble_full_width=False)
358
- input_textbox = gr.Textbox(label="Digite sua pergunta...", lines=3)
359
- with gr.Row():
360
- submit_button = gr.Button("🚀 Enviar", variant="primary", scale=1)
361
- file_upload = gr.File(label="Anexar Arquivo", scale=1)
362
- with gr.Column(scale=2):
363
- dna_view = gr.Code(label="DNA (Estado da Conversa)", language="json", interactive=False, value=json.dumps(criar_dna(), indent=2, ensure_ascii=False))
364
- dna_json_hidden = gr.Textbox(value=json.dumps(criar_dna()), visible=False)
365
- submit_button.click(fn=chat_interface, inputs=[input_textbox, chatbot, file_upload, dna_json_hidden], outputs=[chatbot, input_textbox, dna_json_hidden, file_upload])
366
- input_textbox.submit(fn=chat_interface, inputs=[input_textbox, chatbot, file_upload, dna_json_hidden], outputs=[chatbot, input_textbox, dna_json_hidden, file_upload])
367
- dna_json_hidden.change(fn=lambda x: x, inputs=[dna_json_hidden], outputs=[dna_view])
368
- demo.launch(server_name="0.0.0.0", server_port=7860, share=False)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
  # -*- coding: utf-8 -*-
3
  """
4
+ Pipeline v10 - COM MEMÓRIA DA API E HISTÓRICO DE GOVERNANÇA COMPACTADO (Últimas 10).
 
 
 
5
 
6
  PRINCIPAIS ALTERAÇÕES:
7
+ - MEMÓRIA GEMINI: A API agora recebe o histórico completo da conversa (types.Content) em cada chamada.
8
+ - HISTÓRICO DE GOVERNANÇA: 'historico_chat' substituído por 'historico_governanca', limitado a 10 entradas de debug (P1/P8).
9
+ - VISUALIZADOR DNA: Novo grupo colapsável para ver o Histórico de Governança no DNA View.
 
 
 
 
 
 
10
  """
11
+ # (IMPORTAÇÕES INALTERADAS)
 
 
 
12
  import json
13
  import os
14
  import re
 
17
  from typing import Dict, List, Tuple, Any
18
 
19
  import gradio as gr
 
 
 
20
  from google import genai
21
  from google.genai import types
22
 
23
+ # Define o nome do arquivo de prompts para uso centralizado
24
+ PROMPT_FILENAME = "prompts_pipeline.json"
25
+
26
  warnings.filterwarnings("ignore", category=FutureWarning, module="google.api_core")
27
 
 
28
  API_KEY = os.getenv("GOOGLE_API_KEY")
29
  if not API_KEY:
30
  raise ValueError("A variável de ambiente GOOGLE_API_KEY não foi configurada.")
31
 
 
 
 
32
  CLIENT = genai.Client(api_key=API_KEY)
33
+ COUNSELOR_MODEL_NAME = "gemini-2.5-pro"
34
+ SUPERVISOR_MODEL_NAME = "gemini-2.5-pro"
35
+ TITLE = "# 🚀 Pipeline v10 | Governanca com Memória API"
36
 
37
+ # (O bloco 2. PROMPTS CENTRALIZADOS - com carregar_prompts_externos, etc. - PERMANECE INALTERADO)
38
+ # [ ... SEÇÃO 2 COMPLETA DEVE SER MANTIDA INALTERADA AQUI ... ]
39
+ PROMPTS = carregar_prompts_externos()
 
40
 
 
 
41
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
42
  # ============================================================================
43
  # 3. CLASSES E FUNÇÕES HELPERS
44
  # ============================================================================
 
45
  class Logger:
46
+ # (Logger inalterado)
47
  def __init__(self, verbose: bool = True): self.verbose = verbose
48
  def log(self, msg: str, level: str = "INFO"):
49
  log_msg = f"[{datetime.now().strftime('%H:%M:%S')}] [{level.upper()}] {msg}"; print(log_msg)
50
  if level.upper() in ["TASK", "START", "SUCCESS", "ERROR", "WARN"]: print("=" * 70)
 
51
  logger = Logger(verbose=True)
52
 
53
  def sanitizar_texto(texto: str) -> str:
54
+ # (sanitizar_texto inalterado)
55
  if not isinstance(texto, str): return ""
56
  texto_limpo = re.sub(r'[\x00-\x08\x0b\x0c\x0e-\x1f]', '', texto)
57
  texto_limpo = re.sub(r'\s{2,}', ' ', texto_limpo).replace('\\n', '\n')
58
  return texto_limpo.strip()
59
 
60
+ # ATUALIZADO: Chama a API com Histórico de Conversa (memória nativa)
61
+ def chamar_gemini_json(model_name: str, prompt: str,
62
+ historico_conversa: List[types.Content] = None, # <--- AGORA ACEITA HISTÓRICO
63
+ temperatura: float = 0.4, max_tokens: int = 8192) -> Dict:
64
+ prompt_completo = f"{prompt}\n\n---\n\n**INSTRUÇÃO CRÍTICA: Não use formatação Markdown. Sua resposta DEVE ser estritamente um único objeto JSON válido.**"
65
+ # print(f"\n{'='*25} 💬 API INPUT PARA [{model_name}] {'='*25}\n{prompt_completo}\n{'='*78}\n")
 
 
 
 
 
 
 
 
 
 
66
 
67
  try:
68
+ # 1. Monta o contents, INCLUINDO o histórico E a pergunta atual.
69
+ contents = historico_conversa if historico_conversa else []
70
+ contents.append( # Adiciona o prompt da TAREFA (Triagem, Cenários, etc)
71
+ types.Content(
72
+ role="user", # Sempre 'user' para a instrução da tarefa atual
73
+ parts=[types.Part.from_text(text=prompt_completo)],
74
+ )
75
  )
76
 
77
+ # 2. Configuração (Thinking e Tools - inalterado)
78
+ tools = [types.Tool(google_search=types.GoogleSearch())]
 
 
 
 
 
 
79
  generate_content_config = types.GenerateContentConfig(
80
+ temperature=temperatura,
81
  max_output_tokens=max_tokens,
82
+ thinking_config=types.ThinkingConfig(thinking_budget=8192),
83
  tools=tools
84
  )
85
 
86
+ # 3. Chamada de API Corrigida
 
 
 
 
 
 
 
 
87
  stream = CLIENT.models.generate_content_stream(
88
+ model=model_name, contents=contents, config=generate_content_config
 
 
89
  )
90
+ # ... (Agregação de resposta, sanitização e extração de JSON inalterados) ...
91
+ resposta_bruta = "".join(chunk.text for chunk in stream if chunk.text)
92
+ resposta_sanitizada = sanitizar_texto(resposta_bruta)
93
+
94
+ if not resposta_sanitizada:
95
+ return {"erro": "API_EMPTY_RESPONSE"}
96
 
97
+ match = re.search(r'\{.*\}', resposta_sanitizada, re.DOTALL)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
98
  if match:
99
+ return json.loads(match.group(0))
 
100
  else:
101
+ return json.loads(resposta_sanitizada)
102
+
 
 
 
 
 
 
 
103
  except Exception as e:
104
+ logger.log(f"Falha na chamada da API ou no parse do JSON: {e}", "ERROR")
105
  return {"erro": "API_CALL_FAILED", "detalhes": str(e)}
106
 
107
+ # ATUALIZADO: Helper para Histórico (Memória API)
108
+ def to_gemini_contents(historico: List[Dict]) -> List[types.Content]:
109
+ """Converte o histórico Gradio-compatível para o formato genai.types.Content (user/model)."""
110
+ gemini_contents = []
111
+ for entry in historico:
112
+ role = "model" if entry["role"] == "assistant" else "user"
113
+ # Garante que só há conteúdo para a API (exclui a JUSTIFICATIVA DE RESPOSTA DIRETA/formatação do Gradio)
114
+ content_for_api = re.sub(r'\*\*JUSTIFICATIVA DE RESPOSTA DIRETA:\*\*.*?\-\-\-\n\n', '', entry["content"], flags=re.DOTALL)
115
 
116
+ if content_for_api:
117
+ gemini_contents.append(
118
+ types.Content(
119
+ role=role,
120
+ parts=[types.Part.from_text(text=content_for_api)]
121
+ )
122
+ )
123
+ return gemini_contents
124
+
125
+ # ATUALIZADO: DNA para o Novo Histórico de Governança
126
  def criar_dna() -> Dict:
127
+ return {
128
+ "historico_governanca": [], # <--- NOVO: Listagem das últimas 10 iterações de debug
129
+ "meta": {"total_turnos": 0},
130
+ "pipeline_state": {
131
+ "status": "completed",
132
+ "paused_at_step": None,
133
+ "saved_data": {}
134
+ },
135
+ # Campos de debug/visualização do TURNO ATUAL
136
+ "P1_data": {}, "P2_data": {}, "P4_data": {}, "P5_data": {}, "P8_data": {},
137
+ }
138
+
139
+ def formatar_dna_json(data: Dict) -> str:
140
+ # (formatar_dna_json inalterado)
141
+ try:
142
+ return json.dumps(data, indent=2, ensure_ascii=False)
143
+ except:
144
+ return f"Erro ao serializar dados: {data}"
145
 
146
  # ============================================================================
147
+ # 4. PASSOS DA PIPELINE E 5. ORQUESTRADOR
148
  # ============================================================================
149
+
150
+ # Os passos precisam receber historico_conversa, mas como o prompt é fechado em JSON,
151
+ # só P1 e o Bypass Resposta Direta P8 usam a memória, por conveniência e performance.
152
+ def passo_1_triagem(pergunta: str, dna: Dict, historico_memoria: List[types.Content]) -> Tuple[Dict, Dict]:
153
  logger.log("📊 PASSO 1: TRIAGEM INICIAL", "TASK")
154
+ p1 = chamar_gemini_json(COUNSELOR_MODEL_NAME, PROMPTS["P1_TRIAGEM"].format(pergunta=pergunta), historico_conversa=historico_memoria)
155
+ dna["P1_data"] = p1
156
+ return p1, dna
157
+ def passo_gerar_resposta_direta(pergunta: str, historico_memoria: List[types.Content]) -> Dict:
158
  logger.log("⚡ FAST PATH: GERANDO RESPOSTA DIRETA", "TASK")
159
+ # Usa a memória para contextuar a resposta
160
+ return chamar_gemini_json(COUNSELOR_MODEL_NAME, PROMPTS["GERAR_RESPOSTA_DIRETA"].format(pergunta=pergunta), historico_conversa=historico_memoria)
161
+
162
+ # Os outros passos (P2, P4, P5, P8) podem continuar usando o formato antigo por eficiência/objetividade,
163
+ # pois os prompts internos dependem principalmente dos JSONs gerados anteriormente (meta-cognição).
164
  def passo_justificar_bypass(p1: Dict) -> Dict:
165
  logger.log("⚡ FAST PATH: GERANDO JUSTIFICATIVA", "TASK")
166
  return chamar_gemini_json(COUNSELOR_MODEL_NAME, PROMPTS["JUSTIFICAR_BYPASS"].format(p1=json.dumps(p1)))
167
+
168
+ def passo_2_cenarios(pergunta: str, p1: Dict, dna: Dict) -> Tuple[Dict, Dict]:
169
  logger.log("🧠 PASSO 2: GERAÇÃO DE CENÁRIOS", "TASK")
170
+ # Apenas o prompt da tarefa: Não passamos histórico para focar no JSON de meta-cognição
171
+ p2 = chamar_gemini_json(COUNSELOR_MODEL_NAME, PROMPTS["P2_CENARIOS"].format(p1=json.dumps(p1), pergunta=pergunta))
172
+ dna["P2_data"] = p2
173
+ return p2, dna
174
+
175
+ # ... (outros passos com chamadas inalteradas: passo_4_cruzar_validacoes, passo_5_lacunas_finais, passo_8_verificar, e os simulados)
176
+ def passo_4_cruzar_validacoes(p1: Dict, p2: Dict, p3: Dict, dna: Dict) -> Tuple[Dict, Dict]:
177
  logger.log("🧠 PASSO 4: IDENTIFICAÇÃO DO PRINCÍPIO CENTRAL", "TASK")
178
+ p4 = chamar_gemini_json(COUNSELOR_MODEL_NAME, PROMPTS["P4_CRUZAR_VALIDACOES"].format(p1=json.dumps(p1), p2=json.dumps(p2), p3=json.dumps(p3)))
179
+ dna["P4_data"] = p4
180
+ return p4, dna
181
+ def passo_5_lacunas_finais(pergunta: str, p4: Dict, dna: Dict) -> Tuple[Dict, Dict]:
182
  logger.log("🧠 PASSO 5: ANÁLISE DE INCERTEZA", "TASK")
183
+ p5 = chamar_gemini_json(COUNSELOR_MODEL_NAME, PROMPTS["P5_LACUNAS_FINAIS"].format(p4=json.dumps(p4), pergunta=pergunta))
184
+ dna["P5_data"] = p5
185
+ return p5, dna
186
+ def passo_8_verificar(resposta_a_verificar: str, dna: Dict) -> Tuple[Dict, Dict]:
187
  logger.log("✅ PASSO 8: VERIFICAÇÃO FINAL", "TASK")
188
+ # Passamos o histórico aqui apenas para fins de contextuação na checagem final.
189
+ p8 = chamar_gemini_json(SUPERVISOR_MODEL_NAME, PROMPTS["P8_VERIFICAR"].format(resposta_a_verificar=resposta_a_verificar))
190
+ dna["P8_data"] = p8
191
+ return p8, dna
192
+
193
  def passo_3_isolar_cenarios(p2: Dict) -> Dict: return {"simulado": True}
194
  def passo_6_ponderar(p2: Dict, p4: Dict, p5: Dict) -> Dict: return {"simulado": True}
195
+ def passo_7_sintetizar(p6: Dict) -> Dict: return {"resposta": "Resposta sintetizada vinda da pipeline completa (simulado)."}
 
 
 
 
196
 
197
+ def iniciar_nova_pipeline(pergunta_original: str, historico: List[Dict], anexo: Any, dna: Dict, historico_memoria: List[types.Content]) -> Tuple[str, List, Dict]: # <--- historico_memoria adicionado
198
+ # (Lógica de orquestrador, atualizada com historico_memoria)
199
  pergunta = sanitizar_texto(pergunta_original)
 
200
 
201
+ # PASSO 1
202
+ p1, dna = passo_1_triagem(pergunta, dna, historico_memoria) # <--- PASSANDO MEMÓRIA
203
  if "erro" in p1: return f"Erro na Triagem: {p1.get('detalhes', 'Não foi possível classificar a pergunta.')}", historico, dna
204
 
205
+ # FAST PATH / FULL PATH
206
  if p1.get("decisao") == "responder_direto":
207
+
208
+ # Gerar e Justificar
209
+ resposta_direta_data = passo_gerar_resposta_direta(pergunta, historico_memoria) # <--- PASSANDO MEMÓRIA
210
  justificativa_data = passo_justificar_bypass(p1)
211
 
212
+ if "erro" in resposta_direta_data:
213
+ return f"Erro ao gerar a resposta direta: {resposta_direta_data['detalhes']}", historico, dna
214
+
215
+ # P8, Justificativa e Consolidação...
216
+ # ... (consolidação final da resposta é a mesma)
217
 
218
  resposta_direta = resposta_direta_data.get("resposta_direta", "Não foi possível gerar a resposta.")
219
  justificativa = justificativa_data.get("justificativa_bypass", {})
 
 
220
 
221
+ verificacao, dna = passo_8_verificar(resposta_direta, dna)
222
+ resposta_final = verificacao.get("resposta_corrigida") or resposta_direta
223
+ justificativa_texto = f"**JUSTIFICATIVA DE RESPOSTA DIRETA:**\n- **Tipo/Confiança (P1):** {p1.get('tipo', 'N/A')}/{p1.get('confianca', 'N/A')}\n- **Motivo:** {justificativa.get('motivo', 'N/A')}\n\n---\n"
224
  resposta_formatada = justificativa_texto + resposta_final
225
 
226
  novo_historico = historico + [{"role": "user", "content": pergunta_original}, {"role": "assistant", "content": resposta_formatada}]
227
+
228
+ # --- ATUALIZAÇÃO DO HISTÓRICO DE GOVERNANÇA ---
229
+ dna["historico_governanca"].append({"turno": dna['meta']['total_turnos'], "tipo": "FAST_PATH", "p1": p1, "p8": verificacao, "pergunta": pergunta_original})
230
+ if len(dna["historico_governanca"]) > 10: dna["historico_governanca"] = dna["historico_governanca"][-10:]
231
+
232
  return "PIPELINE_COMPLETED", novo_historico, dna
233
+
234
+ else: # FULL PATH
235
+ # ... (P2, P3, P4, P5 - Lógica inalterada - os passos P2-P5 não precisam de memória API)
236
+
237
+ # PASSO 2, 4, 5 (chamadas com retorno pX, dna = passo_X...)
238
+ p2, dna = passo_2_cenarios(pergunta, p1, dna)
239
  p3 = passo_3_isolar_cenarios(p2)
240
+ p4, dna = passo_4_cruzar_validacoes(p1, p2, p3, dna)
241
+ p5, dna = passo_5_lacunas_finais(pergunta, p4, dna)
242
 
243
  if p5.get("decisao_interna") == "questionar":
244
+ # ... (Lógica de Pausa inalterada)
245
+ # SALVAR A GOVERNANÇA ANTES DE PAUSAR:
246
+ dna["historico_governanca"].append({"turno": dna['meta']['total_turnos'], "tipo": "FULL_PATH_PAUSA", "p1": p1, "p5": p5, "pergunta": pergunta_original})
247
+ if len(dna["historico_governanca"]) > 10: dna["historico_governanca"] = dna["historico_governanca"][-10:]
248
+
249
  dna['pipeline_state'] = {"status": "paused", "paused_at_step": "P5", "saved_data": {'p1':p1, 'p2':p2, 'p3':p3, 'p4':p4, 'pergunta_original': pergunta, 'historico_original': historico}}
250
  pergunta_do_bot = p5.get('pergunta_chave_para_usuario', 'Preciso de mais informações.')
251
  historico_atualizado = historico + [{"role": "user", "content": pergunta_original}, {"role": "assistant", "content": pergunta_do_bot}]
252
  return "PIPELINE_PAUSED", historico_atualizado, dna
253
+
254
+ # PASSO 6, 7 (Simulados)
255
  p6 = passo_6_ponderar(p2, p4, p5)
256
  p7 = passo_7_sintetizar(p6)
257
+
258
+ # PASSO 8
259
+ p8, dna = passo_8_verificar(p7.get("resposta", ""), dna)
260
+
261
  resposta_final = p8.get("resposta_corrigida") or p7.get("resposta", "Não foi possível gerar a resposta.")
262
  novo_historico = historico + [{"role": "user", "content": pergunta_original}, {"role": "assistant", "content": resposta_final}]
263
+ dna['pipeline_state']['status'] = 'completed'
264
+
265
+ # --- ATUALIZAÇÃO DO HISTÓRICO DE GOVERNANÇA ---
266
+ dna["historico_governanca"].append({"turno": dna['meta']['total_turnos'], "tipo": "FULL_PATH_CONCLUIDO", "p1": p1, "p2": p2, "p4": p4, "p5": p5, "p8": p8, "pergunta": pergunta_original})
267
+ if len(dna["historico_governanca"]) > 10: dna["historico_governanca"] = dna["historico_governanca"][-10:]
268
+
269
  return "PIPELINE_COMPLETED", novo_historico, dna
270
 
 
 
 
 
 
 
271
 
272
+ # ATUALIZADO: Executar pipeline agora aceita a memória.
273
+ def executar_pipeline(pergunta: str, historico: List[Dict], anexo: Any, dna: Dict, historico_memoria: List[types.Content]) -> Tuple[str, List, Dict]:
274
+ # (Orquestrador de alto nível com gestão de estados)
275
  try:
276
+ # ... (contagem de turno e limpeza de Px_data para turno atual)
277
  if 'pipeline_state' not in dna: dna.update(criar_dna())
278
+ dna['meta']['total_turnos'] = dna['meta'].get('total_turnos', 0) + 1
279
+
280
  if dna['pipeline_state']['status'] == 'paused':
281
  return resumir_pipeline(pergunta, dna)
282
+
283
+ for key in ["P1_data", "P2_data", "P4_data", "P5_data", "P8_data"]:
284
+ dna[key] = {}
285
+
286
+ return iniciar_nova_pipeline(pergunta, historico, anexo, dna, historico_memoria) # <--- PASSA MEMÓRIA
287
+
288
  except Exception as e:
289
+ # ... (gestão de erro inalterada)
290
  logger.log(f"Erro catastrófico no orquestrador: {e}", "ERROR")
291
  resposta_erro = f"Ocorreu um erro inesperado na pipeline: {e}"
292
+ dna['pipeline_state']['status'] = 'error'
293
+
294
+ # SALVAR GOVERNANÇA DE ERRO:
295
+ dna["historico_governanca"].append({"turno": dna['meta']['total_turnos'], "tipo": "ERRO_CATASTRÓFICO", "erro": str(e), "pergunta": pergunta})
296
+ if len(dna["historico_governanca"]) > 10: dna["historico_governanca"] = dna["historico_governanca"][-10:]
297
+
298
  return "PIPELINE_ERROR", historico + [{"role": "user", "content": pergunta}, {"role": "assistant", "content": resposta_erro}], dna
299
 
300
+
301
  # ============================================================================
302
+ # 6. INTERFACE COM GRADIO (Incluindo Expansor de Governança no Rodapé do DNA)
303
  # ============================================================================
304
+
305
+ def chat_interface(pergunta: str, historico_gradio: List[List[str]], anexo: Any, dna_json_str: str) -> Tuple[List, str, str, None, str, str, str, str, str, str, str]: # AGORA 11 RETORNOS
306
+
307
+ # ... (Inicialização do DNA)
308
  try:
309
  dna = json.loads(dna_json_str) if dna_json_str and dna_json_str.strip() else criar_dna()
310
  except:
311
  dna = criar_dna()
312
 
313
+ # Converte Histórico Gradio -> Histórico Interno (para orquestrador e conversão para Gemini)
314
+ historico_interno = []
315
+ for turno in historico_gradio:
316
+ if turno and turno[0]: historico_interno.append({"role": "user", "content": turno[0]})
317
+ if turno and turno[1]: historico_interno.append({"role": "assistant", "content": turno[1]})
318
+
319
+ # --- NOVO: Histórico no formato Gemini ---
320
+ historico_para_gemini = to_gemini_contents(historico_interno)
321
+
322
+ # Executa a Pipeline, passando o historico_memoria para as funções de baixo nível.
323
+ _ , novo_historico_para_exibir, dna_atualizado = executar_pipeline(
324
+ pergunta, historico_interno, anexo, dna, historico_para_gemini
325
+ )
326
 
327
+ # ... (Conversão de novo_historico_para_exibir para novo_historico_gradio é inalterada) ...
328
  novo_historico_gradio = []
329
  for i in range(0, len(novo_historico_para_exibir), 2):
330
+ user_msg = novo_historico_para_exibir[i]['content']
331
+ assistant_msg = novo_historico_para_exibir[i+1]['content'] if i + 1 < len(novo_historico_para_exibir) else ""
332
+ novo_historico_gradio.append([user_msg, assistant_msg])
333
+
334
+
335
+ # Prepara as Saídas de Debug/Visualização
336
+ dna_completo_str = json.dumps(dna_atualizado, indent=2, ensure_ascii=False)
337
+ p1_data_str = formatar_dna_json(dna_atualizado.get('P1_data', {}))
338
+ p2_data_str = formatar_dna_json(dna_atualizado.get('P2_data', {}))
339
+ p4_data_str = formatar_dna_json(dna_atualizado.get('P4_data', {}))
340
+ p5_data_str = formatar_dna_json(dna_atualizado.get('P5_data', {}))
341
+ p8_data_str = formatar_dna_json(dna_atualizado.get('P8_data', {}))
342
+
343
+ # --- NOVO: Histórico de Governança para o Rodapé (11º retorno) ---
344
+ historico_gov_str = formatar_dna_json(dna_atualizado.get('historico_governanca', []))
345
+
346
+ return (novo_historico_gradio,
347
+ "",
348
+ json.dumps(dna_atualizado, indent=2, ensure_ascii=False), # 3: dna_json_hidden (Oculto)
349
+ None, # 4: file_upload
350
+ dna_completo_str, # 5: dna_view (Completo Formatado)
351
+ p1_data_str, # 6: P1
352
+ p2_data_str, # 7: P2
353
+ p4_data_str, # 8: P4
354
+ p5_data_str, # 9: P5
355
+ p8_data_str, # 10: P8
356
+ historico_gov_str # 11: Novo Painel de Governança
357
+ )
358
 
 
359
 
360
  if __name__ == "__main__":
361
+ prompts_text_inicial = get_prompts_raw_text()
362
+
363
+ # Variáveis de Visualização (Declaradas na seção 1 do if __name__ e renderizadas na seção 2)
364
+ # Componentes P1-P8... inalterados aqui
365
+
366
  with gr.Blocks(title="Pipeline v10 - Raciocínio Adaptativo", theme=gr.themes.Soft()) as demo:
367
+ # Variáveis de Visualização do Turno Atual
368
+ p1_out = gr.Code(label="1. TRIAGEM (P1)", language="json", interactive=False, value="{}")
369
+ p2_out = gr.Code(label="2. CENÁRIOS (P2)", language="json", interactive=False, value="{}")
370
+ p4_out = gr.Code(label="4. PRINCÍPIO (P4)", language="json", interactive=False, value="{}")
371
+ p5_out = gr.Code(label="5. LACUNAS/DECISÃO (P5)", language="json", interactive=False, value="{}")
372
+ p8_out = gr.Code(label="8. VERIFICAÇÃO (P8)", language="json", interactive=False, value="{}")
373
+
374
+ # NOVO: Variável de Visualização de Governança
375
+ gov_out = gr.Code(label=f"Histórico de Governança (Últimos 10 Turnos)", language="json", interactive=False, value="[]")
376
+
377
  gr.Markdown(TITLE)
378
+
379
+ with gr.Tab("💬 Chat e Resposta"):
380
+ with gr.Row():
381
+ with gr.Column(scale=3):
382
+ chatbot = gr.Chatbot(label="Chat", height=500, bubble_full_width=False)
383
+ input_textbox = gr.Textbox(label="Digite sua pergunta...", lines=3)
384
+ with gr.Row():
385
+ submit_button = gr.Button("🚀 Enviar", variant="primary", scale=1)
386
+ file_upload = gr.File(label="Anexar Arquivo", scale=1)
387
+
388
+ with gr.Column(scale=2):
389
+ # Elementos de DNA (Oculto + Visível)
390
+ dna_json_hidden = gr.Textbox(value=json.dumps(criar_dna()), visible=False)
391
+ dna_view = gr.Code(label="DNA (Estado Completo da Conversa/Meta-Dados)", language="json", interactive=False, value=json.dumps(criar_dna(), indent=2, ensure_ascii=False))
392
+
393
+ # --- NOVO: Grupo Expansível para Governança ---
394
+ with gr.Group():
395
+ with gr.Accordion("📜 Ver Histórico de Governança", open=False):
396
+ gov_out.render()
397
+
398
+
399
+ with gr.Tab("🛠️ Debug da Pipeline"):
400
+ gr.Markdown("## Visualização JSON por Passo do TURNO ATUAL")
401
+ with gr.Row():
402
+ p1_out.render() # 1. P1 - Triagem
403
+ p2_out.render() # 2. P2 - Geração de Cenários
404
+ with gr.Row():
405
+ p4_out.render() # 4. P4 - Princípio Central
406
+ p5_out.render() # 5. P5 - Lacunas/Decisão (Pausa)
407
+ with gr.Row():
408
+ p8_out.render() # 8. P8 - Verificação Final
409
+
410
+ with gr.Tab("📝 Editor de Prompts"):
411
+ # ... (Lógica de editor inalterada)
412
+ gr.Markdown(f"## Editor Dinâmico de Prompts ({PROMPT_FILENAME})")
413
+ prompts_editor = gr.Code(label="Conteúdo do JSON de Prompts", language="json", value=prompts_text_inicial, interactive=True, lines=30)
414
+ save_button = gr.Button("💾 SALVAR E RECARREGAR PROMPTS NO SERVIDOR", variant="stop")
415
+ save_status = gr.Textbox(label="Status do Servidor", interactive=False)
416
+
417
+ save_button.click(fn=salvar_e_recarregar_prompts, inputs=[prompts_editor], outputs=[save_status])
418
+
419
+
420
+ # Ações do Botão/Entrada do Chat (Incluindo novo retorno: gov_out)
421
+ fn_args = {
422
+ "fn": chat_interface,
423
+ "inputs": [input_textbox, chatbot, file_upload, dna_json_hidden],
424
+ "outputs": [chatbot,
425
+ input_textbox,
426
+ dna_json_hidden,
427
+ file_upload,
428
+ dna_view, # 5
429
+ p1_out, # 6
430
+ p2_out, # 7
431
+ p4_out, # 8
432
+ p5_out, # 9
433
+ p8_out, # 10
434
+ gov_out # 11 <--- NOVO
435
+ ]
436
+ }
437
+
438
+ submit_button.click(**fn_args)
439
+ input_textbox.submit(**fn_args)
440
+
441
+ demo.launch(server_name="0.0.0.0", server_port=7860, share=False)
442
+ ---