拆解 LangChain 的大模型記憶方案
之前我們聊過(guò)如何使用LangChain給LLM(大模型)裝上記憶,里面提到對(duì)話鏈ConversationChain和MessagesPlaceholder,可以簡(jiǎn)化安裝記憶的流程。下文來(lái)拆解基于LangChain的大模型記憶方案。
1. 安裝記憶的原理
(1) 核心步驟
給LLM安裝記憶的核心步驟就3個(gè):
- 在對(duì)話之前調(diào)取之前的歷史消息。
- 將歷史消息填充到Prompt里。
- 對(duì)話結(jié)束后,繼續(xù)將歷史消息保存到到memory記憶中。
(2) 常規(guī)使用方法的弊端
了解這3個(gè)核心步驟后,在開(kāi)發(fā)過(guò)程中,就需要手動(dòng)寫(xiě)代碼實(shí)現(xiàn)這3步,這也比較麻煩,不僅代碼冗余,而且容易遺漏這些模板代碼。
為了讓開(kāi)發(fā)者聚焦于業(yè)務(wù)實(shí)現(xiàn),LangChain貼心地封裝了這一整套實(shí)現(xiàn)。使用方式如下。
2. 記憶的種類(lèi)
記憶分為 短時(shí)記憶 和 長(zhǎng)時(shí)記憶。
在LangChain中使用ConversationBufferMemory作為短時(shí)記憶的組件,實(shí)際上就是以鍵值對(duì)的方式將消息存在內(nèi)存中。
如果碰到較長(zhǎng)的對(duì)話,一般使用ConversationSummaryMemory對(duì)上下文進(jìn)行總結(jié),再交給大模型。或者使用ConversationTokenBufferMemory基于固定的token數(shù)量進(jìn)行內(nèi)存刷新。
如果想對(duì)記憶進(jìn)行長(zhǎng)時(shí)間的存儲(chǔ),則可以使用向量數(shù)據(jù)庫(kù)進(jìn)行存儲(chǔ)(比如FAISS、Chroma等),或者存儲(chǔ)到Redis、Elasticsearch中。
下面以ConversationBufferMemory為例,對(duì)如何快速安裝記憶做個(gè)實(shí)踐。
3. 給LLM安裝記憶 — 非MessagesPlaceholder
(1)ConversationBufferMemory使用示例
使用ConversationBufferMemory進(jìn)行記住上下文:
memory = ConversationBufferMemory()
memory.save_context(
{"input": "你好,我的名字是半支煙,我是一個(gè)程序員"}, {"output": "你好,半支煙"}
)
memory.load_memory_variables({})
(2)LLMChain+ConversationBufferMemory使用示例
# prompt模板
template = """
你是一個(gè)對(duì)話機(jī)器人,以下<history>標(biāo)簽中是AI與人類(lèi)的歷史對(duì)話記錄,請(qǐng)你參考?xì)v史上下文,回答用戶輸入的問(wèn)題。
歷史對(duì)話:
<history>
{customize_chat_history}
</history>
人類(lèi):{human_input}
機(jī)器人:
"""
prompt = PromptTemplate(
template=template,
input_variables=["customize_chat_history", "human_input"],
)
memory = ConversationBufferMemory(
memory_key="customize_chat_history",
)
model = ChatOpenAI(
model="gpt-3.5-turbo",
)
chain = LLMChain(
llm=model,
memory=memory,
prompt=prompt,
verbose=True,
)
chain.predict(human_input="你知道我的名字嗎?")
# chain.predict(human_input="我叫半支煙,我是一名程序員")
# chain.predict(human_input="你知道我的名字嗎?")
此時(shí),已經(jīng)給LLM安裝上記憶了,免去了我們寫(xiě)那3步核心的模板代碼。
對(duì)于PromptTemplate使用以上方式,但ChatPromptTemplate因?yàn)橛卸嘟巧孕枰褂肕essagesPlaceholder。具體使用方式如下。
4. 給LLM安裝記憶 — MessagesPlaceholder
MessagesPlaceholder主要就是用于ChatPromptTemplate場(chǎng)景。ChatPromptTemplate模式下,需要有固定的格式。
(1) PromptTemplate和ChatPromptTemplate區(qū)別
ChatPromptTemplate主要用于聊天場(chǎng)景。ChatPromptTemplate有多角色,第一個(gè)是System角色,后續(xù)的是Human與AI角色。因?yàn)樾枰杏洃洠灾暗臍v史消息要放在最新問(wèn)題的上方。
(2) 使用MessagesPlaceholder安裝
最終的ChatPromptTemplate + MessagesPlaceholder代碼如下:
chat_prompt = ChatPromptTemplate.from_messages(
[
("system", "你是一個(gè)樂(lè)于助人的助手。"),
MessagesPlaceholder(variable_name="customize_chat_history"),
("human", "{human_input}"),
]
)
memory = ConversationBufferMemory(
memory_key="customize_chat_history",
return_messages=True,
)
model = ChatOpenAI(
model="gpt-3.5-turbo",
)
chain = LLMChain(
llm=model,
memory=memory,
prompt=chat_prompt,
verbose=True,
)
chain.predict(human_input="你好,我叫半支煙,我是一名程序員。")
至此,我們使用了ChatPromptTemplate簡(jiǎn)化了構(gòu)建prompt的過(guò)程。
5.使用對(duì)話鏈ConversationChain
如果連ChatPromptTemplate都懶得寫(xiě)了,那直接使用對(duì)話鏈ConversationChain,讓一切變得更簡(jiǎn)單。實(shí)踐代碼如下:
memory = ConversationBufferMemory(
memory_key="history", # 此處的占位符必須是history
return_messages=True,
)
model = ChatOpenAI(
model="gpt-3.5-turbo",
)
chain = ConversationChain(
llm=model,
memory=memory,
verbose=True,
)
chain.predict(input="你好,我叫半支煙,我是一名程序員。") # 此處的變量必須是input
ConversationChain提供了包含AI角色和人類(lèi)角色的對(duì)話摘要格式。ConversationChain實(shí)際上是對(duì)Memory和LLMChain和ChatPrompt進(jìn)行了封裝,簡(jiǎn)化了初始化Memory和構(gòu)建ChatPromptTemplate的步驟。
6. ConversationBufferMemory
(1) memory_key
ConversationBufferMemory有一個(gè)入?yún)⑹莔emory_key,表示內(nèi)存中存儲(chǔ)的本輪對(duì)話的鍵,后續(xù)可以根據(jù)鍵找到對(duì)應(yīng)的值。
(2) 使用"chat_history"還是"history"
ConversationBufferMemory的memory_key,有些資料里是設(shè)置是memory_key="history",有些資料里是"chat_history"。
這里有2個(gè)規(guī)則,如下:
- 在使用MessagesPlaceholder和ConversationBufferMemory時(shí),MessagesPlaceholder的variable_name和ConversationBufferMemory的memory_key可以自定義,只要相同就可以。比如這樣:
chat_prompt = ChatPromptTemplate.from_messages(
[
("system", "你是一個(gè)樂(lè)于助人的助手。"),
MessagesPlaceholder(variable_name="customize_chat_history"),
("human", "{input}"),
]
)
memory = ConversationBufferMemory(
memory_key="customize_chat_history", # 此處的占位符可以是自定義
return_messages=True,
)
model = ChatOpenAI(
model="gpt-3.5-turbo",
)
chain = ConversationChain(
llm=model,
memory=memory,
prompt=chat_prompt,
verbose=True,
)
chain.predict(input="你好,我叫半支煙,我是一名程序員。") # 此處的變量必須是input
- 如果只是使用ConversationChain,又沒(méi)有使用MessagesPlaceholder的場(chǎng)景下,ConversationBufferMemory的memory_key,必須用history。
7. MessagesPlaceholder的使用場(chǎng)景
MessagesPlaceholder其實(shí)就是在與AI對(duì)話過(guò)程中的Prompt的一部分,它代表Prompt中的歷史消息這部分。它提供了一種結(jié)構(gòu)化和可配置的方式來(lái)處理這些消息列表,使得在構(gòu)建復(fù)雜Prompt時(shí)更加靈活和高效。
說(shuō)白了它就是個(gè)占位符,相當(dāng)于把從memory讀取的歷史消息插入到這個(gè)占位符里了。
比如這樣,就可以表示之前的歷史對(duì)話消息:
chat_prompt = ChatPromptTemplate.from_messages(
[
("system", "你是一個(gè)樂(lè)于助人的助手。"),
MessagesPlaceholder(variable_name="customize_chat_history"),
("human", "{human_input}"),
]
)
是否需要使用MessagesPlaceholder,記住2個(gè)原則:
- PromptTemplate類(lèi)型的模板,無(wú)需使用MessagesPlaceholder
- ChatPromptTemplate 類(lèi)型的聊天模板,需要使用MessagesPlaceholder。但是在使用ConversationChain時(shí),可以省去創(chuàng)建ChatPromptTemplate的過(guò)程(也可以不省去)。省去和不省去在輸出過(guò)程中有些區(qū)別,如下:
總結(jié)
本文主要聊了安裝記憶的基本原理、快速給LLM安裝記憶、ConversationBufferMemory、MessagesPlaceholder的使用、對(duì)話鏈ConversationChain的使用和原理。希望對(duì)你有幫助!