reichaves commited on
Commit
58f9077
·
unverified ·
1 Parent(s): 8eff883

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +141 -40
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
- import streamlit as st
11
- import os
12
- import tempfile
13
- from typing import List, Dict, Any, Optional
14
- from tenacity import retry, stop_after_attempt, wait_exponential
15
- from cachetools import TTLCache
16
- import logging
17
- from datetime import datetime
18
 
19
- # Configurar logging
 
 
 
 
 
 
 
 
 
 
 
 
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
- # Configurar ambiente
27
  os.environ["TOKENIZERS_PARALLELISM"] = "false"
28
 
29
- # Imports do LangChain
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
- """Wrapper para o modelo MariTalk compatível com LangChain"""
 
 
 
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
- # Lidar com ChatPromptValue
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
- # Lidar com dicionário
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
- # Lidar com string
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. Portanto, evite compartilhar documentos PDF que contenham:
 
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 entender contextos - que depois devem ser checados. Inteligência Artificial comete erros (alucinações, viés, baixa qualidade, problemas éticos)!
 
 
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 [repositório](https://github.com/reichaves/chatbotmaritacaai)
 
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
- # Se for resposta do assistente, extrair e formatar o texto
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
- # Substituir \n por <br> para quebras de linha HTML
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
- """Exibe a interface do chat com o campo de entrada fixo"""
390
- # Container para o histórico do chat
 
 
 
391
  chat_container = st.container()
392
 
393
- # Container fixo para o campo de entrada
394
  input_container = st.container()
395
 
396
- # Usar o container de entrada
397
  with input_container:
398
  user_input = st.text_input("💭 Sua pergunta:", key=f"user_input_{len(st.session_state.get('messages', []))}")
399
 
400
- # Usar o container do chat para exibir mensagens
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
- # Adicionar ao histórico
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
- # Configurar ambiente
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
- # Inicializar sessão se necessário
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
- # Criar RAG chain logo após processar documentos
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 de chat com campo de entrada fixo
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
- # Atualizar o histórico
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
- # Forçar rerun para atualizar a interface
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()