“好记性不如烂笔头。”
——这句老话,放在大模型时代,竟然成了最佳工程实践。
你有没有遇到过这样的场景?
用户:“我叫太空人,喜欢吃喜之郎。”
AI:“好的,太空人!”
用户:“那我刚才说喜欢吃什么?”
AI:“抱歉,我不记得了。”
是不是很无奈?
其实不是 AI “笨”,而是它天生没有记忆——每次对话都是“第一次见面”。
那怎么办?
LangChain 给出的答案是:别指望它记性好,咱们自己给它配个“记事本” 。
而这个“记事本系统”,由三个关键组件组成:
InMemoryChatMessageHistory:真正的记事本ChatPromptTemplate:记事本的书写格式规范RunnableWithMessageHistory:规定“什么时候翻本子、抄哪页”的秘书
今天,我们就用这个生活化比喻,彻底搞懂 LangChain 的聊天记忆链。
一、第一步:先给 AI 配个“记事本”——InMemoryChatMessageHistory
大模型就像一个健忘的朋友,但我们可以给它配个随身小本子,把每次对话都记下来。
import { InMemoryChatMessageHistory } from '@langchain/core/chat_history';
const messageHistory = new InMemoryChatMessageHistory();
这个 messageHistory 就是它的“记事本”。
每当你和 AI 聊一句,它就会自动记下两条:
- 你说了什么(
HumanMessage) - AI 回了什么(
AIMessage)
比如聊完“我叫太空人,喜欢吃喜之郎”后,本子上就多了:
[ { role: 'human', content: '我叫太空人,喜欢吃喜之郎' }, { role: 'ai', content: '好的,太空人!' }]
注意:这个“本子”目前只存在内存里,程序一关就清空。
生产环境可以用 Redis、数据库等做持久化,但原理一样——先有本子,才能记事。
二、第二步:规定“怎么记、怎么读”——ChatPromptTemplate
有了本子,还得有书写规范。
你不能让 AI 看一堆乱七八糟的文字,它需要清晰的结构:
- 哪句是系统指令?
- 哪些是历史对话?
- 哪句是当前问题?
这就是 ChatPromptTemplate 的作用——它定义了消息的排版格式。
import { ChatPromptTemplate } from '@langchain/core/prompts';
const prompt = ChatPromptTemplate.fromMessages([
['system', '你是一个有记忆的助手'],
['placeholder', '{history}'], // 这里要插入记事本内容
['human', '{input}'] // 当前用户输入
]);
关键就在 ['placeholder', '{history}'] ——
它相当于在模板里留了个插槽:“等会儿把记事本里的内容,按原格式插进来”。
为什么必须用 ChatPromptTemplate 而不是普通 PromptTemplate?
因为记事本里存的是结构化消息对象(带 human/ai 角色),不是字符串。
只有 ChatPromptTemplate 能正确“展开”这些消息,生成模型能理解的上下文。
想象你在写会议纪要:
不能只写“他说了这个那个”,而要写“张三:……”、“李四:……”。
ChatPromptTemplate 就是那个排版模板。
三、第三步:安排一个“翻本子的秘书”——RunnableWithMessageHistory
光有本子和格式还不够。
谁来负责:
- 调用前:去翻本子,把历史抄进提示?
- 调用后:把新对话记回本子?
这时候,RunnableWithMessageHistory 就登场了——它就是那个贴心的秘书。
import { RunnableWithMessageHistory } from '@langchain/core/runnables';
const chain = new RunnableWithMessageHistory({
runnable: prompt.pipe(model), // 要执行的任务
getMessageHistory: async () => messageHistory, // 翻哪个本子?
inputMessagesKey: 'input', // 用户当前问的是啥?
historyMessagesKey: 'history', // 历史记录放哪儿?
});
当你调用:
await chain.invoke({ input: "我刚才说喜欢吃什么?" });
这位“秘书”会自动:
- 打开
messageHistory这个本子 - 把里面所有记录作为
{ history: [...] }传给你的 runnable - 拿到 AI 回复后,把“用户新问题 + AI 新回答”记回本子
整个过程完全自动化,你只需要关心业务逻辑。
四、三者协作全景图
用一张流程图总结:
用户提问
↓
[RunnableWithMessageHistory] ← 秘书:我去翻本子!
↓
从 [InMemoryChatMessageHistory] 读取历史(记事本)
↓
注入 { input: "...", history: [Human, AI, ...] } 到 runnable
↓
[ChatPromptTemplate] ← 排版员:按格式插进去!
↓
生成完整消息列表 → 交给 ChatModel
↓
AI 回答
↓
[RunnableWithMessageHistory] ← 秘书:记回本子!
↓
更新 [InMemoryChatMessageHistory]
三者各司其职:
- 记事本(
InMemory...):存 - 排版模板(
ChatPromptTemplate):格式化 - 秘书(
RunnableWithMessageHistory):调度 + 自动读写
五、结语:AI 不需要好记性,但我们需要好设计
大模型永远不会记住你是谁——
但我们可以用“烂笔头”帮它记住。
InMemoryChatMessageHistory 是本子,
ChatPromptTemplate 是格式,
RunnableWithMessageHistory 是秘书。
三者配合,让无状态的 AI 表现出“有记忆”的智能。
而这,正是 LangChain 的魅力所在:
不幻想模型全能,而是用工程手段弥补它的短板。