在AI應用中,無論是多輪對話場景、RAG場景還是AI Agent場景中,記憶能力都是不可或缺的一部分。然而,記憶能力是目前大模型的短板,所以,現(xiàn)在很多框架,諸如 LangChain、MetaGPT 等,都封裝了自己的記憶模塊,以方便開發(fā)者實現(xiàn)自己大模型應用的記憶功能。 之前我們簡單概覽了一下 LangChain 的 Memory 模塊(【AI大模型應用開發(fā)】【LangChain系列】3. 一文了解LangChain的記憶模塊(理論實戰(zhàn)+細節(jié))),那只是在多輪對話場景中,簡單的取最近幾次的對話歷史作為記憶。這是最簡單的使用記憶的方法,也是短期記憶的一種。 本文我們來系統(tǒng)看下實現(xiàn)大模型應用記憶的方法,包括短期記憶和長期記憶。還是以LangChain為例來進行實戰(zhàn)。 0. LangChain中 Memory 實戰(zhàn)我這里將記憶簡單理解為對話歷史,查詢歷史等歷史記錄。
0.1 記憶封裝羅列在 LangChain 中提供了多種獲取記憶的封裝,例如ConversationBufferMemory 、ConversationBufferWindowMemory 、ConversationTokenBufferMemory 等。 簡單羅列如下: · ConversationBufferMemory 可以理解為通用的將全部的歷史記錄取出來。 · ConversationBufferWindowMemory 可以理解為滑動窗口,每次只取最近的K條記錄。 · ConversationTokenBufferMemory 可以理解為控制每次取的歷史記錄的Token數(shù)。 · ConversationSummaryMemory : 對上下文做摘要 · ConversationSummaryBufferMemory : 保存 Token 數(shù)限制內(nèi)的上下文,對更早的做摘要 · VectorStoreRetrieverMemory : 將 Memory 存儲在向量數(shù)據(jù)庫中,根據(jù)用戶輸入檢索回最相關(guān)的部分 · ConversationEntityMemory :保存一些實體信息,例如從輸入中找出一個人名,保存這個人的信息。 · ConversationKGMemory :將歷史記錄按知識圖譜的形式保存和查詢
這里面的大部分記憶封裝,之前咱們已經(jīng)學習過了,這里不再重復。詳細的使用教程可以參考我之前的文章:【AI大模型應用開發(fā)】【LangChain系列】3. 一文了解LangChain的記憶模塊(理論實戰(zhàn)+細節(jié))。
下面看下 VectorStoreRetrieverMemory 的使用和實現(xiàn)效果。 0.2 實踐:VectorStoreRetrieverMemory的使用0.2.1 完整代碼from langchain.memory import VectorStoreRetrieverMemory from langchain_openai import ChatOpenAI from langchain.embeddings.openai import OpenAIEmbeddings from langchain.vectorstores import Chroma from langchain.chains import ConversationChain from langchain.prompts import PromptTemplate
vectorstore = Chroma(embedding_function=OpenAIEmbeddings()) retriever = vectorstore.as_retriever(search_kwargs=dict(k=1)) memory = VectorStoreRetrieverMemory(retriever=retriever)
memory.save_context({"input": "我喜歡學習"}, {"output": "你真棒"}) memory.save_context({"input": "我不喜歡玩兒"}, {"output": "你可太棒了"})
PROMPT_TEMPLATE = """以下是人類和 AI 之間的友好對話。AI 話語多且提供了許多來自其上下文的具體細節(jié)。如果 AI 不知道問題的答案,它會誠實地說不知道。
以前對話的相關(guān)片段: {history}
(如果不相關(guān),你不需要使用這些信息)
當前對話: 人類:{input} AI: """
prompt = PromptTemplate(input_variables=["history", "input"], template=PROMPT_TEMPLATE) chat_model = ChatOpenAI() conversation_with_summary = ConversationChain( llm=chat_model, prompt=prompt, memory=memory, verbose=True )
print(conversation_with_summary.predict(input="你好,我叫同學小張,你叫什么")) print(conversation_with_summary.predict(input="我喜歡干什么?"))
0.2.2 代碼解釋(1)代碼中我們使用了 VectorStoreRetrieverMemory 作為記憶存儲和獲取的模塊。它既然是向量存儲和查詢,所以接收參數(shù):retriever=retriever ,必須要穿給它一個向量數(shù)據(jù)庫才能工作。 (2)然后使用了 ConversationChain 作為對話的Chain。它接收一個 memory = memory 參數(shù)設(shè)置,指定使用的記憶類型。默認是最普通的 ConversationBufferMemory 類型。 (3)什么時候會去檢索記憶呢?在Chain運行 invoke 的一開始,就加載了。源碼如下: 可以看到,最后就是用用戶的輸入,去向量數(shù)據(jù)庫中檢索相關(guān)的片段作為需要的記憶。 0.2.3 運行效果展示第一個問題,檢索到的內(nèi)容不相關(guān),但是也得檢索出一條。 第二個問題,檢索到的內(nèi)容相關(guān),用檢索到的內(nèi)容回答問題。 1. 如何讓AI應用具備長期記憶?我這里將“長期記憶”理解為持久化記憶或者長上下文記憶。也就是兩種形式的記憶我都認為是“長期記憶”:
1.1 LangChain 中的記憶模塊是否具有長期記憶的能力?上面羅列的和實戰(zhàn)的 LangChain 中的記憶模塊,ConversationBufferMemory 、 ConversationBufferWindowMemory 、ConversationTokenBufferMemory 看起來都無法實現(xiàn)長期記憶的能力:無法持久化(看源碼,底層都是一個List類型,保存到內(nèi)存,隨著進程消亡而消亡),也沒法查詢長的上下文。 ConversationSummaryMemory 、ConversationSummaryBufferMemory 在一定程度上能提供更多的記憶信息(因為其對之前的歷史記錄做了總結(jié)壓縮),所以在某些上下文不是特別長的場景中,還是可以用一用來實現(xiàn)簡單的長期記憶能力的。 ConversationEntityMemory 、ConversationKGMemory 一個只保存實體信息,一個將歷史記錄組織成知識圖譜,會對長上下文場景中的長時記憶功能非常有用。它可以從全局的角度將用戶提問中的實體或相關(guān)知識作補充,而不是關(guān)注最近的幾次對話。
VectorStoreRetrieverMemory 應該是最好和最能實現(xiàn)長期記憶能力的類型了。一方面,它是向量數(shù)據(jù)庫存儲,可以方便的持久化數(shù)據(jù),另一方面,它的向量檢索能力,本來就是針對用戶提問檢索出最相關(guān)的文檔片段,不受長上下文的窗口限制。但是其檢索的相關(guān)片段之間是否存在信息缺失等,會影響長時記憶的準確性,從而影響最終的結(jié)果。
所以,ConversationEntityMemory 、ConversationKGMemory + VectorStoreRetrieverMemory 是否可以一試?三者結(jié)合,保持相關(guān)片段的相關(guān)性,同時利用實體關(guān)系和知識圖譜進行補充,是否可以更好地實現(xiàn)長時記憶的能力?感興趣的可以一起討論~
1.2 關(guān)于讓AI應用具備長期記憶的一些研究1.2.1 記憶思考:回憶和后思考使LLM具有長期記憶論文原文:Think-in-Memory: Recalling and Post-thinking Enable LLMs with Long-Term Memory
這篇文章提出了一種名為TiM(Think-in-Memory)的記憶機制,旨在使LLM在對話過程中保持記憶,存儲歷史思考。TiM包括兩個關(guān)鍵階段:在生成回復之前,LLM從記憶中回想相關(guān)思考;在生成回復之后,LLM進行后思考并將歷史和新思考結(jié)合起來更新記憶。 下圖描述了TiM方法的使用方式: (1)在回答第二個問題時,需要考慮問題1的內(nèi)容,從問題1中推理出答案,而后在回答問題2。 (2)在回答第三個問題時,需要同時考慮問題1和問題2,從問題1和問題2中推理出答案,而后再回答問題3。 這就導致了問題的存在:問題1被推理了兩遍,兩遍的結(jié)果還可能不一樣,導致最終的錯誤。 而TiM的思路,是將每一個問題的思考也存起來,這樣,在回答問題3時,可以使用問題2之前的思考,避免重新思考問題1,從而避免多次思考結(jié)果不一致導致的錯誤。 具體步驟如下: 總的原理是,將相關(guān)的記憶放到一起,例如上圖中,關(guān)于book的談話放到index 0中,關(guān)于moive的談話放到index 1中。 如何將相關(guān)內(nèi)容放到一起的?論文中實現(xiàn)了一種基于局部敏感哈希(LSH)的存儲系統(tǒng),用于高效地存儲和檢索大規(guī)模的向量數(shù)據(jù)。LSH的作用是將每個向量映射到一個哈希索引,相似的向量有更高的概率被映射到相同的哈希索引。 而相同的哈希索引可以將用戶問題固定到某一塊記憶中,然后只在這一塊記憶中進行向量檢索,大大提高了檢索效率。 這篇文章還是值得精讀一下的,數(shù)據(jù)的組織方式和索引方式都比較高級,很有啟發(fā)。
1.2.2 遞歸總結(jié)在大型語言模型中實現(xiàn)長期對話記憶論文原文:Recursively Summarizing Enables Long-Term Dialogue Memory in Large Language Models
這篇文章提出了一種遞歸總結(jié)的方法,用于增強大模型的長期記憶能力,以解決在長對話中無法回憶過去信息和生成不一致響應的問題。該方法首先刺激LLM記憶小的對話上下文,然后遞歸地使用先前的記憶和后續(xù)的上下文生成新的記憶。 其流程如下: 簡單概括,就是:上一輪的內(nèi)容總結(jié) + 本輪的問題回答 = 本輪的內(nèi)容總結(jié)。本輪的內(nèi)容總結(jié) + 下輪的問題回答 = 下輪的內(nèi)容總結(jié)。...... 不斷迭代。與 LangChain中ConversationSummaryMemory 的實現(xiàn)很類似。 這種方法每一輪都要總結(jié)一次,也就是調(diào)用一次大模型,使用成本很高啊...... 實際生產(chǎn)中應該落地比較難。
1.2.3 更多研究更多關(guān)于AI應用 Memory 的研究可以參考下面這個文章: 加個TODO,還沒看完,大家可以一起看。
如果覺得本文對你有幫助,麻煩點個贊和關(guān)注唄 ~~~
|