# -*- coding: utf-8
# Reinaldo Chaves (reichaves@gmail.com)
# Este projeto implementa um sistema de Recuperação de Informações Aumentada por Geração (RAG) conversacional
# usando Streamlit, LangChain, e modelos de linguagem de grande escala - para entrevistar PDFs
# Geração de respostas usando o modelo Gemma2-9b-It da Groq
# Embeddings de texto usando o modelo all-MiniLM-L6-v2 do Hugging Face
##
import streamlit as st
from langchain.chains import create_history_aware_retriever, create_retrieval_chain
from langchain.chains.combine_documents import create_stuff_documents_chain
from langchain_community.chat_message_histories import ChatMessageHistory
from langchain_core.chat_history import BaseChatMessageHistory
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain_groq import ChatGroq
from langchain_core.runnables.history import RunnableWithMessageHistory
from langchain_community.embeddings import HuggingFaceEmbeddings
from langchain_text_splitters import RecursiveCharacterTextSplitter
from langchain_community.document_loaders import PyPDFLoader
from langchain_community.vectorstores import FAISS
import os
import tempfile
# Configurar o tema para dark
st.set_page_config(page_title="RAG Q&A Conversacional", layout="wide", initial_sidebar_state="expanded", page_icon="🤖", menu_items=None)
# Aplicar o tema dark com CSS
st.markdown("""
""", unsafe_allow_html=True)
# Sidebar com orientações
st.sidebar.markdown("
Orientações
", unsafe_allow_html=True)
st.sidebar.markdown("""
* Se encontrar erros de processamento, reinicie com F5. Utilize arquivos .PDF com textos não digitalizados como imagens.
* Para recomeçar uma nova sessão pressione F5.
**Obtenção de chaves de API:**
* Você pode fazer uma conta no Groq Cloud e obter uma chave de API [aqui](https://console.groq.com/login)
* Você pode fazer uma conta no Hugging Face e obter o token de API Hugging Face [aqui](https://huggingface.co/docs/hub/security-tokens)
**Atenção:** Os documentos que você compartilhar com o modelo de IA generativa podem ser usados pelo LLM para treinar o sistema. Portanto, evite compartilhar documentos PDF que contenham:
1. Dados bancários e financeiros
2. Dados de sua própria empresa
3. Informações pessoais
4. Informações de propriedade intelectual
5. Conteúdos autorais
E não use IA para escrever um texto inteiro! O auxílio é melhor para gerar resumos, filtrar informações ou auxiliar a entender contextos - que depois devem ser checados. Inteligência Artificial comete erros (alucinações, viés, baixa qualidade, problemas éticos)!
Este projeto não se responsabiliza pelos conteúdos criados a partir deste site.
**Sobre este app**
Este aplicativo foi desenvolvido por Reinaldo Chaves. Para mais informações, contribuições e feedback, visite o [repositório do projeto no GitHub](https://github.com/reichaves/rag_chat_gemma2).
""")
st.markdown("Chatbot com modelos opensource - entrevista PDFs ✏️
", unsafe_allow_html=True)
st.write("Carregue PDFs e converse com o conteúdo deles - aqui é usado o modelo de LLM Gemma2-9b-It e a plataforma de embeddings é all-MiniLM-L6-v2")
# Solicitar as chaves de API
groq_api_key = st.text_input("Insira sua chave de API Groq:", type="password")
huggingface_api_token = st.text_input("Insira seu token de API Hugging Face:", type="password")
if groq_api_key and huggingface_api_token:
# Configurar o token da API do Hugging Face
os.environ["HUGGINGFACEHUB_API_TOKEN"] = huggingface_api_token
# Inicializar o modelo de linguagem e embeddings
#llm = ChatGroq(groq_api_key=groq_api_key, model_name="Gemma2-9b-It", temperature=0)
llm = ChatGroq(groq_api_key=groq_api_key, model_name="llama-3.2-90b-text-preview", temperature=0)
embeddings = HuggingFaceEmbeddings(model_name="all-MiniLM-L6-v2")
session_id = st.text_input("Session ID", value="default_session")
if 'store' not in st.session_state:
st.session_state.store = {}
uploaded_files = st.file_uploader("Faça o upload de um ou mais arquivos PDF: ", type="pdf", accept_multiple_files=True)
if uploaded_files:
documents = []
for uploaded_file in uploaded_files:
with tempfile.NamedTemporaryFile(delete=False, suffix=".pdf") as temp_file:
temp_file.write(uploaded_file.getvalue())
temp_file_path = temp_file.name
loader = PyPDFLoader(temp_file_path)
docs = loader.load()
documents.extend(docs)
os.unlink(temp_file_path) # Remove temporary file
text_splitter = RecursiveCharacterTextSplitter(chunk_size=10000, chunk_overlap=1000)
splits = text_splitter.split_documents(documents)
# Create FAISS vector store
vectorstore = FAISS.from_documents(splits, embeddings)
st.success(f"Processed {len(splits)} document chunks.")
retriever = vectorstore.as_retriever()
contextualize_q_system_prompt = (
"Given a chat history and the latest user question "
"which might reference context in the chat history, "
"formulate a standalone question which can be understood "
"without the chat history. Do NOT answer the question, "
"just reformulate it if needed and otherwise return it as is."
)
contextualize_q_prompt = ChatPromptTemplate.from_messages([
("system", contextualize_q_system_prompt),
MessagesPlaceholder("chat_history"),
("human", "{input}"),
])
history_aware_retriever = create_history_aware_retriever(llm, retriever, contextualize_q_prompt)
system_prompt = (
"Você é um assistente especializado em analisar documentos PDF com um contexto jornalístico, "
"como documentos da Lei de Acesso à Informação, contratos públicos e processos judiciais. "
"Sempre coloque no final das respostas: 'Todas as informações devem ser checadas com a(s) fonte(s) original(ais)'"
"Responda em Português do Brasil a menos que seja pedido outro idioma"
"Se você não sabe a resposta, diga que não sabe"
"Siga estas diretrizes:\n\n"
"1. Explique os passos de forma simples e mantenha as respostas concisas.\n"
"2. Inclua links para ferramentas, pesquisas e páginas da Web citadas.\n"
"3. Ao resumir passagens, escreva em nível universitário.\n"
"4. Divida tópicos em partes menores e fáceis de entender quando relevante.\n"
"5. Seja claro, breve, ordenado e direto nas respostas.\n"
"6. Evite opiniões e mantenha-se neutro.\n"
"7. Base-se nas classes processuais do Direito no Brasil conforme o site do CNJ.\n"
"8. Se não souber a resposta, admita que não sabe.\n\n"
"Ao analisar processos judiciais, priorize:\n"
"- Identificar se é petição inicial, decisão ou sentença\n"
"- Apresentar a ação e suas partes\n"
"- Explicar os motivos do ajuizamento\n"
"- Listar os requerimentos do autor\n"
"- Expor o resultado das decisões\n"
"- Indicar o status do processo\n\n"
"Para licitações ou contratos públicos, considere as etapas do processo licitatório e as modalidades de licitação.\n\n"
"Para documentos da Lei de Acesso à Informação (LAI), inclua:\n"
"- Data\n"
"- Protocolo NUP\n"
"- Nome do órgão público\n"
"- Nomes dos responsáveis pela resposta\n"
"- Data da resposta\n"
"- Se o pedido foi totalmente atendido, parcialmente ou negado\n\n"
"Use o seguinte contexto para responder à pergunta: {context}\n\n"
"Sempre termine as respostas com: 'Todas as informações precisam ser checadas com as fontes das informações'."
)
qa_prompt = ChatPromptTemplate.from_messages([
("system", system_prompt),
MessagesPlaceholder("chat_history"),
("human", "{input}"),
])
question_answer_chain = create_stuff_documents_chain(llm, qa_prompt)
rag_chain = create_retrieval_chain(history_aware_retriever, question_answer_chain)
def get_session_history(session: str) -> BaseChatMessageHistory:
if session not in st.session_state.store:
st.session_state.store[session] = ChatMessageHistory()
return st.session_state.store[session]
conversational_rag_chain = RunnableWithMessageHistory(
rag_chain, get_session_history,
input_messages_key="input",
history_messages_key="chat_history",
output_messages_key="answer"
)
user_input = st.text_input("Sua pergunta:")
if user_input:
with st.spinner("Processando sua pergunta..."):
session_history = get_session_history(session_id)
response = conversational_rag_chain.invoke(
{"input": user_input},
config={"configurable": {"session_id": session_id}},
)
st.write("Assistente:", response['answer'])
with st.expander("Ver histórico do chat"):
for message in session_history.messages:
st.write(f"**{message.type}:** {message.content}")
else:
st.warning("Por favor, insira tanto a chave da API do Groq quanto o token da API do Hugging Face.")