以下提及課程為 LLM Twin: Building Your Production-Ready AI Replica
在整理完在 GenAI 開發過程中,隨著規模大,影響開發效率最顯著的部份後,接著回課程內容,由於現在還在鐵人賽期間,每天能產出的篇幅有限,我的修課方式會是每天從課程中提取一個重點來延伸撰寫。
今天的主題聚焦於設計模式,在第一堂爬蟲系統開發中的應用中,作者介紹了 LinkedIn、Medium、GitHub 和 Substack 等平台的資料爬取工具的程式碼,並將內容分別整理為文章、社群貼文和程式碼進行儲存。而這即是借用設計模式的幫助,不僅提高了程式的重用性、也增強了系統的可維護性和擴展性。
其中作者主要使用了三種設計模式,首先是在 BaseAbstractCrawler 類別中實踐 模板方法模式(Template Method Pattern),將共用邏輯像是登入與頁面滾動這些重複的部分集中管理,減少冗餘的程式碼;而 策略模式(Strategy Pattern) 則讓系統更有彈性和擴展性,使得增加新類型的爬蟲變得簡單而直觀;最後是 工廠方法模式(Factory Method Pattern) 的運用,讓系統可以依據不同參數動態調整的爬蟲實體,提強了整個系統的適應能力。
這樣的設計為接下來的擴展帶來了便利性,舉例來說,當需要新增新平台(如Twitter)的爬蟲工具時,我們只需創建一個新的 TwitterCrawler 類別並實作其中的功能,就可以在不改動現有程式的情況下,實現無縫整合。不僅簡化了開發流程,同時維持系統的一致性和可靠性。
在總共 23 個設計模式當中,我挑選了六個使用頻率高、也是相對好理解的方法來介紹,並各自列舉幾個能在 GenAI 開發應用的情境,配合了一個程式範例:
| 設計模式 | 重點 | GenAI 應用 | 
|---|---|---|
| 單例模式 (Singleton) | 確保全局唯一實例 | LLM 模型初始化 | 
| 工廠模式 (Factory Method) | 靈活創建對象 | 跨型態檔案讀取 | 
| 觀察者模式 (Observer) | 實現事件驅動 | 資料庫更新觸發處理 | 
| 模板模式 (Template Method) | 定義算法骨架 | 資料前處理流程框架 | 
| 裝飾器模式 (Decorator) | 動態添加功能 | 靈活組合資料處理步驟 | 
| 策略模式 (Strategy) | 動態切換算法 | 檢索策略調整 | 
import time
class LargeLanguageModel:
    _instance = None
    def __new__(cls):
        if cls._instance is None:
            print("初始化大型語言模型...")
            cls._instance = super().__new__(cls)
            cls._instance.model = "GPT-4"  # 假設使用 GPT-4
            time.sleep(2)  # 模擬模型加載時間
        return cls._instance
    def generate_response(self, prompt: str) -> str:
        return f"這是 {self.model} 對 '{prompt}' 的回應:..."
def chat_with_ai(prompt: str) -> str:
    llm = LargeLanguageModel() 
    return llm.generate_response(prompt)
def main():
    prompts = [
        "什麼是機器學習?",
        "解釋一下深度學習",
        "AI 的未來發展趨勢是什麼?"
    ]
    # 當多次呼叫 LLM 時,只會建立一個實例
    for prompt in prompts:
        print(f"\n用戶: {prompt}")
        response = chat_with_ai(prompt)
        print(f"AI: {response}")
if __name__ == "__main__":
    main()
LargeLanguageModel 類使用 __new__ 實現單例模式。首次創建實體時,它會初始化模型。chat_with_ai 函數每次調用都會獲取 LLM 實例,但實際上總是使用同一個實體。from abc import ABC, abstractmethod
import csv
from typing import List, Dict
class DocumentLoader(ABC):
    @abstractmethod
    def load(self, file_path: str) -> str:
        pass
class PDFLoader(DocumentLoader):
    def load(self, file_path: str) -> str:
        # 在實際應用中,這裡會使用如 PyPDF2 之類的庫來讀取 PDF
        print(f"使用 PDF 加載器讀取文件:{file_path}")
        return f"這是從 {file_path} 讀取的 PDF 內容"
class TXTLoader(DocumentLoader):
    def load(self, file_path: str) -> str:
        print(f"使用 TXT 加載器讀取文件:{file_path}")
        with open(file_path, 'r', encoding='utf-8') as file:
            return file.read()
class DocumentLoaderFactory:
    @staticmethod
    def create_loader(file_type: str) -> DocumentLoader:
        if file_type.lower() == "pdf":
            return PDFLoader()
        elif file_type.lower() == "txt":
            return TXTLoader()
        else:
            raise ValueError(f"不支持的文件類型: {file_type}")
# 使用範例
def process_document(file_path: str, file_type: str):
    factory = DocumentLoaderFactory()
    loader = factory.create_loader(file_type)
    content = loader.load(file_path)
    return content
def main():
    # 模擬處理不同類型的文件
    pdf_content = process_document("example.pdf", "pdf")
    txt_content = process_document("example.txt", "txt")
if __name__ == "__main__":
    main()
DocumentLoader 是一個抽象基類,定義了文件加載器的通用接口。PDFLoader 和 TXTLoader 是具體的加載器類,實現了特定文件類型的加載邏輯。DocumentLoaderFactory 是工廠類,根據文件類型創建相應的加載器。process_document 函數展示了如何使用工廠來處理不同類型的文件,而不需要知道具體的加載器實現細節。這種設計允許我們輕鬆地添加新的文件類型支持(如 CSV、JSON 等),只需創建新的加載器類並在工廠中添加相應的邏輯。這提高了系統的可擴展性和維護性,特別適合處理多種數據源的RAG系統。
接下來的四個案例會在明天繼續介紹:)
ref.
Refactoring.Guru | design-patterns