如何在RAG系统中使用记忆
2️⃣

如何在RAG系统中使用记忆

 

一、背景

在我们使用Dify的时候,我们如果按照官方的教程,将上下文放置到system prompt中,我们会发现,如果多询问几次,后面的资料就会替代原来召回的内容,下面我举一个例子进行说明。
notion image
在这个流程中我模拟了不同的问题取回不同材料的流程。
  1. 如果输入包含“1”的问题,则从模拟资料1中获取。如果输入包含“2”的问题,则从模拟资料2中获取。以此类推
  1. 我按照要求开启了记忆功能。将获取的资料作为上下文放置到system中去。
notion image
 
现在我们开始展开三轮对话,我会分别输入问题“你好-1”,“你好-2”,“你好-3”,让我们来进行观察。

阶段一:输入问题“你好-1”

notion image
流程成功进行,我们来查看回答这个节点的数据处理
{ "model_mode": "chat", "prompts": [ { "role": "system", "text": "我的资料是{包含测试1111的内容}\n随便回答点什么", "files": [] }, { "role": "user", "text": "你好-1", "files": [] } ], "model_provider": "langgenius/tongyi/tongyi", "model_name": "qwen-turbo" }
在该流程中,我们开单到了资料成功获取,并进行了提问。

阶段二:输入问题“你好-2”

阶段二流程完成以后,我们继续查看节点的数据处理
{ "model_mode": "chat", "prompts": [ { "role": "system", "text": "我的资料是{包含测试22222的内容}\n随便回答点什么", "files": [] }, { "role": "user", "text": "你好-1", "files": [] }, { "role": "assistant", "text": "你好!有什么问题或需要帮助的吗?", "files": [] }, { "role": "user", "text": "你好-2", "files": [] } ], "model_provider": "langgenius/tongyi/tongyi", "model_name": "qwen-turbo" }
我们发现了system prompt 中的 资料被替换成了 资料库2中的内容,资料1 的内容完全消失了。

阶段三:输入问题“你好-3”

阶段三流程完成以后,我们继续查看节点的数据处理
{ "model_mode": "chat", "prompts": [ { "role": "system", "text": "我的资料是{包含测试3333的内容}\n随便回答点什么", "files": [] }, { "role": "user", "text": "你好-1", "files": [] }, { "role": "assistant", "text": "你好!有什么问题或需要帮助的吗?", "files": [] }, { "role": "user", "text": "你好-2", "files": [] }, { "role": "assistant", "text": "你好!很高兴能为你提供帮助。你需要咨询或者讨论些什么呢?", "files": [] }, { "role": "user", "text": "你好-3", "files": [] } ], "model_provider": "langgenius/tongyi/tongyi", "model_name": "qwen-turbo" }
我们发现了system prompt 中的 资料被替换成了 资料库3中的内容,资料1 和资料2中的的内容完全消失了。

阶段总结

通过刚才的实验我们发现Dify在处理对话流程的时候,会使用最新的资料代替system promp中资料的部分,会造成资料的丢失,尤其当我们需要在对话中涉及到产品对比,第一条资料和第二条资料对比的时候,资料就会丢失。

解决方案

方案一:在输出的时候带上原来的资料

notion image
既然我们system prompt无法记录原先找到的内容,那么我们是否可以将材料记录到问题中呢?
 
notion image
{ "model_mode": "chat", "prompts": [ { "role": "system", "text": "我的资料是\n随便回答点什么", "files": [] }, { "role": "user", "text": "1", "files": [] }, { "role": "assistant", "text": "找到的资料:\n包含测试1111的内容\n看起来你正在寻找一些与“测试1111”相关的信息或结果。虽然我手头的资料有限,无法直接提供关于“测试1111”的具体内容,但通常这样的术语可能指的是某种特定的测试、实验或者评估过程的编号。如果你能提供更多背景信息或是具体想了解哪方面的内容,我会尽力帮助你找到相关信息或给出更具体的建议。例如,这可能是软件测试的一部分,也可能是某种学术研究中的步骤编号等。请告诉我更多细节,好吗?", "files": [] }, { "role": "user", "text": "2", "files": [] }, { "role": "assistant", "text": "看起来您提到的是一个包含“测试22222”的材料,不过您似乎还没有具体说明您的问题是什么。如果您能提供更多详细信息,比如您想了解这个测试的具体内容、目的或者是如何进行的等等,我将能够更好地为您提供帮助。\n\n如果“测试22222”是指某个特定领域的测试(如软件测试、心理测试、教育测试等),提供更多的上下文会非常有帮助。这样我可以更有针对性地为您查找相关信息或解答您的疑问。\n\n请告诉我您具体想了解什么,好吗?", "files": [] }, { "role": "user", "text": "找到的材料:包含测试3333的内容\n我的问题:\n3", "files": [] } ], "model_provider": "langgenius/tongyi/tongyi", "model_name": "qwen-turbo" }
经过测试,Dify不会按照我们的user格式进行请求,只会按照用户的请求数据构造。
那我们将找到的资料放到输出的内容是不是可行?
我们直接试一试
第一步:先进行LLM节点的设置
notion image
第二步:设置最后䣌输出节点
notion image
第三步:开始测试,查看多轮对话以后的数据变化
notion image
{ "model_mode": "chat", "prompts": [ { "role": "system", "text": "我的资料是\n随便回答点什么", "files": [] }, { "role": "user", "text": "你好-1", "files": [] }, { "role": "assistant", "text": "我找到的资料:\n包含测试1111的内容\n助手回答:\n你好!很高兴为你提供帮助。你可以问我任何问题,或者告诉我你感兴趣的话题。", "files": [] }, { "role": "user", "text": "你好-2", "files": [] }, { "role": "assistant", "text": "我找到的资料:\n包含测试22222的内容\n助手回答:\n你好!有什么可以帮到你的吗?如果你有任何问题或需要信息,尽管告诉我。", "files": [] }, { "role": "user", "text": "你好-3", "files": [] } ], "model_provider": "langgenius/tongyi/tongyi", "model_name": "qwen-turbo" }
查看日志,我们发现assistant的节点包含了我们找到的内容。
说明这样的方案是可行的。对话中保留了原来找到资料的内容。
这样的展示太影响展示了。我们可以设置自定义的标签,方便客户端去处理
例如
notion image
在这个例子中我们将查找到的资料放置到<CONTEXT>的标签中,客户端就可以识别
notion image
只要我们和客户端约定好数据结构,客户端也就能实现类似资料索引的效果了。这样的数据可以是文本、也可以是视频和图片,
notion image
 

方案二:通过变量赋值实现召回数据的写入

在Dify中有一个会话变量的参数,他能够帮助我们储存一些信息。我们可以实现一个小型的召回资料的存储器。
notion image
我们可以通过设置一个名叫 storage的变量来进行数据的存储。
notion image
然后我们增加流程
notion image
代码执行主要工作是保证存储只保留最新的4条记录
notion image
function main({arg1, arg2}) { const result = []; // 如果有新内容,添加到结果的开头 if(!!arg2){ result.push(arg2); } // 然后添加历史数据 if(Array.isArray(arg1)) { result.push(...arg1); } // 只保留最新的4条数据 return { result: result.slice(0, 4) } }
处理完以后就加入到会话变量中
notion image
我们开始最后的测试
notion image
对话记录
{ "model_mode": "chat", "prompts": [ { "role": "system", "text": "我的资料是{包含测试3333的内容\n包含测试3333的内容\n包含测试22222的内容\n包含测试1111的内容}\n随便回答点什么", "files": [] }, { "role": "user", "text": "1", "files": [] }, { "role": "assistant", "text": "历史记录:\n包含测试1111的内容\n你的资料提到“包含测试1111的内容”,让我很感兴趣呢。不过我更想听听这测试1111具体是什么,是某种神秘的代码还是有趣的实验结果呀?", "files": [] }, { "role": "user", "text": "2", "files": [] }, { "role": "assistant", "text": "历史记录:\n包含测试22222的内容\n包含测试1111的内容\n看起来你可能对某些特定的信息或内容感兴趣。如果你有任何问题或者需要讨论某个话题,请告诉我更多细节,我会尽力提供帮助或进行相关的讨论。无论是关于“包含测试1111的内容”还是其他任何主题,都欢迎你分享更多信息!", "files": [] }, { "role": "user", "text": "3", "files": [] }, { "role": "assistant", "text": "历史记录:\n包含测试3333的内容\n包含测试22222的内容\n包含测试1111的内容\n历史记录:\n包含测试3333的内容\n包含测试22222的内容\n包含测试1111的内容\n\n从你的资料来看,“包含测试3333的内容”似乎是一个特别的条目。你是否希望了解更多关于这个内容的信息,或者有其他方面想要探讨的?", "files": [] }, { "role": "user", "text": "3", "files": [] } ], "model_provider": "langgenius/tongyi/tongyi", "model_name": "qwen-turbo" }
我们发现对话中的system promp 就包含了最新获取的资料,
这样就保证了连续对话的时候最新搜索到资料不会覆盖原有查找到的内容。
但是遇到一个问题,如果内容太多怎么办呢?或者说我们是否可以总结以往的对话?

方案三:召回资料总结

有时候我们还需要对召回的资料进行总结,这样的优势
  1. 减少召回资料上下文的长度
  1. 针对问题和资料进行总结,更加聚焦
notion image
增加可以增加LLM总结节点
notion image
这样,我们就完成了一个初版的资料记忆的功能。
notion image

方案四:对话总结功能

召回的资料既然能总结,我们是否对历史的对话记录也可以进行总结。这样在一定程度保证对话的连贯性。
notion image
在Dify中提供了记忆的功能,但是只提供了窗口的功能,无法做到总结对话历史,那我们可以自己实现。
首先,我们增加一个变量就叫 summary
notion image
让我们来设计一条链路来处理历史记录
  1. 可以是并行任务,这样需要在下次聊天的时候才会了解前面的内容,这样并发请求,不影响主流程的进度
  1. 也可以是穿行任务,这样总结,总结内容会及时插入,可能需要多一次llm的请求
我们按照并发任务开始
notion image
在上图中,我么能增加了一路保存历史总结的内容,我们在对话超过5次的时候自动总结。
在回答的文档中,我们增加了历史记录的信息
notion image
现在让我们来测试下,我们围绕西湖、杭州、西湖醋鱼片儿川等问题进行了提问。在变量中我们也看到了dify成功的存储了对话总结
notion image
我们再来看看请求的日志
{ "model_mode": "chat", "prompts": [ { "role": "system", "text": "我的资料是{{#context#}}\n随便回答点什么", "files": [] }, { "role": "user", "text": "我们刚才聊天的记录为:### 对话总结\n\n在我们之前的对话中,我们讨论了以下几个主题:\n\n1. **西湖**\n - 提到西湖的美丽景色,如苏堤春晓、断桥残雪等。\n\n2. **杭州**\n - 讨论了杭州作为一个历史悠久且充满魅力的城市,提到了其他景点如宋城和灵隐寺,以及杭州的茶文化。\n\n3. **西湖醋鱼**\n - 介绍了这道杭州传统名菜的历史背景和特点,强调了其独特的烹饪方法和美味口感。\n\n4. **片儿川**\n - 讲解了这道杭州传统面食的主要配料和风味,突出了它的鲜美和受欢迎程度。\n\n5. **苏东坡**\n - 详细介绍了苏东坡的生平及其文学成就,特别提到他在杭州任职期间对当地的影响和贡献。\n\n6. **西湖十景**\n - 列举并简要描述了西湖十景中的十个著名景点,包括苏堤春晓、断桥残雪、曲院风荷等。\n\n7. **雷峰塔**\n - 特别提到雷峰塔作为西湖十景之一的独特之处,尤其是在夕照时的美丽景色。\n\n这些话题围绕着杭州的文化、历史和自然景观展开,展示了这座城市的丰富内涵和独特魅力。你对其中哪个方面最感兴趣呢?", "files": [] }, { "role": "user", "text": "西湖", "files": [] }, { "role": "assistant", "text": "西湖真的很美呢,尤其是苏堤春晓、断桥残雪这些著名景色。你去过西湖吗?或者有什么关于西湖的故事想分享吗🧐", "files": [] }, { "role": "user", "text": "杭州", "files": [] }, { "role": "assistant", "text": "杭州是一个历史悠久且充满魅力的城市,除了著名的西湖之外,还有许多其他的旅游景点和文化特色。比如宋城可以体验宋代的文化氛围,灵隐寺则是一个非常有名的佛教圣地。此外,杭州还是茶文化的重要代表,龙井茶就是这里的特产之一。你对杭州的哪个方面最感兴趣呢?", "files": [] }, { "role": "user", "text": "西湖醋鱼", "files": [] }, { "role": "assistant", "text": "西湖醋鱼是一道非常有特色的杭州传统名菜。这道菜选用的是草鱼,通过特殊的烹饪手法,使得鱼肉嫩滑,酸甜适口,非常美味。据说这道菜的历史可以追溯到南宋时期,至今仍然深受人们的喜爱。\n\n如果你有机会去杭州,一定不要错过品尝这道地道的杭州美食。你在尝试过西湖醋鱼之后有什么感想吗?或者你有没有什么做这道菜的小秘诀想要分享呢?", "files": [] }, { "role": "user", "text": "片儿川", "files": [] }, { "role": "assistant", "text": "片儿川是杭州非常有名的一种传统面食,以其独特的风味和制作工艺受到很多人的喜爱。它主要由细长的面条配上雪菜(即腌制的白菜)、笋片和瘦肉丝组成。汤头鲜美,面条滑爽,配料丰富,味道层次分明,既可作为主食饱腹,也是一道美味的菜肴。\n\n如果你喜欢面食的话,片儿川绝对值得一试。你平时还喜欢吃哪些面食呢?或者你有没有自己特别钟爱的家乡小吃?", "files": [] }, { "role": "user", "text": "苏东坡", "files": [] }, { "role": "assistant", "text": "苏东坡(1037年—1101年),字子瞻,号东坡居士,是北宋时期著名的文学家、书画家,也是唐宋八大家之一。他的文学成就非常高,诗词、散文都有卓越的表现,尤其以豪放派诗词著称。苏东坡不仅在文学上有很高的造诣,在书法和绘画上也有极高的成就。\n\n苏东坡一生经历坎坷,曾多次被贬官,但他始终保持乐观豁达的人生态度,留下了许多脍炙人口的作品。他的作品中常常流露出对自然美景的热爱以及对生活的深刻感悟,如《赤壁赋》、《念奴娇·赤壁怀古》等。\n\n苏东坡与杭州也有很深的渊源,他曾担任杭州的地方官,并在此期间为当地做了不少好事,留下了诸多佳话。你对苏东坡有哪些了解或特别感兴趣的地方呢?", "files": [] }, { "role": "user", "text": "西湖十景", "files": [] }, { "role": "assistant", "text": "西湖十景是指位于中国浙江省杭州市西湖周边的十个著名景点,每个景点都有其独特的风景和历史背景。这十个景点分别是:\n\n1. **苏堤春晓**:春天时,苏堤两岸的柳树和桃花盛开,景色十分迷人。\n2. **断桥残雪**:冬天雪后,断桥上的积雪与湖面的雾气交相辉映,形成一幅美丽的画面。\n3. **曲院风荷**:夏季荷花盛开,漫步于曲折的小径间,欣赏荷花的美丽。\n4. **平湖秋月**:秋天夜晚,湖面上的月亮倒映,景色宁静而美丽。\n5. **柳浪闻莺**:春天柳树成荫,莺鸟鸣叫,是赏柳听鸟的好地方。\n6. **花港观鱼**:这里有一个小池塘,里面养着各种颜色的金鱼,游客可以喂鱼。\n7. **雷峰夕照**:夕阳西下时,雷峰塔在夕阳的照射下显得格外美丽。\n8. **双峰插云**:两座山峰高耸入云,尤其是在雨后的清晨,云雾缭绕,景色壮观。\n9. **南屏晚钟**:晚上南屏山上净慈寺的钟声响起,回荡在西湖上空,给人以宁静的感觉。\n10. **三潭印月**:三潭映月是西湖的一个小岛,上面有三个石塔,夜晚时分,灯光透过石塔,形成美丽的光影效果。\n\n这些景点不仅展示了西湖的自然美景,还蕴含了丰富的历史文化内涵,吸引着无数游客前来游览。你最喜欢西湖十景中的哪一个呢?", "files": [] }, { "role": "user", "text": "雷峰塔", "files": [] }, { "role": "assistant", "text": "雷峰塔位于浙江省杭州市西湖风景区南岸的夕照山上,是中国著名的古塔之一。雷峰塔最初建于公元975年,由吴越国王钱弘俶为庆祝王妃生子而建,原名为“皇妃塔”。后来由于塔身逐渐倾斜,最终在1924年倒塌。\n\n现在的雷峰塔是在原址重建的,于2002年建成并对外开放。新塔保留了旧塔的部分遗址,并且在内部设有展览馆,展示了与雷峰塔相关的文物和历史资料。雷峰塔不仅是西湖十景之一“雷峰夕照”的重要组成部分,还因为《白蛇传》这部民间传说而闻名遐迩。在《白蛇传》中,雷峰塔是法海和尚镇压白娘子的地方。\n\n登上雷峰塔,可以俯瞰整个西湖的美景,特别是在夕阳西下的时候,景色尤为迷人。你有没有去过雷峰塔?或者有没有听说过有关雷峰塔的一些有趣的故事?", "files": [] }, { "role": "user", "text": "平湖秋月", "files": [] } ], "model_provider": "langgenius/tongyi/tongyi", "model_name": "qwen-turbo" }
我们可以看到在最新一条的user数据中,就有了历史对话的总结

总结

在对话中一些数据的存储可以方便的为我们提供组合数据与对话的能力,在形式上可以分为并发与串行,在储存内容上,我们可以储存历史对话,可以储存召回的资料。核心是赋予我们组装对话的能力。