iT邦幫忙

2025 iThome 鐵人賽

DAY 4
0

Day 4: 第一個簡單的對話機器人

經過前三天的基礎學習,今天我們要整合所學的知識,建立第一個功能完整的對話機器人!這個機器人將具備基本的對話能力、記憶功能,以及簡單的個性設定。

🎯 專案目標

我們將建立一個名為「小助手」的對話機器人,具備以下功能:

  • 🗣 自然對話:流暢的中文對話能力
  • 🧠 記憶功能:記住對話歷史和使用者偏好
  • 🎭 個性設定:友善、專業的助手角色
  • 📝 指令處理:支援特殊指令和功能

🏗 專案結構

首先建立專案結構:

chatbot/
├── main.py           # 主程式
├── chatbot.py        # 機器人核心類別
├── config.py         # 設定檔
├── utils.py          # 工具函數
└── conversation_log/ # 對話記錄目錄

🔧 核心程式碼實作

1. 設定檔 (config.py)

import os
from dotenv import load_dotenv

load_dotenv()

# API 設定
GEMINI_API_KEY = os.getenv('GEMINI_API_KEY')

# 機器人設定
BOT_NAME = "小助手"
BOT_PERSONALITY = """
你是一個友善、專業的 AI 助手,名字叫做小助手。
你的特質:
- 樂於助人,總是以正面積極的態度回應
- 專業可靠,能提供準確的資訊和建議
- 有耐心,會詳細解釋複雜的概念
- 記住用戶的偏好和之前的對話內容
- 用繁體中文回應,語氣親切自然
"""

# 系統設定
MAX_HISTORY = 50  # 最多保留 50 輪對話
SAVE_CONVERSATION = True

2. 工具函數 (utils.py)

import json
import os
from datetime import datetime
import re

def save_conversation(conversation_log):
    """儲存對話記錄到檔案"""
    try:
        # 確保目錄存在,如果不存在則創建
        log_dir = "conversation_log"
        if not os.path.exists(log_dir):
            os.makedirs(log_dir)
        
        # 生成檔案名稱
        timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
        filename = f"{log_dir}/conversation_{timestamp}.json"
        
        # 儲存對話記錄
        with open(filename, 'w', encoding='utf-8') as f:
            json.dump(conversation_log, f, ensure_ascii=False, indent=2)
        
        print(f"💾 對話記錄已儲存至: {filename}")
        
    except Exception as e:
        print(f"❌ 儲存對話記錄時發生錯誤: {str(e)}")

def format_response(response_text):
    """格式化機器人回應"""
    # 移除多餘的空行
    response_text = re.sub(r'\n\s*\n\s*\n', '\n\n', response_text)
    
    # 確保回應不為空
    if not response_text.strip():
        return "抱歉,我沒有收到完整的回應。請再試一次。"
    
    return response_text.strip()

def load_conversation(filename):
    """載入對話記錄"""
    try:
        with open(filename, 'r', encoding='utf-8') as f:
            return json.load(f)
    except FileNotFoundError:
        print(f"❌ 找不到檔案: {filename}")
        return []
    except Exception as e:
        print(f"❌ 載入對話記錄時發生錯誤: {str(e)}")
        return []

def get_conversation_files():
    """取得所有對話記錄檔案"""
    log_dir = "conversation_log"
    if not os.path.exists(log_dir):
        return []
    
    files = []
    for filename in os.listdir(log_dir):
        if filename.startswith("conversation_") and filename.endswith(".json"):
            filepath = os.path.join(log_dir, filename)
            files.append(filepath)
    
    return sorted(files, reverse=True)  # 最新的檔案在前面

def clean_old_conversations(max_files=10):
    """清理舊的對話記錄,只保留最新的幾個檔案"""
    files = get_conversation_files()
    if len(files) > max_files:
        for old_file in files[max_files:]:
            try:
                os.remove(old_file)
                print(f"🗑️ 已刪除舊對話記錄: {old_file}")
            except Exception as e:
                print(f"❌ 刪除檔案時發生錯誤: {str(e)}")

3. 機器人核心類別 (chatbot.py)

import google.generativeai as genai
from datetime import datetime
import config
from utils import save_conversation, format_response

class SimpleChatBot:
    def __init__(self):
        # 初始化 Gemini
        genai.configure(api_key=config.GEMINI_API_KEY)
        self.model = genai.GenerativeModel('gemini-2.5-flash')
        self.system_instruction = config.BOT_PERSONALITY
        
        # 初始化對話狀態 - 在歷史中加入系統指令
        initial_history = [
            {
                "role": "user",
                "parts": [self.system_instruction]
            },
            {
                "role": "model",
                "parts": ["我理解了,我會按照這個人格設定來回應用戶的問題。"]
            }
        ]
        
        self.chat = self.model.start_chat(history=initial_history)
        self.conversation_log = []
        self.user_name = "朋友"
        self.start_time = datetime.now()
        
        print(f"🤖 {config.BOT_NAME}已啟動!輸入 '/help' 查看可用指令。")
    
    def process_command(self, user_input):
        """處理特殊指令"""
        if user_input.startswith('/'):
            command = user_input[1:].lower()
            
            if command == 'help':
                return self._show_help()
            elif command == 'clear':
                return self._clear_history()
            elif command.startswith('name '):
                return self._set_name(command[5:])
            elif command == 'stats':
                return self._show_stats()
            elif command == 'save':
                return self._save_conversation()
            elif command == 'quit' or command == 'exit':
                return self._quit()
            else:
                return "❓ 未知指令,輸入 '/help' 查看可用指令。"
        
        return None
    
    def _show_help(self):
        help_text = f"""
🔧 {config.BOT_NAME} 可用指令:
/help - 顯示此說明
/clear - 清除對話歷史
/name <名字> - 設定你的名字
/stats - 顯示對話統計
/save - 儲存對話記錄
/quit, /exit - 結束對話
        """
        return help_text.strip()
    
    def _clear_history(self):
        # 重新初始化對話,保留系統指令
        initial_history = [
            {
                "role": "user",
                "parts": [self.system_instruction]
            },
            {
                "role": "model",
                "parts": ["我理解了,我會按照這個人格設定來回應用戶的問題。"]
            }
        ]
        self.chat = self.model.start_chat(history=initial_history)
        self.conversation_log = []
        return "🧹 對話歷史已清除!"
    
    def _set_name(self, name):
        self.user_name = name.strip()
        return f"👋 很高興認識你,{self.user_name}!"
    
    def _show_stats(self):
        duration = datetime.now() - self.start_time
        stats = f"""
📊 對話統計:
• 對話輪數:{len(self.conversation_log) // 2}
• 對話時間:{duration.seconds // 60} 分鐘
• 使用者名稱:{self.user_name}
• 開始時間:{self.start_time.strftime('%Y-%m-%d %H:%M:%S')}
        """
        return stats.strip()
    
    def _save_conversation(self):
        if config.SAVE_CONVERSATION:
            save_conversation(self.conversation_log)
            return "💾 對話記錄已儲存!"
        return "⚠️ 對話儲存功能未啟用。"
    
    def _quit(self):
        if config.SAVE_CONVERSATION:
            save_conversation(self.conversation_log)
        return "👋 再見!對話記錄已自動儲存。"
    
    def chat_with_user(self, user_input):
        """處理使用者輸入"""
        # 檢查是否為指令
        command_response = self.process_command(user_input)
        if command_response:
            if '/quit' in user_input or '/exit' in user_input:
                return command_response, True  # True 表示要結束程式
            return command_response, False
        
        try:
            # 記錄使用者輸入
            self.conversation_log.append({
                "role": "user",
                "content": user_input,
                "timestamp": datetime.now().isoformat()
            })
            
            # 發送訊息給 Gemini
            response = self.chat.send_message(user_input)
            bot_response = format_response(response.text)
            
            # 記錄機器人回應
            self.conversation_log.append({
                "role": "assistant", 
                "content": bot_response,
                "timestamp": datetime.now().isoformat()
            })
            
            # 維護歷史記錄長度
            if len(self.conversation_log) > config.MAX_HISTORY * 2:
                self.conversation_log = self.conversation_log[-config.MAX_HISTORY * 2:]
            
            return bot_response, False
            
        except Exception as e:
            error_msg = f"❌ 抱歉,我遇到了一些技術問題:{str(e)}"
            return error_msg, False

4. 主程式 (main.py)

from chatbot import SimpleChatBot
import config

def main():
    """主程式入口"""
    print(f"🚀 歡迎使用 {config.BOT_NAME}!")
    print("💡 提示:輸入 '/help' 查看可用指令,'/quit' 結束對話。")
    print("=" * 50)
    
    # 初始化機器人
    bot = SimpleChatBot()
    
    # 主要對話迴圈
    while True:
        try:
            # 獲取使用者輸入
            user_input = input(f"\n{bot.user_name}:").strip()
            
            # 跳過空白輸入
            if not user_input:
                continue
            
            # 處理使用者輸入
            response, should_quit = bot.chat_with_user(user_input)
            
            # 顯示機器人回應
            print(f"{config.BOT_NAME}:{response}")
            
            # 檢查是否要結束程式
            if should_quit:
                break
                
        except KeyboardInterrupt:
            print(f"\n\n👋 {config.BOT_NAME}:再見!")
            break
        except Exception as e:
            print(f"\n❌ 系統錯誤:{e}")
            print("請重新啟動程式。")
            break

if __name__ == "__main__":
    main()

🎮 使用示範

啟動機器人:

python main.py

對話範例:

朋友:你好!
小助手:你好!很高興見到你!我是小助手,隨時為你提供協助。有什麼我可以幫你的嗎?

朋友:/name 小明
小助手:👋 很高興認識你,小明!

小明:你能幫我解釋什麼是機器學習嗎?
小助手:當然可以!機器學習是人工智慧的一個分支...

小明:/stats
小助手:📊 對話統計:
• 對話輪數:2
• 對話時間:3 分鐘
• 使用者名稱:小明
• 開始時間:2024-01-15 14:30:25

🚀 擴展功能建議

  1. 情感分析:檢測使用者情緒並調整回應風格
  2. 主題追蹤:記住對話主題,提供更相關的回應
  3. 多輪推理:支援複雜的多步驟問題解決
  4. 外部整合:連接天氣、新聞等 API 服務

🎯 今日總結

今天我們成功建立了第一個功能完整的對話機器人!它不僅能進行自然對話,還具備記憶功能、指令處理和對話記錄等實用特性。

明天我們將引入 LangGraph,學習如何用圖形化的方式設計更複雜的 AI 工作流程。記得多和你的機器人聊天,測試各種功能!


上一篇
Day 3: Gemini CLI 安裝與基本操作
下一篇
Day 5: LangGraph 核心概念介紹
系列文
30 天從零到 AI 助理:Gemini CLI 與 LangGraph 輕鬆上手7
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言