Modelos não têm memória entre chamadas — você reconstrói a janela cada vez. Aqui você aprende a simular memória: buffer de últimos turns, sumarização incremental, vetor para longo prazo, recall por similaridade. Um chat 'que lembra' é resultado dessas técnicas combinadas.
🏗️ Camadas de memória
Não é uma estratégia única — é um stack de camadas, cada uma com trade-off de custo, fidelidade e velocidade.
- •Curto prazo: buffer dos últimos N turns (simples, alto detalhe).
- •Médio prazo: sumarização hierárquica (compacto, perde detalhe).
- •Longo prazo: vetor + recall (escala, exige busca).
- •Estado do usuário: ficha estruturada (estável, no system prompt).
💡 Não invente memória — comece com buffer
Para a maioria dos casos, buffer de 10 últimos turns + system prompt resolve. Adicione sumarização quando buffer estourar; vetor quando precisar de meses-atrás real.
📥 Buffer de turns: memória de curto prazo
Últimos N turns
Para chats curtos (5-10 turns), buffer FIFO dos últimos N turns inteiros é o suficiente. Modelos atendem bem em janela <10k. Implementação trivial: deque(maxlen=N). Não invente memória vetorial dia 1.
from collections import deque
buffer = deque(maxlen=10)
def turn(user_msg: str) -> str:
buffer.append({'role': 'user', 'content': user_msg})
msgs = [{'role':'system', 'content': SYSTEM}] + list(buffer)
resp = client.chat(messages=msgs)
buffer.append({'role': 'assistant', 'content': resp.content})
return resp.content
📑 Resumo navegável
📝 Summarização incremental
Comprimir o que sai do buffer
Quando o buffer estoura, em vez de descartar mensagens antigas, sumarize. Sumário fica no system prompt; perde detalhe mas mantém continuidade. Trigger típico: a cada 10 turns, sumarize os 5 mais antigos em parágrafo curto.
def maybe_sumarizar(state):
if len(state.buffer) >= 10:
antigos = list(state.buffer)[:5]
novos = list(state.buffer)[5:]
sumario = llm.generate(
'Sumarize esta conversa em 1 parágrafo:\n' + format(antigos)
)
state.sumario = state.sumario + '\n' + sumario
state.buffer = deque(novos, maxlen=10)
📑 Resumo navegável
🌳 Sumarização hierárquica: árvore de memória
Múltiplos níveis
Para chats muito longos (centenas de turns), uma única sumarização perde detalhe demais. Hierarquia: turns viram sumário-de-1; sumários-de-1 viram sumário-de-2; etc. Estrutura tipo árvore. MemGPT (Packer et al. 2023) é a referência sistemática.
níveis típicos
- ▸Nível 0: turns brutos (últimos 5).
- ▸Nível 1: sumário dos 5 turns anteriores (1 parágrafo).
- ▸Nível 2: sumário das últimas 10 sessões (1 parágrafo).
- ▸Nível 3: perfil do usuário consolidado (estrutura JSON).
📑 Resumo navegável
🗃️ Memória vetorial: longo prazo
Embedding de turns + recall
Para 'lembrar' meses depois, sumário hierárquico não basta — você precisa indexar turns como vetores e recuperar por similaridade quando relevante. Cada turn vira embedding; em novo turn, busca turns similares ao tópico atual e injeta no contexto. Fica grande? Use TTL ou compactação periódica.
def turn_com_memoria(query):
relevantes = vector_store.search(query, top_k=3, namespace=user_id)
msgs = [
Message(SYSTEM, SYSTEM_PROMPT),
Message(SYSTEM, f'Contexto histórico relevante:\n{relevantes}'),
*recent_buffer,
Message(USER, query),
]
resp = client.chat(msgs)
vector_store.add(embed(f'Q: {query}\nA: {resp.content}'), namespace=user_id)
return resp.content
📑 Resumo navegável
🔍 Recall sob demanda: tool de memória
Agente busca a memória
Alternativa ao injection automático: agente tem tool buscar_memoria(termos) e decide quando usar. Mais explícito (debugável); custo: 1 roundtrip extra quando aciona. Padrão útil em assistentes onde a memória é explícita ('lembre-se que eu sou vegetariano').
tool_memoria = Tool(
name='buscar_memoria',
description='Busca em conversas anteriores. Use quando o usuário se referir a algo dito antes.',
parameters={'type': 'object', 'properties': {
'termos': {'type': 'string', 'description': 'palavras-chave da memória'}
}, 'required': ['termos']}
)
📑 Resumo navegável
🧠 Personalização: persona + perfil do usuário
Memória como facto
Memória conversacional ≠ estado do usuário. Perfil estruturado (nome, preferências, contexto profissional) fica em JSON pequeno no system prompt — atualizado incrementalmente quando o usuário diz algo novo. Mais estável e barato que injetar histórico inteiro a cada turn.
perfil = {
'nome': 'Nei',
'idioma_preferido': 'pt-BR',
'expertise': 'engenharia de software, LLM em produção',
'notas': ['vegetariano', 'reside em SC, Brasil']
}
system = (
SYSTEM_PROMPT_BASE + '\n\n'
f'Perfil do usuário:\n{json.dumps(perfil, ensure_ascii=False, indent=2)}'
)
📑 Resumo navegável
📑 Resumo navegável dos tópicos
1 📥 Buffer de turns: memória de curto prazo — Últimos N turns
2 📝 Summarização incremental — Comprimir o que sai do buffer
3 🌳 Sumarização hierárquica: árvore de memória — Múltiplos níveis
4 🗃️ Memória vetorial: longo prazo — Embedding de turns + recall
5 🔍 Recall sob demanda: tool de memória — Agente busca a memória
6 🧠 Personalização: persona + perfil do usuário — Memória como facto
✓ O que FAZER
- ✓Buffer simples para chats curtos (<10 turns)
- ✓Sumarização incremental quando buffer estoura
- ✓Ficha estruturada do usuário no system prompt
- ✓Recall explícito via tool em casos críticos
✗ O que NÃO fazer
- ✗Implementar memória vetorial dia 1
- ✗Crescer janela indefinidamente
- ✗Spreadar info do usuário em N turns
- ✗Injection automático sem controle
🚫 Quando NÃO usar
- •Chat estateless de 1-2 turns: nem precisa de memória, contexto da query basta.
- •Quando privacy não permite armazenar: conformidade limita memória persistente.
- •Casos onde fato muda: memória vetorial não distingue atual de antigo bem.
💻 Exemplo de código
from collections import deque
from fec_sdk import Message, MessageRole
from fec_sdk.adapters import get_adapter
class ChatComMemoria:
def __init__(self, max_buffer: int = 10):
self.buffer = deque(maxlen=max_buffer)
self.sumario_antigo = ""
self.client = get_adapter("mock")
def turn(self, user_msg: str) -> str:
# Constrói janela: system com sumário + buffer + nova msg
system = f"Histórico antigo (sumarizado): {self.sumario_antigo}"
msgs = [Message(role=MessageRole.SYSTEM, content=system)] + list(self.buffer)
msgs.append(Message(role=MessageRole.USER, content=user_msg))
resp = self.client.chat(msgs)
# Atualiza buffer
self.buffer.append(Message(role=MessageRole.USER, content=user_msg))
self.buffer.append(Message(role=MessageRole.ASSISTANT, content=resp.content))
# Se buffer está cheio, sumariza o que cai fora
if len(self.buffer) == self.buffer.maxlen:
self._atualizar_sumario()
return resp.content
def _atualizar_sumario(self):
# Pseudo: chama LLM para sumarizar buffer + sumário antigo
pass
🏋️ Exercício hands-on
Implemente chat com 3 camadas (buffer + sumarização + vetorial). Teste com 50 turns, medindo custo médio e recall de fatos antigos. Em exercicios/modulo-5-1/.
📚 Bibliografia
- Packer et al. (2023) — MemGPT: Towards LLMs as Operating Systems
- Park et al. (2023) — Generative Agents (memória episódica)
- Wu et al. (2022) — Recursive Summarization
- LangChain memory — Padrões de memória em frameworks
🎯 Resumo do Módulo
- ✓Memória é construção — modelos não têm memória nativa.
- ✓Buffer simples resolve chats curtos.
- ✓Sumarização hierárquica escala para chats longos.
- ✓Vetor + recall permite memória de longo prazo.
- ✓Estado do usuário = ficha estruturada no system prompt.
Próximo Módulo:
Caching e compressão (beta)