reichaves commited on
Commit
c3becbd
·
unverified ·
1 Parent(s): f47b254

Add files via upload

Browse files
Files changed (2) hide show
  1. app.py +547 -0
  2. requirements.txt +103 -0
app.py ADDED
@@ -0,0 +1,547 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # -*- coding: utf-8
2
+ # Reinaldo Chaves ([email protected])
3
+ # Este projeto implementa um sistema de Recuperação de Informações Aumentada por Geração (RAG) conversacional
4
+ # usando Streamlit, LangChain, e modelos de linguagem de grande escala - para entrevistar PDFs
5
+ # Geração de respostas usando o modelo sabia-3 da Maritaca AI especializado em Português do Brasil
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
33
+ from langchain_core.chat_history import BaseChatMessageHistory
34
+ from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
35
+ from langchain_core.runnables.history import RunnableWithMessageHistory
36
+ from langchain_huggingface import HuggingFaceEmbeddings
37
+ from langchain_text_splitters import RecursiveCharacterTextSplitter
38
+ from langchain_community.document_loaders import PyPDFLoader
39
+ 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)
61
+ response = self.maritalk_model.generate(formatted_messages)
62
+ if isinstance(response, str):
63
+ return response
64
+ elif isinstance(response, dict) and 'text' in response:
65
+ return response['text']
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"]
73
+ formatted_messages = self._format_messages(messages)
74
+ response = self.maritalk_model.generate(formatted_messages)
75
+ if isinstance(response, str):
76
+ return response
77
+ elif isinstance(response, dict) and 'text' in response:
78
+ return response['text']
79
+ else:
80
+ return response[0]['content'] if isinstance(response, list) else str(response)
81
+ elif "answer" in input:
82
+ return str(input["answer"])
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
+
90
+ else:
91
+ raise TypeError(f"Tipo de input não suportado: {type(input)}")
92
+
93
+ except Exception as e:
94
+ logger.error(f"Erro na chamada à API MariTalk: {str(e)}")
95
+ logger.error(f"Input recebido: {str(input)[:200]}")
96
+ logger.error(f"Tipo do input: {type(input)}")
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"
103
+ if hasattr(msg, "type"):
104
+ if msg.type == "human":
105
+ role = "user"
106
+ elif msg.type in ["ai", "assistant"]:
107
+ role = "assistant"
108
+ elif msg.type == "system":
109
+ role = "system"
110
+ formatted.append({"role": role, "content": msg.content})
111
+ elif isinstance(msg, dict):
112
+ role = msg.get("role", "user")
113
+ content = msg.get("content", "")
114
+ formatted.append({"role": role, "content": content})
115
+ else:
116
+ formatted.append({"role": "user", "content": str(msg)})
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
123
+ elif isinstance(response, dict) and 'text' in response:
124
+ return response['text']
125
+ elif isinstance(response, list) and len(response) > 0:
126
+ return response[0].get('content', str(response))
127
+ else:
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",
134
+ initial_sidebar_state="expanded",
135
+ page_icon="📚"
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;
148
+ margin-bottom: 1rem;
149
+ display: flex;
150
+ flex-direction: column;
151
+ width: 100%;
152
+ }
153
+ .user-message {
154
+ background-color: #2e2e2e;
155
+ }
156
+ .assistant-message {
157
+ background-color: #1e1e1e;
158
+ }
159
+ .chat-header {
160
+ font-weight: bold;
161
+ margin-bottom: 0.5rem;
162
+ }
163
+ .chat-content {
164
+ margin-left: 1rem;
165
+ white-space: pre-line;
166
+ }
167
+ .chat-content em {
168
+ color: #888;
169
+ font-size: 0.9em;
170
+ display: block;
171
+ margin-top: 10px;
172
+ border-top: 1px solid #444;
173
+ padding-top: 8px;
174
+ }
175
+ .main-title {
176
+ color: #FFA500;
177
+ font-size: 2.5em;
178
+ font-weight: bold;
179
+ margin-bottom: 30px;
180
+ }
181
+ .stButton > button {
182
+ background-color: #262730;
183
+ color: #4F8BF9;
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;
196
+ margin-top: 10px;
197
+ padding-top: 8px;
198
+ border-top: 1px solid #444;
199
+ }
200
+ </style>
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.
207
+ * Para recomeçar uma nova sessão pressione F5.
208
+
209
+ **Obtenção de chaves de API:**
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/rag_chat_llama3)
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:
242
+ content = f"{content}<br><br><em>Total tokens: {tokens}</em>"
243
+ else:
244
+ content = str(message)
245
+ else:
246
+ content = message
247
+
248
+ st.markdown(f"""
249
+ <div class="chat-message {class_name}">
250
+ <div class="chat-header">{role}:</div>
251
+ <div class="chat-content">{content}</div>
252
+ </div>
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,
270
+ como documentos da Lei de Acesso à Informação, contratos públicos e processos judiciais.
271
+ Sempre coloque no final das respostas: 'Todas as informações devem ser checadas com a(s) fonte(s) original(ais)'
272
+ Responda em Português do Brasil a menos que seja pedido outro idioma
273
+ Se você não sabe a resposta, diga que não sabe
274
+ Siga estas diretrizes:\n\n
275
+ 1. Explique os passos de forma simples e mantenha as respostas concisas.\n
276
+ 2. Inclua links para ferramentas, pesquisas e páginas da Web citadas.\n
277
+ 3. Ao resumir passagens, escreva em nível universitário.\n
278
+ 4. Divida tópicos em partes menores e fáceis de entender quando relevante.\n
279
+ 5. Seja claro, breve, ordenado e direto nas respostas.\n
280
+ 6. Evite opiniões e mantenha-se neutro.\n
281
+ 7. Base-se nas classes processuais do Direito no Brasil conforme o site do CNJ.\n
282
+ 8. Se não souber a resposta, admita que não sabe.\n\n
283
+ Ao analisar processos judiciais, priorize:\n
284
+ - Identificar se é petição inicial, decisão ou sentença\n
285
+ - Apresentar a ação e suas partes\n
286
+ - Explicar os motivos do ajuizamento\n
287
+ - Listar os requerimentos do autor\n
288
+ - Expor o resultado das decisões\n
289
+ - Indicar o status do processo\n\n
290
+ Para licitações ou contratos públicos, considere as etapas do processo licitatório e as modalidades de licitação.\n\n
291
+ Para documentos da Lei de Acesso à Informação (LAI), inclua:\n
292
+ - Data\n
293
+ - Protocolo NUP\n
294
+ - Nome do órgão público\n
295
+ - Nomes dos responsáveis pela resposta\n
296
+ - Data da resposta\n
297
+ - Se o pedido foi totalmente atendido, parcialmente ou negado\n\n
298
+ Use o seguinte contexto para responder à pergunta: {context}\n\n
299
+ Sempre termine as respostas com: 'Todas as informações precisam ser checadas com as fontes das informações'."
300
+ )
301
+ """),
302
+ MessagesPlaceholder("chat_history"),
303
+ ("human", "{input}"),
304
+ ])
305
+
306
+ qa_prompt = ChatPromptTemplate.from_messages([
307
+ ("system", """
308
+ Você é um assistente especializado em análise de documentos.
309
+
310
+ Use este contexto para responder à pergunta: {context}
311
+
312
+ Diretrizes:
313
+ 1. Use o contexto fornecido E o histórico do chat para suas respostas
314
+ 2. Mantenha consistência com respostas anteriores
315
+ 3. Se uma informação não estiver no contexto mas foi mencionada antes, você pode usá-la
316
+ 4. Seja conciso mas mantenha a coerência com o histórico
317
+ 5. Se houver contradição entre o histórico e o novo contexto, mencione isso
318
+
319
+ Responda em Português do Brasil.
320
+
321
+ Mais orientações:
322
+ Você é um assistente especializado em analisar documentos PDF com um contexto jornalístico,
323
+ como documentos da Lei de Acesso à Informação, contratos públicos e processos judiciais.
324
+ Sempre coloque no final das respostas: 'Todas as informações devem ser checadas com a(s) fonte(s) original(ais)'
325
+ Responda em Português do Brasil a menos que seja pedido outro idioma
326
+ Se você não sabe a resposta, diga que não sabe
327
+ Siga estas diretrizes:\n\n
328
+ 1. Explique os passos de forma simples e mantenha as respostas concisas.\n
329
+ 2. Inclua links para ferramentas, pesquisas e páginas da Web citadas.\n
330
+ 3. Ao resumir passagens, escreva em nível universitário.\n
331
+ 4. Divida tópicos em partes menores e fáceis de entender quando relevante.\n
332
+ 5. Seja claro, breve, ordenado e direto nas respostas.\n
333
+ 6. Evite opiniões e mantenha-se neutro.\n
334
+ 7. Base-se nas classes processuais do Direito no Brasil conforme o site do CNJ.\n
335
+ 8. Se não souber a resposta, admita que não sabe.\n\n
336
+ Ao analisar processos judiciais, priorize:\n
337
+ - Identificar se é petição inicial, decisão ou sentença\n
338
+ - Apresentar a ação e suas partes\n
339
+ - Explicar os motivos do ajuizamento\n
340
+ - Listar os requerimentos do autor\n
341
+ - Expor o resultado das decisões\n
342
+ - Indicar o status do processo\n\n
343
+ Para licitações ou contratos públicos, considere as etapas do processo licitatório e as modalidades de licitação.\n\n
344
+ Para documentos da Lei de Acesso à Informação (LAI), inclua:\n
345
+ - Data\n
346
+ - Protocolo NUP\n
347
+ - Nome do órgão público\n
348
+ - Nomes dos responsáveis pela resposta\n
349
+ - Data da resposta\n
350
+ - Se o pedido foi totalmente atendido, parcialmente ou negado\n\n
351
+ Use o seguinte contexto para responder à pergunta: {context}\n\n
352
+ Sempre termine as respostas com: 'Todas as informações precisam ser checadas com as fontes das informações'."
353
+ )
354
+ """),
355
+ MessagesPlaceholder("chat_history"),
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:
404
+ display_chat_message(msg["content"], msg["role"] == "user")
405
+
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")
440
+ except Exception as e:
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]
458
+
459
+ if session_id in st.session_state.store:
460
+ st.session_state.store[session_id] = ChatMessageHistory()
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",
468
+ accept_multiple_files=True,
469
+ help="Selecione um ou mais arquivos PDF"
470
+ )
471
+
472
+ if not uploaded_files:
473
+ st.info("📤 Faça upload de PDFs para começar")
474
+ return
475
+
476
+ # Processamento de documentos
477
+ if uploaded_files:
478
+ if 'documents_processed' not in st.session_state or not st.session_state.documents_processed:
479
+ documents = process_documents(uploaded_files)
480
+ if not documents:
481
+ st.error("❌ Nenhum documento processado")
482
+ return
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
490
+ st.success(f"✅ {len(documents)} documentos processados")
491
+ except Exception as e:
492
+ logger.error(f"Erro ao configurar RAG chain: {str(e)}")
493
+ st.error("Erro ao configurar o sistema")
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,
505
+ input_messages_key="input",
506
+ history_messages_key="chat_history",
507
+ output_messages_key="answer"
508
+ )
509
+ except Exception as e:
510
+ logger.error(f"Erro ao configurar RAG chain: {str(e)}")
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:
535
+ history.add_ai_message(response['answer'])
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()
requirements.txt ADDED
@@ -0,0 +1,103 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ aiohappyeyeballs==2.4.4
2
+ aiohttp==3.11.11
3
+ aiosignal==1.3.2
4
+ altair==5.5.0
5
+ annotated-types==0.7.0
6
+ anyio==4.7.0
7
+ async-timeout==4.0.3
8
+ attrs==24.3.0
9
+ blinker==1.9.0
10
+ cachetools==5.5.0
11
+ certifi==2024.12.14
12
+ charset-normalizer==3.4.1
13
+ click==8.1.8
14
+ dataclasses-json==0.6.7
15
+ distro==1.9.0
16
+ exceptiongroup==1.2.2
17
+ faiss-cpu==1.9.0.post1
18
+ filelock==3.16.1
19
+ frozenlist==1.5.0
20
+ fsspec==2024.12.0
21
+ gitdb==4.0.12
22
+ GitPython==3.1.44
23
+ greenlet==3.1.1
24
+ h11==0.14.0
25
+ httpcore==1.0.7
26
+ httpx==0.28.1
27
+ httpx-sse==0.4.0
28
+ huggingface-hub==0.27.0
29
+ idna==3.10
30
+ Jinja2==3.1.5
31
+ jiter==0.8.2
32
+ joblib==1.4.2
33
+ jsonpatch==1.33
34
+ jsonpointer==3.0.0
35
+ jsonschema==4.23.0
36
+ jsonschema-specifications==2024.10.1
37
+ langchain==0.3.13
38
+ langchain-community==0.3.13
39
+ langchain-core==0.3.28
40
+ langchain-huggingface==0.1.2
41
+ langchain-text-splitters==0.3.4
42
+ langsmith==0.2.7
43
+ maritalk==0.2.6
44
+ markdown-it-py==3.0.0
45
+ MarkupSafe==3.0.2
46
+ marshmallow==3.23.2
47
+ mdurl==0.1.2
48
+ mpmath==1.3.0
49
+ multidict==6.1.0
50
+ mypy-extensions==1.0.0
51
+ narwhals==1.20.1
52
+ networkx==3.4.2
53
+ numpy==1.26.4
54
+ openai==1.58.1
55
+ orjson==3.10.13
56
+ packaging==24.2
57
+ pandas==2.2.3
58
+ pillow==11.1.0
59
+ propcache==0.2.1
60
+ protobuf==5.29.2
61
+ pyarrow==18.1.0
62
+ pydantic==2.10.4
63
+ pydantic-settings==2.7.1
64
+ pydantic_core==2.27.2
65
+ pydeck==0.9.1
66
+ Pygments==2.18.0
67
+ pypdf==5.1.0
68
+ PyPDF2==3.0.1
69
+ python-dateutil==2.9.0.post0
70
+ python-dotenv==1.0.1
71
+ pytz==2024.2
72
+ PyYAML==6.0.2
73
+ referencing==0.35.1
74
+ regex==2024.11.6
75
+ requests==2.32.3
76
+ requests-toolbelt==1.0.0
77
+ rich==13.9.4
78
+ rpds-py==0.22.3
79
+ safetensors==0.5.0
80
+ scikit-learn==1.6.0
81
+ scipy==1.14.1
82
+ sentence-transformers==3.3.1
83
+ six==1.17.0
84
+ smmap==5.0.2
85
+ sniffio==1.3.1
86
+ SQLAlchemy==2.0.36
87
+ streamlit==1.41.1
88
+ sympy==1.13.3
89
+ tenacity==9.0.0
90
+ threadpoolctl==3.5.0
91
+ tiktoken==0.7.0
92
+ tokenizers==0.21.0
93
+ toml==0.10.2
94
+ torch==2.2.2
95
+ tornado==6.4.2
96
+ tqdm==4.67.1
97
+ transformers==4.47.1
98
+ typing-inspect==0.9.0
99
+ typing_extensions==4.12.2
100
+ tzdata==2024.2
101
+ urllib3==2.3.0
102
+ watchdog==6.0.0
103
+ yarl==1.18.3