iT邦幫忙

2026 iThome 鐵人賽

DAY 12
0
自我挑戰組

AI 不只會回答:30 天打造一套真正能上線的智慧助理系列 第 12

[Day 12] 建立自動化 RAG Pipeline:讓每次修改都有依據

  • 分享至 

  • xImage
  •  

[Day 12] 建立自動化 RAG Pipeline:讓每次修改都有依據

今天為什麼要做這件事

在 Day 11 中,我們認識了 RAG Evaluation,開始從不同面向評估 AI 助理的品質

不過,如果每次測試都需要手動輸入問題、複製回答,再自行判斷結果,將會遇到幾個問題:

  • 測試流程耗時
  • 每次評估標準可能不同
  • 難以比較模型或參數的差異
  • 修改程式後,可能破壞原本正常的功能
  • 無法長期追蹤系統品質

因此,今天要將前一天的評估概念,進一步實作成一套 自動化 RAG Evaluation Pipeline

今天的核心目標是:

讓每次修改 RAG 系統後,都能透過固定測試與數據,確認系統是否真的改善

今天要解決什麼問題

今天將建立以下功能:

  1. 載入固定的評估資料集
  2. 自動執行多筆 RAG 測試案例
  3. 評估回答與檢索結果
  4. 將結果儲存成 JSON 或 CSV
  5. 比較不同模型與參數
  6. 分析失敗案例
  7. 建立自動化回歸測試

建立評估資料集

[
  {
    "id": "case_001",
    "question": "產品支援哪些作業系統?",
    "expected_keywords": [
      "Windows 10",
      "Windows 11",
      "Ubuntu 22.04",
      "Ubuntu 24.04"
    ],
    "reference_answer": "本產品支援 Windows 10、Windows 11、Ubuntu 22.04 與 Ubuntu 24.04。",
    "should_answer": true
  },
  {
    "id": "case_002",
    "question": "安裝完成後需要做什麼?",
    "expected_keywords": [
      "重新啟動"
    ],
    "reference_answer": "安裝完成後,需要重新啟動系統。",
    "should_answer": true
  },
  {
    "id": "case_003",
    "question": "是否支援 macOS?",
    "expected_keywords": [],
    "reference_answer": "目前文件沒有足夠資訊確認是否支援 macOS。",
    "should_answer": false
  }
]

為什麼要加入 should_answer

RAG 系統不只需要處理「有答案」的問題,也需處理「文件沒有提供答案」的情境

Keyword Coverage

先使用簡單的關鍵字覆蓋率,作為自動化評估的起點

def keyword_coverage(answer, expected_keywords):
    if not expected_keywords:
        return None

    answer_lower = answer.lower()

    matched_count = sum(
        1
        for keyword in expected_keywords
        if keyword.lower() in answer_lower
    )

    return matched_count / len(expected_keywords)

這個方法可以快速檢查回答是否包含必要資訊,但不能代表回答一定正確

Context Precision

def context_precision(relevance_labels):
    if not relevance_labels:
        return 0.0

    relevant_count = sum(
        1
        for label in relevance_labels
        if label > 0
    )

    return relevant_count / len(relevance_labels)

這裡使用:

  • 0:不相關
  • 1:部份相關
  • 2:高度相關

只要分數大於 0,就視為具備某種程度的相關性

延遲時間

除了品質指標,也需要記錄 RAG Pipeline 的執行時間

import time


def measure_latency(function, *args, **kwargs):
    start_time = time.perf_counter()

    result = function(*args, **kwargs)

    end_time = time.perf_counter()

    latency_ms = (end_time - start_time) * 1000

    return result, latency_ms

為什麼要記錄 Latency

當我們加入 Reranking 或更複雜的評估模型後,品質可能提升,但執行時間也可能增加

因此,不能只觀察準確度,也要確認系統是否符合實際應用需求

統一 RAG Pipeline 回傳格式

為了讓評估程式能夠處理不同測試案例,RAG Pipeline 應該回傳一致的資料格式

建議格式:

{
    "question": "安裝完成後需要做什麼?",
    "answer": "安裝完成後,需要重新啟動系統。",
    "contexts": [
        {
            "document": "安裝完成後,需要重新啟動系統。",
            "metadata": {
                "source": "sample.txt",
                "chunk_id": "chunk_002"
            }
        }
    ],
    "latency_ms": 1240
}

至少需要包含:

  • 使用者問題
  • AI 回答
  • 檢索到的 Context
  • Metadata
  • 執行時間

建立單筆案例評估

from src.evaluation.metrics import (
    keyword_coverage,
    measure_latency
)


def evaluate_case(case, rag_pipeline):
    question = case["question"]
    expected_keywords = case["expected_keywords"]

    result, latency_ms = measure_latency(
        rag_pipeline.run,
        question
    )

    answer = result.get("answer", "")

    keyword_score = keyword_coverage(
        answer,
        expected_keywords
    )

    return {
        "id": case["id"],
        "question": question,
        "answer": answer,
        "keyword_coverage": keyword_score,
        "latency_ms": round(latency_ms, 2),
        "contexts": result.get("contexts", []),
        "reference_answer": case.get(
            "reference_answer",
            ""
        )
    }

將「產生回答」與「評估回答」分離,方便未來替換評估方法

建立完整資料集評估

def evaluate_dataset(dataset, rag_pipeline):
    results = []

    for case in dataset:
        result = evaluate_case(
            case,
            rag_pipeline
        )

        results.append(result)

    return results

此時,我們已經可以一次執行多筆測試案例,而不需要逐筆手動操作

將結果儲存成 JSON

import json
from pathlib import Path


def save_json(results, output_path):
    path = Path(output_path)

    path.parent.mkdir(
        parents=True,
        exist_ok=True
    )

    with open(
        path,
        "w",
        encoding="utf-8"
    ) as file:
        json.dump(
            results,
            file,
            ensure_ascii=False,
            indent=2
        )

為什麼需要保存 JSON

JSON 可以保留完整資料,適合:

  • 除錯
  • 查看失敗案例
  • 保存 Context
  • 追蹤引用來源
  • 後續再次分析

將結果儲存成 CSV

CSV 適合用來查看表格與進行統計分析

import csv
from pathlib import Path


def save_csv(results, output_path):
    path = Path(output_path)

    path.parent.mkdir(
        parents=True,
        exist_ok=True
    )

    fieldnames = [
        "id",
        "question",
        "answer",
        "keyword_coverage",
        "latency_ms"
    ]

    with open(
        path,
        "w",
        encoding="utf-8-sig",
        newline=""
    ) as file:
        writer = csv.DictWriter(
            file,
            fieldnames=fieldnames
        )

        writer.writeheader()

        for result in results:
            row = {
                field: result.get(field)
                for field in fieldnames
            }

            writer.writerow(row)

JSON 與 CSV 的差異

適合用途

JSON: 保存完整結構與 Context
CSV: 表格檢視與統計分析

實務上可以兩種格式都可以保存

建立評估摘要

完整結果之外,也需要產生摘要資訊

def summarize_results(results):
    valid_keyword_scores = [
        result["keyword_coverage"]
        for result in results
        if result["keyword_coverage"] is not None
    ]

    latencies = [
        result["latency_ms"]
        for result in results
    ]

    summary = {
        "total_cases": len(results),
        "average_latency_ms": 0.0,
        "average_keyword_coverage": None
    }

    if latencies:
        summary["average_latency_ms"] = (
            sum(latencies) / len(latencies)
        )

    if valid_keyword_scores:
        summary["average_keyword_coverage"] = (
            sum(valid_keyword_scores)
            / len(valid_keyword_scores)
        )

    return summary

如何區分 Retrieval Error 與 Generation Error

這是分析 RAG 問題時非常重要的一步

Retrieval Error

檢索階段沒有找到必要文件

這可能是:

  • Chunk 切分不適當
  • Embedding 效果不足
  • Keyword Search 沒有匹配成功
  • Top-K 太小
  • Reranking 排序錯誤

可能的改善方式

  • 調整 Chunk Size
  • 增加 Chunk Overlap
  • 使用 Hybrid Search
  • 調整 Reranking 策略
  • 增加查詢改寫

Generation Error

檢索結果已經包含正確文件,但 LLM 沒有正確使用

  • Prompt 約束不足
  • Context 格式不清楚
  • LLM 忽略文件內容
  • 回答產生幻覺
  • 引用與回答不一致

可能的改善方式

  • 強化 Prompt
  • 明確要求只能根據 Context 回答
  • 加入「資訊不足時拒答」規則
  • 強制輸出引用來源
  • 加入 Faithfulness 評估
  • 使用人工抽查確認錯誤類型

建立自動化回歸測試

什麼是回歸測試

回歸測試是指:

在修改系統後,重新執行既有測試,確認原本正常的功能沒有被破壞

雖然系統仍然能產生文字,但原本的功能已經退化

建立簡單的回歸判斷

def regression_check(
    results,
    minimum_score=0.8
):
    failures = []

    for result in results:
        score = result["keyword_coverage"]

        if score is None:
            continue

        if score < minimum_score:
            failures.append({
                "id": result["id"],
                "score": score
            })

    return failures

注意事項

這只是簡化版回歸測試

環境應該考慮:

  • 檢索指標
  • 回答相關性
  • Faithfulness
  • 不可回答問題的拒答能力
  • 延遲時間
  • 引用正確性

不能只依賴單一關鍵字分數決定系統是否通過

如何讓評估流程整合至 CI/CD

當評估程式可以透過指令執行後,就能進一步整合至 CI/CD

品質門檻範例

這些數值只是示範,實際門檻應根據:

  • 測試資料量
  • 使用情境
  • 評估方法
  • 業務風險
  • 使用者期待

遇到什麼問題

問題一:自動化評估分數與人工判斷不一致

原因可能是:

  • 關鍵字出現,但語意不正確
  • 評估規則過於簡單
  • 參考答案不完整
  • 沒有考慮否定句
  • 沒有檢查 Context 依據

解決方式

採用多層次評估:

第一層:關鍵字與格式檢查
第二層:語意與相關性評估
第三層:Faithfulness 檢查
第四層:人工抽查

自動化評估可以提高效率,但不應完全取代人工審查

問題二:平均分數很好,但重要案例失敗

平均分數仍可能偏高,但案例 D 可能是使用者最常詢問的問題

解決方式

除了平均分數,也應記錄:

  • 各案例分數
  • 重要問題的失敗率
  • 不同問題類型的表現
  • 高風險問題的最低品質
  • 最常發生的錯誤類型

評估不應只追求整體平均值

問題三:評估模型本身也可能產生錯誤

如果使用 LLM Judge 評估回答,評估模型可能:

  • 誤判文件是否支持回答
  • 受到回答語氣影響
  • 對不同答案格式產生不同評分
  • 無法正確處理專業領域內容

解決方式

  • 固定評估 Prompt
  • 建立人工標註的驗證資料
  • 定期抽查評估結果
  • 比較不同評估方式
  • 記錄評估模型與版本
  • 不將單次 LLM 評分視為絕對真實

建議觀察的問題

  • Hybrid Search 是否找到更多必要文件
  • Reranking 是否改善前段結果
  • Top-K 增加後,延遲是否變高
  • 哪些問題仍然無法正確回答
  • 錯誤主要發生在檢索還是生成
  • 自動化分數是否與人工判斷一致

今天學到什麼

今天將 RAG Evaluation 從概念轉換成可執行的自動化流程

評估流程需要標準化

固定的資料集、評估方法與輸出格式,才能讓不同實驗具備可比較性

JSON 與 CSV 各有用途

  • JSON:保存完整結果與 Context。
  • CSV:方便查看表格與統計。
  • 兩者搭配:兼顧詳細紀錄與資料分析。

失敗案例比單一平均分數更有價值

透過分析個別錯誤,可以確認問題來自:

  • 檢索不足
  • 排序錯誤
  • Context 不完整
  • LLM 生成錯誤
  • 引用缺乏依據

回歸測試可以避免品質退化

每次修改模型、Prompt 或檢索參數後,都應重新執行固定測試,確認原本的功能仍然正常

自動化評估是持續改善的基礎

RAG 系統不是完成一次就不需要調整,而是需要持續:

目前系統進度

目前系統這代表我們開始從「建立 AI 助理功能」,進一步進入「驗證與維護 AI 助理品質」的階段

明天要做什麼

今天完成了自動化 RAG Evaluation Pipeline,但目前仍以固定測試資料集與基本評估指標為主

接下來可以進一步研究:

  • 如何建立更完整的評估資料集
  • 如何自動產生問題與參考答案
  • 如何處理不同類型的文件
  • 如何評估長文件與多段 Context
  • 如何建立評估結果儀表板
  • 如何將錯誤案例回饋至 Chunking、Retrieval 與 Prompt

Day 13:建立高品質評估資料集——讓測試案例更接近真實使用情境

從今天的「讓評估流程自動化」,進一步走向「建立具有代表性的測試資料,讓 AI 助理的品質改善更有方向」


上一篇
[Day 11] RAG Evaluation:如何知道 AI 助理回答得好不好
系列文
AI 不只會回答:30 天打造一套真正能上線的智慧助理12
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言