如何为基于Discord.js与OpenAI API的聊天机器人添加记忆功能?
Discord.js + OpenAI 聊天机器人记忆功能实现方案
优先方案:基于MongoDB存储对话历史(适配现有已连接的数据库)
这个方案无需额外依赖,直接利用现有MongoDB连接,开发成本低,适合独立开发者快速落地。
1. 数据结构设计(Mongoose示例)
针对Discord用户的对话场景,设计如下Schema,区分不同用户/服务器的对话,同时保留时间戳用于管理:
const mongoose = require('mongoose'); const conversationSchema = new mongoose.Schema({ userId: { type: String, required: true }, // Discord用户唯一ID guildId: String, // 可选,区分不同服务器的对话上下文 history: [{ role: { type: String, enum: ['user', 'assistant'], required: true }, content: { type: String, required: true }, timestamp: { type: Date, default: Date.now } }], lastActive: { type: Date, default: Date.now } // 用于自动清理长期未活跃的对话 }); // 建立复合索引,提升查询效率 conversationSchema.index({ userId: 1, guildId: 1 }); module.exports = mongoose.model('Conversation', conversationSchema);
2. 对话存储与查询逻辑
- 存储:每次用户发送消息时,先通过
userId(加guildId可选)查找对应对话文档,不存在则新建;将用户消息添加到history数组,调用OpenAI得到回复后,再将机器人回复也存入数组,同时更新lastActive时间。 - 查询:调用OpenAI前,查询该用户的最近N条对话(比如10条,可根据模型token上限调整),避免Prompt过长导致token超限。
3. 按要求构建OpenAI Prompt
直接基于查询到的历史对话拼接成指定格式的Prompt,适配Curie/Babbage模型:
const Conversation = require('./models/Conversation'); async function buildOpenAIPrompt(userId, guildId, currentMessage) { // 硬编码系统介绍 const systemIntro = "你是一个活跃在Discord的聊天机器人,会结合用户的历史对话上下文给出自然的回复。"; // 查询最近10条对话 const conversation = await Conversation.findOne( { userId, guildId }, { history: { $slice: -10 } } // 只取最后10条 ) || { history: [] }; // 转换历史对话为字符串 const historyStr = conversation.history.map(item => { return item.role === 'user' ? `用户:${item.content}` : `机器人:${item.content}`; }).join('\n'); // 拼接完整Prompt return `${systemIntro}\n\n对话历史:\n${historyStr}\n\n用户:${currentMessage}\n机器人:`; }
4. 优化建议
- 限制对话历史长度:Curie/Babbage的token上限为2048,建议用
openai-count-tokens包计算Prompt的token数,超过阈值时截断早期对话。 - 定期清理过期数据:通过定时任务删除
lastActive超过30天的对话文档,减少数据库占用。
轻量化补充:关键信息提取(无需复杂NLP库)
如果觉得全量对话存储占用资源,可同时提取对话中的关键信息(如用户爱好、提到的事件等),存储到用户对话文档中,进一步精简Prompt:
- 在上述Schema中新增
keyInfo字段:
keyInfo: { userNickname: String, preferences: [String], recentTopics: [String] }
- 用简单字符串匹配提取关键信息,比如:
// 示例:提取用户提到的爱好 function extractKeyInfo(messageContent, existingKeyInfo) { const hobbyKeywords = ['喜欢', '爱好', '玩', '看']; hobbyKeywords.forEach(keyword => { if (messageContent.includes(keyword)) { const hobby = messageContent.split(keyword)[1]?.trim().split(' ')[0]; if (hobby && !existingKeyInfo.preferences.includes(hobby)) { existingKeyInfo.preferences.push(hobby); } } }); return existingKeyInfo; }
- 构建Prompt时可将
keyInfo加入系统介绍,让机器人快速掌握用户基本信息。
代码改造注意事项
- 模块化拆分:将对话存储、Prompt构建等逻辑封装成独立服务类,与现有机器人逻辑解耦,避免大规模修改原有代码。
- 灰度测试:先在测试Discord服务器部署验证功能,确认稳定后再推广到正式环境。
- 错误处理:为数据库操作、OpenAI API调用添加try-catch,避免单个功能异常导致整个机器人崩溃。
内容的提问来源于stack exchange,提问作者Op12
相关产品推荐
相关产品推荐

