RunnableWithMessageHistory: память в цепочке

Урок показывает штатный способ добавить память к LCEL-цепочке.

RunnableWithMessageHistory — обёртка, которая автоматически подставляет историю сообщений по идентификатору сессии и дописывает в неё новые реплики.

Плейсхолдер для истории

Чтобы история попадала в промпт, в шаблон добавляют специальное место — MessagesPlaceholder. Туда обёртка вставит накопленные сообщения.

from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder

prompt = ChatPromptTemplate.from_messages([
    ("system", "Ты дружелюбный помощник."),
    MessagesPlaceholder(variable_name="history"),
    ("human", "{input}"),
])

Обёртка с историей по сессии

from langchain_core.runnables.history import RunnableWithMessageHistory
from langchain_community.chat_message_histories import ChatMessageHistory
from langchain_openai import ChatOpenAI

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

chain = prompt | ChatOpenAI(model="gpt-4o-mini")
bot = RunnableWithMessageHistory(
    chain, get_history,
    input_messages_key="input",
    history_messages_key="history",
)
bot.invoke({"input": "Привет, я Лена"},
           config={"configurable": {"session_id": "u1"}})
print(bot.invoke({"input": "Как меня зовут?"},
                 config={"configurable": {"session_id": "u1"}}).content)

Как работает под капотом

При вызове обёртка читает session_id из config и через вашу функцию get_history достаёт нужный объект истории. Она подставляет его сообщения в плейсхолдер history, прогоняет цепочку, а затем дописывает новую пару «вход пользователя — ответ модели» обратно в историю этой сессии. Благодаря тому, что историю возвращает ваша функция, хранилище может быть любым: память процесса, Redis, база данных — интерфейс один.

Частые ошибки

  • Забыть MessagesPlaceholder. Без него истории некуда подставиться, и память «не работает».
  • Один session_id на всех. Тогда диалоги пользователей перемешаются. У каждого — свой идентификатор.
  • Несовпадение ключей. input_messages_key и history_messages_key должны совпадать с именами в шаблоне.

Итог

  • MessagesPlaceholder — место в шаблоне, куда вставляется история.
  • RunnableWithMessageHistory автоматически подставляет историю по session_id и дописывает реплики.
  • У каждого диалога — свой session_id; хранилище истории заменяемо.
Проверьте себя
1. Что вставляет историю сообщений в промпт?
AStrOutputParser
BMessagesPlaceholder
CRunnableLambda
DJsonOutputParser
2. Зачем нужен session_id в RunnableWithMessageHistory?
AЧтобы ускорить модель
BЧтобы у каждого диалога была своя отдельная история
CЧтобы задать температуру
DЧтобы выбрать модель