Skip to content

Commit 822786e

Browse files
committed
Add initial project setup with Streamlit app, utility functions, and dependencies
0 parents  commit 822786e

14 files changed

+551
-0
lines changed

.env.exemple

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
OPENAI_API_KEY="Sua chave de API do OpenAI"
2+
3+
## Se você não tiver uma chave de API do OpenAI, você pode obter uma em https://platform.openai.com/signup
4+
5+
## Hugging Face API Token
6+
## Você pode obter um token da API Hugging Face em https://huggingface.co/join
7+
8+
HUGGINGFACE_API_TOKEN="Seu token da API Hugging Face"

.gitignore

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
.venv
2+
__pycache__
3+
.env
4+
teste.ipynb
5+
arquivos
6+
.streamlit

Home.py

+52
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import streamlit as st
2+
from utils.sidebar_utils import sidebar_home
3+
from utils.chat_utils import get_chat_mensage
4+
5+
6+
def chat_window():
7+
st.header(
8+
"📝 DocsTalker: Seu bate-papo com PDFs",
9+
help="Chatbot para leitura de documentos PDF",
10+
divider=True
11+
)
12+
13+
if not "chain" in st.session_state:
14+
st.warning("Inicialize o ChatBot para começar", icon="⚠️")
15+
st.stop()
16+
17+
chain = st.session_state["chain"]
18+
memory = chain.memory
19+
20+
mensagens = memory.load_memory_variables({})["chat_history"]
21+
22+
container = st.container()
23+
for mensagem in mensagens:
24+
get_chat_mensage(st, mensagem, container)
25+
26+
input_mensagem = st.chat_input(
27+
placeholder="Converse com os seus documentos pdfs...", key="input_mensagem")
28+
if input_mensagem:
29+
get_chat_mensage(
30+
st,
31+
input_mensagem,
32+
container,
33+
input_mensagem,
34+
chain,
35+
is_input=True
36+
)
37+
38+
def app():
39+
st.set_page_config(
40+
page_title="DocsTalker - Chatbot para PDFs",
41+
page_icon="📝",
42+
layout="centered",
43+
initial_sidebar_state="expanded",
44+
menu_items={"Get Help": "https://www.streamlit.io/docs"}
45+
)
46+
with st.sidebar:
47+
sidebar_home(st)
48+
chat_window()
49+
50+
51+
if __name__ == "__main__":
52+
app()

images/avatar_assistant.png

62.7 KB
Loading

images/avatar_user.png

58.4 KB
Loading

pages/Config.py

+98
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
import streamlit as st
2+
from utils.config_utils import get_config, MODEL_OPTIONS, SEARCH_TYPES
3+
from utils.langchain import create_chain_conversation, ARQUIVOS
4+
from utils.sidebar_utils import sidebar_config
5+
from utils.utils import start_chatbot
6+
7+
8+
def config_window() -> None:
9+
"""Configure the Streamlit window and chatbot parameters."""
10+
st.set_page_config(
11+
page_title="Configurações do Chatbot",
12+
page_icon="⚙️",
13+
layout="centered",
14+
initial_sidebar_state="expanded",
15+
menu_items={"Get Help": "https://www.streamlit.io/docs"}
16+
)
17+
st.header(
18+
"⚙️ Configurações de Parâmetros do Chatbot",
19+
help="Configurações do ChatBot para leitura de documentos PDF",
20+
divider=True
21+
)
22+
23+
model_name = st.selectbox(
24+
"Modelo de Linguagem",
25+
MODEL_OPTIONS,
26+
index=MODEL_OPTIONS.index(get_config("model_name")),
27+
key="model_name_input",
28+
help="Modelo de linguagem a ser utilizado pelo ChatBot"
29+
)
30+
31+
retrieval_search_type = st.selectbox(
32+
"Tipo de Busca",
33+
options=SEARCH_TYPES,
34+
index=SEARCH_TYPES.index(get_config("retrieval_search_type")),
35+
key="retrieval_search_type_selectbox",
36+
help="Tipo de busca a ser utilizado pelo ChatBot"
37+
)
38+
retrieval_kwargs: dict = {}
39+
40+
retrieval_kwargs['k'] = st.select_slider(
41+
"Número de Documentos Retornados (k) - Recomendado: 5",
42+
options=list(range(1, 101)),
43+
value=get_config("retrieval_kwargs")['k'],
44+
key="retrieval_kwargs_k_slider",
45+
help="Número de documentos mais relevantes a serem retornados",
46+
on_change=lambda: st.session_state.update(
47+
{"retrieval_kwargs": retrieval_kwargs})
48+
)
49+
50+
retrieval_kwargs['fetch_k'] = st.select_slider(
51+
"Número de Documentos Buscados (fetch_k) - Recomendado: 20",
52+
options=list(range(1, 101)),
53+
value=get_config("retrieval_kwargs")['fetch_k'],
54+
key="retrieval_kwargs_fetch_k_slider",
55+
help="Número total de documentos a serem buscados antes da filtragem",
56+
on_change=lambda: st.session_state.update(
57+
{"retrieval_kwargs": retrieval_kwargs})
58+
)
59+
60+
prompt = st.text_area(
61+
label="Prompt Template - Modelo de Prompt",
62+
value=get_config("prompt"),
63+
height=400,
64+
key="prompt_slider",
65+
help="Template de prompt a ser utilizado pelo ChatBot"
66+
67+
)
68+
69+
if st.button(
70+
"Salvar Configurações",
71+
key="salvar_config",
72+
help="Salva as configurações do ChatBot",
73+
use_container_width=True
74+
):
75+
st.session_state["model_name"] = model_name
76+
st.session_state["retrieval_search_type"] = retrieval_search_type
77+
st.session_state["retrieval_kwargs"] = retrieval_kwargs
78+
st.session_state["prompt"] = prompt
79+
st.toast("Configurações Salvas com Sucesso!", icon="✔️")
80+
st.rerun()
81+
82+
if st.button(
83+
"Atualizar ChatBot",
84+
key="atualizar_chatbot",
85+
help="Atualiza o ChatBot com as novas configurações",
86+
type="primary",
87+
use_container_width=True
88+
):
89+
start_chatbot(st, ARQUIVOS, create_chain_conversation)
90+
91+
def app() -> None:
92+
"""Main Streamlit application."""
93+
config_window()
94+
with st.sidebar:
95+
sidebar_config(st)
96+
97+
98+
app()

pages/Debug.py

+49
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import streamlit as st
2+
from langchain.prompts import PromptTemplate
3+
from utils.config_utils import get_config
4+
5+
6+
def debug_window():
7+
st.set_page_config(
8+
page_title="Debug Window",
9+
page_icon="🐞",
10+
layout="centered",
11+
initial_sidebar_state="expanded",
12+
menu_items={
13+
"Get Help": "https://www.streamlit.io/docs",
14+
"Report a bug": "https://github.com/ArkaNiightt"
15+
},
16+
)
17+
st.header(
18+
"Visualização de Dados e Depuração",
19+
help="Esta página é destinada a depuração e teste de funcionalidades.",
20+
divider=True
21+
)
22+
23+
prompt_template = get_config("prompt")
24+
prompt_template = PromptTemplate.from_template(prompt_template)
25+
26+
if not "ultima_resposta" in st.session_state:
27+
st.warning("Nenhuma resposta foi gerada ainda.", icon="⚠️")
28+
st.stop()
29+
30+
ultima_resposta = st.session_state['ultima_resposta']
31+
32+
contexto_docs = ultima_resposta['source_documents']
33+
contexto_list = [doc.page_content for doc in contexto_docs]
34+
contexto_str = '\n\n'.join(contexto_list)
35+
36+
chain = st.session_state['chain']
37+
memory = chain.memory
38+
chat_history = memory.buffer_as_str
39+
40+
with st.container(border=True):
41+
prompt = prompt_template.format(
42+
chat_history=chat_history,
43+
context=contexto_str,
44+
question=''
45+
)
46+
st.code(prompt)
47+
48+
49+
debug_window()

requirements.txt

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
ipykernel==6.29.4
2+
langchain==0.1.16
3+
langchain-community==0.0.34
4+
langchain-openai==0.1.4
5+
openai==1.55.3
6+
pypdf==4.2.0
7+
streamlit==1.33.0
8+
python-dotenv==1.0.1
9+
faiss-cpu==1.8.0

utils/avatar_image.py

+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
from PIL import Image
2+
import requests
3+
from io import BytesIO
4+
import os
5+
6+
def load_avatar(image_source):
7+
"""
8+
Carrega uma imagem de avatar a partir de um caminho local ou URL.
9+
10+
:param image_source: String contendo o caminho local ou URL da imagem
11+
:return: Objeto PIL.Image ou None se a imagem não puder ser carregada
12+
"""
13+
try:
14+
if image_source.startswith(("http://", "https://")):
15+
# Se for uma URL
16+
response = requests.get(image_source)
17+
img = Image.open(BytesIO(response.content))
18+
else:
19+
# Se for um caminho local
20+
if os.path.exists(image_source):
21+
img = Image.open(image_source)
22+
else:
23+
raise FileNotFoundError(
24+
f"Arquivo não encontrado: {image_source}")
25+
26+
# Redimensiona a imagem para um tamanho padrão (opcional)
27+
img = img.resize((128, 128))
28+
return img
29+
except Exception as e:
30+
print(f"Não foi possível carregar a imagem do avatar: {e}")
31+
return None

utils/chat_utils.py

+41
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
from utils.avatar_image import load_avatar
2+
import streamlit
3+
from time import sleep
4+
5+
6+
def get_chat_mensage(st: streamlit, mensagem, container, input_mensagem=None, chain=None, is_input=False):
7+
"""
8+
Lida com a exibição de mensagens de chat em uma aplicação Streamlit.
9+
10+
Parâmetros:
11+
st (streamlit): A instância do Streamlit.
12+
mensagem: O objeto de mensagem contendo o tipo e o conteúdo da mensagem.
13+
container: O container no qual as mensagens de chat serão exibidas.
14+
input_mensagem (str, opcional): A mensagem de entrada do usuário. Padrão é None.
15+
chain (opcional): O objeto chain usado para gerar respostas. Padrão é None.
16+
is_input (bool, opcional): Flag indicando se a mensagem é uma mensagem de entrada. Padrão é False.
17+
18+
Retorna:
19+
Nenhum
20+
"""
21+
if is_input:
22+
chat = container.chat_message(
23+
"human", avatar=load_avatar("images/avatar_user.png"))
24+
chat.markdown(mensagem)
25+
chat = container.chat_message(
26+
"ai", avatar=load_avatar("images/avatar_assistant.png"))
27+
chat.markdown("Gerando Resposta...")
28+
with st.spinner(""):
29+
sleep(1)
30+
resposta = chain.invoke({"question": input_mensagem})
31+
st.session_state["ultima_resposta"] = resposta
32+
st.rerun()
33+
else:
34+
if mensagem.type == "human":
35+
chat = container.chat_message(
36+
"human", avatar=load_avatar("images/avatar_user.png"))
37+
chat.markdown(mensagem.content)
38+
elif mensagem.type == "ai":
39+
chat = container.chat_message(
40+
"ai", avatar=load_avatar("images/avatar_assistant.png"))
41+
chat.markdown(mensagem.content)

utils/config_utils.py

+46
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import streamlit as st
2+
3+
MODEL_NAME = "gpt-4o"
4+
RETRIEVAL_SEARCH_TYPE = "mmr"
5+
RETRIEVAL_KWARGS = {"k": 5, "fetch_k": 20}
6+
PROMPT = """
7+
Você é um chatbot que conversa sobre documentos PDF que lhe são fornecidos.
8+
No contexto fornecido, você deve ser capaz de responder a perguntas sobre o conteúdo dos documentos.
9+
Se você não souber a resposta, você pode dizer que não sabe e não tente inventar a resposta.
10+
11+
Contexto:
12+
{context}
13+
14+
Conversa atual:
15+
{chat_history}
16+
17+
Human: {question}
18+
AI:"""
19+
20+
MODEL_OPTIONS = ["gpt-4o", "gpt-4o-mini", "gpt-3.5-turbo", "o1-mini"]
21+
SEARCH_TYPES = ["mmr", "similarity", "similarity_score_threshold", "hybrid"]
22+
23+
def get_config(config: str, st=st):
24+
"""
25+
Recupera um valor de configuração do estado da sessão ou valores padrão.
26+
27+
Args:
28+
config (str): O nome da configuração a ser recuperada.
29+
st: O módulo streamlit, que contém o estado da sessão.
30+
31+
Retorna:
32+
O valor da configuração solicitada se existir no estado da sessão,
33+
caso contrário, retorna um valor padrão com base no nome da configuração.
34+
Retorna None se o nome da configuração não for reconhecido.
35+
"""
36+
if config.lower() in st.session_state:
37+
return st.session_state[config.lower()]
38+
if config.lower() == "model_name":
39+
return MODEL_NAME
40+
elif config.lower() == "retrieval_search_type":
41+
return RETRIEVAL_SEARCH_TYPE
42+
elif config.lower() == "retrieval_kwargs":
43+
return RETRIEVAL_KWARGS
44+
elif config.lower() == "prompt":
45+
return PROMPT
46+
return None

0 commit comments

Comments
 (0)