Aula 3: Memoria e Contexto para Chatbots
Video da aula
Video em breve. O conteudo completo em texto esta disponivel abaixo.
O que voce vai aprender
- Por que LLMs nao tem memoria por padrao
- Como implementar memoria de conversa com LangChain
- Diferentes tipos de memoria (buffer, resumo, janela)
- Combinar RAG + memoria para um chatbot completo
- Persistir conversas em disco
O problema: LLMs nao lembram
Cada chamada ao LLM e independente. Se voce perguntar “O que e Python?” e depois “Quais sao seus principais frameworks?”, o modelo nao sabe que “seus” se refere a Python. Precisamos enviar o historico manualmente.
1. Memoria simples com buffer
import os
from dotenv import load_dotenv
from langchain_openai import ChatOpenAI
from langchain_core.messages import HumanMessage, AIMessage, SystemMessage
load_dotenv()
llm = ChatOpenAI(model="gpt-4o-mini", temperature=0.7)
system = SystemMessage(content="Voce e um assistente tecnico de Python. Responda em portugues.")
# Historico manual
history = [system]
def chat(pergunta: str) -> str:
history.append(HumanMessage(content=pergunta))
response = llm.invoke(history)
history.append(AIMessage(content=response.content))
return response.content
# Teste
print(chat("O que e FastAPI?"))
print(chat("Como instalo ele?")) # "ele" = FastAPI, o modelo entende!
print(chat("Mostre um exemplo basico")) # continua no contexto
Funciona, mas tem um problema: o historico cresce infinitamente e voce paga por todos os tokens a cada chamada.
2. Memoria com janela (ultimas N mensagens)
from collections import deque
class WindowMemory:
"""Mantem apenas as ultimas N trocas de mensagem."""
def __init__(self, max_pairs: int = 10):
self.max_pairs = max_pairs
self.messages = deque(maxlen=max_pairs * 2) # human + ai = 2 msgs
def add_user(self, content: str):
self.messages.append(HumanMessage(content=content))
def add_ai(self, content: str):
self.messages.append(AIMessage(content=content))
def get_messages(self) -> list:
return list(self.messages)
def clear(self):
self.messages.clear()
memory = WindowMemory(max_pairs=10)
def chat_with_memory(pergunta: str) -> str:
memory.add_user(pergunta)
messages = [system] + memory.get_messages()
response = llm.invoke(messages)
memory.add_ai(response.content)
return response.content
Agora so as ultimas 10 trocas sao enviadas. Conversas longas nao estouram o contexto.
3. Memoria com resumo
Para conversas muito longas, voce pode resumir o historico antigo:
from langchain_openai import ChatOpenAI
summarizer = ChatOpenAI(model="gpt-4o-mini", temperature=0)
class SummaryMemory:
"""Resume conversas antigas para manter contexto sem gastar tokens."""
def __init__(self, max_recent: int = 6):
self.max_recent = max_recent
self.recent = []
self.summary = ""
def add_exchange(self, human: str, ai: str):
self.recent.append(HumanMessage(content=human))
self.recent.append(AIMessage(content=ai))
# Quando ultrapassa o limite, resume as mais antigas
if len(self.recent) > self.max_recent * 2:
old = self.recent[:4] # 2 trocas mais antigas
self.recent = self.recent[4:]
self._summarize(old)
def _summarize(self, messages: list):
text = "\n".join([f"{'User' if isinstance(m, HumanMessage) else 'AI'}: {m.content}" for m in messages])
prompt = f"Resuma esta conversa em 2-3 frases:\n\n{text}"
result = summarizer.invoke([HumanMessage(content=prompt)])
if self.summary:
self.summary += " " + result.content
else:
self.summary = result.content
def get_messages(self) -> list:
msgs = []
if self.summary:
msgs.append(SystemMessage(content=f"Resumo da conversa anterior: {self.summary}"))
msgs.extend(self.recent)
return msgs
4. Persistindo conversas em JSON
import json
from datetime import datetime
def save_conversation(memory: WindowMemory, filepath: str = "conversa.json"):
"""Salva o historico em JSON."""
data = {
"timestamp": datetime.now().isoformat(),
"messages": [
{"role": "human" if isinstance(m, HumanMessage) else "ai", "content": m.content}
for m in memory.get_messages()
]
}
with open(filepath, "w", encoding="utf-8") as f:
json.dump(data, f, ensure_ascii=False, indent=2)
def load_conversation(filepath: str = "conversa.json") -> WindowMemory:
"""Carrega historico de um JSON."""
memory = WindowMemory()
try:
with open(filepath, "r", encoding="utf-8") as f:
data = json.load(f)
for msg in data["messages"]:
if msg["role"] == "human":
memory.add_user(msg["content"])
else:
memory.add_ai(msg["content"])
except FileNotFoundError:
pass
return memory
5. Chatbot completo com RAG + memoria
Combinando as aulas 2 e 3:
from langchain_community.vectorstores import Chroma
from langchain_community.embeddings import HuggingFaceEmbeddings
# Carregar vectorstore existente (criado na Aula 2)
embeddings = HuggingFaceEmbeddings(model_name="all-MiniLM-L6-v2")
vectorstore = Chroma(persist_directory="./chroma_db", embedding_function=embeddings)
memory = WindowMemory(max_pairs=10)
def rag_chat(pergunta: str) -> str:
"""Chat com RAG + memoria."""
# 1. Buscar contexto relevante
docs = vectorstore.similarity_search(pergunta, k=3)
context = "\n".join([doc.page_content for doc in docs])
# 2. Montar mensagens com memoria + contexto
memory.add_user(pergunta)
messages = [
SystemMessage(content=f"""Voce e um assistente que responde com base no contexto fornecido.
Se nao souber, diga que nao encontrou no documento.
Contexto dos documentos:
{context}"""),
] + memory.get_messages()
# 3. Gerar resposta
response = llm.invoke(messages)
memory.add_ai(response.content)
return response.content
if __name__ == "__main__":
print("Chatbot RAG com memoria. Digite 'sair' para encerrar.\n")
while True:
q = input("Voce: ").strip()
if q.lower() in ("sair", "exit"):
save_conversation(memory)
print("Conversa salva. Ate mais!")
break
if not q:
continue
r = rag_chat(q)
print(f"\nBot: {r}\n")
Comparativo dos tipos de memoria
| Tipo | Quando usar | Custo de tokens | Contexto |
|---|---|---|---|
| Buffer completo | Conversas curtas (menos de 10 trocas) | Alto | Total |
| Janela (window) | Conversas medias | Medio | Ultimas N |
| Resumo (summary) | Conversas longas | Baixo | Resumido + recentes |
| RAG + Memoria | Perguntas sobre documentos | Medio | Docs + conversa |
Exercicio pratico
- Implemente o chatbot RAG + memoria usando os documentos da Aula 2
- Teste fazendo perguntas de acompanhamento: “explique melhor”, “e sobre o ponto X?”
- Salve e carregue a conversa para continuar depois
- Compare o custo de tokens entre buffer completo e janela de 5
Proximo passo
Na Aula 4, vamos dar superpoderes ao nosso agente com Tools e Function Calling. Ele vai poder buscar na web, fazer calculos, acessar APIs e executar acoes reais.