Memória em agentes de IA: como fazer o sistema lembrar do contexto

Guia técnico sobre memória em agentes de IA: tipos de memória, implementações com vetores e bancos de dados, gestão de contexto em conversas longas e padrões para produção.

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:

  1. Limite de contexto: modelos têm limite de tokens (ex: GPT-4o = 128k tokens)
  2. Custo crescente: cada mensagem adiciona tokens → cada turno custa mais
  3. Latência: mais tokens = mais tempo de processamento

Exemplo de crescimento de custo:

TurnoTokens entradaCusto (GPT-4o)
1100$0.0003
5800$0.0024
101.800$0.0054
204.200$0.0126
5012.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.

Pronto para sair do manual?

Agende o diagnóstico gratuito. Vamos mapear o gargalo, estimar o impacto e definir o primeiro resultado mensurável.

Você sai com clareza — não com um pitch de vendas.