| | """ |
| | AgentGraph - Aplicação principal com interface Gradio e LangGraph |
| | """ |
| | import asyncio |
| | import logging |
| | import gradio as gr |
| | import tempfile |
| | import os |
| | from typing import List, Tuple, Optional, Dict |
| | from PIL import Image |
| |
|
| | from graphs.main_graph import initialize_graph, get_graph_manager |
| | from utils.config import ( |
| | AVAILABLE_MODELS, |
| | DEFAULT_MODEL, |
| | GRADIO_SHARE, |
| | GRADIO_PORT, |
| | validate_config, |
| | is_langsmith_enabled, |
| | LANGSMITH_PROJECT |
| | ) |
| | from utils.object_manager import get_object_manager |
| |
|
| | |
| | logging.basicConfig( |
| | level=logging.INFO, |
| | format='%(asctime)s - %(levelname)s - %(message)s' |
| | ) |
| |
|
| | |
| | graph_manager = None |
| | show_history_flag = False |
| |
|
| | async def initialize_app(): |
| | """Inicializa a aplicação""" |
| | global graph_manager |
| | |
| | try: |
| | |
| | validate_config() |
| | |
| | |
| | graph_manager = await initialize_graph() |
| |
|
| | |
| | if is_langsmith_enabled(): |
| | logging.info(f"✅ LangSmith habilitado - Projeto: '{LANGSMITH_PROJECT}'") |
| | logging.info("🔍 Traces serão enviados para LangSmith automaticamente") |
| | else: |
| | logging.info("ℹ️ LangSmith não configurado - Executando sem observabilidade") |
| |
|
| | logging.info("Aplicação inicializada com sucesso") |
| | return True |
| | |
| | except Exception as e: |
| | logging.error(f"Erro ao inicializar aplicação: {e}") |
| | return False |
| |
|
| | def run_async(coro): |
| | """Executa corrotina de forma síncrona""" |
| | try: |
| | loop = asyncio.get_event_loop() |
| | except RuntimeError: |
| | loop = asyncio.new_event_loop() |
| | asyncio.set_event_loop(loop) |
| | |
| | return loop.run_until_complete(coro) |
| |
|
| | def chatbot_response(user_input: str, selected_model: str, advanced_mode: bool = False) -> Tuple[str, Optional[str]]: |
| | """ |
| | Processa resposta do chatbot usando LangGraph |
| | |
| | Args: |
| | user_input: Entrada do usuário |
| | selected_model: Modelo LLM selecionado |
| | advanced_mode: Se deve usar refinamento avançado |
| | |
| | Returns: |
| | Tupla com (resposta_texto, caminho_imagem_grafico) |
| | """ |
| | global graph_manager |
| |
|
| | if not graph_manager: |
| | return "❌ Sistema não inicializado. Tente recarregar a página.", None |
| |
|
| | try: |
| | |
| | result = run_async(graph_manager.process_query( |
| | user_input=user_input, |
| | selected_model=selected_model, |
| | advanced_mode=advanced_mode |
| | )) |
| |
|
| | response_text = result.get("response", "Erro ao processar resposta") |
| | graph_image_path = None |
| |
|
| | |
| | if result.get("graph_generated", False) and result.get("graph_image_id"): |
| | graph_image_path = save_graph_image_to_temp(result["graph_image_id"]) |
| |
|
| | |
| | if graph_image_path: |
| | graph_type = result.get("graph_type", "gráfico") |
| | response_text += f"\n\n📊 **Gráfico gerado**: {graph_type.replace('_', ' ').title()}" |
| |
|
| | return response_text, graph_image_path |
| |
|
| | except Exception as e: |
| | error_msg = f"Erro no chatbot: {e}" |
| | logging.error(error_msg) |
| | logging.error(f"Detalhes do erro: {type(e).__name__}: {str(e)}") |
| | return error_msg, None |
| |
|
| | def save_graph_image_to_temp(graph_image_id: str) -> Optional[str]: |
| | """ |
| | Salva imagem do gráfico em arquivo temporário para exibição no Gradio |
| | |
| | Args: |
| | graph_image_id: ID da imagem no ObjectManager |
| | |
| | Returns: |
| | Caminho do arquivo temporário ou None se falhar |
| | """ |
| | try: |
| | obj_manager = get_object_manager() |
| | graph_image = obj_manager.get_object(graph_image_id) |
| |
|
| | if graph_image and isinstance(graph_image, Image.Image): |
| | |
| | temp_file = tempfile.NamedTemporaryFile(delete=False, suffix='.png') |
| | graph_image.save(temp_file.name, format='PNG') |
| | temp_file.close() |
| |
|
| | logging.info(f"[GRADIO] Gráfico salvo em: {temp_file.name}") |
| | return temp_file.name |
| |
|
| | except Exception as e: |
| | logging.error(f"[GRADIO] Erro ao salvar gráfico: {e}") |
| |
|
| | return None |
| |
|
| | def handle_csv_upload(file) -> str: |
| | """ |
| | Processa upload de arquivo CSV |
| | |
| | Args: |
| | file: Arquivo enviado pelo Gradio |
| | |
| | Returns: |
| | Mensagem de feedback |
| | """ |
| | global graph_manager |
| |
|
| | if not graph_manager: |
| | return "❌ Sistema não inicializado." |
| |
|
| | if not file: |
| | return "❌ Nenhum arquivo selecionado." |
| |
|
| | try: |
| | |
| | logging.info(f"[UPLOAD] Arquivo recebido: {file}") |
| | logging.info(f"[UPLOAD] Nome do arquivo: {file.name}") |
| | logging.info(f"[UPLOAD] Tipo do arquivo: {type(file)}") |
| |
|
| | |
| | import os |
| | if not os.path.exists(file.name): |
| | return f"❌ Arquivo não encontrado: {file.name}" |
| |
|
| | |
| | if not file.name.lower().endswith('.csv'): |
| | return "❌ Por favor, selecione um arquivo CSV válido." |
| |
|
| | |
| | file_size = os.path.getsize(file.name) |
| | file_size_mb = file_size / (1024 * 1024) |
| | file_size_gb = file_size / (1024 * 1024 * 1024) |
| |
|
| | if file_size_gb >= 1: |
| | size_str = f"{file_size_gb:.2f} GB" |
| | else: |
| | size_str = f"{file_size_mb:.2f} MB" |
| |
|
| | logging.info(f"[UPLOAD] Tamanho do arquivo: {file_size} bytes ({size_str})") |
| |
|
| | if file_size == 0: |
| | return "❌ O arquivo está vazio." |
| |
|
| | if file_size > 5 * 1024 * 1024 * 1024: |
| | return "❌ Arquivo muito grande. Máximo permitido: 5GB." |
| |
|
| | |
| | if file_size_mb > 100: |
| | logging.info(f"[UPLOAD] Arquivo grande detectado ({size_str}). Processamento pode demorar...") |
| | return f"⏳ Processando arquivo grande ({size_str}). Aguarde..." |
| |
|
| | |
| | logging.info(f"[UPLOAD] Iniciando processamento do arquivo: {file.name}") |
| | result = run_async(graph_manager.handle_csv_upload(file.name)) |
| |
|
| | logging.info(f"[UPLOAD] Resultado do processamento: {result}") |
| | return result.get("message", "Erro no upload") |
| |
|
| | except Exception as e: |
| | error_msg = f"❌ Erro ao processar upload: {e}" |
| | logging.error(error_msg) |
| | logging.error(f"[UPLOAD] Detalhes do erro: {type(e).__name__}: {str(e)}") |
| | import traceback |
| | logging.error(f"[UPLOAD] Traceback: {traceback.format_exc()}") |
| | return error_msg |
| |
|
| | def reset_system() -> str: |
| | """ |
| | Reseta o sistema ao estado inicial |
| | |
| | Returns: |
| | Mensagem de feedback |
| | """ |
| | global graph_manager |
| | |
| | if not graph_manager: |
| | return "❌ Sistema não inicializado." |
| | |
| | try: |
| | |
| | result = run_async(graph_manager.reset_system()) |
| | |
| | return result.get("message", "Erro no reset") |
| | |
| | except Exception as e: |
| | error_msg = f"❌ Erro ao resetar sistema: {e}" |
| | logging.error(error_msg) |
| | return error_msg |
| |
|
| | def toggle_advanced_mode(enabled: bool) -> str: |
| | """ |
| | Alterna modo avançado |
| | |
| | Args: |
| | enabled: Se deve habilitar modo avançado |
| | |
| | Returns: |
| | Mensagem de status |
| | """ |
| | global graph_manager |
| | |
| | if not graph_manager: |
| | return "❌ Sistema não inicializado." |
| | |
| | return graph_manager.toggle_advanced_mode(enabled) |
| |
|
| | def toggle_history(): |
| | """Alterna exibição do histórico""" |
| | global show_history_flag, graph_manager |
| | |
| | show_history_flag = not show_history_flag |
| | |
| | if show_history_flag and graph_manager: |
| | return graph_manager.get_history() |
| | else: |
| | return {} |
| |
|
| | def respond(message: str, chat_history: List[Dict[str, str]], selected_model: str, advanced_mode: bool): |
| | """ |
| | Função de resposta para o chatbot Gradio |
| | |
| | Args: |
| | message: Mensagem do usuário |
| | chat_history: Histórico do chat (formato messages) |
| | selected_model: Modelo selecionado |
| | advanced_mode: Modo avançado habilitado |
| | |
| | Returns: |
| | Tupla com (mensagem_vazia, histórico_atualizado, imagem_grafico) |
| | """ |
| | if not message.strip(): |
| | return "", chat_history, None |
| |
|
| | |
| | response, graph_image_path = chatbot_response(message, selected_model, advanced_mode) |
| |
|
| | |
| | chat_history.append({"role": "user", "content": message}) |
| | chat_history.append({"role": "assistant", "content": response}) |
| |
|
| | return "", chat_history, graph_image_path |
| |
|
| | def handle_csv_and_clear_chat(file): |
| | """ |
| | Processa CSV e limpa chat |
| | |
| | Args: |
| | file: Arquivo CSV |
| | |
| | Returns: |
| | Tupla com (feedback, chat_limpo, grafico_limpo) |
| | """ |
| | feedback = handle_csv_upload(file) |
| | return feedback, [], gr.update(visible=False) |
| |
|
| | def reset_all(): |
| | """ |
| | Reseta tudo e limpa interface |
| | |
| | Returns: |
| | Tupla com (feedback, chat_limpo, arquivo_limpo, grafico_limpo) |
| | """ |
| | feedback = reset_system() |
| | return feedback, [], None, gr.update(visible=False) |
| |
|
| | |
| | def create_interface(): |
| | """Cria interface Gradio""" |
| |
|
| | |
| | custom_css = """ |
| | .gradio-container { |
| | padding: 20px 30px !important; |
| | } |
| | """ |
| |
|
| | with gr.Blocks(theme=gr.themes.Soft(), css=custom_css) as demo: |
| |
|
| | with gr.Row(): |
| | with gr.Column(scale=1): |
| | gr.Markdown("## Configurações") |
| | model_selector = gr.Dropdown(list(AVAILABLE_MODELS.keys()), value=DEFAULT_MODEL, label="") |
| | csv_file = gr.File(file_types=[".csv"], label="") |
| | upload_feedback = gr.Markdown() |
| | advanced_checkbox = gr.Checkbox(label="Refinar Resposta") |
| |
|
| | |
| | if is_langsmith_enabled(): |
| | gr.Markdown(f"🔍 **LangSmith**: Ativo") |
| | else: |
| | gr.Markdown("🔍 **LangSmith**: Desabilitado") |
| |
|
| | reset_btn = gr.Button("Resetar") |
| | |
| | with gr.Column(scale=4): |
| | gr.Markdown("## Agent86") |
| | chatbot = gr.Chatbot( |
| | height=600, |
| | show_label=False, |
| | container=True, |
| | type="messages" |
| | ) |
| |
|
| | msg = gr.Textbox(placeholder="Digite sua pergunta aqui...", lines=1, label="") |
| | btn = gr.Button("Enviar", variant="primary") |
| | history_btn = gr.Button("Histórico", variant="secondary") |
| | history_output = gr.JSON() |
| |
|
| | |
| | graph_image = gr.Image( |
| | label="📊 Visualização de Dados", |
| | visible=False, |
| | height=500, |
| | show_label=True, |
| | container=True, |
| | interactive=False, |
| | show_download_button=True |
| | ) |
| |
|
| | download_file = gr.File(visible=False) |
| | |
| | |
| | def handle_response_with_graph(message, chat_history, model, advanced): |
| | """Wrapper para lidar com resposta e gráfico""" |
| | empty_msg, updated_history, graph_path = respond(message, chat_history, model, advanced) |
| |
|
| | |
| | if graph_path: |
| | return empty_msg, updated_history, gr.update(value=graph_path, visible=True) |
| | else: |
| | return empty_msg, updated_history, gr.update(visible=False) |
| |
|
| | msg.submit( |
| | handle_response_with_graph, |
| | inputs=[msg, chatbot, model_selector, advanced_checkbox], |
| | outputs=[msg, chatbot, graph_image] |
| | ) |
| |
|
| | btn.click( |
| | handle_response_with_graph, |
| | inputs=[msg, chatbot, model_selector, advanced_checkbox], |
| | outputs=[msg, chatbot, graph_image] |
| | ) |
| |
|
| | csv_file.change( |
| | handle_csv_and_clear_chat, |
| | inputs=csv_file, |
| | outputs=[upload_feedback, chatbot, graph_image] |
| | ) |
| |
|
| | reset_btn.click( |
| | reset_all, |
| | outputs=[upload_feedback, chatbot, csv_file, graph_image] |
| | ) |
| |
|
| | advanced_checkbox.change( |
| | toggle_advanced_mode, |
| | inputs=advanced_checkbox, |
| | outputs=[] |
| | ) |
| |
|
| | history_btn.click( |
| | toggle_history, |
| | outputs=history_output |
| | ) |
| | |
| | return demo |
| |
|
| | async def main(): |
| | """Função principal""" |
| | |
| | success = await initialize_app() |
| |
|
| | if not success: |
| | logging.error("Falha na inicialização. Encerrando aplicação.") |
| | return |
| |
|
| | |
| | demo = create_interface() |
| |
|
| | |
| | ports_to_try = [GRADIO_PORT, 7861, 7862, 7863, 7864, 0] |
| |
|
| | for port in ports_to_try: |
| | try: |
| | logging.info(f"Tentando iniciar interface Gradio na porta {port}") |
| | demo.launch( |
| | share=GRADIO_SHARE, |
| | server_port=port if port != 0 else None, |
| | show_error=True, |
| | quiet=False |
| | ) |
| | break |
| | except OSError as e: |
| | if "Cannot find empty port" in str(e) and port != ports_to_try[-1]: |
| | logging.warning(f"Porta {port} ocupada, tentando próxima...") |
| | continue |
| | else: |
| | logging.error(f"Erro ao iniciar servidor: {e}") |
| | raise |
| | except Exception as e: |
| | logging.error(f"Erro inesperado ao iniciar interface: {e}") |
| | raise |
| |
|
| | if __name__ == "__main__": |
| | run_async(main()) |
| |
|