摘要:那是因为我们使用的工具在大模型上层加了一层的问答历史记录,在多轮问答的时候,会从历史问答中提取内容,作为上下文和新的问题一起送给大模型,这大模型的回答就具备了多轮对话能力。
大家好,我是程序员寒山。
在传统RAG系统中,每个查询都被视为独立事件。但在实际应用场景中,用户提问往往具有连续性特征。
我们在搭建RAG本地知识库的时候也发现,RAG没有记忆能力,没法多轮问答,每次都要输入完整的问题,才能够检索出合适的内容来。
其实不只是知识库,大模型也是没有记忆能力的,如果大家直接使用api调用模型就是发现这个问题,那么很多人会问我们平时使用的大模型问答是有记忆能力的啊?
那是因为我们使用的工具在大模型上层加了一层的问答历史记录,在多轮问答的时候,会从历史问答中提取内容,作为上下文和新的问题一起送给大模型,这大模型的回答就具备了多轮对话能力。
那么RAG怎么实现呢?
下面是我们今天的主要内容:
RAG为什么需要记忆如何实现RAG的记忆怎么对问题分词和主体追踪问题意图理解,问题重构Python实现记忆Dify演示如何实现RAG多轮问答 # 原始问题: 麻婆豆腐怎么做? # 重构问题: 麻婆豆腐怎么做? # # 原始问题: 它是川菜么? # 重构问题: 麻婆豆腐是川菜么? # # 原始问题: 它的食材有哪些? # 重构问题: 麻婆豆腐的食材有哪些?在烹饪相关的问答场景中,用户通常会进行多轮对话,但传统RAG系统缺乏对上下文的持续追踪能力。例如:
第一轮提问:“如何做麻婆豆腐?” → 系统回答:“需要豆腐、牛肉末、豆瓣酱...步骤1...步骤2...”第二轮提问:“可以用鸡肉代替牛肉吗?” → 系统可能错误地返回“宫保鸡丁的鸡肉处理方法”(上下文丢失)问题根源:
实体漂移:未跟踪核心实体(如“麻婆豆腐”)属性失联:未关联菜谱的食材、步骤、替代方案等结构化信息短期记忆缺失:无法继承前序对话的上下文用户提问 │ ▼实体识别(NER) → 更新当前实体焦点 │ ▼结构化记忆查询 → 存在? → 返回属性值 │ 否 ▼问题意图分析 → 存在? → 返回属性值 │ 否 ▼问题重构 → 生成新问题 │ ▼原始RAG检索 → 生成答案 │ ▼上下文更新与确认jieba 是一个 Python 实现的中文分词工具,具有简单易用、功能丰富等特点,开源且免费使用,
支持精确模式、全模式、搜索引擎模式等多种分词模式,可以实现基本的中文分词、词性标注、关键词提取等功能,还支持自定义词典,方便用户根据特定领域或需求进行个性化分词。
HanLP 是由一系列模型与算法组成的 NLP 工具包,支持 Java、Python 等多种编程语言,涵盖了中文分词、词性标注、命名实体识别、依存句法分析、文本分类等多个自然语言处理任务。
HanLP 的功能更为全面,除了基本的分词功能外,还涵盖了命名实体识别、依存句法分析等多种高级 NLP 任务;而 jieba 主要侧重于中文分词及相关的基础功能,如关键词提取等。
jieba 的运行速度较快,占用资源相对较少,适合处理大规模文本数据的分词任务;HanLP 由于功能复杂,涉及多种模型计算,尤其是在处理大规模数据时,运行时间可能会更长。
创建领域词典(recipe_lexicon.txt):
麻婆豆腐 3 n宫保鸡丁 3 n郫县豆瓣酱 4 n焯水 2 v炝锅 2 v代码实现:
import jiebajieba.load_userdict("recipe_lexicon.txt")def recipe_segment(text): words = jieba.lcut(text, use_paddle=True) return [(w, flag) for w, flag in posseg.lcut(text)]class EntityTracker: def __init__(self, max_memory=3): self.history = deque(maxlen=max_memory) self.current_focus = None def update(self, entities): # 实体权重计算 weights = {} for ent in entities: weights[ent] = weights.get(ent, 0) + 1 if ent in self.history: weights[ent] *= 1.5 # 焦点实体选择 self.current_focus = max(weights, key=weights.get, default=None) self.history.append(self.current_focus)问题意图理解是指分析用户输入问题的目的和需求,确定其背后所表达的真实意图。在多轮问答场景中,准确理解问题意图至关重要,它能帮助模型更好地把握用户需求,提供更准确、更相关的回答,使对话更加流畅和自然。
预训练模型的强大表征能力:transformers 架构的预训练模型,如 BERT、GPT 等,在大规模语料上进行预训练,学习到了丰富的语言知识和语义信息,能够很好地捕捉问题中的语义特征和意图线索。
灵活的微调机制:可以基于预训练模型,通过在特定的问题意图理解数据集上进行微调,使模型适应具体的任务和领域,从而提高意图理解的准确性。
# 原始问题: 麻婆豆腐怎么做? # 重构问题: 麻婆豆腐怎么做? # # 原始问题: 它是川菜么? # 重构问题: 麻婆豆腐是川菜么? # # 原始问题: 食材呢? # 重构问题: 麻婆豆腐的食材有哪些? def reconstruct_question(self, current_question): if not current_question or not isinstance(current_question, str): raise ValueError("问题不能为空且必须是字符串") if not self.context: current_tokens = self.track_question(current_question) self.original_subject = self.get_subject(current_tokens) self.context.append(current_question) return current_question current_tokens = self.track_question(current_question) current_subject = self.get_subject(current_tokens) last_reconstructed_question = self.context[-1]class MultiRoundQuestionMemory: def __init__(self, memory_rounds=3): self.memory_rounds = memory_rounds self.context = self.original_subject = None def reset_memory(self): self.context = self.original_subject = None def set_memory_rounds(self, rounds): self.memory_rounds = rounds def track_question(self, question): return jieba.lcut(question)由于代码比较复杂和第三方库的原因,代码不能直接在dify运行,需要提供http的接口供Dify调用。
@app.route('/reconstruct', methods=['POST'])def reconstruct: data = request.get_json if not data or 'question' not in data: return jsonify({'error': 'Invalid request, question is required'}), 400 question = data['question'] try: reconstructed = memory.reconstruct_question(question) return jsonify({ 'original_question': question, 'reconstructed_question': reconstructed }) except ValueError as e: return jsonify({'error': str(e)}), 400@app.route('/reset', methods=['POST'])def reset: memory.reset_memory return jsonify({'status': 'memory reset successfully'})通过Http请求实现多轮问答的功能,后面的流程就和我们之前讲的类似了。
本文通过菜谱问答场景,详细阐述了如何给RAG增加多轮问答的记忆功能。
在加入记忆模块后,系统在测试集上的准确率会有一个明显的提升。大家可以通过文中原型系统来提升自己的私有RAG的能力。
有问题欢迎留言,只有看到都会给大家回复的,有想深入了解的问题也请评论留言。
来源:半月叨