上一篇文章實作了 RAG 的實作,我們從資料庫檢索提示的資料給 AI,讓 AI 回答正確的內容。那接下來要再繼續看一個技術,就是 Memory 功能啦!
這個技術很好理解是做什麼的,那就是實現在聊天的感覺。那為什麼會有這個功能的出現呢,因為每次使用 API 呼叫 LLM 都互不相關,每次呼叫都是新的一個對話,所以有了這個功能的誕生。
# 匯入套件
from langchain_ffm import ChatFormosaFoundationModel
from langchain_core.prompts import ChatPromptTemplate, HumanMessagePromptTemplate, MessagesPlaceholder
from langchain_core.messages import SystemMessage, HumanMessage, AIMessage
# 建立模型和聊天提示模板
llm = ChatFormosaFoundationModel()
prompt = ChatPromptTemplate.from_messages(
[
SystemMessage(content="你是一位樂於助人的 AI 助手"),
MessagesPlaceholder(variable_name="chat_history"),
HumanMessagePromptTemplate.from_template("{input}")
]
)
# Chain 起來
chain = prompt | llm
# 定義聊天函數
def chat_with_ffm(chain, user_input, chat_history):
response = chain.invoke({
"input": user_input,
"chat_history": chat_history
})
return response.content
# 開始與 AI 對話
if __name__ == "__main__":
chat_history = []
while True:
user_input = input("You: ")
if user_input.lower() == "exit":
break
response = chat_with_ffm(chain, user_input, chat_history)
chat_history.append(HumanMessage(content=user_input))
chat_history.append(AIMessage(content=response))
print(f"Assiatant: {response}")
程式碼結果探討 🧐:
MessagesPlaceholder(variable_name="chat_history")
這個函數是用於在對話過程中插入消息歷史記錄。它允許模型在生成新的回應時,參考先前的對話內容,從而使回應更加符合上下文。variable_name="chat_history"
表示你在執行 chain.invoke()
時,需要提供一個名為 chat_history
的變數,其內容將會被插入到這個佔位符的位置。invoke
的部分封裝成一個函數,在主程式的地方被呼叫,並且使用 while
迴圈,就可以實作一個簡單的對話功能。# 1.匯入套件
from langchain_ffm import ChatFormosaFoundationModel
from langchain_core.prompts import ChatPromptTemplate, HumanMessagePromptTemplate, MessagesPlaceholder
from langchain_core.messages import SystemMessage
from langchain_community.chat_message_histories import ChatMessageHistory
from langchain_core.chat_history import BaseChatMessageHistory
from langchain_core.runnables.history import RunnableWithMessageHistory
# 2.選擇模型和聊天提示模板
llm = ChatFormosaFoundationModel()
prompt = ChatPromptTemplate.from_messages(
[
SystemMessage(content="你是一位樂於助人的 AI 助手"),
MessagesPlaceholder(variable_name="history"),
HumanMessagePromptTemplate.from_template("{input}")
]
)
chain = prompt | llm
# 3.聊天會話管理功能,可以管理多個對話
store = {}
def get_session_history(session_id: str) -> BaseChatMessageHistory:
if session_id not in store:
store[session_id] = ChatMessageHistory()
return store[session_id]
# 4.將聊天歷史與 LLM 串在一起
with_message_history = RunnableWithMessageHistory(
chain,
get_session_history,
input_messages_key="input",
history_messages_key="history",
)
# 5.定義聊天函數
def chat_with_ffm(user_input, session_id):
response = with_message_history.invoke(
{"input": user_input},
config={"configurable": {"session_id": session_id}},
)
return response.content
# 6.開始與 AI 對話
if __name__ == "__main__":
while True:
session_id = "Sean"
user_input = input("You: ")
if user_input.lower() == "exit":
break
elif user_input.lower() == "clear":
session_id = input("Enter a new session ID: ")
user_input = input("You: ")
response = chat_with_ffm(user_input, session_id)
print(f"Assiatant: {response}")
程式碼結果探討 🧐 (與註解編號一致):
BaseChatMessageHistory
作為聊天歷史的基類,類似之前自定義 LLM 的 BaseLanguageModel
。RunnableWithMessageHistory
是 LCEL 架構中包裝一個 LCEL 的 Chain,能夠管理消息歷史。ChatMessageHistory
是管理聊天歷史記錄。store = {}
是根據提供的 session_id
獲取或創建相應的聊天歷史記錄。這個函數確保了每個 id 有獨立的聊天歷史,並且這些歷史記錄可以被持續儲存和檢索。RunnableWithMessageHistory
將 get_session_history
與 Runnable 元素的 prompt 和 llm 串接起來,並設定 input 和 history 兩個變數分別代表 使用者輸入內容
和 對話歷史紀錄
的傳遞。session_id
來查詢對應的對話紀錄,以上下文的方式提供給 AI,使其可以生成相關的回應。session_id
,並且透過特定指令來另起一個 session_id
,因此開啟一個新的對話。session_id
來開啟一個新的對話可以看到兩組對話紀錄是互不相關的!前面的兩個實作都是存在我們所設定的環境變數中,那如果想要將對話紀錄永久保存的話,我一開始的作法是使用 MySQL,但是要自己寫很多連線和新增查詢的函數。其實 LangChain 有整合很多資料庫可以使用,而且存入或取出資料都很方便,那我這邊選擇 Upstash-Redis,他每天有 10k 的免費 tokens,我自己是覺得如果有儲存對話紀錄的需求的話很值得使用。他們家最近也出了向量資料庫的服務,有機會再來玩看看,但目前是 Qdrant 死忠用戶😆
-> 點我看 Upstash-Redis
註冊並登入之後就可以來的 Upstash-Redis 的首頁,非常簡約風格~點選 Create database
即可進入設定資料庫頁面。
這個其實就是 Upstash-Redis 他們家的雲端服務,需要選一個區域和設定名字,區域我自己試的時候是沒有什麼區域是有限制的,不像 Azure 不同區域有一堆服務限制。那麼在方案的部分當然是選擇免費版囉~~
建立資料庫完成之後,要來取得連線資料庫的參數,有 URL 和 TOKEN。所以進到資料庫裡面之後,往下滾找到 REST API 的地方,先將這兩個需要的參數儲存起來,後續要在程式碼中調用。
# 匯入套件
from langchain_ffm import ChatFormosaFoundationModel
from langchain_core.prompts import ChatPromptTemplate, HumanMessagePromptTemplate, MessagesPlaceholder
from langchain_core.messages import SystemMessage
from langchain_core.chat_history import BaseChatMessageHistory
from langchain_core.runnables.history import RunnableWithMessageHistory
from langchain_community.chat_message_histories.upstash_redis import UpstashRedisChatMessageHistory
from dotenv import load_dotenv
load_dotenv()
import os
# 選擇模型和聊天提示模板
llm = ChatFormosaFoundationModel()
prompt = ChatPromptTemplate.from_messages(
[
SystemMessage(content="你是一位樂於助人的 AI 助手"),
MessagesPlaceholder(variable_name="history"),
HumanMessagePromptTemplate.from_template("{input}")
]
)
chain = prompt | llm
# 從 Upstash-Redis 資料庫中取得和儲存對應 session_id 的對話紀錄
def get_upstash_session_history(session_id: str) -> BaseChatMessageHistory:
return UpstashRedisChatMessageHistory(
url=os.environ['UPSTASH_REDIS_REST_URL'],
token=os.environ['UPSTASH_REDIS_REST_TOKEN'],
session_id=session_id
)
# 將聊天歷史與 LLM 串在一起
with_message_history = RunnableWithMessageHistory(
chain,
get_upstash_session_history,
input_messages_key="input",
history_messages_key="history",
)
# 定義聊天函數
def chat_with_ffm(user_input, session_id):
response = with_message_history.invoke(
{"input": user_input},
config={"configurable": {"session_id": session_id}},
)
return response.content
# 開始與 AI 對話
if __name__ == "__main__":
while True:
session_id = input("Enter a new session ID: ")
user_input = input("You: ")
if user_input.lower() == "exit":
break
elif user_input.lower() == "clear":
session_id = input("Enter a new session ID: ")
user_input = input("You: ")
response = chat_with_ffm(user_input, session_id)
print(f"Assiatant: {response}\n")
程式碼結果探討 🧐:
ChatMessageHistory()
的改成 Upstash-Redis,他們都同樣是 ChatMessageHistory 物件。from langchain_community.chat_message_histories import UpstashRedisChatMessageHistory
from dotenv import load_dotenv
load_dotenv()
import pprint
import os
URL = os.environ["UPSTASH_REDIS_REST_URL"]
TOKEN = os.environ["UPSTASH_REDIS_REST_TOKEN"]
history = UpstashRedisChatMessageHistory(
url=URL, token=TOKEN, session_id="Sean"
)
pprint.pprint(history.messages)
程式碼結果探討 🧐:
messages
即可查詢到對應 session 下的所以對話紀錄。如果希望對話紀錄存在超過一定時間後自己刪除的話,可以設定 ttl
參數。ttl
是以秒為單位,我這邊設為 10,代表 10 秒後這筆對話會從資料庫自行刪除。
UpstashRedisChatMessageHistory(
url=URL, token=TOKEN, ttl=10, session_id="test"
)
今天使用一個簡單的 list 來儲存對話紀錄,也有使用 LCEL 內建的函數搭配 dict 來儲存對話紀錄,確實使用 LCEL 還多了可以管理不同對話紀錄的功能,但除了這部分實現的內容其實是一樣的。最後實作了一個將對話紀錄存放在雲端,透過 Upstash-Redis 結合 LangChain 用起來是很直覺很方便!
今天喝酒有點宿醉哈哈哈~但還是打起精神來寫文章!