本來是想要寫針對整個 AI 工具人進行的評估,發現範圍有點太大了,好像不是在剩下的天數可以寫完的東西,所以這裡縮小以下範圍,只用來評估我們在以下幾天實作的的功能 :
接下來就來研究要如何評估這些功能的品質吧。
我這要先簡單補充一下我們那幾天實作做了什麼 :
所以首先我們可以先來想一下,先從最簡單的問與答,我們要用什麼指標來說明 AI 回答的很棒棒呢 ?
問與答我自已是覺得最難做與評估的東西,因為範圍很廣
上面提到範圍很廣
有一部份是指根據用戶問的問題,我們可能就會需要用不同的指標,例如 Summary 這種情況就是用戶會問說,可以幫我總結這個章節的重點嗎
,這樣是不是就沒有所謂的正確答案了 ? 所以這種情況我們是不是就不適用 Answer Accuracy 這種了 ?
所以這裡我會先用分類用戶意圖後,再來給予適當的指標。然後下面是我大概整理的情境。
🤔 類型1: 摘要/總結 ( Summary )
例如問說可以幫我總這個章節的重點嗎,那這種情況下我們適合以下幾個 Ragas 指標。
🤔 類型2: 事實查詢
例如以下幾種問題都算是,那這種情況就是很吃以下幾個指標。
"Docker 的定義在哪個章節?"
"講師在哪裡提到容器化的優勢?"
"關於微服務架構的部分在第幾分鐘?"
然後以下就是我們適合的指標。
Answer Correctness 與 Answer Relevancy 在這裡應該有人會覺得混亂,感覺不是差不多的比較,有點像但不太對,以下為主要差別與範例說明
兩者的差別在於 Answer Correctness 一定要有 Ground Truth 才能判斷,而 Answer Relevancy 則否,但不代表在有 Ground Truth 的情況下不需要 Answer Relevancy。
以下為如果只有 Answer Correctness 情況可能會有問題的案例,你看答案 B 是不是說了一堆廢話後,還有答案,但看的人可不想知道啊 :
問題: "Docker 在哪個章節?"
ground_truth = "第 2 章"
# === 答案 A ===
answer_A = "第 2 章"
Answer Correctness: 1.0 ✅
Answer Relevancy: 1.0 ✅
用戶體驗: ⭐⭐⭐⭐⭐ 完美!
# === 答案 B ===
answer_B = """
Docker 在第 2 章有詳細介紹。
Docker 是由 Solomon Hykes 於 2013 年創建的容器化平台。
它的主要優勢包括:
1. 輕量化 - 相比虛擬機更節省資源
2. 快速部署 - 秒級啟動時間
3. 跨平台支援 - 可在多種作業系統運行
4. 易於擴展 - 支援微服務架構
在第 2 章中,講師從基本概念開始,逐步說明 Docker
的工作原理、安裝步驟,以及基本指令的使用方式...
"""
Answer Correctness: 0.85 ✅ (包含「第2章」,語義相近)
Answer Relevancy: 0.35 ❌ (大部分內容與「在哪裡」無關)
用戶體驗: ⭐⭐ 糟糕! (用戶只想要位置,卻得到一大段說明)
🤔 類型 3: 流程/如何做 ( How-to )
它事實上和事實類有點像,但是實際上有個重點有差 :
那就是完整性 Completeness
例如下面的範例,如果沒有考慮 Completeness,那就會發生一個流程,有 5 個部份,但如果 1 個部份,那分數還是很高,所以這裡才會需要分開來判斷 :
# === 事實查詢的「完整」===
問題: "講師在哪裡提到 Docker Compose?"
完整答案 A: "第 5 章 23:15"
→ Completeness: 1.0 ✅
過度完整 B: """
第 5 章 23:15 首次提到,
第 6 章 12:30 再次說明,
第 7 章 05:00 進階用法
"""
→ Completeness: 1.0,但用戶可能不需要這麼多!
# === 流程查詢的「完整」===
問題: "如何建立 Docker 映像?"
不完整答案 A: "使用 docker build 命令"
→ Completeness: 0.2 ❌ (缺少前置、參數、驗證)
完整答案 B: """
1. 準備 Dockerfile
2. 執行 docker build -t name:tag .
3. 驗證:docker images
4. 測試映像
"""
→ Completeness: 0.9 ✅
# 關鍵差異:
# 事實查詢:回答「那個問題」就完整了
# 流程查詢:必須包含「所有步驟」才完整
所以這裡類型 2 大部份相同,但是會多加一個 Completeness。
🤔 類型4: 開放討論/觀念釐清
例如這些都算,然後它們有個特性,就是我們很難有一定的標準答案,所以不太會用什麼 Answer Correctness 之類的。
"為什麼要使用 Docker 而不是虛擬機?"
"容器化和虛擬化的本質差異是什麼?"
"什麼情況下適合使用 Docker Compose?"
"Docker 和 Kubernetes 的關係如何理解?"
"微服務架構為什麼需要容器化?"
‼️‼️ 類型1: 摘要/總結 ( Summary )這種為主,不然這篇文章會長到還大約是 4 篇文章的 size
會需要這個是因為,我們如果是新產品,不太可能什麼都沒測過後就上線,所以還是要先自已建立一些實驗資料,來看看我們的分數後,才能決定要不要上線
❓ 那要產生什麼實驗資料呢 ?
這裡會以上面 4 種意圖,來建立不同的 Dataset,然後標準就是以下三個欄位,其中 expectedOutput 要說明一下,就是我們很多時後,都沒有標準答案,但這個時後 expectedOutput 還是建議填寫,因為會用它來當 reference 來當做是語義比對,或是意思是否等價之類的。
{
input: '我想學習 LangGraph',
expectedOutput: '自已去看文件',
metadata (這個可選): {}
}
然後我們是以字幕來當這個單元的原始內容,那接下來另一個問題就是。
❓ 我們每個單元,都要同時產這 4 種類型的實驗資料嗎 ?
我覺得要分以下兩個方向來設計這兩種 Dataset。
🤔 第一種類型: Context 固定
這種實驗的重點是觀察模型在使用已知 Context 下的表現
,所以會關閉 Retrieval 機制,直接把固定的 Context 餵給模型使用。
適用的情境:
這種情況下,我們可以不用理什麼課程或單元是什麼,因為 Context 都寫死了。
*🤔 第二類型: Context 不固定
用來驗證整條流程的整體品質,包含 Retrieval 與 Generation。
這種實驗會保留真實的 Retrieval 過程。每次執行實驗時,系統會根據 Dataset 中的 metadata.lectureId(或 courseId )告訴 Retrieval 該從哪個單元的資料中取出 Context。
不過理想上是每個單元每個課都有一定等級的實驗資料,我覺得在實務上可以先以例如 3 堂最熱門、最多人且是不同類型的課程,來當整個 AI Application 來當黃金資料集,如果這三堂的實驗評分都很高,那事實上整體系統穩定性應該也不會差到那。
不然如果真的要做每堂課,那個我自已現在是覺得成本很高。
🤔❓ 那我們這種從 0 開始的要用那種
簡單來說,兩個都要,然後從下面流程先建立第一類與第二類資料的。
🤔 程式碼
程式碼就大概如下,流程就是 :
async function main() {
const langfuse = new Langfuse({
publicKey: process.env.LANGFUSE_PUBLIC_KEY,
secretKey: process.env.LANGFUSE_SECRET_KEY,
baseUrl: process.env.LANGFUSE_BASE_URL,
});
// 1. 讀取 SRT 檔案+處理
const srtContent = fs.readFileSync("./test.srt", "utf-8");
const processedChunks = SRTProcessor.process(srtContent);
// 2. 生成多個意圖的測試資料
const summaryPrompt = `
# Context:
${processedChunks
.map((chunk) => `[${chunk.startAt}~${chunk.endAt}] ${chunk.textClean}`)
.join("\n")}
# Task:
根據上述 context 產生 5 個「摘要/總結」類型的問答對。
# 摘要問題的特徵:
✅ 必須包含:
- 問題要明確要求「總結」、「歸納」、「整理」、「概括」內容
- 答案要涵蓋**多個關鍵要點**(至少 3 個)
- 答案要用**結構化方式**呈現(條列式或分段)
- 答案要完整但簡潔,濃縮原內容的核心資訊
- 至少要有一題是問時間範圍內的總結
❌ 絕對不要:
- 不要問「為什麼」、「如何」、「差異是什麼」(這些是討論題)
- 不要問單一概念的解釋(如「什麼是 RAG?」)
- 不要問比較題(如「A 和 B 的差別?」)
- 答案不要只深入解釋一個概念
# 問題類型範例:
✅ 好的摘要問題:
- "總結這段內容提到的核心概念"
- "歸納講師討論的主要議題有哪些"
- "整理 14:00~15:00 的重點內容"
- "概括這個單元涵蓋的技術方法"
❌ 不是摘要問題:
- "為什麼要使用 RAG?" (這是討論題)
- "RAG 和 RALM 的差異?" (這是比較題)
- "什麼是 hallucination?" (這是定義題)
# 答案格式要求:
- 必須使用條列式(1. 2. 3.)或結構化段落
- 每個要點要簡潔(1-2 句話)
- 至少包含 3 個不同的關鍵要點
- 字數控制在 200-300 字
# Output Format:
請輸出 JSON 陣列,格式如下:
[{
"question": "問題(必須包含「總結」「歸納」「整理」等關鍵字)",
"answer": "結構化的答案(條列式或分段,涵蓋多個要點)"
}]
# Examples:
[{
"question": "總結這段內容討論的 RAG 相關概念與技術",
"answer": "這段內容討論的 RAG 相關概念包括:\n\n1. RAG vs RALM:RAG 強調生成,RALM 泛指所有檢索輔助的語言模型功能\n\n2. 長尾知識問題:預訓練資料中低頻資訊記憶薄弱,容易產生 hallucination\n\n3. 檢索機制優勢:提供 open-book 能力,可引用外部資源提升準確性\n\n4. 實作方法:如 WebGPT 使用搜尋、點擊頁面、引用段落的方式"
}, {
"question": "歸納講師說明的語言模型主要限制有哪些",
"answer": "講師說明的語言模型限制包括:\n\n1. 知識過時:預訓練後的模型內部知識無法更新\n\n2. 長尾知識薄弱:低頻資訊在訓練資料中出現少,模型記憶不足\n\n3. Hallucination 風險:對不確定的資訊會憑機率猜測,產生錯誤答案\n\n4. 缺乏來源驗證:無法提供資訊來源,難以 fact-check"
}, {
"question": "整理 14:00~16:00 時間區段的核心內容",
"answer": "這個時間區段的核心內容涵蓋:\n\n1. 檢索輔助系統的定義與分類\n\n2. 長尾知識問題的成因與影響\n\n3. 檢索機制如何改善模型表現\n\n4. WebGPT 等實作案例的具體做法"
}]
# Important:
- 每個問題都必須明確包含「總結」、「歸納」、「整理」、「概括」等摘要關鍵字
- 答案必須包含至少 3 個不同的要點
- 答案必須使用條列式或結構化格式
- 不要產生「為什麼」、「如何」、「差異」等討論型問題
`;
const model = new ChatOpenAI({
modelName: "gpt-5-mini",
});
const originalResult = await model.invoke([
{ role: "user", content: summaryPrompt },
]);
const result = JSON.parse(originalResult.content.toString());
// 3. 將結果寫入 Langfuse 的 dataset
// 好像找不到 batch 的 api
await Promise.all(
result.map(async (item) => {
await langfuse.api.datasetItemsCreate({
datasetName: "30-28",
input: item.question,
expectedOutput: item.answer,
metadata: {
model: "gpt-5-mini",
lectureId: "30-28",
},
});
})
);
console.log("✅ 已完成,請到 Langfuse 查看結果");
}
因為我們這篇文章主要是聚焦在類型1: 摘要/總結 ( Summary )
這種類型的情境,因為我們這裡評產生題目的題目是否優質就可以用在流程 1 寫的這個 2 個 :
下面這個是我根據 Ragas 的 summarization 的指標來寫成的一個 Evaluator prompt 可以參考看看。
https://github.com/explodinggradients/ragas/blob/main/src/ragas/metrics/_summarization.py
import { ChatOpenAI } from "@langchain/openai";
export class SummaryEvaluator {
static async evaluate(
content: string,
summary: string
): Promise<{ score: number; comment: string }> {
const prompt = `
你的任務是基於 content 生成封閉式問題,並評估 summary 是否能回答這些問題。
content: ${content}}
summary: ${summary}}
## 第一部分:生成問題
基於給定的 content 生成封閉式問題。
問題生成規則:
1. 所有問題都必須能夠「僅使用」原文來回答
2. 問題應該測試 content 中的具體資訊是否存在
3. 重點關注與關鍵詞組相關的事實資訊
4. 生成的問題基於原文都應該回答為 1 (是)
5. 使用簡單、清晰的問題格式
問題模式範例:
- "[主體] 是 [類型] 嗎?"
- "[主體] 位於 [地點] 嗎?"
- "[主體] 由 [人物] 創立嗎?"
- "[主體] 在 [時間] [動作] 嗎?"
## 第二部分:評估答案
針對每個生成的問題,判斷 summary 是否包含足夠資訊來回答。
答案評估規則:
1. 答案必須是 1 或 0 ( number )
2. 如果 summary「包含」足夠資訊來回答問題,答案為 1
3. 如果 summary「不包含」足夠資訊來回答問題,答案為 0
4. 只考慮 summary 中的內容,不使用外部知識
5. 評估資訊是否「存在」於 summary 中
評估標準:
- 1: summary 明確或隱含地包含回答問題所需的資訊
- 0: summary 沒有提及或提供回答問題所需的資訊
## 第三部分: 總計與回傳結果
將每一個問題評分的結果,計算平均值,並且回傳評分結果的 comment
請輸出 JSON 物件,格式如下:
{
"score": 0.4
"comment": "評分結果的 comment"
}
範例:
第二部份總共產生 5 題,分數分別為 1,0,0.5,0.5,0,
因為 score 為 0.4 (1+0+0.5+0.5+0)/5
{
"score": 0.4
"comment": "評分結果的 comment"
}
`;
const model = new ChatOpenAI({
modelName: "gpt-5-mini",
});
const result = await model.invoke([{ role: "user", content: prompt }]);
console.log(result.content);
return JSON.parse(result.content.toString());
}
}
然後接下是外面使用時的樣子,他會先根據我們流程 2-1 所產生的實驗資料,來進行評份,透過 SummaryEvaluator,然後接下來我們將分數高於 0.6 的放到 Langfuse 收到 Dataset,當做我們未來的實驗資料。
// 3. 將結果寫入 Langfuse 的 dataset
// 好像找不到 batch 的 api
console.log("開始評估摘要問題");
const scores = await Promise.all(
result
.map(async (item) => {
const score = await SummaryEvaluator.evaluate(context, item.answer);
return {
question: item.question,
answer: item.answer,
score: score.score,
scoreComment: score.comment,
};
})
);
const filteredScores = scores.filter((item) => item.score > 0.6);
console.log("評估結果", filteredScores);
// 4. 將結果寫入 Langfuse 的 dataset
await Promise.all(
filteredScores.map(async (item) => {
await langfuse.api.datasetItemsCreate({
datasetName: "30-28",
input: item.question,
expectedOutput: item.answer,
metadata: {
model: "gpt-5-mini",
lectureId: "30-28",
score: item.score,
},
});
})
);
這裡是等到上了正式環境以後,我們事實上接下來很有產生很多 trace,然後這裡就是要進行我們有提到的Human Annotation
流程,將他放到 Dataset 中。
這裡的參考包含了 :
本篇文章中,我們嘗試進行對我們之前做的功能『 課程問與答的功能 』研究要如何進行評估,但是寫到一半後來發現這個主題真的很大,主要的原因在於,問與答實際上還要考慮很多的情境,加上很多問題是沒有標準答案的,這裡也只能先寫一些發現的東西,之後光這個主題應該就可以開很多篇了。