第 17 章:会话记忆和多轮对话
本章目标
这一章让知识库助手支持多轮对话。用户可以继续追问,系统能理解上下文。
为什么需要记忆
用户不会每次都问完整问题。
第一轮:
报销超过 5000 元要怎么审批?
第二轮:
那差旅费也是这样吗?
没有会话记忆时,模型不知道“这样”指什么。
最简单的短期记忆
前端保存消息历史,并传给后端:
const messages = [
{ role: "user", content: "报销超过 5000 元要怎么审批?" },
{ role: "assistant", content: "需要部门负责人审批。" },
{ role: "user", content: "那差旅费也是这样吗?" }
];
后端调用:
const result = await model.invoke([
{ role: "system", content: KB_ASSISTANT_SYSTEM_PROMPT },
...messages
]);
问题改写
RAG 场景里,多轮追问最好先改写成独立问题。
原问题:那差旅费也是这样吗?
改写后:差旅费超过 5000 元是否也需要部门负责人审批?
可以用模型做问题改写:
const rewrite = await model.invoke([
{
role: "system",
content: "你负责把多轮对话中的最后一个问题改写成独立问题。只输出改写后的问题。"
},
...messages
]);
然后用改写后的问题去检索知识库。
消息截断
不能无限传历史。可以保留最近 N 条:
export function trimMessages<T>(messages: T[], maxMessages = 12) {
return messages.slice(-maxMessages);
}
更高级的做法是对旧消息做摘要。
会话 ID
生产环境需要会话 ID:
export interface ChatSession {
id: string;
userId: string;
title: string;
createdAt: number;
updatedAt: number;
}
每条消息关联 session:
export interface StoredMessage {
id: string;
sessionId: string;
role: "user" | "assistant";
content: string;
createdAt: number;
}
小册第一版可以先用前端状态,后面再接数据库。
实战任务
完成:
- 多轮消息传递
- 最近 12 条消息截断
- 追问改写函数
- 用改写问题做 RAG 检索
- 页面支持连续追问
常见坑
不要把所有历史都塞给模型。成本和延迟会快速上升。
不要直接用追问做检索。追问通常缺主语,检索效果会差。
不要把会话记忆当成永久知识库。用户聊天历史和企业文档是两类数据。
本章小结
多轮对话让知识库助手更接近真实产品。下一章进入 LangGraph,学习如何把 Agent 流程变得更可控。