Por que memória?

LLMs são stateless - cada chamada é independente. Sem memória, o modelo não lembra o que foi dito antes:

# Sem memória
llm.invoke("Meu nome é Eduardo")  # "Prazer, Eduardo!"
llm.invoke("Qual meu nome?")      # "Não sei seu nome"

Memória com mensagens

A forma moderna de implementar memória no LangChain é gerenciar o histórico de mensagens:

from langchain_openai import ChatOpenAI
from langchain_core.messages import HumanMessage, AIMessage

llm = ChatOpenAI(model="gpt-4o-mini")

# Histórico de mensagens
historico = []

def chat(mensagem: str) -> str:
    historico.append(HumanMessage(content=mensagem))
    resposta = llm.invoke(historico)
    historico.append(resposta)
    return resposta.content

chat("Meu nome é Eduardo")
chat("Qual meu nome?")  # "Seu nome é Eduardo!"

ChatMessageHistory

Classe utilitária para gerenciar histórico:

from langchain_community.chat_message_histories import ChatMessageHistory

historia = ChatMessageHistory()

historia.add_user_message("Olá!")
historia.add_ai_message("Oi! Como posso ajudar?")
historia.add_user_message("Qual a capital do Brasil?")

# Acessa as mensagens
print(historia.messages)

RunnableWithMessageHistory

Integra memória com cadeias LCEL:

from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain_core.runnables.history import RunnableWithMessageHistory
from langchain_community.chat_message_histories import ChatMessageHistory

# Store de sessões
store = {}

def get_session_history(session_id: str):
    if session_id not in store:
        store[session_id] = ChatMessageHistory()
    return store[session_id]

# Prompt com placeholder para histórico
prompt = ChatPromptTemplate.from_messages([
    ("system", "Você é um assistente útil."),
    MessagesPlaceholder(variable_name="history"),
    ("human", "{input}")
])

cadeia = prompt | llm

# Adiciona memória à cadeia
cadeia_com_memoria = RunnableWithMessageHistory(
    cadeia,
    get_session_history,
    input_messages_key="input",
    history_messages_key="history"
)

# Usa com session_id
config = {"configurable": {"session_id": "usuario_123"}}

cadeia_com_memoria.invoke({"input": "Meu nome é Eduardo"}, config=config)
cadeia_com_memoria.invoke({"input": "Qual meu nome?"}, config=config)
# "Seu nome é Eduardo!"

Limitando o histórico

Conversas longas estouram o limite de tokens. Soluções:

Janela de mensagens

from langchain_core.messages import trim_messages

# Mantém apenas as últimas N mensagens
mensagens_trimadas = trim_messages(
    historico.messages,
    max_tokens=1000,
    token_counter=llm,
    strategy="last"
)

Resumo do histórico

from langchain_core.prompts import ChatPromptTemplate

prompt_resumo = ChatPromptTemplate.from_template(
    "Resuma esta conversa em 2-3 frases:\n\n{conversa}"
)

def resumir_historico(mensagens):
    conversa = "\n".join([f"{m.type}: {m.content}" for m in mensagens])
    resumo = (prompt_resumo | llm).invoke({"conversa": conversa})
    return resumo.content

Persistência com Redis

Para manter memória entre reinicializações:

from langchain_community.chat_message_histories import RedisChatMessageHistory

def get_session_history(session_id: str):
    return RedisChatMessageHistory(
        session_id=session_id,
        url="redis://localhost:6379"
    )

# Histórico persiste no Redis
cadeia_com_memoria = RunnableWithMessageHistory(
    cadeia,
    get_session_history,
    input_messages_key="input",
    history_messages_key="history"
)

Persistência com PostgreSQL

from langchain_community.chat_message_histories import PostgresChatMessageHistory

def get_session_history(session_id: str):
    return PostgresChatMessageHistory(
        session_id=session_id,
        connection_string="postgresql://user:pass@localhost/db"
    )

Persistência com arquivo

Para desenvolvimento local:

from langchain_community.chat_message_histories import FileChatMessageHistory

def get_session_history(session_id: str):
    return FileChatMessageHistory(f"./historico_{session_id}.json")

Exemplo completo: Chatbot com memória

from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain_core.runnables.history import RunnableWithMessageHistory
from langchain_community.chat_message_histories import ChatMessageHistory

# Setup
llm = ChatOpenAI(model="gpt-4o-mini")
store = {}

def get_history(session_id: str):
    if session_id not in store:
        store[session_id] = ChatMessageHistory()
    return store[session_id]

prompt = ChatPromptTemplate.from_messages([
    ("system", "Você é um assistente amigável chamado Bot."),
    MessagesPlaceholder(variable_name="history"),
    ("human", "{input}")
])

chatbot = RunnableWithMessageHistory(
    prompt | llm,
    get_history,
    input_messages_key="input",
    history_messages_key="history"
)

# Loop de conversa
session = {"configurable": {"session_id": "user_1"}}

while True:
    user_input = input("Você: ")
    if user_input.lower() == "sair":
        break
    response = chatbot.invoke({"input": user_input}, config=session)
    print(f"Bot: {response.content}")

Resumo

Tipo Uso
ChatMessageHistory Memória em RAM
RedisChatMessageHistory Persistência Redis
PostgresChatMessageHistory Persistência PostgreSQL
FileChatMessageHistory Persistência arquivo
trim_messages Limitar tokens
RunnableWithMessageHistory Integrar com LCEL

Memória transforma um LLM stateless em um chatbot que lembra do contexto.