之前的程式例子中,我們幾乎都只有讓模型進行一次推論,一點都不像在聊天。你可能會寫出這樣的程式碼 。然後就會像圖中的輸出一樣,模型總是會忘記我們先前說的話。
相較之下,ChatGPT 就比較聰明,記得我說過的話
要實作記憶功能,最簡單的方法就是直接讓 LLM 看到你的「聊天記錄」,他就會跟著這個聊天記錄接龍下去。
client.chat.completions.create
的 message 參數格式是一個 List,這個 List 的元素為字典,字典中有兩個鍵值對,一個是 role 另一個是 content。參考官方文件,可以知道 role 就有 user
, assistant
, system
, tool
可以設定。
現在,我們只要再傳入 message 的 List 中,直接將未來使用者的輸入、LLM 回應的內容,append 到這個 List 就可以形成一個簡單的聊天記錄了,讓 LLM 好像有記憶一樣。
# 使用 openai 的 API 來生成文本
import openai
from dotenv import load_dotenv # 載入 dotenv 套件
import os
load_dotenv() # 載入環境變數
model_name = "gpt-4o-mini"
# 從環境變數中取得 API 金鑰,並且設定給 openai
openai.api_key = os.getenv("OPENAI_API_KEY")
client = openai.OpenAI() # 建立 OpenAI 客戶端
# 初始化對話歷史
messages = [
{
"role": "system",
"content": "使用繁體中文回答問題"
}
]
while True:
# 獲取用戶輸入
user_input = input("你:")
# 將用戶輸入加到對話歷史
messages.append({"role": "user", "content": user_input})
# 如果用戶輸入 '再見',結束對話
if user_input.lower() == '再見':
print("AI:再見!很高興與你交談。")
break
# 發送請求給 OpenAI API
completion = client.chat.completions.create(
model=model_name,
messages=messages
)
# 獲取 AI 的回應
ai_response = completion.choices[0].message.content
# 將 AI 的回應添加到對話歷史
messages.append({"role": "assistant", "content": ai_response})
# 印出 AI 的回應
print(f"AI:{ai_response}")
測試結果,他果然認得我是誰了哈哈哈
看到這邊聰明的你應該有想到一個問題,因為我們目前的記憶功能純粹把先前的對話紀錄給他看而已,那會不會有一天我跟他聊太久,聊到超過上限文限制了呢?
我們先來看看 gpt-4o-mini
的 token 限制,context window 表示上下文的限制;max output tokens 則是輸出的最大數量。https://platform.openai.com/docs/models/gpt-4o-mini
再來是 llama3.1 的 context window 上限 (https://github.com/meta-llama/llama-models/blob/main/models/llama3_1/MODEL_CARD.md https://console.groq.com/docs/models#llama-31-70b-preview)
你會發現,夭壽喔大家都在比大的,AI 就是軍備競賽。十二萬多的 token 你可能很難想像怎麼可能用得完…。想像如果你給 AI 看西遊記這本書,全書約 58.5 萬字,那一定會超過對吧;再或者你想要打造一個專屬的助理系統,他可以記得你的所有食衣住行育樂,有一天它也一定會記超過模型可以負荷的 token 數量,進而崩潰。
如果你有用過剛問世的 ChatGPT,應該會發現當你在同一個聊天室中聊越久,ChatGPT 回覆的東西明顯越爛越敷衍。這其實是因為它很有可能「不記得」你先前和他的對話了,ChatGPT 用了一些方式讓妳好像可以一直和他對話,彷彿 LLM 沒有上下文限制。
你可以利用 tiktoken 這個 OpenAI 推出的 Python 程式庫來將字串轉換成 token,再去算數量 (或者用 token-count),自己設定一個上限,當超過上限就直接刪除先前的記憶。
import tiktoken
def calculate_token_count(input_string: str, model_name: str) -> int:
# 使用 tiktoken 建立編碼器
encoding = tiktoken.encoding_for_model(model_name)
# 計算並印出 token 數量
print(f"實際上的 token 組成:{encoding.encode(input_string)}")
token_count = len(encoding.encode(input_string))
print(f"token 數量:{token_count}")
return token_count
if __name__ == "__main__":
model_name = "gpt-4o-mini"
sample_input = "你好啊"
token_count = calculate_token_count(sample_input, model_name)
sample_input = "Hello my friend"
token_count = calculate_token_count(sample_input, model_name)
把計算 token 的機制加入到剛剛的聊天記憶功能吧!
import openai
from dotenv import load_dotenv
import os
import tiktoken
load_dotenv() # 載入環境變數
model_name = "gpt-4o-mini"
# 從環境變數中取得 API 金鑰,並且設定給 openai
openai.api_key = os.getenv("OPENAI_API_KEY")
client = openai.OpenAI() # 建立 OpenAI 客戶端
# 使用 tiktoken 建立編碼器
encoding = tiktoken.encoding_for_model(model_name)
# 初始化
messages = [
{
"role": "system",
"content": "使用繁體中文回答問題"
}
]
while True:
# 獲取用戶輸入
user_input = input("你:")
# 將用戶輸入加到對話歷史
messages.append({"role": "user", "content": user_input})
# 如果用戶輸入 '再見',結束對話
if user_input.lower() == '再見':
print("AI:再見!很高興與你交談。")
break
total_tokens = sum(len(encoding.encode(msg["content"])) for msg in messages)
print(f"目前 token 數量:{total_tokens}")
# 當 token 超過 1000 時,移除最早的消息
while total_tokens > 1000:
messages.pop(1) # 移除最早的用戶或 AI 回應
total_tokens = sum(len(encoding.encode(msg["content"])) for msg in messages)
# 發送請求給 OpenAI API
completion = client.chat.completions.create(
model=model_name,
messages=messages
)
# 獲取 AI 的回應
ai_response = completion.choices[0].message.content
# 將 AI 的回應添加到對話歷史
messages.append({"role": "assistant", "content": ai_response})
# 印出 AI 的回應
print(f"AI:{ai_response}")
不過很明顯的,這樣不夠聰明對吧,直接忘掉最一開始發生的事對長期記憶來說是很傷的。就好比我不會忘記我的幼稚園在哪裡,儘管那已經是十幾年前的事情了。你跟這個 LLM 系統從白天到晚上對話一整天,他搞不好就忘記早上的東西了。
剛剛提到幼稚園的例子,我雖然記得我的幼稚園長什麼樣子但裡面的教室、同學的臉龐甚至是遊樂設施,但我幾乎都沒辦法很清晰的想起來。我的腦容量是有限的,所以我只能記得一個大概、一個輪廓。LLM 記憶系統當然也可以用一樣的策略,把先前的對話內容先摘要,讓之後的對話如果牽扯到先前的記憶,LLM 也是可以唬個幾句。
這邊就不示範了,有興趣的朋友可以參考 Mem0, MemGPT 這些工具來儲存記憶,它已經把剛剛提到的摘要功能做好了。不過因為它使用到檢索增強生成 (Retrieval-Augmented Generation, RAG) 的技術,等介紹完 RAG 之後再回頭來 Mem0 會更好理解它的原理,到時候要做一個 AI 老婆也不是夢XD
明天我們來讓模型計算 strawberry 有 r 吧,不過是要讓他使用工具來計算,期待一下吧~