Um dos desafios mais práticos ao construir agentes de IA para uso empresarial é a memória. LLMs são, por natureza, stateless — cada requisição começa do zero. Para um agente que precisa lembrar do que o usuário disse há três dias, ou manter contexto de um processo que dura semanas, isso é um problema de arquitetura que precisa ser resolvido explicitamente.
Este artigo é sobre como resolver isso de forma que funcione em produção.
Por que memória importa para agentes empresariais
Um chatbot de FAQ pode funcionar sem memória — cada pergunta é independente.
Mas agentes empresariais precisam de continuidade:
- Sales assistant: lembrar contexto de reuniões anteriores com o cliente
- Customer success agent: manter histórico de problemas reportados
- Assistente de onboarding: acompanhar progresso de treinamento ao longo de semanas
- Agente de diagnóstico: correlacionar sintomas mencionados em conversas diferentes
Sem memória, o agente vira um disco arranhado — o usuário precisa repetir contexto a cada interação.
Os quatro tipos de memória em agentes
1. Memória de contexto imediato (working memory)
O histórico da conversa atual. Funciona colocando as mensagens anteriores no contexto de cada nova requisição.
Implementação básica:
from openai import OpenAI
client = OpenAI()
# Conversa simples com memória de contexto
messages = [
{"role": "system", "content": "Você é um assistente de suporte técnico."}
]
def chat(user_input: str) -> str:
# Adiciona mensagem do usuário
messages.append({"role": "user", "content": user_input})
# Chama LLM com todo o histórico
response = client.chat.completions.create(
model="gpt-4o-mini",
messages=messages
)
# Adiciona resposta do assistente ao histórico
reply = response.choices[0].message.content
messages.append({"role": "assistant", "content": reply})
return reply
# Uso
print(chat("Meu sistema está lento"))
# → "Vou te ajudar a diagnosticar. Qual sistema especificamente?"
print(chat("O ERP"))
# → "Entendi, o ERP está lento. Quando começou a lentidão?"
# (note que o agente lembra que estamos falando de ERP)
Vantagens:
- Simples de implementar
- Mantém contexto conversacional
Limitações:
- Limite de contexto: modelos têm limite de tokens (ex: GPT-4o = 128k tokens)
- Custo crescente: cada mensagem adiciona tokens → cada turno custa mais
- Latência: mais tokens = mais tempo de processamento
Exemplo de crescimento de custo:
| Turno | Tokens entrada | Custo (GPT-4o) |
|---|---|---|
| 1 | 100 | $0.0003 |
| 5 | 800 | $0.0024 |
| 10 | 1.800 | $0.0054 |
| 20 | 4.200 | $0.0126 |
| 50 | 12.000 | $0.036 |
Para conversas longas ou alto volume de usuários, custo escala rapidamente.
2. Memória episódica (histórico de sessões anteriores)
O agente precisa lembrar de conversas passadas com o mesmo usuário.
Casos de uso:
- “Você me disse na semana passada que seu orçamento era X”
- “Da última vez resolvemos o problema Y fazendo Z”
- “Nosso último contato foi dia 10, você mencionou que testaria a solução”
Implementação com banco de dados + embeddings:
from datetime import datetime
import json
from openai import OpenAI
client = OpenAI()
# Estrutura de armazenamento de episódios
class MemoriaEpisodica:
def __init__(self, db_connection, vector_store):
self.db = db_connection
self.vectors = vector_store
def salvar_episodio(self, usuario_id: str, conversa: list):
# Gera resumo da conversa
resumo = self._gerar_resumo(conversa)
# Gera embedding do resumo para busca semântica
embedding = client.embeddings.create(
model="text-embedding-3-small",
input=resumo
).data[0].embedding
# Salva no banco
episodio = {
"id": f"{usuario_id}_{datetime.now().timestamp()}",
"usuario_id": usuario_id,
"timestamp": datetime.now().isoformat(),
"mensagens": conversa,
"resumo": resumo,
"embedding": embedding
}
self.db.insert("episodios", episodio)
self.vectors.index(episodio["id"], embedding)
def _gerar_resumo(self, conversa: list) -> str:
# LLM resume a conversa
prompt = f"""
Resuma esta conversa em 2-3 frases, focando em:
- Problema ou objetivo do usuário
- Informações chave fornecidas
- Resultado ou próximos passos
Conversa:
{json.dumps(conversa, indent=2)}
Resumo:
"""
response = client.chat.completions.create(
model="gpt-4o-mini",
messages=[{"role": "user", "content": prompt}]
)
return response.choices[0].message.content
def recuperar_episodios_relevantes(
self,
usuario_id: str,
contexto_atual: str,
top_k: int = 3
) -> list:
# Gera embedding do contexto atual
query_embedding = client.embeddings.create(
model="text-embedding-3-small",
input=contexto_atual
).data[0].embedding
# Busca episódios similares do mesmo usuário
episodios_ids = self.vectors.search(
query_embedding,
filter={"usuario_id": usuario_id},
top_k=top_k
)
# Recupera episódios completos do banco
episodios = [
self.db.get("episodios", ep_id)
for ep_id in episodios_ids
]
return episodios
Uso em produção:
def chat_com_memoria_episodica(usuario_id: str, mensagem: str) -> str:
# Recupera episódios relevantes de conversas anteriores
episodios_passados = memoria.recuperar_episodios_relevantes(
usuario_id,
mensagem,
top_k=2
)
# Monta contexto com resumos dos episódios
contexto_episodios = "\n\n".join([
f"Conversa anterior ({ep['timestamp'][:10]}):\n{ep['resumo']}"
for ep in episodios_passados
])
# System message com contexto histórico
messages = [
{
"role": "system",
"content": f"""Você é um assistente de vendas.
Histórico de interações com este cliente:
{contexto_episodios}
Use esse contexto para personalizar sua resposta."""
},
{"role": "user", "content": mensagem}
]
response = client.chat.completions.create(
model="gpt-4o",
messages=messages
)
return response.choices[0].message.content
Benefício: usuário não precisa repetir contexto — agente “lembra” de conversas anteriores.
3. Memória semântica (conhecimento persistente sobre a entidade)
Fatos estruturados sobre o usuário/empresa que o agente precisa lembrar sempre:
Exemplos:
- “Empresa do setor de logística, 150 funcionários”
- “Usa SAP, não gosta de soluções cloud”
- “Orçamento aprovado: R$ 200k”
- “Decisor: Maria (CFO)”
- “Prazo de implementação: até junho”
Diferença vs. memória episódica:
- Episódica = “o que aconteceu”
- Semântica = “o que eu sei sobre esta entidade”
Implementação com perfil estruturado:
class PerfilCliente:
def __init__(self, db_connection):
self.db = db_connection
def buscar_perfil(self, cliente_id: str) -> dict:
perfil = self.db.get("perfis", cliente_id)
if not perfil:
# Perfil vazio default
perfil = {
"cliente_id": cliente_id,
"empresa": {},
"contexto_negocio": {},
"preferencias": {},
"restricoes": [],
"proximos_passos": []
}
return perfil
def atualizar_perfil(
self,
cliente_id: str,
nova_conversa: str
):
perfil_atual = self.buscar_perfil(cliente_id)
# LLM extrai novos fatos da conversa e atualiza perfil
prompt = f"""
Dado o perfil atual do cliente e a nova conversa,
extraia informações relevantes e atualize o perfil.
Regras:
- Adicione novos fatos mencionados
- Atualize fatos se houver nova informação
- Não remova informações existentes salvo se explicitamente corrigidas
- Mantenha estrutura JSON
Perfil atual:
{json.dumps(perfil_atual, indent=2, ensure_ascii=False)}
Nova conversa:
{nova_conversa}
Retorne o perfil atualizado em JSON válido.
"""
response = client.chat.completions.create(
model="gpt-4o",
messages=[{"role": "user", "content": prompt}],
response_format={"type": "json_object"}
)
perfil_atualizado = json.loads(
response.choices[0].message.content
)
# Salva perfil atualizado
self.db.upsert("perfis", cliente_id, perfil_atualizado)
return perfil_atualizado
Exemplo de perfil extraído:
{
"cliente_id": "CLIENTE-123",
"empresa": {
"nome": "Acme Logística",
"setor": "Logística e Transporte",
"porte": "Médio (150 funcionários)",
"localizacao": "São Paulo, SP",
"faturamento_anual": "R$ 80M"
},
"contexto_negocio": {
"problema_principal": "Paradas não planejadas de caminhões causam atrasos nas entregas",
"situacao_atual": "Manutenção preventiva por calendário, ainda têm falhas inesperadas",
"objetivo": "Reduzir downtime de frota em 60%",
"urgencia": "Alta - temporada de pico em 3 meses"
},
"stakeholders": [
{
"nome": "Maria Costa",
"cargo": "CFO",
"preocupacao": "ROI e payback claro"
},
{
"nome": "João Silva",
"cargo": "Gerente de Manutenção",
"preocupacao": "Facilidade de uso pela equipe técnica"
}
],
"preferencias": {
"evitar_solucoes_cloud": true,
"prefere_on_premise": true,
"quer_integracao_com_sap": true
},
"restricoes": [
"Orçamento máximo: R$ 250k",
"Implementação até junho/2026",
"Precisa aprovação da matriz (EUA) para investimentos > R$ 200k"
],
"proximos_passos": [
"Enviar proposta técnica até 05/04",
"Agendar demo para equipe de manutenção",
"Preparar business case para CFO"
]
}
Uso em conversas:
def chat_com_perfil(cliente_id: str, mensagem: str) -> str:
# Carrega perfil do cliente
perfil = perfil_cliente.buscar_perfil(cliente_id)
# System message com contexto do perfil
system_msg = f"""Você é um assistente de vendas.
Contexto do cliente:
{json.dumps(perfil, indent=2, ensure_ascii=False)}
Use esse conhecimento para personalizar suas respostas."""
messages = [
{"role": "system", "content": system_msg},
{"role": "user", "content": mensagem}
]
response = client.chat.completions.create(
model="gpt-4o",
messages=messages
)
resposta = response.choices[0].message.content
# Atualiza perfil com novos fatos da conversa
perfil_cliente.atualizar_perfil(cliente_id, mensagem + "\n" + resposta)
return resposta
4. Memória procedural (como fazer as coisas)
O agente aprende com o tempo quais abordagens funcionam para quais tipos de problema.
Implementação: armazena pares (problema, solução bem-sucedida) e recupera por similaridade.
class MemoriaProcedural:
def __init__(self, vector_store):
self.vectors = vector_store
def registrar_sucesso(
self,
problema: str,
solucao: str,
resultado: str
):
# Armazena caso de sucesso
caso = {
"problema": problema,
"solucao": solucao,
"resultado": resultado,
"timestamp": datetime.now().isoformat()
}
# Gera embedding do problema
embedding = client.embeddings.create(
model="text-embedding-3-small",
input=problema
).data[0].embedding
# Indexa para busca futura
self.vectors.index(
id=f"caso_{datetime.now().timestamp()}",
vector=embedding,
metadata=caso
)
def buscar_solucoes_similares(
self,
problema_atual: str,
top_k: int = 3
) -> list:
# Busca casos similares bem-sucedidos
query_embedding = client.embeddings.create(
model="text-embedding-3-small",
input=problema_atual
).data[0].embedding
casos_similares = self.vectors.search(
query_embedding,
top_k=top_k
)
return casos_similares
Uso:
def resolver_problema(problema: str) -> str:
# Busca soluções similares que funcionaram antes
casos_sucesso = memoria_procedural.buscar_solucoes_similares(problema)
contexto_casos = "\n\n".join([
f"Problema similar: {caso['problema']}\n"
f"Solução aplicada: {caso['solucao']}\n"
f"Resultado: {caso['resultado']}"
for caso in casos_sucesso
])
prompt = f"""
Problema atual:
{problema}
Casos similares bem-sucedidos:
{contexto_casos}
Com base nesses casos, sugira uma solução para o problema atual.
"""
response = client.chat.completions.create(
model="gpt-4o",
messages=[{"role": "user", "content": prompt}]
)
return response.choices[0].message.content
Gestão de contexto em conversas longas
Para conversas que crescem além do limite de contexto, existem três estratégias:
Estratégia 1: Truncagem (mais simples)
Remove mensagens mais antigas.
def truncar_historico(messages: list, max_messages: int = 10) -> list:
# Mantém system message + últimas N mensagens
if len(messages) <= max_messages + 1:
return messages
return [messages[0]] + messages[-(max_messages):]
Problema: perde contexto importante mencionado no início da conversa.
Estratégia 2: Sumarização periódica (recomendada)
Condensa mensagens antigas em resumos.
def gerenciar_contexto_com_resumo(
messages: list,
limite_tokens: int = 3000
) -> list:
# Conta tokens atuais
tokens_usados = contar_tokens(messages)
if tokens_usados > limite_tokens:
# Mantém as últimas 6 mensagens intactas (contexto imediato)
mensagens_recentes = messages[-6:]
mensagens_antigas = messages[1:-6] # exclui system message
# Sumariza as antigas
resumo = gerar_resumo_conversa(mensagens_antigas)
# Reconstrói contexto:
# [system] + [resumo do passado] + [mensagens recentes]
return [
messages[0], # system message original
{
"role": "system",
"content": f"Resumo da conversa anterior:\n{resumo}"
},
*mensagens_recentes
]
return messages
def gerar_resumo_conversa(messages: list) -> str:
conversa_texto = "\n".join([
f"{m['role']}: {m['content']}"
for m in messages
])
prompt = f"""
Resuma esta conversa em um parágrafo conciso, preservando:
- Informações chave trocadas
- Decisões tomadas
- Fatos importantes mencionados
Conversa:
{conversa_texto}
Resumo:
"""
response = client.chat.completions.create(
model="gpt-4o-mini", # modelo mais barato para resumo
messages=[{"role": "user", "content": prompt}]
)
return response.choices[0].message.content
def contar_tokens(messages: list) -> int:
# Estimativa simplificada: 4 chars = 1 token
total_chars = sum(len(m.get("content", "")) for m in messages)
return total_chars // 4
Benefício: mantém contexto importante em formato compacto.
Estratégia 3: RAG sobre histórico (para conversas muito longas)
Trata o histórico como base de conhecimento — recupera apenas trechos relevantes.
class HistoricoRAG:
def __init__(self, vector_store):
self.vectors = vector_store
def adicionar_mensagem(
self,
conversa_id: str,
mensagem: dict
):
# Indexa cada mensagem do histórico
embedding = client.embeddings.create(
model="text-embedding-3-small",
input=mensagem["content"]
).data[0].embedding
self.vectors.index(
id=f"{conversa_id}_{datetime.now().timestamp()}",
vector=embedding,
metadata={
"conversa_id": conversa_id,
"role": mensagem["role"],
"content": mensagem["content"],
"timestamp": datetime.now().isoformat()
}
)
def recuperar_contexto_relevante(
self,
conversa_id: str,
mensagem_atual: str,
top_k: int = 5
) -> list:
# Busca mensagens anteriores relevantes para mensagem atual
query_embedding = client.embeddings.create(
model="text-embedding-3-small",
input=mensagem_atual
).data[0].embedding
mensagens_relevantes = self.vectors.search(
query_embedding,
filter={"conversa_id": conversa_id},
top_k=top_k
)
return mensagens_relevantes
Uso:
def chat_com_historico_rag(
conversa_id: str,
mensagem_atual: str
) -> str:
# Recupera apenas mensagens relevantes do histórico longo
contexto_relevante = historico_rag.recuperar_contexto_relevante(
conversa_id,
mensagem_atual,
top_k=5
)
# Monta contexto com mensagens relevantes
contexto_texto = "\n\n".join([
f"{m['role']}: {m['content']}"
for m in contexto_relevante
])
messages = [
{
"role": "system",
"content": f"Contexto relevante da conversa:\n{contexto_texto}"
},
{"role": "user", "content": mensagem_atual}
]
response = client.chat.completions.create(
model="gpt-4o",
messages=messages
)
resposta = response.choices[0].message.content
# Indexa mensagem nova
historico_rag.adicionar_mensagem(
conversa_id,
{"role": "user", "content": mensagem_atual}
)
historico_rag.adicionar_mensagem(
conversa_id,
{"role": "assistant", "content": resposta}
)
return resposta
Arquitetura completa de memória em produção
Combinando os 4 tipos de memória:
class AgenteComMemoria:
def __init__(self):
self.perfil = PerfilCliente(db)
self.episodios = MemoriaEpisodica(db, vector_db)
self.procedural = MemoriaProcedural(vector_db)
self.working_memory = [] # contexto imediato
def processar_mensagem(
self,
usuario_id: str,
mensagem: str
) -> str:
# 1. Carrega perfil do usuário (memória semântica)
perfil = self.perfil.buscar_perfil(usuario_id)
# 2. Recupera episódios relevantes (memória episódica)
episodios = self.episodios.recuperar_episodios_relevantes(
usuario_id,
mensagem,
top_k=2
)
# 3. Busca soluções similares (memória procedural)
solucoes = self.procedural.buscar_solucoes_similares(
mensagem,
top_k=2
)
# 4. Monta contexto completo
system_msg = self._montar_system_message(
perfil,
episodios,
solucoes
)
# 5. Adiciona à working memory
self.working_memory.append({"role": "user", "content": mensagem})
# 6. Gerencia tamanho do contexto
messages = self._gerenciar_contexto([
{"role": "system", "content": system_msg},
*self.working_memory
])
# 7. Chama LLM
response = client.chat.completions.create(
model="gpt-4o",
messages=messages
)
resposta = response.choices[0].message.content
# 8. Atualiza memórias
self.working_memory.append(
{"role": "assistant", "content": resposta}
)
self.perfil.atualizar_perfil(usuario_id, mensagem + "\n" + resposta)
return resposta
def finalizar_sessao(self, usuario_id: str):
# Salva episódio quando conversa termina
self.episodios.salvar_episodio(usuario_id, self.working_memory)
self.working_memory = [] # limpa contexto imediato
Privacidade e retenção de memória (LGPD)
Armazenar memória sobre usuários tem implicações de LGPD que precisam ser consideradas.
Requisitos legais
1. Consentimento explícito:
def solicitar_consentimento_memoria(usuario_id: str) -> bool:
"""
Apresenta aviso ao usuário sobre armazenamento de dados.
"""
aviso = """
Para melhorar sua experiência, nosso assistente armazena:
- Histórico de conversas anteriores
- Preferências e informações que você compartilha
- Soluções que funcionaram para seus problemas
Você pode:
- Acessar seus dados a qualquer momento
- Solicitar exclusão completa dos dados
- Opt-out do armazenamento de memória
Aceita que armazenemos essas informações?
[Sim] [Não] [Ler política completa]
"""
# Salva consentimento no banco
resposta = apresentar_ao_usuario(aviso)
db.salvar_consentimento(usuario_id, resposta, timestamp=datetime.now())
return resposta == "Sim"
2. Política de retenção:
# Configuração de retenção
RETENCAO = {
"working_memory": timedelta(hours=24), # limpa após 24h de inatividade
"episodios": timedelta(days=90), # mantém por 90 dias
"perfil": "permanente_com_opt_out", # enquanto usuário permitir
}
def limpar_dados_expirados():
"""
Job agendado (daily) para limpar dados expirados.
"""
# Limpa episódios > 90 dias
db.delete_where(
"episodios",
"timestamp < NOW() - INTERVAL '90 days'"
)
# Limpa working memory de sessões inativas > 24h
db.delete_where(
"working_memory",
"last_activity < NOW() - INTERVAL '24 hours'"
)
3. Direito ao esquecimento:
def exercer_direito_esquecimento(usuario_id: str):
"""
Exclui todos os dados de memória do usuário.
"""
# Exclui perfil
db.delete("perfis", usuario_id)
# Exclui episódios
db.delete_where("episodios", f"usuario_id = '{usuario_id}'")
# Exclui embeddings do vector store
vector_db.delete_where(f"usuario_id = '{usuario_id}'")
# Exclui working memory ativa
db.delete_where("working_memory", f"usuario_id = '{usuario_id}'")
# Registra exclusão para auditoria
db.insert("audit_log", {
"usuario_id": usuario_id,
"acao": "direito_esquecimento",
"timestamp": datetime.now()
})
4. Transparência (acesso aos dados):
def exportar_dados_usuario(usuario_id: str) -> dict:
"""
Exporta todos os dados armazenados do usuário (LGPD Art. 18).
"""
return {
"perfil": db.get("perfis", usuario_id),
"episodios": db.get_all_where("episodios", f"usuario_id = '{usuario_id}'"),
"consentimento": db.get("consentimentos", usuario_id),
"data_exportacao": datetime.now().isoformat()
}
Custos de operação de memória em escala
Componentes de custo
1. Armazenamento de dados:
- PostgreSQL: R$ 50-150/mês para 100GB
- Vector database (Pinecone, Weaviate): R$ 300-800/mês para 1M vetores
2. Embeddings:
- text-embedding-3-small: $0.02 / 1M tokens
- Exemplo: 10.000 conversas/mês × 500 tokens/resumo = R$ 30-50/mês
3. LLM calls para resumos e atualizações:
- GPT-4o-mini para resumos: $0.15 / 1M tokens de entrada
- 10.000 resumos/mês × 1.000 tokens = R$ 45-60/mês
Total estimado para 10.000 conversas/mês: R$ 500-1.200/mês
Se você está construindo um agente que precisa de memória persistente e quer ajuda com a arquitetura, fale com a gente. Implementamos sistemas de agentes com memória robusta para uso empresarial — incluindo conformidade com LGPD e gestão eficiente de custos em escala.