LangChain 課程 – LLM 評估(Evaluation)
主題:RAG Benchmarks
簡介
在 Retrieval‑Augmented Generation(RAG) 的工作流程中,LLM(大型語言模型)不再只是單純產生文字,而是會結合外部知識庫的檢索結果,提供更具事實性的回應。隨著 RAG 應用越來越多,如何有效評估 這樣的系統成為關鍵。
傳統的生成模型評估指標(BLEU、ROUGE、BLEURT 等)只能衡量語言流暢度或與參考答案的相似度,卻無法直接捕捉檢索與生成之間的交互效果。為此,社群提出了 RAG Benchmarks——一套專門設計來測試檢索、證據整合與生成品質的基準測試。
本篇文章將帶你了解 RAG Benchmarks 的核心概念、在 LangChain 中如何實作、常見陷阱與最佳實踐,並提供完整的程式碼範例,協助你快速上手並在實務專案中驗證 RAG 系統的效能。
核心概念
1. 為什麼需要 RAG Benchmarks?
- 證據依賴度(Evidence Dependency):RAG 系統的答案是否真的使用了檢索到的文件作為依據?
- 檢索正確率(Retrieval Accuracy):檢索階段是否找到了正確且相關的文件?
- 生成真實性(Factuality):即使檢索成功,生成的文字仍可能出現「幻覺」(hallucination),需要額外檢測。
RAG Benchmarks 透過 多層次評分(檢索、證據引用、生成)提供更細緻的診斷資訊,讓開發者能針對瓶頸進行優化。
2. 主流 RAG Benchmark 介紹
| Benchmark | 主要特點 | 資料來源 | 評分維度 |
|---|---|---|---|
| RAG‑QA | QA 形式,要求模型引用檔案頁碼 | Wikipedia、ArXiv | Retrieval@k、Citation‑F1、Answer‑Exact |
| MIRAGE | 多輪對話,測試檢索在上下文中的持續性 | 科技新聞、法律文書 | Context‑Recall、Fact‑Consistency |
| LLM‑Eval‑RAG (LangChain 官方) | 直接整合到 LangChain 評估框架 | 自建測試集 | Retrieval‑Score、Citation‑Score、BLEU/ROUGE |
本文將以 LangChain 官方的 LLM‑Eval‑RAG 為例,示範如何在程式碼層面實作完整的評估流程。
3. LangChain 中的 RAG 評估流程
- 建構測試資料集:每筆測試包含問題、期望答案、以及參考文件(或文件 ID)。
- 執行檢索:使用
Retriever(如FAISS,Pinecone,ElasticSearch)取得前k個相關文件。 - 生成答案:把檢索結果(或其摘要)作為
PromptTemplate的上下文,呼叫 LLM(OpenAI, Anthropic, etc.)。 - 比對與計分:
- Retrieval Score:檢查正確文件是否在前
k名。 - Citation Score:解析 LLM 回答中的引用(如
【1】、(source: doc_id)),比對是否對應到檢索結果。 - Answer Score:使用 ROUGE、BLEU、或 LLM‑based 評分(如 GPT‑4 評分)測量答案與參考答案的相似度。
- Retrieval Score:檢查正確文件是否在前
- 匯總報告:產出每個維度的平均分數與詳細錯誤案例,方便後續改進。
以下章節將提供實作範例,帶你一步步完成上述流程。
程式碼範例
⚠️ 注意:以下範例以 Node.js 為基礎,使用
langchainjs(v0.0.200 以上)與openaiSDK。若使用 Python,概念相同,只是套件與語法略有差異。
1. 安裝必要套件
npm install langchain openai @pinecone-database/pinecone-client
2. 建立測試資料集
// data/test_set.js
/**
* 每筆測試案例的結構
* @typedef {Object} RAGTestCase
* @property {string} question 使用者的提問
* @property {string} answer 參考答案(完整敘述)
* @property {string[]} docIds 正確文件的 ID(可在向量資料庫中查找)
*/
/** @type {RAGTestCase[]} */
const TEST_SET = [
{
question: "什麼是量子糾纏?請舉例說明。",
answer:
"量子糾纏是指兩個或多個量子系統的狀態彼此關聯,即使相隔遙遠也會即時影響。舉例而言,若兩顆電子的自旋被糾纏,測量其中一顆的自旋會立即決定另一顆的自旋方向。",
docIds: ["wiki_quantum_entanglement"],
},
{
question: "請說明 Linux 中的 `inode` 概念。",
answer:
"`inode` 是檔案系統中用來描述檔案屬性的資料結構,包含檔案大小、擁有者、權限、資料塊位置等資訊。每個檔案在磁碟上都有唯一的 inode 編號。",
docIds: ["linux_inode_manual"],
},
// …更多案例
];
module.exports = { TEST_SET };
說明:測試資料集以 JSON 形式儲存,
docIds用來驗證檢索正確性。
3. 設定 Retriever(以 Pinecone 為例)
// services/retriever.js
const { PineconeClient } = require("@pinecone-database/pinecone-client");
const { OpenAIEmbeddings } = require("langchain/embeddings/openai");
const { PineconeStore } = require("langchain/vectorstores/pinecone");
/**
* 建立 Pinecone 向量資料庫的 Retriever
* @returns {Promise<PineconeStore>}
*/
async function initRetriever() {
const pinecone = new PineconeClient();
await pinecone.init({
environment: process.env.PINECONE_ENV,
apiKey: process.env.PINECONE_API_KEY,
});
const index = pinecone.Index(process.env.PINECONE_INDEX);
const embeddings = new OpenAIEmbeddings({ openAIApiKey: process.env.OPENAI_API_KEY });
// PineconeStore 同時提供 asRetriever() 方法
const vectorStore = await PineconeStore.fromExistingIndex(embeddings, {
pineconeIndex: index,
namespace: "rag-benchmarks",
});
// 只取前 5 個最相似的文件
return vectorStore.asRetriever(5);
}
module.exports = { initRetriever };
重點:
asRetriever(k)會回傳一個符合 LangChainBaseRetriever介面的物件,方便後續直接呼叫getRelevantDocuments。
4. 建立 LLM 生成器與 Prompt
// services/llmGenerator.js
const { OpenAI } = require("langchain/llms/openai");
const { PromptTemplate } = require("langchain/prompts");
const { ConversationalRetrievalQAChain } = require("langchain/chains");
// 1. LLM 設定
const llm = new OpenAI({
temperature: 0.0, // 評估時盡量保持 deterministic
openAIApiKey: process.env.OPENAI_API_KEY,
modelName: "gpt-4o-mini",
});
// 2. Prompt 範本(含檔案引用格式)
const QA_PROMPT = PromptTemplate.fromTemplate(`
以下是從檔案中取得的相關資訊,請根據這些資訊回答使用者的問題,並在答案末尾以「[來源: <doc_id>]」的形式標註引用的檔案。
相關文件:
{context}
問題: {question}
答案:
`);
/**
* 建立一條結合檢索與生成的 Chain
* @param {BaseRetriever} retriever
* @returns {ConversationalRetrievalQAChain}
*/
function buildRAGChain(retriever) {
return ConversationalRetrievalQAChain.fromLLM(
llm,
retriever,
{
prompt: QA_PROMPT,
// 讓 Chain 回傳檢索到的文件與產生的答案
returnSourceDocuments: true,
}
);
}
module.exports = { buildRAGChain };
說明:此 Prompt 強制模型在答案中加入
[來源: <doc_id>],方便後續 Citation Score 的計算。
5. 評分函式:Retrieval、Citation、Answer
// evaluation/metrics.js
const { rougeL } = require("langchain/evaluation/metrics");
const { getEmbedding } = require("langchain/embeddings/openai");
/**
* 檢查正確文件是否出現在檢索結果中
* @param {string[]} retrievedIds
* @param {string[]} goldIds
* @returns {number} 1 = 命中, 0 = 未命中
*/
function retrievalScore(retrievedIds, goldIds) {
return goldIds.some((id) => retrievedIds.includes(id)) ? 1 : 0;
}
/**
* 解析模型答案中的引用標記,回傳引用的 docId 陣列
* 例: "......[來源: wiki_quantum_entanglement]" → ["wiki_quantum_entanglement"]
* @param {string} answer
* @returns {string[]}
*/
function parseCitations(answer) {
const regex = /\[來源:\s*([^\]\s]+)\]/g;
const ids = [];
let match;
while ((match = regex.exec(answer)) !== null) {
ids.push(match[1]);
}
return ids;
}
/**
* 計算 Citation Score:模型引用的文件是否與檢索結果相符
* @param {string[]} citedIds
* @param {string[]} retrievedIds
* @returns {number} 1 = 正確引用, 0 = 錯誤或未引用
*/
function citationScore(citedIds, retrievedIds) {
if (citedIds.length === 0) return 0;
// 至少有一個引用在檢索結果內即算通過
return citedIds.some((id) => retrievedIds.includes(id)) ? 1 : 0;
}
/**
* 使用 ROUGE‑L 計算答案相似度
* @param {string} pred
* @param {string} gold
* @returns {Promise<number>} 0~1 的分數
*/
async function answerScore(pred, gold) {
const { rougeLScore } = await rougeL(pred, gold);
return rougeLScore; // 直接回傳 F1 分數
}
module.exports = {
retrievalScore,
citationScore,
parseCitations,
answerScore,
};
提示:若想要更精細的事實性評估,可改用 GPT‑4 評分(
LLMChain呼叫評分模型),但在本教學中保持簡潔,使用 ROUGE‑L 作為基礎指標。
6. 主評估腳本
// run_evaluation.js
require("dotenv").config();
const { TEST_SET } = require("./data/test_set");
const { initRetriever } = require("./services/retriever");
const { buildRAGChain } = require("./services/llmGenerator");
const {
retrievalScore,
citationScore,
parseCitations,
answerScore,
} = require("./evaluation/metrics");
async function evaluateOneCase(chain, testCase) {
// 1. 執行 RAG
const { answer, sourceDocuments } = await chain.call({
question: testCase.question,
});
// 2. 取得檢索到的 docId 列表
const retrievedIds = sourceDocuments.map((doc) => doc.metadata.doc_id);
// 3. 計算各項分數
const rScore = retrievalScore(retrievedIds, testCase.docIds);
const citedIds = parseCitations(answer);
const cScore = citationScore(citedIds, retrievedIds);
const aScore = await answerScore(answer, testCase.answer);
return {
question: testCase.question,
answer,
retrievedIds,
citedIds,
scores: {
retrieval: rScore,
citation: cScore,
answer: aScore,
},
};
}
async function main() {
const retriever = await initRetriever();
const ragChain = buildRAGChain(retriever);
const results = [];
for (const tc of TEST_SET) {
const res = await evaluateOneCase(ragChain, tc);
results.push(res);
console.log(`✅ ${tc.question} → Retrieval:${res.scores.retrieval} Citation:${res.scores.citation} Answer:${res.scores.answer.toFixed(3)}`);
}
// 匯總統計
const avg = (arr, key) => arr.reduce((sum, r) => sum + r.scores[key], 0) / arr.length;
console.log("\n=== 總體報告 ===");
console.log(`Retrieval Avg : ${avg(results, "retrieval").toFixed(2)}`);
console.log(`Citation Avg : ${avg(results, "citation").toFixed(2)}`);
console.log(`Answer Avg : ${avg(results, "answer").toFixed(3)}`);
}
main().catch(console.error);
執行方式
node run_evaluation.js
執行後會看到每筆測試的個別分數與最終的平均指標,這就是 RAG Benchmarks 在 LangChain 中的完整實作流程。
常見陷阱與最佳實踐
| 陷阱 | 為何會發生 | 解決方法 / 最佳實踐 |
|---|---|---|
| 檢索結果不穩定 | 向量資料庫的隨機抽樣或 temperature 影響 |
設定 temperature = 0,使用 deterministic embedding(如 text-embedding-3-large),固定 k 值。 |
| 引用格式不一致 | Prompt 未明確要求模型使用統一標記,導致解析失敗 | 在 Prompt 中加入 嚴格的範例,如 "[來源: doc_id]",並在 output_parser 中驗證。 |
| 答案過長導致 token 截斷 | LLM 輸出超過模型上限,部分內容被截斷,影響 ROUGE 計算 | 使用 maxTokens 或 stop 參數限制長度,或在前端先 summarize 檢索結果。 |
| 測試集與實務資料不匹配 | 測試時使用的 docIds 與實際向量庫不一致,導致 retrievalScore 偏低 |
在建置測試集時,直接從向量庫抽樣取得 doc_id,確保一致性。 |
| 評分指標單一 | 只使用 ROUGE 可能忽略事實性錯誤 | 結合 LLM‑based fact‑checking(如 GPT‑4 判斷)或 FactScore,多指標加權。 |
最佳實踐總結
- 固定隨機種子:對於檢索向量化與 LLM 呼叫,都設定 deterministic 參數。
- 統一引用規範:Prompt 必須明確說明引用格式,並在後處理階段驗證。
- 多層次指標:同時追蹤 Retrieval、Citation、Answer,必要時加入 FactScore。
- 持續迭代測試集:隨著資料庫更新,定期抽樣新案例,避免過度擬合舊測試。
- 自動化報告:將評估結果寫入 JSON/CSV,結合 CI/CD 流程,讓每次模型升級都有可比較的基準。
實際應用場景
| 場景 | 為何需要 RAG Benchmark | 可能的改進方向 |
|---|---|---|
| 客服聊天機器人 | 客服需引用產品手冊、FAQ,避免錯誤資訊。 | 透過 Benchmark 調整檢索 top‑k、增強文檔切分策略,提高 Citation Score。 |
| 法律文件分析 | 法律問答必須精準引用條文、案例。 | 使用長文本向量化(如 text-embedding-3-large)與多輪檢索,並在 Benchmark 中加入 Citation‑Precision。 |
| 醫療知識庫查詢 | 醫師查詢必須根據最新臨床指南給出答案。 | 結合時間戳記過濾,Benchmark 中加入 Recency Score 以確保最新資訊被檢索。 |
| 研發文獻搜尋 | 研究人員需要快速定位相關論文並產出摘要。 | 在 Benchmark 中加入 Summarization Quality,使用 LLM 產生的摘要與原文比對。 |
| 企業內部搜尋 | 員工搜尋內部政策、報告,需要明確來源。 | 用 RAG Benchmark 監控內部向量庫的 Coverage,確保所有關鍵文件都有被檢索到的機會。 |
在這些場景中,持續的 RAG Benchmark 能夠幫助團隊量化改進效果,快速定位檢索或生成的瓶頸,最終提升使用者信任度與業務價值。
總結
- RAG Benchmarks 為評估結合檢索與生成的系統提供了 多維度、可操作 的指標,解決了僅靠傳統 NLG 評分無法捕捉事實性與證據依賴的問題。
- 在 LangChain 中,我們只需要:
- 建立測試集(包含正確
docId), - 設定
Retriever與 LLM, - 透過 Prompt 強制模型輸出引用,
- 使用簡易的 retrieval、citation、answer 計分函式,即可完成完整的基準測試。
- 建立測試集(包含正確
- 注意常見陷阱(檢索不穩、引用格式不統一、指標單一)並依照 最佳實踐(固定隨機種子、統一引用、結合多指標)調整,即可在 客服、法律、醫療、研發等 各種實務場景中快速驗證與優化 RAG 系統。
透過持續的 RAG Benchmark,開發者不僅能量化模型的事實性與證據引用能力,更能在迭代過程中快速定位瓶頸、證明改進成果,最終交付更可靠、更具信任感的 AI 服務。祝你在 LangChain 的 RAG 之路上跑出好成績! 🚀