Iza / app.py
caarleexx's picture
Update app.py
6bb8fa6 verified
#--- 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 ---