caarleexx commited on
Commit
8ef55f5
·
verified ·
1 Parent(s): ed06b0f

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +62 -37
app.py CHANGED
@@ -1,5 +1,5 @@
1
  # ╔════════════════════════════════════════════════════════════════════════════╗
2
- # ║ PIPELINE v29: ANÁLISE EPISTÊMICA (COM LÓGICA REAL PYPDF)
3
  # ║ Layout: Chat (Aba 1) | Debug (Aba 2) | Config (Aba 3) ║
4
  # ╚════════════════════════════════════════════════════════════════════════════╝
5
 
@@ -10,7 +10,7 @@ import time
10
  from datetime import datetime
11
  import gradio as gr
12
  import google.generativeai as genai
13
- import pypdf # <--- BIBLIOTECA NECESSÁRIA AGORA
14
 
15
  # ==================== 1. CONFIGURAÇÃO ====================
16
  api_key = os.getenv("GOOGLE_API_KEY", "SUA_API_KEY_AQUI")
@@ -19,36 +19,32 @@ if api_key: genai.configure(api_key=api_key)
19
  model_flash = genai.GenerativeModel("gemini-flash-latest")
20
  model_pro = genai.GenerativeModel("gemini-pro-latest")
21
 
22
- # Usando o nome do protocolo de análise epistêmica
23
- ARQUIVO_CONFIG = "protocolo_epistemico_forense.json"
24
 
25
  # ==================== 2. UTILIDADES ====================
26
 
27
  def carregar_protocolo():
28
  try:
 
29
  with open(ARQUIVO_CONFIG, "r", encoding="utf-8") as f: return f.read()
30
  except: return "[]"
31
 
32
  def salvar_protocolo(conteudo):
33
  try:
34
  json.loads(conteudo)
35
- # Assumindo que o Gradio salva como 'protocolo.json'
36
  with open("protocolo.json", "w", encoding="utf-8") as f: f.write(conteudo)
37
  return "✅ Salvo"
38
  except: return "❌ Erro JSON"
39
 
40
  # **FUNÇÃO REAL: Lógica de Fragmentação de PDF com pypdf**
41
  def ler_anexo_e_fragmentar(arquivo, paginas_por_fragmento=5):
42
- """Lê o anexo. Se for PDF, usa pypdf para dividir em fragmentos de X páginas.
43
- Se for TXT/Outro, retorna o texto completo em uma lista de 1 fragmento."""
44
  if arquivo is None: return [], ""
45
  filename = arquivo.name
46
- anexo_info = f"\n\n[ANEXO SISTEMA: {os.path.basename(filename)}]\n"
47
 
48
- # Lógica REAL para PDF
49
  if filename.lower().endswith(".pdf"):
50
- print(f"DEBUG: Arquivo PDF detectado. Iniciando fragmentação de {paginas_por_fragmento} páginas.")
51
-
52
  fragments = []
53
  try:
54
  reader = pypdf.PdfReader(filename)
@@ -60,19 +56,20 @@ def ler_anexo_e_fragmentar(arquivo, paginas_por_fragmento=5):
60
  end_page = min(i + paginas_por_fragmento, total_pages)
61
 
62
  for j in range(start_page, end_page):
63
- # pypdf usa índice 0, então página N é reader.pages[N-1]
64
  try:
65
- fragment_text.append(reader.pages[j].extract_text())
 
 
66
  except Exception as e:
67
- fragment_text.append(f"[ERRO DE EXTRAÇÃO NA PÁGINA {j+1}: {e}]")
68
 
69
- fragment_header = f"FRAGMENTO (Pgs {start_page+1}-{end_page} / Total {total_pages}):\n"
 
70
  fragments.append(fragment_header + "\n".join(fragment_text))
71
 
72
  return fragments, anexo_info
73
 
74
  except Exception as e:
75
- # Em caso de erro na leitura/extração do PDF
76
  return [f"ERRO CRÍTICO NA LEITURA DE PDF: {e}"], anexo_info
77
 
78
  # Para arquivos não PDF, lê o conteúdo como um único fragmento.
@@ -84,13 +81,15 @@ def ler_anexo_e_fragmentar(arquivo, paginas_por_fragmento=5):
84
 
85
  # ==================== 3. ENGINE DE EXECUÇÃO ====================
86
 
87
- # Funções inalteradas, pois a lógica de loop foi centralizada no ORQUESTRADOR
88
  def executar_no(timeline, config, fragmento_input=None):
89
  modelo = model_pro if config.get("modelo") == "pro" else model_flash
90
 
91
  if fragmento_input is not None:
 
92
  input_para_prompt = fragmento_input
93
  else:
 
94
  contexto = json.dumps(timeline, ensure_ascii=False, indent=2)
95
  input_para_prompt = contexto
96
 
@@ -103,13 +102,15 @@ def executar_no(timeline, config, fragmento_input=None):
103
  out = resp.text
104
  tempo = time.time() - inicio
105
 
106
- content = json.loads(out.strip().replace('```json','').replace('```','')) if config['tipo_saida']=='json' and fragmento_input is None else out
 
107
  log += f" (OK - {tempo:.2f}s)"
108
  return {"role": "assistant", "agent": config['nome'], "content": content}, log, out
109
  except Exception as e:
110
  return {"role": "system", "error": str(e)}, f" (ERRO: {e})", str(e)
111
 
112
- # ==================== 4. ORQUESTRADOR (LÓGICA DO LOOP/FRAGMENTAÇÃO) ====================
 
113
 
114
  def orquestrador(texto, arquivo, history, json_config):
115
  # 1. Input Check e Fragmentação
@@ -127,33 +128,56 @@ def orquestrador(texto, arquivo, history, json_config):
127
  yield history, {}, "Erro JSON"
128
  return
129
 
130
- # A TIMELINE começa com o input do usuário (a pergunta/instrução)
131
  timeline = [{"role": "user", "content": texto}]
132
  logs = f"🚀 START: {datetime.now().strftime('%H:%M:%S')}\n"
133
  history[-1][1] = "⏳ Iniciando análise..."
134
  yield history, timeline, logs
135
 
136
- # 3. Lógica de Pré-processamento/Conciliação de Fragmentos
 
137
 
138
- if len(fragmentos) > 0:
139
- concatenated_input = anexo_info + "\n\n" + "\n\n".join(fragmentos)
140
- full_input_to_pass = f"{texto}\n{concatenated_input}".strip()
141
 
142
- # O full_input_to_pass torna-se o 'content' do primeiro item da timeline
143
- timeline[0]['content'] = full_input_to_pass
144
- history[-1][1] = "✅ Pré-processamento: Anexo lido e concatenado. Iniciando FASE 0..."
145
- yield history, timeline, logs
146
 
147
- else:
148
- # Se não anexo ou a pergunta
149
- pass
 
 
 
 
 
 
 
 
 
 
 
 
 
150
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
151
 
152
- # 4. PASSOS SEGUINTES: EXECUÇÃO SEQUENCIAL (Protocolo Epistêmico)
153
  final_response = ""
154
- for cfg in protocolo:
155
 
156
- history[-1][1] = f"⚙️ FASE {cfg.get('fase', '?')}: {cfg['nome']} trabalhando..."
157
  yield history, timeline, logs
158
 
159
  res, log_add, raw = executar_no(timeline, cfg)
@@ -166,10 +190,11 @@ def orquestrador(texto, arquivo, history, json_config):
166
 
167
  yield history, timeline, logs
168
 
169
- logs += "✅ FIM. (Análise Epistêmica Concluída)"
170
  yield history, timeline, logs
171
 
172
- # ==================== 5. UI LIMPA (v27) ====================
 
173
 
174
  def ui_clean():
175
  css = """
@@ -179,7 +204,7 @@ def ui_clean():
179
 
180
  config_init = carregar_protocolo()
181
 
182
- with gr.Blocks(title="Protocolo Epistêmico Forense", css=css, theme=gr.themes.Soft()) as app:
183
 
184
  with gr.Tabs():
185
 
 
1
  # ╔════════════════════════════════════════════════════════════════════════════╗
2
+ # ║ PIPELINE v31: FRAGMENTAÇÃO, TRANSCRIÇÃO (LOOP) E CATALOGAÇÃO
3
  # ║ Layout: Chat (Aba 1) | Debug (Aba 2) | Config (Aba 3) ║
4
  # ╚════════════════════════════════════════════════════════════════════════════╝
5
 
 
10
  from datetime import datetime
11
  import gradio as gr
12
  import google.generativeai as genai
13
+ import pypdf # Usando pypdf para a lógica de fragmentação real.
14
 
15
  # ==================== 1. CONFIGURAÇÃO ====================
16
  api_key = os.getenv("GOOGLE_API_KEY", "SUA_API_KEY_AQUI")
 
19
  model_flash = genai.GenerativeModel("gemini-flash-latest")
20
  model_pro = genai.GenerativeModel("gemini-pro-latest")
21
 
22
+ # **ATUALIZAÇÃO: Novo protocolo de fragmentação/catalogação**
23
+ ARQUIVO_CONFIG = "protocolo_fragmentacao_transcricao.json"
24
 
25
  # ==================== 2. UTILIDADES ====================
26
 
27
  def carregar_protocolo():
28
  try:
29
+ # Tenta carregar o protocolo que está sendo usado
30
  with open(ARQUIVO_CONFIG, "r", encoding="utf-8") as f: return f.read()
31
  except: return "[]"
32
 
33
  def salvar_protocolo(conteudo):
34
  try:
35
  json.loads(conteudo)
 
36
  with open("protocolo.json", "w", encoding="utf-8") as f: f.write(conteudo)
37
  return "✅ Salvo"
38
  except: return "❌ Erro JSON"
39
 
40
  # **FUNÇÃO REAL: Lógica de Fragmentação de PDF com pypdf**
41
  def ler_anexo_e_fragmentar(arquivo, paginas_por_fragmento=5):
42
+ """Lê o anexo. Se for PDF, usa pypdf para dividir em fragmentos de X páginas."""
 
43
  if arquivo is None: return [], ""
44
  filename = arquivo.name
45
+ anexo_info = f"[ANEXO SISTEMA: {os.path.basename(filename)}]"
46
 
 
47
  if filename.lower().endswith(".pdf"):
 
 
48
  fragments = []
49
  try:
50
  reader = pypdf.PdfReader(filename)
 
56
  end_page = min(i + paginas_por_fragmento, total_pages)
57
 
58
  for j in range(start_page, end_page):
 
59
  try:
60
+ # Extrai o texto; se vazio (OCR ou complexo), o LLM tentará limpá-lo
61
+ text = reader.pages[j].extract_text() or f"[PAG {j+1}: EXTRAÇÃO VAZIA - OCR NECESSÁRIO]"
62
+ fragment_text.append(text)
63
  except Exception as e:
64
+ fragment_text.append(f"[PAG {j+1}: ERRO DE EXTRAÇÃO/ENCODING - {e}]")
65
 
66
+ # Monta o input para o LLM: cabeçalho + texto extraído (mesmo que vazio)
67
+ fragment_header = f"Fragmento {i//paginas_por_fragmento + 1} (Pgs {start_page+1}-{end_page} / Total {total_pages}):\n"
68
  fragments.append(fragment_header + "\n".join(fragment_text))
69
 
70
  return fragments, anexo_info
71
 
72
  except Exception as e:
 
73
  return [f"ERRO CRÍTICO NA LEITURA DE PDF: {e}"], anexo_info
74
 
75
  # Para arquivos não PDF, lê o conteúdo como um único fragmento.
 
81
 
82
  # ==================== 3. ENGINE DE EXECUÇÃO ====================
83
 
84
+ # Modificada para aceitar um fragmento de texto como input (apenas para o Agente de Loop)
85
  def executar_no(timeline, config, fragmento_input=None):
86
  modelo = model_pro if config.get("modelo") == "pro" else model_flash
87
 
88
  if fragmento_input is not None:
89
+ # Se for o Agente de Loop (Passo 0), o prompt é apenas a missão + o fragmento
90
  input_para_prompt = fragmento_input
91
  else:
92
+ # Para os agentes sequenciais, o prompt é a timeline completa (que inclui as transcrições)
93
  contexto = json.dumps(timeline, ensure_ascii=False, indent=2)
94
  input_para_prompt = contexto
95
 
 
102
  out = resp.text
103
  tempo = time.time() - inicio
104
 
105
+ # O Agente de Transcrição retorna 'texto', os outros retornam 'json'
106
+ content = json.loads(out.strip().replace('```json','').replace('```','')) if config['tipo_saida']=='json' else out
107
  log += f" (OK - {tempo:.2f}s)"
108
  return {"role": "assistant", "agent": config['nome'], "content": content}, log, out
109
  except Exception as e:
110
  return {"role": "system", "error": str(e)}, f" (ERRO: {e})", str(e)
111
 
112
+
113
+ # ==================== 4. ORQUESTRADOR (LÓGICA DO LOOP) ====================
114
 
115
  def orquestrador(texto, arquivo, history, json_config):
116
  # 1. Input Check e Fragmentação
 
128
  yield history, {}, "Erro JSON"
129
  return
130
 
131
+ # A TIMELINE começa com o input do usuário (pergunta/instrução)
132
  timeline = [{"role": "user", "content": texto}]
133
  logs = f"🚀 START: {datetime.now().strftime('%H:%M:%S')}\n"
134
  history[-1][1] = "⏳ Iniciando análise..."
135
  yield history, timeline, logs
136
 
137
+ # --- NOVO: Lógica de Loop/Transcrição (PASSO 0) ---
138
+ concatenated_transcription = anexo_info + "\n\n"
139
 
140
+ if protocolo and protocolo[0]['nome'] == 'TRANSCRITOR_FRAGMENTO (PASSO 0 - LOOP)' and len(fragmentos) > 0:
 
 
141
 
142
+ cfg_transcricao = protocolo.pop(0) # Remove o Agente de Loop da lista principal
 
 
 
143
 
144
+ for i, fragmento in enumerate(fragmentos):
145
+ history[-1][1] = f"⚙️ {cfg_transcricao['nome']} trabalhando no fragmento {i+1}/{len(fragmentos)} (5 Pgs)..."
146
+ yield history, timeline, logs
147
+
148
+ # Executa o LLM no fragmento ATUAL, não na timeline
149
+ # Isso força o Gemini a focar APENAS nas 5 páginas para transcrever/limpar
150
+ res, log_add, raw = executar_no(timeline, cfg_transcricao, fragmento_input=fragmento)
151
+ logs += log_add + "\n"
152
+
153
+ if 'error' in res:
154
+ timeline.append(res)
155
+ yield history, timeline, logs
156
+ return
157
+
158
+ # Concatenamos o texto limpo retornado pelo LLM
159
+ concatenated_transcription += res['content'] + "\n"
160
 
161
+ logs += "\n✅ TRANSCRIÇÃO FRAGMENTADA E CONCATENADA CONCLUÍDA.\n"
162
+
163
+ # Adiciona o resultado da transcrição concatenada à timeline para os próximos agentes
164
+ timeline.append({
165
+ "role": "system",
166
+ "agent": "TEXTO_DOCUMENTO_COMPLETO",
167
+ "content": concatenated_transcription
168
+ })
169
+ history[-1][1] = "✅ Transcrição completa. Iniciando Catalogação (Passo 1)..."
170
+ yield history, timeline, logs
171
+
172
+ elif len(fragmentos) > 0:
173
+ # Se não há Passo 0 (Transcrição), injeta o texto extraído cru como um bloco
174
+ timeline.append({"role": "system", "agent": "TEXTO_DOCUMENTO_COMPLETO", "content": concatenated_transcription + "\n".join(fragmentos)})
175
 
176
+ # 5. PASSOS SEGUINTES: EXECUÇÃO SEQUENCIAL (Catalogação)
177
  final_response = ""
178
+ for cfg in protocolo: # Iteramos sobre a lista (agora começa do BIBLIOTECARIO_CATALOGADOR)
179
 
180
+ history[-1][1] = f"⚙️ {cfg['nome']} trabalhando..."
181
  yield history, timeline, logs
182
 
183
  res, log_add, raw = executar_no(timeline, cfg)
 
190
 
191
  yield history, timeline, logs
192
 
193
+ logs += "✅ FIM."
194
  yield history, timeline, logs
195
 
196
+ # ==================== 5. UI LIMPA (v31) ====================
197
+ # (A UI permanece a mesma)
198
 
199
  def ui_clean():
200
  css = """
 
204
 
205
  config_init = carregar_protocolo()
206
 
207
+ with gr.Blocks(title="Protocolo Fragmentação/Transcrição", css=css, theme=gr.themes.Soft()) as app:
208
 
209
  with gr.Tabs():
210