# -*- coding: utf-8
# Reinaldo Chaves (reichaves@gmail.com)
# Este projeto implementa um sistema de Recuperação de Informações Aumentada por Geração (RAG) conversacional
# usando Streamlit, LangChain, e modelos de linguagem de grande escala - para entrevistar PDFs
# Geração de respostas usando o modelo sabia-3 da Maritaca AI especializado em Português do Brasil
# Embeddings de texto usando o modelo all-MiniLM-L6-v2 do Hugging Face
##
"""
Chatbot com RAG (Retrieval Augmented Generation) para PDFs usando MaritacaAI
Este script implementa um chatbot que pode analisar documentos PDF usando:
- Streamlit para interface web
- LangChain para processamento de documentos e gerenciamento de chat
- Modelo sabia-3 da Maritaca AI para geração de respostas em Português
- Embeddings do Hugging Face para processamento de texto
"""
# Importação das bibliotecas principais
import streamlit as st # Framework para interface web
import os # Operações do sistema operacional
import tempfile # Manipulação de arquivos temporários
from typing import List, Dict, Any, Optional # Tipos para type hints
from tenacity import retry, stop_after_attempt, wait_exponential # Gerenciamento de retentativas
from cachetools import TTLCache # Cache com tempo de vida
import logging # Sistema de logging
from datetime import datetime # Manipulação de datas
# Configuração do sistema de logging para debug e monitoramento
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)
# Desativa paralelismo dos tokenizers para evitar deadlocks
os.environ["TOKENIZERS_PARALLELISM"] = "false"
# Importações do LangChain para processamento de documentos e chat
from langchain.chains import create_history_aware_retriever, create_retrieval_chain
from langchain.chains.combine_documents import create_stuff_documents_chain
from langchain_community.chat_message_histories import ChatMessageHistory
from langchain_core.chat_history import BaseChatMessageHistory
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain_core.runnables.history import RunnableWithMessageHistory
from langchain_huggingface import HuggingFaceEmbeddings
from langchain_text_splitters import RecursiveCharacterTextSplitter
from langchain_community.document_loaders import PyPDFLoader
from langchain_community.vectorstores import FAISS
from langchain_core.runnables import Runnable
from maritalk import MariTalk
# Cache para armazenar embeddings e melhorar performance
embeddings_cache = TTLCache(maxsize=100, ttl=3600) # Cache por 1 hora
class MariTalkWrapper(Runnable):
"""
Wrapper para integrar o modelo MaritacaAI com o LangChain.
Gerencia a comunicação com a API e formata mensagens.
"""
def __init__(self, maritalk_model: Any, max_retries: int = 3, timeout: int = 30):
"""
Inicializa o wrapper com configurações de retry e timeout
"""
self.maritalk_model = maritalk_model
self.max_retries = max_retries
self.timeout = timeout
@retry(stop=stop_after_attempt(3), wait=wait_exponential(multiplier=1, min=4, max=10))
def invoke(self, input: Any, config: Optional[Dict] = None) -> str:
"""
Processa entrada e gera resposta, com retries automáticos em caso de falha.
Suporta diferentes formatos de entrada: ChatPromptValue, dict, string
"""
try:
# Processamento de ChatPromptValue (formato LangChain)
if hasattr(input, "to_messages"):
messages = input.to_messages()
formatted_messages = self._format_messages(messages)
response = self.maritalk_model.generate(formatted_messages)
if isinstance(response, str):
return response
elif isinstance(response, dict) and 'text' in response:
return response['text']
else:
return response[0]['content'] if isinstance(response, list) else str(response)
# Processamento de dicionário
elif isinstance(input, dict):
if "messages" in input:
messages = input["messages"]
formatted_messages = self._format_messages(messages)
response = self.maritalk_model.generate(formatted_messages)
if isinstance(response, str):
return response
elif isinstance(response, dict) and 'text' in response:
return response['text']
else:
return response[0]['content'] if isinstance(response, list) else str(response)
elif "answer" in input:
return str(input["answer"])
else:
return self._process_text(str(input))
# Processamento de string simples
elif isinstance(input, str):
return self._process_text(input)
else:
raise TypeError(f"Tipo de input não suportado: {type(input)}")
except Exception as e:
logger.error(f"Erro na chamada à API MariTalk: {str(e)}")
logger.error(f"Input recebido: {str(input)[:200]}")
logger.error(f"Tipo do input: {type(input)}")
raise
def _format_messages(self, messages: List[Any]) -> List[Dict[str, str]]:
"""
Formata mensagens para o formato esperado pela API da Maritaca
Converte entre formatos LangChain e Maritaca
"""
formatted = []
for msg in messages:
role = "user"
if hasattr(msg, "type"):
if msg.type == "human":
role = "user"
elif msg.type in ["ai", "assistant"]:
role = "assistant"
elif msg.type == "system":
role = "system"
formatted.append({"role": role, "content": msg.content})
elif isinstance(msg, dict):
role = msg.get("role", "user")
content = msg.get("content", "")
formatted.append({"role": role, "content": content})
else:
formatted.append({"role": "user", "content": str(msg)})
return formatted
def _process_text(self, text: str) -> str:
"""
Processa texto simples através do modelo Maritaca
Gerencia diferentes formatos de resposta possíveis
"""
response = self.maritalk_model.generate([{"role": "user", "content": text}])
if isinstance(response, str):
return response
elif isinstance(response, dict) and 'text' in response:
return response['text']
elif isinstance(response, list) and len(response) > 0:
return response[0].get('content', str(response))
else:
return str(response)
def init_page_config():
"""
Inicializa a configuração da página Streamlit
Define título, layout e ícone
"""
st.set_page_config(
page_title="Chatbot com IA especializada em Português do Brasil - entrevista PDFs",
layout="wide",
initial_sidebar_state="expanded",
page_icon="📚"
)
def apply_custom_css():
"""
Aplica estilos CSS personalizados à interface.
Compatível com Streamlit e HuggingFace.
"""
st.markdown("""
""", unsafe_allow_html=True)
def create_sidebar():
"""
Cria a barra lateral com instruções e informações importantes
Inclui links para obtenção de API keys e avisos
"""
st.sidebar.markdown("## Orientações")
st.sidebar.markdown("""
* Se encontrar erros de processamento, reinicie com F5. Utilize arquivos .PDF com textos não digitalizados como imagens.
* Para recomeçar uma nova sessão pressione F5.
* As respostas que o modelo de IA sabia-3 encontrar vão estar no campo 'answer'. As demais informações detalham quantos tokens foram gastos na sua conta.
**Obtenção de chaves de API:**
* Você pode fazer uma conta na MaritacaAI e obter uma chave de API [aqui](https://plataforma.maritaca.ai/)
* Você pode fazer uma conta no Hugging Face e obter o token de API Hugging Face [aqui](https://huggingface.co./docs/hub/security-tokens)
Não use IA para escrever um texto inteiro! O auxílio é melhor para gerar resumos, filtrar informações ou auxiliar a
entender contextos - que depois devem ser checados. Inteligência Artificial comete erros (alucinações, viés, baixa qualidade,
problemas éticos)!
Este projeto não se responsabiliza pelos conteúdos criados a partir deste site.
**Sobre este app**
Este aplicativo foi desenvolvido por Reinaldo Chaves. Para mais informações, contribuições e feedback, visite o
[repositório](https://github.com/reichaves/chatbotmaritacaai)
""")
def display_chat_message(message: str, is_user: bool):
"""
Exibe uma mensagem no chat
Formata diferentemente mensagens do usuário e do assistente
Inclui contagem de tokens para respostas do assistente
"""
class_name = "user-message" if is_user else "assistant-message"
role = "Você" if is_user else "Assistente"
# Formata resposta do assistente
if not is_user:
if isinstance(message, dict):
content = message.get('answer', str(message))
tokens = message.get('usage', {}).get('total_tokens', None)
# Quebras de linha em HTML
content = content.replace('\n', '
')
if tokens:
content = f"{content}
Total tokens: {tokens}"
else:
content = str(message)
else:
content = message
# Renderiza mensagem com HTML
st.markdown(f"""