AI 每次都像失忆?给它加上多轮对话和历史记录
第 5 篇
让 AI Demo 更像真实产品:历史记录与多轮对话怎么做?
前面我们已经做了一个最小 AI 学习助手。
但它还有一个明显问题:
每次提问都是独立的,AI 不知道前面聊过什么。
这篇来解决这个问题:给 Demo 加上历史记录和多轮对话。
一、单轮问答的问题
单轮问答的流程是:
这种方式适合简单工具,但不像真实聊天产品。
比如用户第一轮问:
什么是数据库索引?
AI 回答后,用户继续问:
那它为什么能提高查询速度?
如果没有上下文,AI 不一定知道“它”指的是数据库索引。
所以我们需要保存历史消息。
二、多轮对话的基本原理
多轮对话并不神秘。
核心就是:
- 保存用户问题;
- 保存 AI 回答;
- 下一次请求时,把最近几轮历史一起发给模型;
- 模型基于上下文继续回答。
流程如下:
三、消息数据结构
一个最简单的消息结构可以这样设计:
{
conversationId: "会话 ID",
role: "user 或 assistant",
content: "消息内容",
createdAt: "创建时间"
}
字段说明:
| 字段 | 含义 |
| conversationId | 标识属于哪个会话 |
| role | 消息角色,用户或 AI |
| content | 消息内容 |
| createdAt | 创建时间 |
四、先用内存保存历史
正式项目可以用数据库,但学习阶段可以先用内存模拟。
创建 memory.js:
const conversations = {};
function getMessages(conversationId) {
return conversations[conversationId] || [];
}
function addMessage(conversationId, message) {
if (!conversations[conversationId]) {
conversations[conversationId] = [];
}
conversations[conversationId].push({
...message,
createdAt: new Date().toISOString()
});
}
function getRecentMessages(conversationId, limit = 6) {
const messages = getMessages(conversationId);
return messages.slice(-limit);
}
module.exports = {
getMessages,
addMessage,
getRecentMessages
};
这里的 limit = 6 表示只保留最近 6 条消息参与上下文。
这样可以避免上下文太长。
五、改造后端接口
在 server.js 中引入:
const {
addMessage,
getRecentMessages
} = require("./memory");
新增聊天接口:
app.post("/api/chat", async (req, res) => {
try {
const { conversationId, message } = req.body;
if (!conversationId || !message) {
return res.status(400).json({
error: "conversationId 和 message 不能为空"
});
}
const history = getRecentMessages(conversationId, 6);
const messages = [
{
role: "system",
content: "你是一个适合大学生使用的 AI 学习助手,请结合上下文回答。"
},
...history.map(item => ({
role: item.role,
content: item.content
})),
{
role: "user",
content: message
}
];
const response = await fetch(process.env.CHAO_API_BASE_URL, {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": `Bearer ${process.env.CHAO_API_KEY}`
},
body: JSON.stringify({
model: process.env.CHAO_MODEL,
messages
})
});
const data = await response.json();
const answer = JSON.stringify(data);
addMessage(conversationId, {
role: "user",
content: message
});
addMessage(conversationId, {
role: "assistant",
content: answer
});
res.json({
conversationId,
answer
});
} catch (error) {
console.error(error);
res.status(500).json({
error: "多轮对话调用失败"
});
}
});
六、前端如何生成 conversationId
前端可以简单生成一个会话 ID:
let conversationId = localStorage.getItem("conversationId");
if (!conversationId) {
conversationId = `conv_${Date.now()}`;
localStorage.setItem("conversationId", conversationId);
}
发送消息时带上:
await fetch("http://localhost:3000/api/chat", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({
conversationId,
message: userInput
})
});
这样后端就知道这条消息属于哪个会话。
七、为什么不能无限保存上下文
很多初学者会想:既然要多轮对话,那我是不是把所有历史都发给模型?
不建议。
原因有三个:
- 上下文太长会增加成本;
- 太多无关历史会影响回答质量;
- 请求体过大可能导致调用失败。
常见做法:
- 只保留最近几轮;
- 对更早内容做摘要;
- 重要信息单独存储;
- 无关内容不传给模型。
学习阶段可以先用:
getRecentMessages(conversationId, 6)
也就是最近 3 轮对话。
八、正式项目建议用数据库
内存保存有一个问题:
服务重启后,历史记录会丢失。
正式项目可以换成数据库,比如:
- 用户表;
- 会话表;
- 消息表。
消息表可以这样设计:
| 字段 | 类型 | 说明 |
|---|---|---|
| id | string | 消息 ID |
| conversation_id | string | 会话 ID |
| role | string | user / assistant |
| content | text | 消息内容 |
| created_at | datetime | 创建时间 |
九、总结
多轮对话的本质不是模型自动记住一切,而是应用层帮它管理上下文。
核心步骤:
- 保存历史消息;
- 读取最近几轮;
- 接 messages;
- 用 Chao AI API;
- 保存新一轮问答。
做到这一步,你的 AI Demo 就不再只是一次性问答工具,而更像一个真实聊天产品。
下一篇继续升级:给 Demo 加一个简单 RAG 知识库,让 AI 基于资料回答问题。
写在最后的话:
感谢大家对 Chao AI 的支持,对于近期一些常见问题我们在此统一解答:
1️⃣ Chao AI 网址是chaohub.cn/official,注册账号只需要在 Ask Anything 里表明注册账户意图即可注册;
2️⃣ 高等级用户目前我们会在每月月底邀请月活较高的用户优先使用😊;
3️⃣ 近期访问量较大,如果出现卡顿等问题请刷新页面或更换模型重试,敬请谅解,我们也在积极优化中;
4️⃣ 如使用过程中发现 Bug 或有更好的建议,可以在话题中反馈,您的建议一旦被采纳我们会升级您为高等级用户。
欢迎大家评论、点赞、转发、留言。
私信我可进咱们的共创社群,一起发现更多美好吧!
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐

所有评论(0)