Spaces:
Sleeping
Sleeping
| #--- START OF FILE app (23).py --- | |
| import streamlit as st | |
| import time | |
| import os | |
| import tempfile # Para criar diretórios temporários seguros | |
| # --- IMPORTS GROQ --- | |
| from groq import Groq | |
| # --- IMPORTS LANGCHAIN / RAG --- | |
| from langchain_community.document_loaders import TextLoader | |
| from langchain_community.document_loaders import PyPDFLoader | |
| from langchain_text_splitters import RecursiveCharacterTextSplitter | |
| from langchain_huggingface import HuggingFaceEmbeddings | |
| from langchain_community.vectorstores import FAISS | |
| from langchain.chains import RetrievalQA | |
| from langchain_groq import ChatGroq | |
| # -------------------------- | |
| # 1. Título da Página e Configuração de Layout | |
| st.set_page_config(page_title="Iza - Assistente Groq RAG", layout="wide") | |
| # --- CSS CORRIGIDO E ATUALIZADO (REMOÇÃO DO AVATAR E ESPAÇO) --- | |
| st.markdown(""" | |
| <style> | |
| /* NOVO: Oculta o primeiro filho dentro do container de mensagem (que é o avatar/ícone) */ | |
| [data-testid^="chat-message-"] > div:first-child { | |
| display: none !important; | |
| width: 0px !important; | |
| height: 0px !important; | |
| min-width: 0px !important; | |
| } | |
| /* Garante que o container de avatar (o elemento com o testid específico) também não ocupe espaço */ | |
| [data-testid="stChatAvatar"] { | |
| display: none !important; | |
| width: 0px !important; | |
| height: 0px !important; | |
| min-width: 0px !important; | |
| } | |
| /* Remove o espaçamento extra para alinhar o texto à esquerda e remove o gap */ | |
| [data-testid="stChatMessage"] { | |
| padding-left: 0px; | |
| padding-right: 0px; | |
| gap: 0.0rem !important; | |
| } | |
| /* Mantém a justificação do texto e garante a largura total para o conteúdo da mensagem */ | |
| [data-testid="stChatMessageContent"] { | |
| text-align: justify; | |
| width: 100%; | |
| } | |
| /* Regras de Tabela (Mantidas para garantir a legibilidade) */ | |
| table { | |
| width: 100% !important; | |
| table-layout: fixed; | |
| } | |
| th, td { | |
| word-wrap: break-word; | |
| overflow-wrap: break-word; | |
| word-break: break-all; | |
| } | |
| </style> | |
| """, unsafe_allow_html=True) | |
| st.title("Iza - Assistente com Groq RAG 🚀") | |
| st.caption("Um chatbot com memória, upload de arquivos, LangChain RAG e controle de velocidade.") | |
| # 3. Configuração do Cliente Groq | |
| print("DEBUG: Inicializando clientes Groq.") # Log de depuração | |
| client = Groq() | |
| groq_llm = ChatGroq(model_name="mixtral-8x7b-32768", temperature=0.7) | |
| # 2. Barra Lateral e Lógica de Upload/Processamento RAG | |
| with st.sidebar: | |
| st.header("Opções") | |
| if 'retriever' not in st.session_state: | |
| st.session_state.retriever = None | |
| st.session_state.retriever_source = None | |
| print("DEBUG: Estado inicial: retriever=None.") # Log de depuração | |
| uploaded_file = st.file_uploader( | |
| "Anexe um arquivo para pesquisa RAG (opcional)", | |
| type=["txt", "md", "pdf"], | |
| help="O arquivo será processado e a IA poderá responder perguntas sobre seu conteúdo." | |
| ) | |
| # Lógica de Processamento do Arquivo | |
| if uploaded_file: | |
| # Apenas processa se o arquivo for novo | |
| if st.session_state.retriever_source != uploaded_file.name: | |
| file_path = f"./temp_file_{uploaded_file.name.replace('/', '_')}" | |
| try: | |
| # --- USO DO DIRETÓRIO TEMPORÁRIO --- | |
| st.info("Passo 1/5: Salvando arquivo temporariamente. Aguarde...") | |
| print(f"DEBUG: Tentando salvar arquivo: {uploaded_file.name}") # Log de depuração | |
| with tempfile.NamedTemporaryFile(delete=False, suffix=f"_{uploaded_file.name}") as tmp_file: | |
| tmp_file.write(uploaded_file.read()) | |
| file_path = tmp_file.name | |
| print(f"DEBUG: Arquivo salvo em: {file_path}") # Log de depuração | |
| # 2. CONFIGURAÇÃO RAG (Processo de Embedding) | |
| with st.spinner(f"Processando '{uploaded_file.name}' com LangChain..."): | |
| # Carregamento do Documento | |
| if uploaded_file.type == 'application/pdf': | |
| loader = PyPDFLoader(file_path) | |
| elif uploaded_file.type in ['text/markdown', 'text/plain']: | |
| loader = TextLoader(file_path) | |
| else: | |
| raise ValueError("Tipo de arquivo não suportado após o upload.") | |
| documents = loader.load() | |
| st.info(f"Passo 2/5: Carregamento concluído. Documentos carregados: {len(documents)}.") | |
| # Fragmentação do Texto | |
| text_splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=200) | |
| texts = text_splitter.split_documents(documents) | |
| st.info(f"Passo 3/5: Texto fragmentado em {len(texts)} pedaços.") | |
| # HuggingFace Embeddings (Roda na CPU) | |
| st.info("Passo 4/5: Criando Embeddings (vetores) com HuggingFace. Isso pode levar alguns segundos.") | |
| embeddings = HuggingFaceEmbeddings(model_name="all-MiniLM-L6-v2") | |
| # Criar o Vector Store (FAISS) | |
| st.info("Passo 5/5: Criando o Vector Store (FAISS) para busca rápida.") | |
| vectorstore = FAISS.from_documents(texts, embeddings) | |
| # Armazenar na sessão | |
| st.session_state.retriever = vectorstore.as_retriever() | |
| st.session_state.retriever_source = uploaded_file.name | |
| st.success(f"Arquivo '{uploaded_file.name}' processado! Pergunte sobre ele.") | |
| print("DEBUG: Processo RAG concluído e retriever armazenado.") # Log de depuração | |
| except Exception as e: | |
| # Tratamento de erro 403 e outros | |
| if "403" in str(e): | |
| st.error("Erro no upload (403 Forbidden). O servidor de deploy está rejeitando a requisição.") | |
| print(f"ERRO CRÍTICO (403): {e}") # Log de depuração | |
| else: | |
| st.error(f"Erro ao processar o arquivo: {e}") | |
| print(f"ERRO DE PROCESSAMENTO: {e}") # Log de depuração | |
| st.session_state.retriever = None | |
| st.session_state.retriever_source = None | |
| finally: | |
| # Garante que o arquivo temporário seja removido | |
| if os.path.exists(file_path): | |
| os.remove(file_path) | |
| print(f"DEBUG: Arquivo temporário removido: {file_path}") # Log de depuração | |
| else: | |
| st.info(f"O arquivo '{st.session_state.retriever_source}' já foi processado e está ativo.") | |
| elif st.session_state.retriever_source is not None: | |
| st.warning("O arquivo processado foi removido. A IA voltará a usar pesquisa web.") | |
| st.session_state.retriever = None | |
| st.session_state.retriever_source = None | |
| # 4. Inicialização do Histórico da Conversa | |
| if "messages" not in st.session_state: | |
| print("DEBUG: Inicializando histórico de mensagens.") # Log de depuração | |
| system_prompt = ( | |
| "Você é um assistente de pesquisa avançado chamado Iza. " | |
| "Se houver um documento anexo, use-o como primeira fonte de conhecimento. " | |
| "Caso contrário, use as ferramentas 'visit_website' ou 'web_search' para obter informações. " | |
| "Sua tarefa é fornecer um resumo completo, bem estruturado e detalhado em markdown. " | |
| "IMPORTANTE: Ao criar tabelas, elas devem ter no máximo 3 colunas, e de preferência apenas 2, " | |
| "para garantir a legibilidade em todas as telas." | |
| ) | |
| st.session_state.messages = [{"role": "system", "content": system_prompt}] | |
| # 5. Exibição das Mensagens Anteriores (Log de quantas mensagens) | |
| if len(st.session_state.messages) > 1: | |
| print(f"DEBUG: Exibindo {len(st.session_state.messages) - 1} mensagens anteriores.") # Log de depuração | |
| for message in st.session_state.messages: | |
| if message["role"] != "system": | |
| with st.chat_message(message["role"]): | |
| st.markdown(message["content"]) | |
| # 6. Lógica de Interação do Chat | |
| if prompt := st.chat_input("Pergunte algo sobre o documento ou faça uma pesquisa na web..."): | |
| # 6a. Adiciona a mensagem do usuário e exibe | |
| st.session_state.messages.append({"role": "user", "content": prompt}) | |
| with st.chat_message("user"): | |
| st.markdown(prompt) | |
| print(f"DEBUG: Prompt do usuário: '{prompt}'") # Log de depuração | |
| # 6b. Obtém a resposta do assistente (RAG ou Streaming com Tool Use) | |
| with st.chat_message("assistant"): | |
| placeholder = st.empty() | |
| full_response = "" | |
| try: | |
| # --- LÓGICA DE DECISÃO RAG vs GROQ DIRETO --- | |
| if st.session_state.retriever is not None: | |
| # RAG: Caso haja um arquivo anexado. | |
| print("DEBUG: Modo RAG (Documento anexado) ativado.") # Log de depuração | |
| qa_chain = RetrievalQA.from_chain_type( | |
| llm=groq_llm, | |
| chain_type="stuff", | |
| retriever=st.session_state.retriever, | |
| return_source_documents=False | |
| ) | |
| # Resposta RAG | |
| with st.spinner("Buscando no documento e gerando resposta..."): | |
| result = qa_chain.invoke({"query": prompt}) | |
| full_response = result['result'] | |
| else: | |
| # GROQ DIRETO: Caso NÃO haja arquivo (usa Tool Use para pesquisa web). | |
| print("DEBUG: Modo Groq Direto (Web Search) ativado.") # Log de depuração | |
| stream = client.chat.completions.create( | |
| model="groq/compound", | |
| messages=[ | |
| {"role": m["role"], "content": m["content"]} | |
| for m in st.session_state.messages | |
| ], | |
| temperature=0.7, | |
| max_tokens=4096, | |
| stream=True, | |
| compound_custom={"tools": {"enabled_tools": ["web_search", "visit_website"]}} | |
| ) | |
| # Streaming da resposta | |
| for chunk in stream: | |
| full_response += chunk.choices[0].delta.content or "" | |
| placeholder.markdown(full_response + "▌") | |
| time.sleep(0.005) | |
| # Exibe a resposta completa | |
| placeholder.markdown(full_response) | |
| print("DEBUG: Resposta completa enviada ao chat.") # Log de depuração | |
| # 6c. Adiciona a resposta completa ao histórico | |
| st.session_state.messages.append({"role": "assistant", "content": full_response}) | |
| except Exception as e: | |
| st.error(f"Ocorreu um erro na interação: {e}") | |
| print(f"ERRO DE INTERAÇÃO: {e}") # Log de depuração | |
| # --- END OF FILE app (23).py --- |