以前曾對 Gemini 做過初步文字聊天測試, 但後來主要重心放在 OpenAI API 的串接測試, 所以就沒繼續做 Gemini 的測試, 參考 :
前陣子在 render.com 上佈署的 serverless 平台撰寫了一個 LINE Bot 聊天機器人程式 linebot_gemini 來串接 Gemini, 參考 :
但這兩個測試中的每個對話都是獨立無記憶的, 如果要讓聊天機器人記得前後上下文對話脈絡 (即保留前幾輪對話), 必須自行管理對話記憶 (模型本身無記憶).
1. 使用串列管理對話記憶 :
使用串列來對話紀錄是最直覺的方法, 首先匯入 google.generativeai 模型的 API 套件與 dotenv 套件來從環境變數檔 .env 讀取 Gemini 金鑰 :
>>> import google.generativeai as genai
>>> from dotenv import dotenv_values
讀取 Gemini 金鑰 :
>>> config=dotenv_values('.env')
>>> api_key=config.get('GEMINI_API_KEY')
然後自訂一個 GeminiChat 類別來串接 Gemini API :
>>> class GeminiChat:
def __init__(self, api_key, model='gemini-2.5-flash', max_history=20):
genai.configure(api_key=api_key)
self.model=genai.GenerativeModel(model)
self.max_history=max_history
self.history=[] # 用 list 儲存 (user, reply) 對話
def ask(self, prompt):
# 整理上下文(最近 N 輪對話)
context='' # 初始值
for user_msg, ai_msg in self.history[-self.max_history:]: # 拜訪記憶串列
context += f'使用者:{user_msg}\n助理:{ai_msg}\n' # 串接對話記憶為字串
# 組成最終 prompt
full_prompt=f'{context}使用者:{prompt}\n助理:'
# 發送給 Gemini (同時驗證金鑰)
response=self.model.generate_content(full_prompt)
reply=response.text.strip() # 去除左右空格
# 將 (提示詞, 回應) 放入記憶串列
self.history.append((prompt, reply))
# 限制記憶長度 : 刪除記憶串列中的舊對話, 只留最後 max_history 個對話
if len(self.history) > self.max_history:
self.history=self.history[-self.max_history:]
return reply
在此自訂類別的初始化函式 __init__() 中, 我們先呼叫 genai.configure() 來將傳入的 API Key 存進 SDK 的全域設定裡, 這樣之後所有透過 genai.GenerativeModel, genai.list_models(), model.generate_content() 等方法發出的 API 請求都會自動帶上這個 API Key (只有呼叫這些方法時才會驗證金鑰是否有效). 然後呼叫 genai.GenerativeModel() 建構式建立模型物件, 用來與指定 之 Gemini 模型互動. 最後定義 history 與 max_history 屬性來記錄與管控對話記憶, history 是一個串列, 用來儲存對話紀錄; max_history 則是一個整數, 用來設定記憶長度, 預設是 20 個對話.
接著定義一個 ask() 方法來處理對話與管理記憶, 每次詢問都會從記憶串列重新組成上下文字串, 串接目前提問後組成完整之提問上下文, 然後呼叫 Gemini 模型物件的 generate_content() 方法生成回應, 測試如下 :
>>> print(chat.ask('你好'))
你好!有什么我能帮助你的吗?
>>> print(chat.ask('我叫 Tony'))
很高兴认识你,Tony!有什么我能为你做的吗?
>>> print(chat.ask('我叫什麼名字?'))
你叫 Tony。
>>> print(chat.ask('我有兩隻貓, 名叫萬萬與小咪'))
好的,Tony。我記下了你有兩隻貓,名叫萬萬和小咪。還有什麼我能為你做的嗎?
>>> print(chat.ask('你是誰?'))
我是一个大型语言模型,由 Google 训练。
>>> print(chat.ask('我有幾隻貓? 名叫甚麼?'))
你有兩隻貓,名叫萬萬和小咪。
可見模型透過記憶串列中的脈絡得知上下文資訊, 故能回答正確答案.
完整程式碼如下 :
# gemini_chat_memory_1.py
import google.generativeai as genai
from dotenv import dotenv_values
class GeminiChat:
def __init__(self, api_key, model='gemini-2.5-flash', max_history=20):
genai.configure(api_key=api_key)
self.model=genai.GenerativeModel(model)
self.max_history=max_history
self.history=[] # 用 list 儲存 (user, reply) 對話
def ask(self, prompt):
# 整理上下文(最近 N 輪對話)
context='' # 初始值
for user_msg, ai_msg in self.history[-self.max_history:]: 拜訪記憶串列
context += f'使用者:{user_msg}\n助理:{ai_msg}\n' # 串接對話記憶為字串
# 組成最終 prompt
full_prompt=f'{context}使用者:{prompt}\n助理:'
# 發送給 Gemini (同時驗證金鑰)
response=self.model.generate_content(full_prompt)
reply=response.text.strip() # 去除左右空格
# 將 (提示詞, 回應) 放入記憶串列
self.history.append((prompt, reply))
# 限制記憶長度:刪除記憶串列中的舊對話, 只留最後 max_history 個對話
if len(self.history) > self.max_history:
self.history=self.history[-self.max_history:]
return reply
if __name__ == '__main__':
config=dotenv_values('.env')
api_key=config.get('GEMINI_API_KEY')
chat=GeminiChat(api_key, max_history=5)
print(chat.ask('你好'))
print(chat.ask('請記住我叫 Tony'))
print(chat.ask('我叫什麼名字?'))
2. 使用 ChatSession 管理對話記憶 :
除了使用串列自行管理對話記憶, Gemini 官方文件建議使用 genai.GenerativeModel 類別實例的 start_chat() 方法建立一個有上下文的 ChatSession 對話物件來達成同樣目的, 程式碼如下 :
# gemini_chat_memory_2.py
import google.generativeai as genai
from dotenv import dotenv_values
class GeminiChat:
def __init__(self, api_key, model='gemini-2.5-flash', max_history=20):
genai.configure(api_key=api_key)
self.model=genai.GenerativeModel(model)
self.max_history=max_history
self.chat=self.model.start_chat(history=[]) # 初始化空歷史紀錄
def ask(self, prompt):
# 若超過 max_history 則移除最舊的訊息
if len(self.chat.history) > self.max_history * 2:
# 限制歷史對話長度 :每輪對話有 user+model 各一筆故要乘以 2
self.chat.history=self.chat.history[-self.max_history * 2:]
response=self.chat.send_message(prompt)
return response.text
if __name__ == "__main__":
config=dotenv_values('.env')
api_key=config.get('GEMINI_API_KEY')
chat=GeminiChat(api_key, max_history=20)
print(chat.ask('你好'))
print(chat.ask('請記住我叫 Tony'))
print(chat.ask('我叫什麼名字?'))
首先在 GeminiChat 類別初始化時, 呼叫 GenerativeModel 物件的 start_session() 方法並傳入一個 history 參數 (預設空串列) 來建立一個 ChatSession 對話物件, 所以此物件實際上也是使用串列來記錄對話歷史, 然後將此 ChatSession 儲存在 GeminiChat 物件的 chat 屬性裡, 這樣便能自動記錄對話歷史, 在 ask() 方法中只要管理對話歷史的長度即可, 毋須像上例那樣手動串接上下文. 測試如下 :
>>> %Run gemini_chat_memory_2.py
你好!有什么可以帮助你的吗?
好的,我記住了。你叫 Tony。
很高興認識你!
你叫 Tony。
可見效果與上例相同, 但程式碼更簡潔.
3. 加入系統提示 system_instruction :
從上面範例可知, 即使 prompt 是繁體中文, Gemini 的回應幾乎都是用殘體中文回應, 如果要強制它用正體中文回應, 可在呼叫 GenerativeModel() 建構式時傳入系統提示詞參數 system_instruction 解決 :
# gemini_chat_memory_3.py
import google.generativeai as genai
from dotenv import dotenv_values
class GeminiChat:
def __init__(self, api_key, model='gemini-2.5-flash', max_history=20,
system_instruction=None):
genai.configure(api_key=api_key)
# 在建立模型時傳入 system_instruction
self.model=genai.GenerativeModel(
model,
system_instruction=system_instruction
)
self.max_history=max_history
self.chat=self.model.start_chat(history=[])
def ask(self, prompt):
# 限制歷史紀錄長度(每輪 user+model 各一筆)
if len(self.chat.history) > self.max_history * 2:
self.chat.history=self.chat.history[-self.max_history * 2:]
response=self.chat.send_message(prompt)
return response.text
if __name__ == '__main__':
config=dotenv_values('.env')
api_key=config.get('GEMINI_API_KEY')
# 加上系統提示
system_instruction='你是一個繁體中文AI助理,請以台灣人的習慣用語回答。'
chat=GeminiChat(api_key, max_history=20, system_instruction=system_instruction)
print(chat.ask('你好'))
print(chat.ask('請記住我叫 Tony'))
print(chat.ask('我叫什麼名字?'))
測試結果確實能生成繁體中文回應 :
>>> %Run gemini_chat_memory_3.py
哈囉,你好!有什麼需要我幫忙的嗎?
好的,Tony 我記住了!之後就叫你 Tony 囉。
有什麼需要我幫忙的嗎?
你叫 Tony 呀!
沒有留言 :
張貼留言