iT邦幫忙

2024 iThome 鐵人賽

DAY 5
0
生成式 AI

Python 新手的 AI 之旅:從零開始打造屬於你的 AI / LLM 應用系列 第 5

【Day5】初探 AI 記憶功能:讓你的 LLM 不再「失憶」

  • 分享至 

  • xImage
  •  

前言

之前的程式例子中,我們幾乎都只有讓模型進行一次推論,一點都不像在聊天。你可能會寫出這樣的程式碼 。然後就會像圖中的輸出一樣,模型總是會忘記我們先前說的話。

截圖 2024-09-08 下午2.57.54.png

相較之下,ChatGPT 就比較聰明,記得我說過的話

image.png

實作簡單記憶功能

要實作記憶功能,最簡單的方法就是直接讓 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}")

測試結果,他果然認得我是誰了哈哈哈

image.png

上下文限制

看到這邊聰明的你應該有想到一個問題,因為我們目前的記憶功能純粹把先前的對話紀錄給他看而已,那會不會有一天我跟他聊太久,聊到超過上限文限制了呢?

我們先來看看 gpt-4o-mini 的 token 限制,context window 表示上下文的限制;max output tokens 則是輸出的最大數量。https://platform.openai.com/docs/models/gpt-4o-mini

image.png

再來是 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)

image.png

你會發現,夭壽喔大家都在比大的,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)

image.png

把計算 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}")

image.png

不過很明顯的,這樣不夠聰明對吧,直接忘掉最一開始發生的事對長期記憶來說是很傷的。就好比我不會忘記我的幼稚園在哪裡,儘管那已經是十幾年前的事情了。你跟這個 LLM 系統從白天到晚上對話一整天,他搞不好就忘記早上的東西了。

摘要早期記憶

剛剛提到幼稚園的例子,我雖然記得我的幼稚園長什麼樣子但裡面的教室、同學的臉龐甚至是遊樂設施,但我幾乎都沒辦法很清晰的想起來。我的腦容量是有限的,所以我只能記得一個大概、一個輪廓。LLM 記憶系統當然也可以用一樣的策略,把先前的對話內容先摘要,讓之後的對話如果牽扯到先前的記憶,LLM 也是可以唬個幾句。

這邊就不示範了,有興趣的朋友可以參考 Mem0, MemGPT 這些工具來儲存記憶,它已經把剛剛提到的摘要功能做好了。不過因為它使用到檢索增強生成 (Retrieval-Augmented Generation, RAG) 的技術,等介紹完 RAG 之後再回頭來 Mem0 會更好理解它的原理,到時候要做一個 AI 老婆也不是夢XD

明天我們來讓模型計算 strawberry 有 r 吧,不過是要讓他使用工具來計算,期待一下吧~


上一篇
【Day4】提示詞工程 (Prompt Engineering) 這什麼鬼:初探 LLM 操控術與 OpenAI 的六大策略
下一篇
【Day6】讓模型使用工具 (1):連 Strawberry 有幾個 r 都不會算?那就用程式算吧!
系列文
Python 新手的 AI 之旅:從零開始打造屬於你的 AI / LLM 應用12
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言