# app.py -- import sys import os import subprocess import importlib.util ''' print("--- INÍCIO DO DEBUG ---", flush=True) # 1. Onde o Python está procurando por pacotes? print("\n[1] Sys Path (Caminhos de Busca):", flush=True) for path in sys.path: print(f" - {path}", flush=True) # 2. Quais pacotes estão realmente instalados (usando pip)? print("\n[2] Pacotes Instalados (pip list):", flush=True) try: result = subprocess.run( [sys.executable, '-m', 'pip', 'list'], capture_output=True, text=True, check=True ) print(result.stdout, flush=True) except Exception as e: print(f" ERRO ao rodar 'pip list': {e}", flush=True) # 3. Vamos verificar especificamente o langchain-openai print("\n[3] Verificando 'langchain_openai':", flush=True) try: spec = importlib.util.find_spec('langchain_openai') if spec: print(f" Pacote encontrado: {spec.name}", flush=True) print(f" Localização: {spec.origin}", flush=True) if spec.submodule_search_locations: package_dir = spec.submodule_search_locations[0] print(f" Pasta do pacote: {package_dir}", flush=True) print(" Conteúdo da pasta do pacote:", flush=True) try: for item in os.listdir(package_dir): print(f" - {item}", flush=True) except Exception as e: print(f" ERRO ao listar diretório: {e}", flush=True) else: print(" PACOTE 'langchain_openai' NÃO FOI ENCONTRADO PELO IMPORTLIB!", flush=True) except Exception as e: print(f" ERRO ao tentar encontrar 'langchain-openai': {e}", flush=True) print("\n--- FIM DO DEBUG ---", flush=True) ''' from flask import Flask, render_template, request, Response, jsonify import json import time import uuid import threading import concurrent.futures from html import escape, unescape import re from markdown_it import MarkdownIt from markdown2 import markdown as markdown2_render # Força o flush dos prints para aparecer nos logs do container sys.stdout.reconfigure(line_buffering=True) sys.stderr.reconfigure(line_buffering=True) # Importações do LangChain from langchain.prompts import PromptTemplate from langchain_core.output_parsers import StrOutputParser # Importa os LLMs from llms import claude_llm, gemini_llm, openai_llm#, grok_llm # Importa os prompts from config import * # Importa nosso processador RAG from rag_processor import get_relevant_context app = Flask(__name__) # Garante que o diretório de uploads exista if not os.path.exists('uploads'): os.makedirs('uploads') app.config['MAX_CONTENT_LENGTH'] = 100 * 1024 * 1024 # Instancia o conversor de Markdown md = MarkdownIt() # Variável global para controle de interrupção processing_cancelled = False def log_print(message): """Função para garantir que os logs apareçam no container""" print(f"[DEBUG] {message}", flush=True) sys.stdout.flush() def safe_json_dumps(data): """Função para criar JSON de forma segura, com tratamento de strings muito grandes""" try: json_str = json.dumps(data, ensure_ascii=False) # Se o JSON for muito grande (> 50MB), trunca o conteúdo if len(json_str.encode('utf-8')) > 50 * 1024 * 1024: log_print(f"JSON muito grande ({len(json_str)} chars), truncando...") if 'content' in str(data): # Cria uma versão truncada truncated_data = data.copy() if isinstance(data, dict) else {} if 'final_result' in truncated_data and 'content' in truncated_data['final_result']: original_content = truncated_data['final_result']['content'] truncated_content = original_content[:10000] + "\n\n[CONTEÚDO TRUNCADO DEVIDO AO TAMANHO - Use o botão 'Copiar' para obter o texto completo]" truncated_data['final_result']['content'] = truncated_content elif 'partial_result' in truncated_data and 'content' in truncated_data['partial_result']: original_content = truncated_data['partial_result']['content'] truncated_content = original_content[:10000] + "\n\n[CONTEÚDO TRUNCADO DEVIDO AO TAMANHO - Use o botão 'Copiar' para obter o texto completo]" truncated_data['partial_result']['content'] = truncated_content return json.dumps(truncated_data, ensure_ascii=False) return json_str except Exception as e: log_print(f"Erro ao criar JSON: {e}") return json.dumps({'error': f'Erro na serialização JSON: {str(e)}'}) # Variável global para armazenar o conteúdo completo do merge merge_full_content = "" # Função para renderização com fallback: tenta MarkdownIt, depois markdown2 def render_markdown_cascata(texto: str) -> str: try: html_1 = md.render(texto) if not is_html_empty(html_1): return html_1 except Exception as e: log_print(f"MarkdownIt falhou: {e}") try: html_2 = markdown2_render(texto, extras=["fenced-code-blocks", "tables"]) if not is_html_empty(html_2): return html_2 except Exception as e: log_print(f"markdown2 falhou: {e}") return f"
{escape(texto)}" def is_html_empty(html: str) -> bool: """ Verifica de forma robusta se uma string HTML não contém texto visível, lidando com entidades HTML e múltiplos tipos de espaços em branco. """ if not html: return True # 1. Remove todas as tags HTML text_only = re.sub('<[^<]+?>', '', html) # 2. Decodifica entidades HTML (ex: para ' ') decoded_text = unescape(text_only) # 3. Substitui qualquer sequência de caracteres de espaço em branco por um único espaço normalized_space = re.sub(r'\s+', ' ', decoded_text) # 4. Verifica se o texto restante (após remover espaços nas pontas) está de fato vazio return not normalized_space.strip() @app.route('/') def index(): """Renderiza a página inicial da aplicação.""" return render_template('index.html') # ROTA ATUALIZADA: Para converter texto em Markdown sob demanda @app.route('/convert', methods=['POST']) def convert(): data = request.get_json() if not data or 'text' not in data: return jsonify({'error': 'Nenhum texto fornecido'}), 400 text_to_convert = data['text'] # USA A FUNÇÃO DE CASCATA PARA MAIOR ROBUSTEZ converted_html = render_markdown_cascata(text_to_convert) return jsonify({'html': converted_html}) # NOVA ROTA: Para cancelar processamento @app.route('/cancel', methods=['POST']) def cancel(): global processing_cancelled processing_cancelled = True log_print("=== PROCESSAMENTO CANCELADO PELO USUÁRIO ===") return jsonify({'status': 'cancelled'}) # NOVA ROTA: Para obter o conteúdo completo do merge @app.route('/get-full-content', methods=['POST']) def get_full_content(): global merge_full_content data = request.get_json() content_type = data.get('type', 'merge') if content_type == 'merge' and merge_full_content: return jsonify({'content': merge_full_content}) else: return jsonify({'error': 'Conteúdo não encontrado'}), 404 @app.route('/process', methods=['POST']) def process(): """Processa a solicitação do usuário nos modos Hierárquico ou Atômico.""" global processing_cancelled processing_cancelled = False # Reset do flag de cancelamento log_print("=== ROTA PROCESS ACESSADA ===") form_data = request.form files = request.files.getlist('files') contexto = form_data.get('contexto', '').strip() mode = form_data.get('mode', 'real') processing_mode = form_data.get('processing_mode', 'hierarchical') # NOVOS PARÂMETROS: Tamanho do texto min_chars = int(form_data.get('min_chars', 24000)) max_chars = int(form_data.get('max_chars', 30000)) log_print(f"Mode: {mode}, Processing: {processing_mode}") log_print(f"Tamanho solicitado: {min_chars} - {max_chars} caracteres") temp_file_paths = [] if mode == 'real': for file in files: if file and file.filename: unique_filename = str(uuid.uuid4()) + "_" + os.path.basename(file.filename) file_path = os.path.join('uploads', unique_filename) file.save(file_path) temp_file_paths.append(file_path) def generate_stream(current_mode, form_data, file_paths): """Gera a resposta em streaming para o front-end.""" global processing_cancelled log_print(f"=== GENERATE_STREAM INICIADO - Mode: {current_mode} ===") solicitacao_usuario = form_data.get('solicitacao', '') contexto = form_data.get('contexto', '') if current_mode == 'test': log_print("=== MODO TESTE EXECUTADO ===") mock_text = form_data.get('mock_text', 'Este é um **texto** de `simulação`.') # json_data = safe_json_dumps({'progress': 100, 'message': 'Simulação concluída!', 'partial_result': {'id': 'grok-output', 'content': mock_text}, 'done': True, 'mode': 'atomic' if processing_mode == 'atomic' else 'hierarchical'}) json_data = safe_json_dumps({'progress': 100, 'message': 'Simulação concluída!', 'partial_result': {'id': 'openai-output', 'content': mock_text}, 'done': True, 'mode': 'atomic' if processing_mode == 'atomic' else 'hierarchical'}) yield f"data: {json_data}\n\n" if processing_mode == 'atomic': json_data = safe_json_dumps({'partial_result': {'id': 'sonnet-output', 'content': mock_text}}) yield f"data: {json_data}\n\n" json_data = safe_json_dumps({'partial_result': {'id': 'gemini-output', 'content': mock_text}}) yield f"data: {json_data}\n\n" else: if not solicitacao_usuario: log_print("=== ERRO: SOLICITAÇÃO VAZIA ===") json_data = safe_json_dumps({'error': 'Solicitação não fornecida.'}) yield f"data: {json_data}\n\n" return try: log_print("=== INICIANDO PROCESSAMENTO REAL ===") json_data = safe_json_dumps({'progress': 0, 'message': 'Processando arquivos e extraindo contexto...'}) yield f"data: {json_data}\n\n" rag_context = get_relevant_context(file_paths, solicitacao_usuario) log_print(f"=== RAG CONTEXT OBTIDO: {len(rag_context)} chars ===") output_parser = StrOutputParser() if processing_mode == 'atomic': log_print("=== MODO ATÔMICO SELECIONADO ===") # --- LÓGICA ATÔMICA (PARALELA) --- results = {} threads = [] def run_chain_with_timeout(chain, inputs, key, timeout=300): def task(): if processing_cancelled: return "CANCELLED" return chain.invoke(inputs) with concurrent.futures.ThreadPoolExecutor() as executor: future = executor.submit(task) try: result = future.result(timeout=timeout) if result == "CANCELLED": results[key] = "CANCELLED" elif not result or not result.strip(): results[key] = "Error:EmptyResponse" else: results[key] = result except concurrent.futures.TimeoutError: results[key] = f"Erro ao processar {key.upper()}: Tempo limite excedido." except Exception as e: results[key] = f"Erro ao processar {key.upper()}: {e}" claude_atomic_llm = claude_llm.bind(max_tokens=60000) #models = {'grok': grok_llm, 'sonnet': claude_atomic_llm, 'gemini': gemini_llm, 'openai': openai_llm} # Melhoria 05/08/2025 - Multi-modelo models = {} if form_data.get('modelo-openai') == 'on': models['openai'] = openai_llm if form_data.get('modelo-sonnet') == 'on': models['sonnet'] = claude_atomic_llm if form_data.get('modelo-gemini') == 'on': models['gemini'] = gemini_llm # Verificação se pelo menos um modelo foi selecionado if not models: json_data = safe_json_dumps({'error': 'Você deve selecionar pelo menos um modelo para processamento.'}) yield f"data: {json_data}\n\n" return # Substituir os placeholders no template updated_prompt_template = PROMPT_ATOMICO_INICIAL.replace( "MIN_CHARS_PLACEHOLDER", str(min_chars) ).replace( "MAX_CHARS_PLACEHOLDER", str(max_chars) ) # --- renderiza e loga o prompt final Atomico --- # ——— log do prompt atômico já formatado ——— log_print(f"[DEBUG] PROMPT ATÔMICO RENDERED:\n" f"{updated_prompt_template.format(contexto=contexto, solicitacao_usuario=solicitacao_usuario, rag_context=rag_context)}\n""-"*80) prompt = PromptTemplate(template=updated_prompt_template, input_variables=["contexto", "solicitacao_usuario", "rag_context"]) json_data = safe_json_dumps({'progress': 15, 'message': 'Iniciando processamento paralelo...'}) yield f"data: {json_data}\n\n" for name, llm in models.items(): if processing_cancelled: json_data = safe_json_dumps({'error': 'Processamento cancelado pelo usuário.'}) yield f"data: {json_data}\n\n" return chain = prompt | llm | output_parser thread = threading.Thread(target=run_chain_with_timeout, args=(chain, {"contexto": contexto, "solicitacao_usuario": solicitacao_usuario, "rag_context": rag_context}, name)) threads.append(thread) thread.start() for thread in threads: thread.join() # Verificar se foi cancelado if processing_cancelled or any(result == "CANCELLED" for result in results.values()): json_data = safe_json_dumps({'error': 'Processamento cancelado pelo usuário.'}) yield f"data: {json_data}\n\n" return for key, result in results.items(): if result == "Error:EmptyResponse" or "Erro ao processar" in result: error_msg = result if "Erro ao processar" in result else f"Falha no serviço {key.upper()}: Sem resposta." json_data = safe_json_dumps({'error': error_msg}) yield f"data: {json_data}\n\n" return json_data = safe_json_dumps({'progress': 80, 'message': 'Todos os modelos responderam. Formatando saídas...'}) yield f"data: {json_data}\n\n" # Envia o texto bruto para cada modelo ##grok_text = results.get('grok', '') ##log_print(f"--- Resposta Bruta do GROK (Atômico) ---\n{grok_text[:200]}...\n--------------------------------------") ##json_data = safe_json_dumps({'partial_result': {'id': 'grok-output', 'content': grok_text}}) ##yield f"data: {json_data}\n\n" openai_text = results.get('openai', '') log_print(f"--- Resposta Bruta do OPEN AI (Atômico) ---\n{openai_text[:200]}...\n--------------------------------------") json_data = safe_json_dumps({'partial_result': {'id': 'openai-output', 'content': openai_text}}) yield f"data: {json_data}\n\n" sonnet_text = results.get('sonnet', '') log_print(f"--- Resposta Bruta do Sonnet (Atômico) ---\n{sonnet_text[:200]}...\n----------------------------------------") json_data = safe_json_dumps({'partial_result': {'id': 'sonnet-output', 'content': sonnet_text}}) yield f"data: {json_data}\n\n" gemini_text = results.get('gemini', '') log_print(f"--- Resposta Bruta do Gemini (Atômico) ---\n{gemini_text[:200]}...\n----------------------------------------") json_data = safe_json_dumps({'partial_result': {'id': 'gemini-output', 'content': gemini_text}}) yield f"data: {json_data}\n\n" json_data = safe_json_dumps({'progress': 100, 'message': 'Processamento Atômico concluído!', 'done': True, 'mode': 'atomic'}) yield f"data: {json_data}\n\n" else: log_print("=== MODO HIERÁRQUICO SELECIONADO ===") # --- LÓGICA HIERÁRQUICA (SEQUENCIAL) --- # Atualizar prompts hierárquicos com parâmetros de tamanho ##updated_grok_template = PROMPT_HIERARQUICO_GROK.replace( updated_openai_template = PROMPT_HIERARQUICO_OPENAI.replace( "MIN_CHARS_PLACEHOLDER", str(min_chars) ).replace( "MAX_CHARS_PLACEHOLDER", str(max_chars) ) # --- renderiza e loga o prompt final Hierárquico Grok --- ##log_print(f"[DEBUG] PROMPT HIERÁRQUICO GROK RENDERED:\n" ## f"{updated_grok_template.format(contexto=contexto, solicitacao_usuario=solicitacao_usuario, rag_context=rag_context)}\n""-"*80) # --- renderiza e loga o prompt final Hierárquico OpenAI --- log_print(f"[DEBUG] PROMPT HIERÁRQUICO OPEN AI RENDERED:\n" f"{updated_openai_template.format(contexto=contexto, solicitacao_usuario=solicitacao_usuario, rag_context=rag_context)}\n""-"*80) updated_sonnet_template = PROMPT_HIERARQUICO_SONNET.replace( "MIN_CHARS_PLACEHOLDER", str(min_chars) ).replace( "MAX_CHARS_PLACEHOLDER", str(max_chars) ) # --- renderiza e loga o prompt final Hierárquico Sonnet --- ##log_print(f"[DEBUG] PROMPT HIERÁRQUICO SONNET RENDERED:\n" ## f"{updated_sonnet_template.format(contexto=contexto, solicitacao_usuario=solicitacao_usuario, texto_para_analise=resposta_grok)}\n""-"*80) log_print(f"[DEBUG] PROMPT HIERÁRQUICO SONNET RENDERED:\n" f"{updated_sonnet_template.format(contexto=contexto, solicitacao_usuario=solicitacao_usuario, texto_para_analise=resposta_openai)}\n""-"*80) updated_gemini_template = PROMPT_HIERARQUICO_GEMINI.replace( "MIN_CHARS_PLACEHOLDER", str(min_chars) ).replace( "MAX_CHARS_PLACEHOLDER", str(max_chars) ) # --- renderiza e loga o prompt final Hierárquico Gemini --- log_print(f"[DEBUG] PROMPT HIERÁRQUICO GEMINI RENDERED:\n" f"{updated_gemini_template.format(contexto=contexto, solicitacao_usuario=solicitacao_usuario, texto_para_analise=resposta_sonnet)}\n""-"*80) ##json_data = safe_json_dumps({'progress': 15, 'message': 'O GROK está processando sua solicitação...'}) json_data = safe_json_dumps({'progress': 15, 'message': 'A OPEN AI está processando sua solicitação...'}) yield f"data: {json_data}\n\n" if processing_cancelled: json_data = safe_json_dumps({'error': 'Processamento cancelado pelo usuário.'}) yield f"data: {json_data}\n\n" return #log_print("=== PROCESSANDO GROK ===") #prompt_grok = PromptTemplate(template=updated_grok_template, input_variables=["contexto", "solicitacao_usuario", "rag_context"]) #chain_grok = prompt_grok | grok_llm | output_parser #resposta_grok = chain_grok.invoke({"contexto": contexto, "solicitacao_usuario": solicitacao_usuario, "rag_context": rag_context}) #log_print(f"=== GROK TERMINOU: {len(resposta_grok)} chars ===") log_print("=== PROCESSANDO OPEN AI ===") openai_with_max_tokens = openai_llm.bind(max_completion_tokens=100000) prompt_openai = PromptTemplate(template=updated_openai_template, input_variables=["contexto", "solicitacao_usuario", "rag_context"]) chain_openai = prompt_openai | openai_with_max_tokens | output_parser resposta_openai = chain_openai.invoke({"contexto": contexto, "solicitacao_usuario": solicitacao_usuario, "rag_context": rag_context}) log_print(f"=== OPEN AI TERMINOU: {len(resposta_openai)} chars ===") if processing_cancelled: json_data = safe_json_dumps({'error': 'Processamento cancelado pelo usuário.'}) yield f"data: {json_data}\n\n" return ##if not resposta_grok or not resposta_grok.strip(): ## log_print("=== ERRO: GROK VAZIO ===") ## json_data = safe_json_dumps({'error': 'Falha no serviço GROK: Sem resposta.'}) ## yield f"data: {json_data}\n\n" ## return ## ##log_print("=== ENVIANDO RESPOSTA GROK PARA FRONTEND ===") ##json_data = safe_json_dumps({'progress': 33, 'message': 'Claude Sonnet está processando...', 'partial_result': {'id': 'grok-output', 'content': resposta_grok}}) ##yield f"data: {json_data}\n\n" if not resposta_openai or not resposta_openai.strip(): log_print("=== ERRO: OPEN AI VAZIO ===") json_data = safe_json_dumps({'error': 'Falha no serviço OPEN AI: Sem resposta.'}) yield f"data: {json_data}\n\n" return log_print("=== ENVIANDO RESPOSTA OPEN AI PARA FRONTEND ===") json_data = safe_json_dumps({'progress': 33, 'message': 'Claude Sonnet está processando...', 'partial_result': {'id': 'openai-output', 'content': resposta_openai}}) yield f"data: {json_data}\n\n" if processing_cancelled: json_data = safe_json_dumps({'error': 'Processamento cancelado pelo usuário.'}) yield f"data: {json_data}\n\n" return log_print("=== PROCESSANDO SONNET ===") prompt_sonnet = PromptTemplate(template=updated_sonnet_template, input_variables=["contexto", "solicitacao_usuario", "texto_para_analise"]) claude_with_max_tokens = claude_llm.bind(max_tokens=60000) chain_sonnet = prompt_sonnet | claude_with_max_tokens | output_parser resposta_sonnet = chain_sonnet.invoke({"contexto": contexto, "solicitacao_usuario": solicitacao_usuario, "texto_para_analise": resposta_openai}) log_print(f"=== SONNET TERMINOU: {len(resposta_sonnet)} chars ===") if processing_cancelled: json_data = safe_json_dumps({'error': 'Processamento cancelado pelo usuário.'}) yield f"data: {json_data}\n\n" return if not resposta_sonnet or not resposta_sonnet.strip(): log_print("=== ERRO: SONNET VAZIO ===") json_data = safe_json_dumps({'error': 'Falha no serviço Claude Sonnet: Sem resposta.'}) yield f"data: {json_data}\n\n" return log_print("=== ENVIANDO RESPOSTA SONNET ===") json_data = safe_json_dumps({'progress': 66, 'message': 'Gemini está processando...', 'partial_result': {'id': 'sonnet-output', 'content': resposta_sonnet}}) yield f"data: {json_data}\n\n" if processing_cancelled: json_data = safe_json_dumps({'error': 'Processamento cancelado pelo usuário.'}) yield f"data: {json_data}\n\n" return log_print("=== PROCESSANDO GEMINI ===") prompt_gemini = PromptTemplate(template=updated_gemini_template, input_variables=["contexto", "solicitacao_usuario", "texto_para_analise"]) chain_gemini = prompt_gemini | gemini_llm | output_parser resposta_gemini = chain_gemini.invoke({"contexto": contexto, "solicitacao_usuario": solicitacao_usuario, "texto_para_analise": resposta_sonnet}) log_print(f"=== GEMINI TERMINOU: {len(resposta_gemini)} chars ===") if processing_cancelled: json_data = safe_json_dumps({'error': 'Processamento cancelado pelo usuário.'}) yield f"data: {json_data}\n\n" return if not resposta_gemini or not resposta_gemini.strip(): log_print("=== ERRO: GEMINI VAZIO ===") json_data = safe_json_dumps({'error': 'Falha no serviço Gemini: Sem resposta.'}) yield f"data: {json_data}\n\n" return log_print("=== ENVIANDO RESPOSTA GEMINI ===") json_data = safe_json_dumps({'progress': 100, 'message': 'Processamento concluído!', 'partial_result': {'id': 'gemini-output', 'content': resposta_gemini}, 'done': True, 'mode': 'hierarchical'}) yield f"data: {json_data}\n\n" log_print("=== PROCESSAMENTO COMPLETO ===") except Exception as e: log_print(f"Ocorreu um erro durante o processamento: {e}") import traceback log_print(f"Traceback: {traceback.format_exc()}") json_data = safe_json_dumps({'error': f'Ocorreu um erro inesperado na aplicação: {e}'}) yield f"data: {json_data}\n\n" return Response(generate_stream(mode, form_data, temp_file_paths), mimetype='text/event-stream') @app.route('/merge', methods=['POST']) def merge(): """Recebe os textos do modo Atômico e os consolida usando Claude Sonnet.""" global merge_full_content, processing_cancelled processing_cancelled = False # Reset do flag data = request.get_json() # Extrair parâmetros que faltavam no scope do merge contexto = data.get('contexto', '') min_chars = int(data.get('min_chars', 24000)) max_chars = int(data.get('max_chars', 30000)) log_print("=== ROTA MERGE ACESSADA ===") log_print("=== USANDO CLAUDE SONNET PARA MERGE ===") def generate_merge_stream(): """Gera a resposta do merge em streaming.""" global merge_full_content, processing_cancelled try: log_print("=== INICIANDO MERGE STREAM ===") json_data = safe_json_dumps({'progress': 0, 'message': 'Iniciando o processo de merge...'}) yield f"data: {json_data}\n\n" if processing_cancelled: json_data = safe_json_dumps({'error': 'Processamento cancelado pelo usuário.'}) yield f"data: {json_data}\n\n" return # Monta lista só com os textos que vieram no payload available = [] for campo in ('openai_text', 'sonnet_text', 'gemini_text'): txt = data.get(campo) if txt and txt.strip(): available.append(txt) if len(available) < 2: json_data = safe_json_dumps({ 'error': 'Para processar o merge, deve haver pelo menos dois textos gerados.' }) yield f"data: {json_data}\n\n" return output_parser = StrOutputParser() # Atualizar o template de merge com os parâmetros de tamanho padrão updated_merge_template = PROMPT_ATOMICO_MERGE.replace( "MIN_CHARS_PLACEHOLDER", str(min_chars) ).replace( "MAX_CHARS_PLACEHOLDER", str(max_chars) ) # --- renderiza e loga o prompt final Atomico Merge -- ##log_print(f"[DEBUG] PROMPT MERGE RENDERED:\n" ## f"{updated_merge_template.format(contexto=contexto, solicitacao_usuario=data.get('solicitacao_usuario'), texto_para_analise_grok=data.get('grok_text'), texto_para_analise_sonnet=data.get('sonnet_text'), texto_para_analise_gemini=data.get('gemini_text'))}\n""-"*80) ## ##prompt_merge = PromptTemplate(template=updated_merge_template, input_variables=["contexto", "solicitacao_usuario", "texto_para_analise_grok", "texto_para_analise_sonnet", "texto_para_analise_gemini"]) log_print(f"[DEBUG] PROMPT MERGE RENDERED:\n" f"{updated_merge_template.format(contexto=contexto, solicitacao_usuario=data.get('solicitacao_usuario'), texto_para_analise_openai=data.get('openai_text'), texto_para_analise_sonnet=data.get('sonnet_text'), texto_para_analise_gemini=data.get('gemini_text'))}\n""-"*80) prompt_merge = PromptTemplate(template=updated_merge_template, input_variables=["contexto", "solicitacao_usuario", "texto_para_analise_openai", "texto_para_analise_sonnet", "texto_para_analise_gemini"]) # MUDANÇA: Usar Claude Sonnet para o merge claude_with_max_tokens = claude_llm.bind(max_tokens=64000) chain_merge = prompt_merge | claude_with_max_tokens | output_parser json_data = safe_json_dumps({'progress': 50, 'message': 'Enviando textos para o Claude Sonnet para consolidação...'}) yield f"data: {json_data}\n\n" log_print("=== INVOCANDO CLAUDE SONNET PARA MERGE ===") if processing_cancelled: json_data = safe_json_dumps({'error': 'Processamento cancelado pelo usuário.'}) yield f"data: {json_data}\n\n" return resposta_merge = chain_merge.invoke({ "contexto": data.get('contexto'), "solicitacao_usuario": data.get('solicitacao_usuario'), ##"texto_para_analise_grok": data.get('grok_text'), "texto_para_analise_openai": data.get('openai_text'), "texto_para_analise_sonnet": data.get('sonnet_text'), "texto_para_analise_gemini": data.get('gemini_text') }) log_print(f"=== MERGE CLAUDE SONNET CONCLUÍDO: {len(resposta_merge)} chars ===") if processing_cancelled: json_data = safe_json_dumps({'error': 'Processamento cancelado pelo usuário.'}) yield f"data: {json_data}\n\n" return if not resposta_merge or not resposta_merge.strip(): json_data = safe_json_dumps({'error': 'Falha no serviço de Merge (Claude Sonnet): Sem resposta.'}) yield f"data: {json_data}\n\n" return # Armazena o conteúdo completo na variável global merge_full_content = resposta_merge word_count = len(resposta_merge.split()) log_print(f"=== CRIANDO JSON DE RESPOSTA DO MERGE ===") # Usa a função safe para evitar problemas com JSON muito grande json_data = safe_json_dumps({ 'progress': 100, 'message': 'Merge concluído!', 'final_result': { 'content': resposta_merge, 'word_count': word_count }, 'done': True }) log_print(f"=== JSON CRIADO: {len(json_data)} chars ===") yield f"data: {json_data}\n\n" log_print("=== MERGE STREAM FINALIZADO ===") except Exception as e: log_print(f"Erro no processo de merge: {e}") import traceback log_print(f"Traceback: {traceback.format_exc()}") json_data = safe_json_dumps({'error': str(e)}) yield f"data: {json_data}\n\n" return Response(generate_merge_stream(), mimetype='text/event-stream') if __name__ == '__main__': log_print("=== SERVIDOR FLASK INICIADO ===") app.run(debug=True)