LLM Zoomcamp 第一堂課:LLM & RAG 介紹

2025年5月27日的錄影 只是課程介紹,所以本筆記還是依照 2024年的 01-intro 撰寫。

本筆記由 Claude 協助(草稿和內容理解討論),有可能不完全正確。

▌關於本課程

指 LLM Zoomcamp 2025

》課程概述

  • 課程名稱: LLM Zoomcamp 2025 - DataTalks.Club
  • 課程目標: 10 週學會建立一個能回答自有知識庫問題的 AI 系統

》學習目標

  1. 簡述什麼是LLM(大型語言模型)和RAG(檢索增強生成)
  2. 實作一個簡單的 RAG 流程
  3. 建立問答系統來回答 Zoomcamp 幾個課程中的 FAQ 文件
  4. 學習使用不同的搜尋引擎(MinSearch 和 ElasticSearch)

講師不會深入說明 LLM 和 RAG,直接視 LLM 為一個黑盒子,RAG 也多著墨在實作。

本課程純粹就是直接實作,透過實作一個簡單專案,來示範 RAG,並要求學生在期末,繳交一個 RAG 成品(題目自訂。不知道做什麼的話,就用講師的範例改進)。

》作業

作業的部分,比較不直觀,不容易找到。我把我建議看的資料,統整在這裡:

▌RAG 系統架構

》什麼是 RAG

RAG(Retrieval Augmented Generation)= Retrieval(檢索)+ Augmented(增強)+ Generation(生成)

  • 傳統LLM的限制:用戶問題 → LLM → 基於訓練資料的回答

  • RAG:用戶問題 → 檢索相關文檔 → LLM + 檢索內容 → 增強後的回答

RAG 包含三個核心組件:

1. 搜尋引擎模組(Search Engine)

  • 用途: 預處理來源文件
  • 目的: 限制提示的大小以應對上下文視窗限制
  • 實作選項:
    • MinSearch(輕量級,記憶體內運行)
    • ElasticSearch(生產環境推薦)

2. 提示建構模組(Prompt Builder)

  • 功能: 組合問題、用戶提示和來源文件
  • 輸出: 傳遞給LLM的完整字串

3. LLM 生成模組(大型語言模型)

  • 功能: 接收前一步的提示作為輸入
  • 實作: API呼叫到選擇的LLM服務

將講師的架構圖,理解如下。

其中以 […] 包起來的模組,就是程式實作的部分,可以被替換或優化(例如由 MinSearch 改為 ElasticSearch)。

用戶提問(資料) 
    ↓
[搜尋引擎模組] ←→ 知識庫(資料)
    ↓
相關文檔(資料)
    ↓  
[提示建構模組]
    ↓
完整提示(資料)
    ↓
[LLM生成模組] ←→ API/模型(外部服務)
    ↓
最終答案(資料)

我用 Mermaid 流程圖 再畫一次如下。

藍色方框 - 資料、橙色菱形 - 處理模組、紫色方框 - 外部服務。

flowchart TD
    A[用戶提問<br/>資料] --> B{搜尋引擎模組<br/>處理組件}
    C[(知識庫<br/>資料)] <--> B
    B --> D[相關文檔<br/>資料]
    D --> E{提示建構模組<br/>處理組件}
    E --> F[完整提示<br/>資料]
    F --> G{LLM生成模組<br/>處理組件}
    H[API/模型<br/>外部服務] <--> G
    G --> I[最終答案<br/>資料]

    %% 樣式設定
    classDef dataNode fill:#e1f5fe,stroke:#01579b,stroke-width:2px,color:#000
    classDef processNode fill:#fff3e0,stroke:#e65100,stroke-width:2px,color:#000
    classDef externalNode fill:#f3e5f5,stroke:#4a148c,stroke-width:2px,color:#000

    %% 套用樣式
    class A,C,D,F,I dataNode
    class B,E,G processNode
    class H externalNode

接著我請 Claude 提出是否有可以改進的地方,以下是回覆。這部分可以作為未來作業的改進方法選項,但與目前進度無關,我先隱藏起來,感興趣的朋友,可以點擊展開觀看。

Claude 建議的改進方法,點擊展開觀看
flowchart TD
    A[用戶提問] --> B{查詢分析器}
    B --> C{混合檢索器}
    
    D[(向量資料庫)] --> C
    E[(關鍵字索引)] --> C
    F[(知識圖譜)] --> C
    
    C --> G{重排序器}
    G --> H[相關文檔]
    
    H --> I{智能提示建構器}
    B --> I
    J[歷史對話] --> I
    
    I --> K[完整提示]
    K --> L{LLM生成器}
    
    L --> M[初步回答]
    M --> N{品質檢查器}
    
    N --> O{回答是否合格?}
    O -->|是| P[最終答案]
    O -->|否| Q{回饋優化}
    Q --> I
    
    N --> R[(監控資料庫)]
    P --> S[用戶回饋]
    S --> R

    %% 樣式設定
    classDef input fill:#e8f5e8,stroke:#2e7d32,stroke-width:2px
    classDef process fill:#fff3e0,stroke:#e65100,stroke-width:2px
    classDef storage fill:#e3f2fd,stroke:#1565c0,stroke-width:2px
    classDef output fill:#fce4ec,stroke:#c2185b,stroke-width:2px
    classDef decision fill:#f3e5f5,stroke:#7b1fa2,stroke-width:2px

    class A,J,S input
    class B,C,G,I,L,N,Q process
    class D,E,F,R storage
    class H,K,M,P output
    class O decision

▌各Module對RAG組件的具體影響

下表是前述三個模組(搜尋引擎模組 | 提示建構模組 | LLM模組)在本課程各章節演進示意(正確性待驗證)。

Module 搜尋引擎模組 提示建構模組 LLM模組 主要改進重點
01-intro MinSearch/ElasticSearch 基本提示模板 OpenAI API 建立基礎架構
02-open-source 沿用Module 1 沿用Module 1 開源LLM (HuggingFace, Ollama) LLM模組替換
03-vector 向量搜尋 (Qdrant, 語意檢索) 沿用或微調 沿用Module 2 搜尋引擎模組升級
04-evaluation 沿用Module 3 評估導向 提示 沿用Module 2/3 整個流程監控改進
05-orchestration 混合搜尋 + 自動化 動態提示 沿用 生產環境整合
06-project 自選最佳組合 自選最佳組合 自選最佳組合 端到端實作

》Module 01-intro (本章內容)

# 建立三個基礎模組
def search_engine():     # MinSearch/ElasticSearch
def prompt_builder():    # 基本模板  
def llm_generator():     # OpenAI API

》Module 02-open-source

# 主要升級:LLM模組
def search_engine():     # 沿用 Module 1
def prompt_builder():    # 沿用 Module 1
def llm_generator():     # 改用 HuggingFace/Ollama

》Module 03-vector

# 主要升級:搜尋引擎模組
def search_engine():     # 改用向量搜尋/語意檢索
def prompt_builder():    # 可能微調以配合向量搜尋
def llm_generator():     # 沿用 Module 2

》Module 04-evaluation

# 主要升級:加入評估和監控機制
def search_engine():     # 沿用 Module 3 + 評估指標
def prompt_builder():    # 加入評估導向的提示優化
def llm_generator():     # 沿用 + 回答品質評估
def evaluation_system(): # 新增:整個流程的監控

▌資料來源與結構

》文件來源

課程使用 DataTalks.Club 各個 Zoomcamp 課程的 FAQ 文件:

  • Data Engineering Zoomcamp
  • MLOps Zoomcamp
  • Machine Learning Zoomcamp

》文件結構

每個文件包含以下欄位(講師已整理為 JSON 格式,並附程式):

{
  "text": "問題的詳細回答內容",
  "section": "所屬章節",
  "question": "問題標題", 
  "course": "課程名稱"
}

》資料載入程式碼

import requests
import json

# 下載FAQ文件
docs_url = 'https://github.com/DataTalksClub/llm-zoomcamp/blob/main/01-intro/documents.json?raw=1'
docs_response = requests.get(docs_url)
documents_raw = docs_response.json()

# 處理文件結構
documents = []
for course in documents_raw:
    course_name = course['course']
    for doc in course['documents']:
        doc['course'] = course_name
        documents.append(doc)

MinSearch vs. ElasticSearch 技術實現

特徵 MinSearch ElasticSearch
實現方式 Python 記憶體內 分散式搜尋引擎
演算法 TF-IDF + 餘弦相似度 BM25 + 更多進階演算法
資料儲存 記憶體中 磁碟持久化
部署方式 嵌入式(Python 程式中的一個模組) 獨立服務(獨立存在於伺服器)

功能目標相同

兩者都是為了在 RAG 系統中擔任搜尋引擎的角色:

  • 建立文檔索引
  • 接受查詢請求
  • 返回相關文檔

工作流程相同

# 兩者都遵循相同的使用模式
# 1. 建立索引
index = create_index()

# 2. 索引文檔
index.fit(documents)  # MinSearch
# 或
index_documents(es_client, documents)  # ElasticSearch

# 3. 搜尋
results = index.search(query)  # MinSearch
# 或  
results = es_client.search(query)  # ElasticSearch

都支援字段權重

ElasticSearch 的權重,也可以不是整數。

python# MinSearch
boost = {"question": 3, "text": 1, "section": 0.5}

# ElasticSearch  
"fields": ["question^3", "text", "section"]

都支援過濾

# MinSearch
filter_dict = {"course": "data-engineering-zoomcamp"}

# ElasticSearch
"filter": {"term": {"course": "data-engineering-zoomcamp"}}

▌MinSearch 實作

MinSearch 簡介

連結為講師改寫的 MinSearch source code,一個 class,裡面就兩個 method: fit & search。

如果你想看 原始的 MinSearch

MinSearch 是一個極簡的記憶體文字搜尋引擎(Python object),主要用於教學目的。
fit method:對提供的文件建立索引。
search method:核心搜尋功能,支援查詢、過濾和權重調整。

  • 使用 TF-IDF 和餘弦相似度進行搜尋
  • 輕量級實作,適合無法託管 ElasticSearch 的環境
  • 在記憶體中運行

》建立索引

import minsearch

# 建立索引
index = minsearch.Index(
    text_fields=["question", "text", "section"],
    keyword_fields=["course"]
)

# 建立索引
index.fit(documents)

》搜尋函數

def search(query, num_results=5):
    boost = {
        "question": 3,      # 問題欄位權重最高
        "text": 1,          # 內容欄位標準權重
        "section": 0.5      # 章節欄位權重較低
    }
    
    results = index.search(
        query=query,
        filter_dict={"course": "data-engineering-zoomcamp"},
        boost_dict=boost,
        num_results=num_results
    )
    return results

▌ElasticSearch 實作

》Docker 啟動指令

docker run -it \
  --rm \
  --name elasticsearch \
  -m 4GB \
  -p 9200:9200 \
  -p 9300:9300 \
  -e "discovery.type=single-node" \
  -e "xpack.security.enabled=false" \
  docker.elastic.co/elasticsearch/elasticsearch:8.4.3

》索引設定

from elasticsearch import Elasticsearch

# 建立連接
es_client = Elasticsearch('http://localhost:9200')

# 索引設定
index_settings = {
    "settings": {
        "number_of_shards": 1,
        "number_of_replicas": 0
    },
    "mappings": {
        "properties": {
            "text": {"type": "text"},
            "section": {"type": "text"},
            "question": {"type": "text"},
            "course": {"type": "keyword"}
        }
    }
}

》ElasticSearch 查詢

def elastic_search(query):
    search_query = {
        "size": 5,
        "query": {
            "bool": {
                "must": {
                    "multi_match": {
                        "query": query,
                        "fields": ["question^3", "text", "section"],
                        "type": "best_fields"
                    }
                },
                "filter": {
                    "term": {
                        "course": "data-engineering-zoomcamp"
                    }
                }
            }
        }
    }
    
    response = es_client.search(index=index_name, body=search_query)
    result_docs = []
    for hit in response["hits"]["hits"]:
        result_docs.append(hit["_source"])
    return result_docs

▌LLM整合

》提示模板

def build_prompt(query, search_results):
    context = ""
    for doc in search_results:
        context += f"section: {doc['section']}\n"
        context += f"question: {doc['question']}\n"  
        context += f"answer: {doc['text']}\n\n"
    
    prompt_template = """
    You're a course teaching assistant. Answer the QUESTION based on the CONTEXT from the FAQ database. 
    Use only the facts from the CONTEXT when answering the QUESTION.

    QUESTION: {question}

    CONTEXT: {context}
    """
    
    prompt = prompt_template.format(question=query, context=context).strip()
    return prompt

》LLM呼叫

以 OpenAI 為例

from openai import OpenAI

client = OpenAI()

def llm(prompt):
    response = client.chat.completions.create(
        model='gpt-3.5-turbo',
        messages=[{"role": "user", "content": prompt}]
    )
    return response.choices[0].message.content

▌完整RAG流程

》整合函數

def rag(query):
    # 1. 搜尋相關文件
    search_results = search(query)  # 或 elastic_search(query)
    
    # 2. 建構提示
    prompt = build_prompt(query, search_results)
    
    # 3. 獲取LLM回應
    answer = llm(prompt)
    
    return answer

》使用範例

query = "I just discovered the course. Can I still join it?"
answer = rag(query)
print(answer)

▌技術要求與環境設定

》Python套件

pip install tqdm notebook openai elasticsearch pandas scikit-learn python-dotenv

》文件結構

01-intro/
├── rag-intro.ipynb
├── documents.json
├── minsearch.py
├── .env
└── requirements.txt

▌設計原則與最佳實踐

》模組化設計

  • 每個組件可以獨立替換
  • 例如:可以輕鬆將MinSearch替換為ElasticSearch
  • 保持整個流程不變的情況下更換個別元件

》權重調整策略

  • question欄位:權重3(最重要)
  • text欄位:權重1(標準)
  • section欄位:權重0.5(輔助)

》性能考量

  • MinSearch:適合開發和小規模部署
  • ElasticSearch:適合生產環境和大規模部署
  • 可根據部署環境選擇合適的搜尋引擎

▌後續方向

  1. 第二章預告:探索開源LLM替代方案
  2. 本地運行:使用Ollama在CPU上運行LLM
  3. 向量搜尋:第三章將介紹向量資料庫
  4. 評估與監控:第四章將涵蓋系統評估
  5. 進階技術:混合搜尋、文件重排序等

》提示

  • 課程提供完整的Jupyter Notebook實作
  • 支援多種LLM提供商(OpenAI、Groq、本地模型等)
  • 強調實用性和生產環境適用性
  • 包含豐富的社群支援和討論

▌參考資料