Update app.py
Browse files
app.py
CHANGED
@@ -6,27 +6,38 @@
|
|
6 |
# Embeddings de texto usando o modelo all-MiniLM-L6-v2 do Hugging Face
|
7 |
##
|
8 |
|
|
|
|
|
9 |
|
10 |
-
|
11 |
-
|
12 |
-
|
13 |
-
|
14 |
-
|
15 |
-
from cachetools import TTLCache
|
16 |
-
import logging
|
17 |
-
from datetime import datetime
|
18 |
|
19 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
20 |
logging.basicConfig(
|
21 |
level=logging.INFO,
|
22 |
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
|
23 |
)
|
24 |
logger = logging.getLogger(__name__)
|
25 |
|
26 |
-
#
|
27 |
os.environ["TOKENIZERS_PARALLELISM"] = "false"
|
28 |
|
29 |
-
#
|
30 |
from langchain.chains import create_history_aware_retriever, create_retrieval_chain
|
31 |
from langchain.chains.combine_documents import create_stuff_documents_chain
|
32 |
from langchain_community.chat_message_histories import ChatMessageHistory
|
@@ -40,21 +51,31 @@ from langchain_community.vectorstores import FAISS
|
|
40 |
from langchain_core.runnables import Runnable
|
41 |
from maritalk import MariTalk
|
42 |
|
43 |
-
# Cache para embeddings
|
44 |
-
embeddings_cache = TTLCache(maxsize=100, ttl=3600)
|
45 |
|
46 |
class MariTalkWrapper(Runnable):
|
47 |
-
"""
|
|
|
|
|
|
|
48 |
|
49 |
def __init__(self, maritalk_model: Any, max_retries: int = 3, timeout: int = 30):
|
|
|
|
|
|
|
50 |
self.maritalk_model = maritalk_model
|
51 |
self.max_retries = max_retries
|
52 |
self.timeout = timeout
|
53 |
|
54 |
@retry(stop=stop_after_attempt(3), wait=wait_exponential(multiplier=1, min=4, max=10))
|
55 |
def invoke(self, input: Any, config: Optional[Dict] = None) -> str:
|
|
|
|
|
|
|
|
|
56 |
try:
|
57 |
-
#
|
58 |
if hasattr(input, "to_messages"):
|
59 |
messages = input.to_messages()
|
60 |
formatted_messages = self._format_messages(messages)
|
@@ -66,7 +87,7 @@ class MariTalkWrapper(Runnable):
|
|
66 |
else:
|
67 |
return response[0]['content'] if isinstance(response, list) else str(response)
|
68 |
|
69 |
-
#
|
70 |
elif isinstance(input, dict):
|
71 |
if "messages" in input:
|
72 |
messages = input["messages"]
|
@@ -83,7 +104,7 @@ class MariTalkWrapper(Runnable):
|
|
83 |
else:
|
84 |
return self._process_text(str(input))
|
85 |
|
86 |
-
#
|
87 |
elif isinstance(input, str):
|
88 |
return self._process_text(input)
|
89 |
|
@@ -97,6 +118,10 @@ class MariTalkWrapper(Runnable):
|
|
97 |
raise
|
98 |
|
99 |
def _format_messages(self, messages: List[Any]) -> List[Dict[str, str]]:
|
|
|
|
|
|
|
|
|
100 |
formatted = []
|
101 |
for msg in messages:
|
102 |
role = "user"
|
@@ -117,6 +142,10 @@ class MariTalkWrapper(Runnable):
|
|
117 |
return formatted
|
118 |
|
119 |
def _process_text(self, text: str) -> str:
|
|
|
|
|
|
|
|
|
120 |
response = self.maritalk_model.generate([{"role": "user", "content": text}])
|
121 |
if isinstance(response, str):
|
122 |
return response
|
@@ -128,6 +157,10 @@ class MariTalkWrapper(Runnable):
|
|
128 |
return str(response)
|
129 |
|
130 |
def init_page_config():
|
|
|
|
|
|
|
|
|
131 |
st.set_page_config(
|
132 |
page_title="Chatbot com IA especializada em Português do Brasil - entrevista PDFs",
|
133 |
layout="wide",
|
@@ -136,12 +169,19 @@ def init_page_config():
|
|
136 |
)
|
137 |
|
138 |
def apply_custom_css():
|
|
|
|
|
|
|
|
|
139 |
st.markdown("""
|
140 |
<style>
|
|
|
141 |
.stApp {
|
142 |
background-color: #0e1117;
|
143 |
color: #fafafa;
|
144 |
}
|
|
|
|
|
145 |
.chat-message {
|
146 |
padding: 1rem;
|
147 |
border-radius: 0.5rem;
|
@@ -156,6 +196,8 @@ def apply_custom_css():
|
|
156 |
.assistant-message {
|
157 |
background-color: #1e1e1e;
|
158 |
}
|
|
|
|
|
159 |
.chat-header {
|
160 |
font-weight: bold;
|
161 |
margin-bottom: 0.5rem;
|
@@ -172,6 +214,8 @@ def apply_custom_css():
|
|
172 |
border-top: 1px solid #444;
|
173 |
padding-top: 8px;
|
174 |
}
|
|
|
|
|
175 |
.main-title {
|
176 |
color: #FFA500;
|
177 |
font-size: 2.5em;
|
@@ -184,12 +228,16 @@ def apply_custom_css():
|
|
184 |
border-radius: 20px;
|
185 |
padding: 10px 20px;
|
186 |
}
|
|
|
|
|
187 |
div[data-testid="stToolbar"] {
|
188 |
display: none;
|
189 |
}
|
190 |
.stDeployButton {
|
191 |
display: none;
|
192 |
}
|
|
|
|
|
193 |
.token-info {
|
194 |
font-style: italic;
|
195 |
color: #888;
|
@@ -201,6 +249,10 @@ def apply_custom_css():
|
|
201 |
""", unsafe_allow_html=True)
|
202 |
|
203 |
def create_sidebar():
|
|
|
|
|
|
|
|
|
204 |
st.sidebar.markdown("## Orientações")
|
205 |
st.sidebar.markdown("""
|
206 |
* Se encontrar erros de processamento, reinicie com F5. Utilize arquivos .PDF com textos não digitalizados como imagens.
|
@@ -210,32 +262,41 @@ def create_sidebar():
|
|
210 |
* Você pode fazer uma conta na MaritacaAI e obter uma chave de API [aqui](https://plataforma.maritaca.ai/)
|
211 |
* Você pode fazer uma conta no Hugging Face e obter o token de API Hugging Face [aqui](https://huggingface.co/docs/hub/security-tokens)
|
212 |
|
213 |
-
**Atenção:** Os documentos que você compartilhar com o modelo de IA generativa podem ser usados pelo LLM para treinar o sistema.
|
|
|
214 |
1. Dados bancários e financeiros
|
215 |
2. Dados de sua própria empresa
|
216 |
3. Informações pessoais
|
217 |
4. Informações de propriedade intelectual
|
218 |
5. Conteúdos autorais
|
219 |
|
220 |
-
E não use IA para escrever um texto inteiro! O auxílio é melhor para gerar resumos, filtrar informações ou auxiliar a
|
|
|
|
|
221 |
|
222 |
Este projeto não se responsabiliza pelos conteúdos criados a partir deste site.
|
223 |
|
224 |
**Sobre este app**
|
225 |
-
Este aplicativo foi desenvolvido por Reinaldo Chaves. Para mais informações, contribuições e feedback, visite o
|
|
|
226 |
""")
|
227 |
|
228 |
def display_chat_message(message: str, is_user: bool):
|
|
|
|
|
|
|
|
|
|
|
229 |
class_name = "user-message" if is_user else "assistant-message"
|
230 |
role = "Você" if is_user else "Assistente"
|
231 |
|
232 |
-
#
|
233 |
if not is_user:
|
234 |
if isinstance(message, dict):
|
235 |
content = message.get('answer', str(message))
|
236 |
tokens = message.get('usage', {}).get('total_tokens', None)
|
237 |
|
238 |
-
#
|
239 |
content = content.replace('\n', '<br>')
|
240 |
|
241 |
if tokens:
|
@@ -245,6 +306,7 @@ def display_chat_message(message: str, is_user: bool):
|
|
245 |
else:
|
246 |
content = message
|
247 |
|
|
|
248 |
st.markdown(f"""
|
249 |
<div class="chat-message {class_name}">
|
250 |
<div class="chat-header">{role}:</div>
|
@@ -253,17 +315,24 @@ def display_chat_message(message: str, is_user: bool):
|
|
253 |
""", unsafe_allow_html=True)
|
254 |
|
255 |
def setup_rag_chain(documents: List[Any], llm: Any, embeddings: Any) -> Any:
|
|
|
|
|
|
|
|
|
|
|
256 |
text_splitter = RecursiveCharacterTextSplitter(
|
257 |
-
chunk_size=1000,
|
258 |
-
chunk_overlap=200,
|
259 |
length_function=len,
|
260 |
is_separator_regex=False
|
261 |
)
|
262 |
|
|
|
263 |
splits = text_splitter.split_documents(documents)
|
264 |
vectorstore = FAISS.from_documents(splits, embeddings)
|
265 |
retriever = vectorstore.as_retriever()
|
266 |
|
|
|
267 |
contextualize_q_prompt = ChatPromptTemplate.from_messages([
|
268 |
("system", """
|
269 |
Você é um assistente especializado em analisar documentos PDF com um contexto jornalístico,
|
@@ -303,6 +372,7 @@ def setup_rag_chain(documents: List[Any], llm: Any, embeddings: Any) -> Any:
|
|
303 |
("human", "{input}"),
|
304 |
])
|
305 |
|
|
|
306 |
qa_prompt = ChatPromptTemplate.from_messages([
|
307 |
("system", """
|
308 |
Você é um assistente especializado em análise de documentos.
|
@@ -356,48 +426,59 @@ def setup_rag_chain(documents: List[Any], llm: Any, embeddings: Any) -> Any:
|
|
356 |
("human", "{input}"),
|
357 |
])
|
358 |
|
|
|
359 |
history_aware_retriever = create_history_aware_retriever(llm, retriever, contextualize_q_prompt)
|
360 |
question_answer_chain = create_stuff_documents_chain(llm, qa_prompt)
|
361 |
|
362 |
return create_retrieval_chain(history_aware_retriever, question_answer_chain)
|
363 |
|
364 |
def process_documents(uploaded_files: List[Any]) -> List[Any]:
|
|
|
|
|
|
|
|
|
365 |
documents = []
|
366 |
progress_bar = st.progress(0)
|
367 |
|
368 |
for i, uploaded_file in enumerate(uploaded_files):
|
369 |
try:
|
|
|
370 |
with tempfile.NamedTemporaryFile(delete=False, suffix=".pdf") as temp_file:
|
371 |
temp_file.write(uploaded_file.getvalue())
|
372 |
temp_file_path = temp_file.name
|
373 |
|
|
|
374 |
loader = PyPDFLoader(temp_file_path)
|
375 |
docs = loader.load()
|
376 |
documents.extend(docs)
|
377 |
-
os.unlink(temp_file_path)
|
378 |
|
|
|
379 |
progress_bar.progress((i + 1) / len(uploaded_files))
|
380 |
|
381 |
except Exception as e:
|
382 |
logger.error(f"Erro ao processar {uploaded_file.name}: {str(e)}")
|
383 |
st.error(f"Erro ao processar {uploaded_file.name}")
|
384 |
|
385 |
-
progress_bar.empty()
|
386 |
return documents
|
387 |
|
388 |
def display_chat_interface():
|
389 |
-
"""
|
390 |
-
|
|
|
|
|
|
|
391 |
chat_container = st.container()
|
392 |
|
393 |
-
# Container fixo para
|
394 |
input_container = st.container()
|
395 |
|
396 |
-
#
|
397 |
with input_container:
|
398 |
user_input = st.text_input("💭 Sua pergunta:", key=f"user_input_{len(st.session_state.get('messages', []))}")
|
399 |
|
400 |
-
#
|
401 |
with chat_container:
|
402 |
if 'messages' in st.session_state:
|
403 |
for msg in st.session_state.messages:
|
@@ -406,34 +487,47 @@ def display_chat_interface():
|
|
406 |
return user_input
|
407 |
|
408 |
def update_chat_history(user_input: str, assistant_response: Any):
|
|
|
|
|
|
|
|
|
409 |
if 'messages' not in st.session_state:
|
410 |
st.session_state.messages = []
|
411 |
|
412 |
-
#
|
413 |
st.session_state.messages.append({"role": "user", "content": user_input})
|
414 |
st.session_state.messages.append({"role": "assistant", "content": assistant_response})
|
415 |
|
416 |
def main():
|
|
|
|
|
|
|
|
|
|
|
417 |
init_page_config()
|
418 |
apply_custom_css()
|
419 |
create_sidebar()
|
420 |
|
|
|
421 |
st.markdown('<h1 class="main-title">Chatbot com modelo de IA especializado em Português do Brasil - entrevista PDFs 📚</h1>', unsafe_allow_html=True)
|
422 |
|
|
|
423 |
col1, col2 = st.columns(2)
|
424 |
with col1:
|
425 |
maritaca_api_key = st.text_input("Chave API Maritaca:", type="password")
|
426 |
with col2:
|
427 |
huggingface_api_token = st.text_input("Token API Hugging Face:", type="password")
|
428 |
|
|
|
429 |
if not (maritaca_api_key and huggingface_api_token):
|
430 |
st.warning("⚠️ Insira as chaves de API para continuar")
|
431 |
return
|
432 |
|
433 |
-
#
|
434 |
os.environ["HUGGINGFACEHUB_API_TOKEN"] = huggingface_api_token
|
435 |
|
436 |
try:
|
|
|
437 |
maritalk_model = MariTalk(key=maritaca_api_key, model="sabia-3")
|
438 |
llm = MariTalkWrapper(maritalk_model)
|
439 |
embeddings = HuggingFaceEmbeddings(model_name="all-MiniLM-L6-v2")
|
@@ -441,17 +535,19 @@ def main():
|
|
441 |
st.error(f"Erro ao inicializar modelos: {str(e)}")
|
442 |
return
|
443 |
|
444 |
-
#
|
445 |
if 'store' not in st.session_state:
|
446 |
st.session_state.store = {}
|
447 |
if 'messages' not in st.session_state:
|
448 |
st.session_state.messages = []
|
449 |
|
|
|
450 |
col1, col2 = st.columns([3, 1])
|
451 |
with col1:
|
452 |
session_id = st.text_input("ID da Sessão:", value=datetime.now().strftime("%Y%m%d_%H%M%S"))
|
453 |
with col2:
|
454 |
if st.button("🗑️ Limpar Chat"):
|
|
|
455 |
for key in ['messages', 'documents', 'documents_processed', 'rag_chain']:
|
456 |
if key in st.session_state:
|
457 |
del st.session_state[key]
|
@@ -461,7 +557,7 @@ def main():
|
|
461 |
st.success("Chat limpo com sucesso!")
|
462 |
st.rerun()
|
463 |
|
464 |
-
# Upload de arquivos
|
465 |
uploaded_files = st.file_uploader(
|
466 |
"Upload de PDFs:",
|
467 |
type="pdf",
|
@@ -483,7 +579,7 @@ def main():
|
|
483 |
st.session_state.documents = documents
|
484 |
st.session_state.documents_processed = True
|
485 |
|
486 |
-
#
|
487 |
try:
|
488 |
rag_chain = setup_rag_chain(documents, llm, embeddings)
|
489 |
st.session_state.rag_chain = rag_chain
|
@@ -494,11 +590,13 @@ def main():
|
|
494 |
return
|
495 |
|
496 |
try:
|
|
|
497 |
def get_session_history(session: str) -> BaseChatMessageHistory:
|
498 |
if session not in st.session_state.store:
|
499 |
st.session_state.store[session] = ChatMessageHistory()
|
500 |
return st.session_state.store[session]
|
501 |
|
|
|
502 |
conversational_rag_chain = RunnableWithMessageHistory(
|
503 |
st.session_state.rag_chain,
|
504 |
get_session_history,
|
@@ -511,24 +609,26 @@ def main():
|
|
511 |
st.error("Erro ao configurar o sistema")
|
512 |
return
|
513 |
|
514 |
-
# Interface
|
515 |
user_input = display_chat_interface()
|
516 |
|
|
|
517 |
if user_input:
|
518 |
with st.spinner("🤔 Pensando..."):
|
519 |
try:
|
|
|
520 |
response = conversational_rag_chain.invoke(
|
521 |
{"input": user_input},
|
522 |
config={"configurable": {"session_id": session_id}}
|
523 |
)
|
524 |
|
|
|
525 |
logger.info(f"Tipo da resposta: {type(response)}")
|
526 |
logger.info(f"Conteúdo da resposta: {str(response)[:200]}...")
|
527 |
|
528 |
-
#
|
529 |
update_chat_history(user_input, response)
|
530 |
|
531 |
-
# Atualizar o histórico do LangChain
|
532 |
history = get_session_history(session_id)
|
533 |
history.add_user_message(user_input)
|
534 |
if isinstance(response, dict) and 'answer' in response:
|
@@ -536,12 +636,13 @@ def main():
|
|
536 |
else:
|
537 |
history.add_ai_message(str(response))
|
538 |
|
539 |
-
#
|
540 |
st.rerun()
|
541 |
|
542 |
except Exception as e:
|
543 |
logger.error(f"Erro ao processar pergunta: {str(e)}", exc_info=True)
|
544 |
st.error(f"❌ Erro ao processar sua pergunta: {str(e)}")
|
545 |
|
|
|
546 |
if __name__ == "__main__":
|
547 |
main()
|
|
|
6 |
# Embeddings de texto usando o modelo all-MiniLM-L6-v2 do Hugging Face
|
7 |
##
|
8 |
|
9 |
+
"""
|
10 |
+
Chatbot com RAG (Retrieval Augmented Generation) para PDFs usando MaritacaAI
|
11 |
|
12 |
+
Este script implementa um chatbot que pode analisar documentos PDF usando:
|
13 |
+
- Streamlit para interface web
|
14 |
+
- LangChain para processamento de documentos e gerenciamento de chat
|
15 |
+
- Modelo sabia-3 da Maritaca AI para geração de respostas em Português
|
16 |
+
- Embeddings do Hugging Face para processamento de texto
|
|
|
|
|
|
|
17 |
|
18 |
+
"""
|
19 |
+
|
20 |
+
# Importação das bibliotecas principais
|
21 |
+
import streamlit as st # Framework para interface web
|
22 |
+
import os # Operações do sistema operacional
|
23 |
+
import tempfile # Manipulação de arquivos temporários
|
24 |
+
from typing import List, Dict, Any, Optional # Tipos para type hints
|
25 |
+
from tenacity import retry, stop_after_attempt, wait_exponential # Gerenciamento de retentativas
|
26 |
+
from cachetools import TTLCache # Cache com tempo de vida
|
27 |
+
import logging # Sistema de logging
|
28 |
+
from datetime import datetime # Manipulação de datas
|
29 |
+
|
30 |
+
# Configuração do sistema de logging para debug e monitoramento
|
31 |
logging.basicConfig(
|
32 |
level=logging.INFO,
|
33 |
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
|
34 |
)
|
35 |
logger = logging.getLogger(__name__)
|
36 |
|
37 |
+
# Desativa paralelismo dos tokenizers para evitar deadlocks
|
38 |
os.environ["TOKENIZERS_PARALLELISM"] = "false"
|
39 |
|
40 |
+
# Importações do LangChain para processamento de documentos e chat
|
41 |
from langchain.chains import create_history_aware_retriever, create_retrieval_chain
|
42 |
from langchain.chains.combine_documents import create_stuff_documents_chain
|
43 |
from langchain_community.chat_message_histories import ChatMessageHistory
|
|
|
51 |
from langchain_core.runnables import Runnable
|
52 |
from maritalk import MariTalk
|
53 |
|
54 |
+
# Cache para armazenar embeddings e melhorar performance
|
55 |
+
embeddings_cache = TTLCache(maxsize=100, ttl=3600) # Cache por 1 hora
|
56 |
|
57 |
class MariTalkWrapper(Runnable):
|
58 |
+
"""
|
59 |
+
Wrapper para integrar o modelo MaritacaAI com o LangChain.
|
60 |
+
Gerencia a comunicação com a API e formata mensagens.
|
61 |
+
"""
|
62 |
|
63 |
def __init__(self, maritalk_model: Any, max_retries: int = 3, timeout: int = 30):
|
64 |
+
"""
|
65 |
+
Inicializa o wrapper com configurações de retry e timeout
|
66 |
+
"""
|
67 |
self.maritalk_model = maritalk_model
|
68 |
self.max_retries = max_retries
|
69 |
self.timeout = timeout
|
70 |
|
71 |
@retry(stop=stop_after_attempt(3), wait=wait_exponential(multiplier=1, min=4, max=10))
|
72 |
def invoke(self, input: Any, config: Optional[Dict] = None) -> str:
|
73 |
+
"""
|
74 |
+
Processa entrada e gera resposta, com retries automáticos em caso de falha.
|
75 |
+
Suporta diferentes formatos de entrada: ChatPromptValue, dict, string
|
76 |
+
"""
|
77 |
try:
|
78 |
+
# Processamento de ChatPromptValue (formato LangChain)
|
79 |
if hasattr(input, "to_messages"):
|
80 |
messages = input.to_messages()
|
81 |
formatted_messages = self._format_messages(messages)
|
|
|
87 |
else:
|
88 |
return response[0]['content'] if isinstance(response, list) else str(response)
|
89 |
|
90 |
+
# Processamento de dicionário
|
91 |
elif isinstance(input, dict):
|
92 |
if "messages" in input:
|
93 |
messages = input["messages"]
|
|
|
104 |
else:
|
105 |
return self._process_text(str(input))
|
106 |
|
107 |
+
# Processamento de string simples
|
108 |
elif isinstance(input, str):
|
109 |
return self._process_text(input)
|
110 |
|
|
|
118 |
raise
|
119 |
|
120 |
def _format_messages(self, messages: List[Any]) -> List[Dict[str, str]]:
|
121 |
+
"""
|
122 |
+
Formata mensagens para o formato esperado pela API da Maritaca
|
123 |
+
Converte entre formatos LangChain e Maritaca
|
124 |
+
"""
|
125 |
formatted = []
|
126 |
for msg in messages:
|
127 |
role = "user"
|
|
|
142 |
return formatted
|
143 |
|
144 |
def _process_text(self, text: str) -> str:
|
145 |
+
"""
|
146 |
+
Processa texto simples através do modelo Maritaca
|
147 |
+
Gerencia diferentes formatos de resposta possíveis
|
148 |
+
"""
|
149 |
response = self.maritalk_model.generate([{"role": "user", "content": text}])
|
150 |
if isinstance(response, str):
|
151 |
return response
|
|
|
157 |
return str(response)
|
158 |
|
159 |
def init_page_config():
|
160 |
+
"""
|
161 |
+
Inicializa a configuração da página Streamlit
|
162 |
+
Define título, layout e ícone
|
163 |
+
"""
|
164 |
st.set_page_config(
|
165 |
page_title="Chatbot com IA especializada em Português do Brasil - entrevista PDFs",
|
166 |
layout="wide",
|
|
|
169 |
)
|
170 |
|
171 |
def apply_custom_css():
|
172 |
+
"""
|
173 |
+
Aplica estilos CSS personalizados à interface
|
174 |
+
Define cores, formatos e layout dos elementos
|
175 |
+
"""
|
176 |
st.markdown("""
|
177 |
<style>
|
178 |
+
/* Estilo global e tema dark */
|
179 |
.stApp {
|
180 |
background-color: #0e1117;
|
181 |
color: #fafafa;
|
182 |
}
|
183 |
+
|
184 |
+
/* Formatação das mensagens do chat */
|
185 |
.chat-message {
|
186 |
padding: 1rem;
|
187 |
border-radius: 0.5rem;
|
|
|
196 |
.assistant-message {
|
197 |
background-color: #1e1e1e;
|
198 |
}
|
199 |
+
|
200 |
+
/* Elementos do chat */
|
201 |
.chat-header {
|
202 |
font-weight: bold;
|
203 |
margin-bottom: 0.5rem;
|
|
|
214 |
border-top: 1px solid #444;
|
215 |
padding-top: 8px;
|
216 |
}
|
217 |
+
|
218 |
+
/* Elementos da interface */
|
219 |
.main-title {
|
220 |
color: #FFA500;
|
221 |
font-size: 2.5em;
|
|
|
228 |
border-radius: 20px;
|
229 |
padding: 10px 20px;
|
230 |
}
|
231 |
+
|
232 |
+
/* Esconde elementos desnecessários */
|
233 |
div[data-testid="stToolbar"] {
|
234 |
display: none;
|
235 |
}
|
236 |
.stDeployButton {
|
237 |
display: none;
|
238 |
}
|
239 |
+
|
240 |
+
/* Informações de tokens */
|
241 |
.token-info {
|
242 |
font-style: italic;
|
243 |
color: #888;
|
|
|
249 |
""", unsafe_allow_html=True)
|
250 |
|
251 |
def create_sidebar():
|
252 |
+
"""
|
253 |
+
Cria a barra lateral com instruções e informações importantes
|
254 |
+
Inclui links para obtenção de API keys e avisos
|
255 |
+
"""
|
256 |
st.sidebar.markdown("## Orientações")
|
257 |
st.sidebar.markdown("""
|
258 |
* Se encontrar erros de processamento, reinicie com F5. Utilize arquivos .PDF com textos não digitalizados como imagens.
|
|
|
262 |
* Você pode fazer uma conta na MaritacaAI e obter uma chave de API [aqui](https://plataforma.maritaca.ai/)
|
263 |
* Você pode fazer uma conta no Hugging Face e obter o token de API Hugging Face [aqui](https://huggingface.co/docs/hub/security-tokens)
|
264 |
|
265 |
+
**Atenção:** Os documentos que você compartilhar com o modelo de IA generativa podem ser usados pelo LLM para treinar o sistema.
|
266 |
+
Portanto, evite compartilhar documentos PDF que contenham:
|
267 |
1. Dados bancários e financeiros
|
268 |
2. Dados de sua própria empresa
|
269 |
3. Informações pessoais
|
270 |
4. Informações de propriedade intelectual
|
271 |
5. Conteúdos autorais
|
272 |
|
273 |
+
E não use IA para escrever um texto inteiro! O auxílio é melhor para gerar resumos, filtrar informações ou auxiliar a
|
274 |
+
entender contextos - que depois devem ser checados. Inteligência Artificial comete erros (alucinações, viés, baixa qualidade,
|
275 |
+
problemas éticos)!
|
276 |
|
277 |
Este projeto não se responsabiliza pelos conteúdos criados a partir deste site.
|
278 |
|
279 |
**Sobre este app**
|
280 |
+
Este aplicativo foi desenvolvido por Reinaldo Chaves. Para mais informações, contribuições e feedback, visite o
|
281 |
+
[repositório](https://github.com/reichaves/chatbotmaritacaai)
|
282 |
""")
|
283 |
|
284 |
def display_chat_message(message: str, is_user: bool):
|
285 |
+
"""
|
286 |
+
Exibe uma mensagem no chat
|
287 |
+
Formata diferentemente mensagens do usuário e do assistente
|
288 |
+
Inclui contagem de tokens para respostas do assistente
|
289 |
+
"""
|
290 |
class_name = "user-message" if is_user else "assistant-message"
|
291 |
role = "Você" if is_user else "Assistente"
|
292 |
|
293 |
+
# Formata resposta do assistente
|
294 |
if not is_user:
|
295 |
if isinstance(message, dict):
|
296 |
content = message.get('answer', str(message))
|
297 |
tokens = message.get('usage', {}).get('total_tokens', None)
|
298 |
|
299 |
+
# Quebras de linha em HTML
|
300 |
content = content.replace('\n', '<br>')
|
301 |
|
302 |
if tokens:
|
|
|
306 |
else:
|
307 |
content = message
|
308 |
|
309 |
+
# Renderiza mensagem com HTML
|
310 |
st.markdown(f"""
|
311 |
<div class="chat-message {class_name}">
|
312 |
<div class="chat-header">{role}:</div>
|
|
|
315 |
""", unsafe_allow_html=True)
|
316 |
|
317 |
def setup_rag_chain(documents: List[Any], llm: Any, embeddings: Any) -> Any:
|
318 |
+
"""
|
319 |
+
Configura a chain RAG (Retrieval Augmented Generation)
|
320 |
+
Processa documentos e configura sistema de recuperação e resposta
|
321 |
+
"""
|
322 |
+
# Divide documentos em chunks menores
|
323 |
text_splitter = RecursiveCharacterTextSplitter(
|
324 |
+
chunk_size=1000, # Tamanho de cada chunk
|
325 |
+
chunk_overlap=200, # Sobreposição entre chunks
|
326 |
length_function=len,
|
327 |
is_separator_regex=False
|
328 |
)
|
329 |
|
330 |
+
# Cria banco de vetores com FAISS
|
331 |
splits = text_splitter.split_documents(documents)
|
332 |
vectorstore = FAISS.from_documents(splits, embeddings)
|
333 |
retriever = vectorstore.as_retriever()
|
334 |
|
335 |
+
# Prompt para contextualização de perguntas
|
336 |
contextualize_q_prompt = ChatPromptTemplate.from_messages([
|
337 |
("system", """
|
338 |
Você é um assistente especializado em analisar documentos PDF com um contexto jornalístico,
|
|
|
372 |
("human", "{input}"),
|
373 |
])
|
374 |
|
375 |
+
# Prompt para respostas
|
376 |
qa_prompt = ChatPromptTemplate.from_messages([
|
377 |
("system", """
|
378 |
Você é um assistente especializado em análise de documentos.
|
|
|
426 |
("human", "{input}"),
|
427 |
])
|
428 |
|
429 |
+
# Cria chain com histórico
|
430 |
history_aware_retriever = create_history_aware_retriever(llm, retriever, contextualize_q_prompt)
|
431 |
question_answer_chain = create_stuff_documents_chain(llm, qa_prompt)
|
432 |
|
433 |
return create_retrieval_chain(history_aware_retriever, question_answer_chain)
|
434 |
|
435 |
def process_documents(uploaded_files: List[Any]) -> List[Any]:
|
436 |
+
"""
|
437 |
+
Processa arquivos PDF enviados
|
438 |
+
Carrega documentos e mostra progresso
|
439 |
+
"""
|
440 |
documents = []
|
441 |
progress_bar = st.progress(0)
|
442 |
|
443 |
for i, uploaded_file in enumerate(uploaded_files):
|
444 |
try:
|
445 |
+
# Cria arquivo temporário
|
446 |
with tempfile.NamedTemporaryFile(delete=False, suffix=".pdf") as temp_file:
|
447 |
temp_file.write(uploaded_file.getvalue())
|
448 |
temp_file_path = temp_file.name
|
449 |
|
450 |
+
# Carrega PDF
|
451 |
loader = PyPDFLoader(temp_file_path)
|
452 |
docs = loader.load()
|
453 |
documents.extend(docs)
|
454 |
+
os.unlink(temp_file_path) # Remove arquivo temporário
|
455 |
|
456 |
+
# Atualiza barra de progresso
|
457 |
progress_bar.progress((i + 1) / len(uploaded_files))
|
458 |
|
459 |
except Exception as e:
|
460 |
logger.error(f"Erro ao processar {uploaded_file.name}: {str(e)}")
|
461 |
st.error(f"Erro ao processar {uploaded_file.name}")
|
462 |
|
463 |
+
progress_bar.empty() # Remove barra de progresso
|
464 |
return documents
|
465 |
|
466 |
def display_chat_interface():
|
467 |
+
"""
|
468 |
+
Exibe a interface do chat com campo de entrada fixo
|
469 |
+
Gerencia histórico e entrada do usuário
|
470 |
+
"""
|
471 |
+
# Container para histórico do chat
|
472 |
chat_container = st.container()
|
473 |
|
474 |
+
# Container fixo para campo de entrada
|
475 |
input_container = st.container()
|
476 |
|
477 |
+
# Campo de entrada de texto
|
478 |
with input_container:
|
479 |
user_input = st.text_input("💭 Sua pergunta:", key=f"user_input_{len(st.session_state.get('messages', []))}")
|
480 |
|
481 |
+
# Exibe histórico de mensagens
|
482 |
with chat_container:
|
483 |
if 'messages' in st.session_state:
|
484 |
for msg in st.session_state.messages:
|
|
|
487 |
return user_input
|
488 |
|
489 |
def update_chat_history(user_input: str, assistant_response: Any):
|
490 |
+
"""
|
491 |
+
Atualiza o histórico do chat com novas mensagens
|
492 |
+
Mantém conversas no estado da sessão
|
493 |
+
"""
|
494 |
if 'messages' not in st.session_state:
|
495 |
st.session_state.messages = []
|
496 |
|
497 |
+
# Adiciona mensagens ao histórico
|
498 |
st.session_state.messages.append({"role": "user", "content": user_input})
|
499 |
st.session_state.messages.append({"role": "assistant", "content": assistant_response})
|
500 |
|
501 |
def main():
|
502 |
+
"""
|
503 |
+
Função principal do aplicativo
|
504 |
+
Gerencia todo o fluxo da aplicação
|
505 |
+
"""
|
506 |
+
# Inicialização da interface
|
507 |
init_page_config()
|
508 |
apply_custom_css()
|
509 |
create_sidebar()
|
510 |
|
511 |
+
# Título principal
|
512 |
st.markdown('<h1 class="main-title">Chatbot com modelo de IA especializado em Português do Brasil - entrevista PDFs 📚</h1>', unsafe_allow_html=True)
|
513 |
|
514 |
+
# Campos de API em duas colunas
|
515 |
col1, col2 = st.columns(2)
|
516 |
with col1:
|
517 |
maritaca_api_key = st.text_input("Chave API Maritaca:", type="password")
|
518 |
with col2:
|
519 |
huggingface_api_token = st.text_input("Token API Hugging Face:", type="password")
|
520 |
|
521 |
+
# Verifica chaves de API
|
522 |
if not (maritaca_api_key and huggingface_api_token):
|
523 |
st.warning("⚠️ Insira as chaves de API para continuar")
|
524 |
return
|
525 |
|
526 |
+
# Configura ambiente com token Hugging Face
|
527 |
os.environ["HUGGINGFACEHUB_API_TOKEN"] = huggingface_api_token
|
528 |
|
529 |
try:
|
530 |
+
# Inicializa modelos de IA
|
531 |
maritalk_model = MariTalk(key=maritaca_api_key, model="sabia-3")
|
532 |
llm = MariTalkWrapper(maritalk_model)
|
533 |
embeddings = HuggingFaceEmbeddings(model_name="all-MiniLM-L6-v2")
|
|
|
535 |
st.error(f"Erro ao inicializar modelos: {str(e)}")
|
536 |
return
|
537 |
|
538 |
+
# Inicializa estado da sessão
|
539 |
if 'store' not in st.session_state:
|
540 |
st.session_state.store = {}
|
541 |
if 'messages' not in st.session_state:
|
542 |
st.session_state.messages = []
|
543 |
|
544 |
+
# ID da sessão e botão limpar
|
545 |
col1, col2 = st.columns([3, 1])
|
546 |
with col1:
|
547 |
session_id = st.text_input("ID da Sessão:", value=datetime.now().strftime("%Y%m%d_%H%M%S"))
|
548 |
with col2:
|
549 |
if st.button("🗑️ Limpar Chat"):
|
550 |
+
# Limpa todos os dados da sessão
|
551 |
for key in ['messages', 'documents', 'documents_processed', 'rag_chain']:
|
552 |
if key in st.session_state:
|
553 |
del st.session_state[key]
|
|
|
557 |
st.success("Chat limpo com sucesso!")
|
558 |
st.rerun()
|
559 |
|
560 |
+
# Upload de arquivos PDF
|
561 |
uploaded_files = st.file_uploader(
|
562 |
"Upload de PDFs:",
|
563 |
type="pdf",
|
|
|
579 |
st.session_state.documents = documents
|
580 |
st.session_state.documents_processed = True
|
581 |
|
582 |
+
# Configura RAG chain
|
583 |
try:
|
584 |
rag_chain = setup_rag_chain(documents, llm, embeddings)
|
585 |
st.session_state.rag_chain = rag_chain
|
|
|
590 |
return
|
591 |
|
592 |
try:
|
593 |
+
# Configuração do histórico da sessão
|
594 |
def get_session_history(session: str) -> BaseChatMessageHistory:
|
595 |
if session not in st.session_state.store:
|
596 |
st.session_state.store[session] = ChatMessageHistory()
|
597 |
return st.session_state.store[session]
|
598 |
|
599 |
+
# Criação da chain conversacional
|
600 |
conversational_rag_chain = RunnableWithMessageHistory(
|
601 |
st.session_state.rag_chain,
|
602 |
get_session_history,
|
|
|
609 |
st.error("Erro ao configurar o sistema")
|
610 |
return
|
611 |
|
612 |
+
# Interface do chat
|
613 |
user_input = display_chat_interface()
|
614 |
|
615 |
+
# Processamento da entrada do usuário
|
616 |
if user_input:
|
617 |
with st.spinner("🤔 Pensando..."):
|
618 |
try:
|
619 |
+
# Gera resposta
|
620 |
response = conversational_rag_chain.invoke(
|
621 |
{"input": user_input},
|
622 |
config={"configurable": {"session_id": session_id}}
|
623 |
)
|
624 |
|
625 |
+
# Logging da resposta
|
626 |
logger.info(f"Tipo da resposta: {type(response)}")
|
627 |
logger.info(f"Conteúdo da resposta: {str(response)[:200]}...")
|
628 |
|
629 |
+
# Atualiza históricos
|
630 |
update_chat_history(user_input, response)
|
631 |
|
|
|
632 |
history = get_session_history(session_id)
|
633 |
history.add_user_message(user_input)
|
634 |
if isinstance(response, dict) and 'answer' in response:
|
|
|
636 |
else:
|
637 |
history.add_ai_message(str(response))
|
638 |
|
639 |
+
# Atualiza interface
|
640 |
st.rerun()
|
641 |
|
642 |
except Exception as e:
|
643 |
logger.error(f"Erro ao processar pergunta: {str(e)}", exc_info=True)
|
644 |
st.error(f"❌ Erro ao processar sua pergunta: {str(e)}")
|
645 |
|
646 |
+
# Ponto de entrada do aplicativo
|
647 |
if __name__ == "__main__":
|
648 |
main()
|