iT邦幫忙

2025 iThome 鐵人賽

DAY 17
0
AI & Data

來都來了,那就做一個GCP從0到100的AI助理系列 第 17

簡單的上下文管理:用 List 打造記憶型對話系統

  • 分享至 

  • xImage
  •  

在前一篇文章中,我們學會了如何簡易開發生成式 AI Chat。但有一個關鍵問題還沒解決:如何讓 AI 記住之前說過的話?

大多數初學者的第一個 AI 應用都是「單次問答」—— 每次都是全新的對話,AI 完全不記得剛才討論了什麼。但真正實用的 AI 助手需要能夠:

  • 記住用戶的偏好設定
  • 延續上一輪的話題討論
  • 在多輪對話中保持邏輯一致性

今天介紹最簡單且實用的解決方案:用 List 管理對話歷史

一、為什麼需要上下文管理?

無記憶的尷尬情況

# 第一次對話
user: "我今年 25 歲,想要減重"
ai: "好的,根據您 25 歲的年齡,建議每日攝取..."

# 第二次對話(沒有上下文)
user: "那我應該做什麼運動?"
ai: "請問您的年齡和減重目標是什麼?我需要更多資訊才能給建議"

這種體驗對用戶來說非常frustrating,明明剛說過的事情,AI 馬上就忘記了。

有記憶的流暢對話

# 有上下文管理的對話
conversation_history = [
    {"role": "user", "parts": [{"text": "我今年 25 歲,想要減重"}]},
    {"role": "model", "parts": [{"text": "好的,根據您 25 歲的年齡,建議每日攝取..."}]},
    {"role": "user", "parts": [{"text": "那我應該做什麼運動?"}]}
]

# AI 可以基於完整歷史回答
ai: "基於您 25 歲的年齡和減重目標,我推薦以下運動..."

二、最簡單的實作:用 List 儲存對話

基礎版本

class SimpleChat:
    def __init__(self):
        self.conversation_history = []
        self.model = genai.GenerativeModel('gemini-pro')
    
    def add_message(self, role, content):
        """新增訊息到對話歷史"""
        message = {
            "role": role,
            "parts": [{"text": content}]
        }
        self.conversation_history.append(message)
    
    def chat(self, user_input):
        """進行對話"""
        # 1. 將用戶訊息加入歷史
        self.add_message("user", user_input)
        
        # 2. 將完整歷史送給模型
        response = self.model.generate_content(self.conversation_history)
        
        # 3. 將 AI 回應也加入歷史
        self.add_message("model", response.text)
        
        return response.text

# 使用範例
chat = SimpleChat()

print(chat.chat("我今年 25 歲,想要減重"))
print(chat.chat("那我應該做什麼運動?"))
print(chat.chat("我比較喜歡室內運動"))

進階版本:加入 System Prompt

class SmartChat:
    def __init__(self, system_prompt="你是一位專業的健康顧問"):
        self.system_prompt = system_prompt
        self.conversation_history = []
        self.model = genai.GenerativeModel('gemini-pro')
    
    def add_message(self, role, content):
        message = {
            "role": role,
            "parts": [{"text": content}]
        }
        self.conversation_history.append(message)
    
    def get_full_context(self):
        """組合 System Prompt + 對話歷史"""
        messages = []
        
        # 先加入 System Prompt
        if self.system_prompt:
            messages.append({
                "role": "user",
                "parts": [{"text": f"請扮演這個角色:{self.system_prompt}"}]
            })
            messages.append({
                "role": "model", 
                "parts": [{"text": "我了解了,我會扮演這個角色來協助您。"}]
            })
        
        # 再加入對話歷史
        messages.extend(self.conversation_history)
        return messages
    
    def chat(self, user_input):
        # 1. 用戶訊息加入歷史
        self.add_message("user", user_input)
        
        # 2. 取得完整上下文(System Prompt + 歷史)
        full_context = self.get_full_context()
        
        # 3. 送給模型處理
        response = self.model.generate_content(
            full_context,
            generation_config={
                "temperature": 0.7,
                "max_output_tokens": 1000
            }
        )
        
        # 4. AI 回應加入歷史
        self.add_message("model", response.text)
        
        return response.text

# 使用範例
health_assistant = SmartChat(
    system_prompt="你是一位專業的健康顧問,會根據用戶的個人資訊提供客製化建議"
)

print(health_assistant.chat("我今年 25 歲,想要減重"))
print(health_assistant.chat("我平常工作很忙,沒時間去健身房"))

三、加入記憶體管理:避免 Token 超限

問題:對話越來越長

隨著對話進行,conversation_history 會越來越長,最終可能:

  • 超過模型的 token 限制
  • 增加 API 費用
  • 降低回應速度

解決方案:滑動視窗(Sliding Window)

class ManagedChat:
    def __init__(self, system_prompt="", max_history_length=10):
        self.system_prompt = system_prompt
        self.conversation_history = []
        self.max_history_length = max_history_length  # 最多保留幾輪對話
        self.model = genai.GenerativeModel('gemini-pro')
    
    def add_message(self, role, content):
        message = {
            "role": role,
            "parts": [{"text": content}]
        }
        self.conversation_history.append(message)
        
        # 保持歷史長度在限制內
        if len(self.conversation_history) > self.max_history_length:
            # 移除最舊的對話(但保留成對的 user-model 對話)
            self.conversation_history = self.conversation_history[-self.max_history_length:]
    
    def get_memory_summary(self):
        """將舊對話總結成摘要"""
        if len(self.conversation_history) <= 4:  # 對話還不夠長,不需要摘要
            return ""
        
        # 取出最舊的幾輪對話來做摘要
        old_messages = self.conversation_history[:6]  # 前3輪對話
        
        summary_prompt = f"""
        請將以下對話總結成重點摘要,保留重要的用戶資訊:
        {old_messages}
        
        摘要格式:用戶基本資訊、主要需求、已討論的重點
        """
        
        # 這裡可以呼叫另一個模型來產生摘要
        # 為了簡化,我們先用簡單的文字描述
        return "之前討論摘要:用戶關心健康議題,想要專業建議"
    
    def chat(self, user_input):
        self.add_message("user", user_input)
        
        # 組合上下文:系統提示 + 記憶摘要 + 近期對話
        context = []
        
        if self.system_prompt:
            context.append({
                "role": "user",
                "parts": [{"text": self.system_prompt}]
            })
        
        # 加入記憶摘要(如果有的話)
        memory_summary = self.get_memory_summary()
        if memory_summary:
            context.append({
                "role": "user", 
                "parts": [{"text": memory_summary}]
            })
        
        # 加入近期對話
        context.extend(self.conversation_history[-6:])  # 只用最近3輪對話
        
        response = self.model.generate_content(context)
        self.add_message("model", response.text)
        
        return response.text

實際應用:健康諮詢助手

class HealthChatBot:
    def __init__(self):
        self.system_prompt = """
        你是一位專業的健康顧問助手。請根據用戶提供的資訊:
        - 記住用戶的個人資料(年齡、體重、目標等)
        - 提供個人化的建議
        - 如果涉及醫療診斷,請建議諮詢專業醫師
        - 保持友善且專業的語調
        """
        
        self.conversation_history = []
        self.user_profile = {}  # 用來記錄用戶的基本資料
        self.model = genai.GenerativeModel('gemini-pro')
    
    def extract_user_info(self, user_input):
        """從用戶輸入中提取個人資訊"""
        # 這裡可以用更複雜的 NLP 技術,簡化版本用關鍵字
        if "歲" in user_input:
            # 提取年齡資訊
            import re
            age_match = re.search(r'(\d+)歲', user_input)
            if age_match:
                self.user_profile['age'] = age_match.group(1)
        
        if "公斤" in user_input or "kg" in user_input.lower():
            # 提取體重資訊
            weight_match = re.search(r'(\d+(?:\.\d+)?)\s*(?:公斤|kg)', user_input.lower())
            if weight_match:
                self.user_profile['weight'] = weight_match.group(1)
    
    def chat(self, user_input):
        # 提取用戶資訊
        self.extract_user_info(user_input)
        
        # 建構包含用戶資料的上下文
        context = []
        
        # System Prompt
        context.append({
            "role": "user",
            "parts": [{"text": self.system_prompt}]
        })
        
        # 用戶資料摘要
        if self.user_profile:
            profile_text = "用戶資料:" + ", ".join([f"{k}: {v}" for k, v in self.user_profile.items()])
            context.append({
                "role": "user",
                "parts": [{"text": profile_text}]
            })
        
        # 對話歷史
        context.extend(self.conversation_history[-6:])  # 最近3輪對話
        
        # 當前用戶輸入
        context.append({
            "role": "user",
            "parts": [{"text": user_input}]
        })
        
        # 呼叫模型
        response = self.model.generate_content(
            context,
            safety_settings=[
                {"category": "HARM_CATEGORY_DANGEROUS_CONTENT", "threshold": "BLOCK_ONLY_HIGH"}
            ]
        )
        
        # 更新對話歷史
        self.conversation_history.append({
            "role": "user", 
            "parts": [{"text": user_input}]
        })
        self.conversation_history.append({
            "role": "model", 
            "parts": [{"text": response.text}]
        })
        
        return response.text

# 使用範例
bot = HealthChatBot()

print("=== 健康諮詢助手 ===")
print(bot.chat("你好,我今年 28 歲,體重 75 公斤,想要減重"))
print("\n" + "="*50 + "\n")
print(bot.chat("我平常很少運動,應該從什麼開始?"))
print("\n" + "="*50 + "\n")
print(bot.chat("我的目標是減到 70 公斤,大概需要多久?"))

List 來存對話紀錄,超直覺也很好 debug、維護起來不麻煩。

  • 快速上手:幾十行程式就能讓 AI 有基本記憶。
  • 彈性高:想加點自訂邏輯也很容易,像是抽取使用者資料、做記憶摘要等等。

不過有幾個缺點:

  • 記憶體會越吃越多:對話愈長,List 就愈大。
  • Token 費用變高:每次都要把完整歷史丟給 API,成本會增加。
  • 理解力有限:沒辦法真正看懂很複雜的語義或長期依賴。

上一篇
讓 AI 認識你:用 Python 實作 Prompt 個人化(角色、口吻、限制條件)- 3
下一篇
長期記憶與短期記憶:打造 AI 的智慧記憶系統 - 1
系列文
來都來了,那就做一個GCP從0到100的AI助理18
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言