RAG系列:系統評估 - 基于LLM-as-judge實現評估系統
引言
在 系統評估 - 五個主流評估指標詳解 中,我們了解了 RAG 系統評估的 5 個主流指標,它們分別是 上下文召回率(Context Recall)、上下文相關性(Context Relevance)、答案忠實度(Faithfulness)、答案相關性(Answer Relevance)以及答案正確性(Answer Correctness),也簡單了解了一些 RAG 系統的評估方法以及主流的評估系統。
今天我們將基于 LLM-as-judge 自己實現一套 RAG 系統評估系統,然后通過該評估系統評估我們在 基于 DeepSeek + Chroma + LangChain 開發一個簡單 RAG 系統 中搭建好的基礎版 RAG 系統,以基礎版 RAG 系統這 5 個評估指標值作為基準,通過學習不同的優化方法來提升這 5 個指標。
因為是通過 LLM 來評估,所以評估 LLM 的能力越強,理論上評估就會越準確,因此在實際的業務場景中,盡可能選用能力更強的 LLM。
為了學習方便,本文采用的評估 LLM 是 Ollama 部署的 qwen2.5 14b。
本文完整代碼地址[1]:https://github.com/laixiangran/ai-learn/blob/main/src/app/rag/03_rag_evaluator/route.ts
下面具體講解下每個指標評估器的代碼實現:
上下文召回率評估器實現
衡量檢索到的上下文是否覆蓋參考答案所需的所有關鍵信息,避免遺漏關鍵信息。取值在 0 到 1 之間,數值越高表示檢索到的上下文覆蓋越全面。計算公式:上下文召回率 = 上下文覆蓋的關鍵信息數量 / 參考答案中關鍵信息總數量。例如,參考答案需要 5 個關鍵信息,若檢索到的上下文覆蓋其中 4 個關鍵信息,則上下文召回率為 0.8。
將參考答案(referenceAnswer)拆分成多個句子/關鍵信息(referenceAnswerStatements)。
舉個例子:
{
"referenceAnswer": "2024年市場規模預計約488億人民幣,未來有望達到千億級別,顯示行業持續增長的潛力。",
"referenceAnswerStatements": [
"2024年市場規模預計約488億人民幣。",
"未來有望達到千億級別,顯示行業持續增長的潛力。"
]
}
代碼實現如下:
/**
* 將文本拆分為多個句子(關鍵信息)
* @param text 待拆分的文本
* @param evaluateLLM 拆分的 LLM
* @returns
*/
async function statementSplit(text, evaluateLLM) {
const prompt = `
你是一個語言專家,你的任務是將以下文本拆分為多個獨立的句子,每個句子獨立表達一個完整含義,同時保留原意的邏輯連貫性。
說明:
1. 嚴格按以下JSON格式返回:["句子1", "句子2", ...],不能輸出其他無關內容。
文本:
${text}
回答:
`;
const res = await evaluateLLM.invoke(prompt);
const data = formatToJson(res) || [];
console.log('statements: ', data);
return data;
}
逐個分析每個句子/關鍵信息(referenceAnswerStatements)是否可歸因于給定的上下文(retrievedContext)并計算上下文召回率(contextRecall)。
舉個例子:
{
"retrievedContext":[
"2016 年,美國教育部撥款40億\n美元用以計算機科學教育普及\n五年計劃。\n英國:滲透率9.3%\n2024 年,英國把編程納入5-12 歲\n少兒的必修課。\n芬蘭\n2016年,芬蘭將編程納入小學教學大綱",
...
],
"referenceAnswer":"2024年市場規模預計約488億人民幣,未來有望達到千億級別,顯示行業持續增長的潛力。",
"referenceAnswerStatements":[
"2024年市場規模預計約488億人民幣。",
"未來有望達到千億級別,顯示行業持續增長的潛力。"
]
"contextRecall":{
"score":1,
"data":[
{"score":1,"statement":"2024年市場規模預計約488億人民幣。"},
{
"score":1,
"statement":"未來有望達到千億級別,顯示行業持續增長的潛力。"
}
]
}
}
代碼實現如下:
/**
* 上下文召回率評估器。
* 實現步驟:
* 1. 將參考答案拆分成多個句子(關鍵信息);
* 2. 逐個分析每個句子(關鍵信息)是否可歸因于給定的上下文;
* 3. 根據每個句子(關鍵信息)的得分,計算上下文召回率。
* @param evaluateData 評估數據
* @param evaluateLLM 評估 LLM
* @returns
*/
asyncfunctioncontextRecallEvaluator(evaluateData, evaluateLLM) {
// retrievedContext 檢索到的上下文
// referenceAnswer 參考答案
// referenceAnswerStatements 參考答案拆分出的多個句子(關鍵信息)
const { retrievedContext, referenceAnswer, referenceAnswerStatements } =
evaluateData;
let newStatements = [];
if (!referenceAnswerStatements) {
newStatements = awaitstatementSplit(referenceAnswer, evaluateLLM);
} else {
newStatements = [...referenceAnswerStatements];
}
const allRes = [];
// 逐個分析每個句子(關鍵信息)是否可歸因于給定的上下文
while (newStatements.length > 0) {
const statement = newStatements.shift();
const prompt = `
你是一個語言專家,你的任務是分析句子是否可歸因于給定的上下文。
說明:
1. 如果句子不能歸因于上下文,則得分為0;
2. 如果句子能夠歸因于上下文,則得分為1;
3. 嚴格按以下JSON格式返回:{"score": "得分"},不能輸出其他無關內容。
句子:
${statement}
上下文:
${retrievedContext.join('\n')}
回答:
`;
const llmRes = await evaluateLLM.invoke(prompt);
const data = formatToJson(llmRes);
allRes.push({
score: data?.score ? +data.score : 0,
statement,
});
console.log('contextRecallEvaluator: ', allRes);
}
// 根據每個句子(關鍵信息)的得分,計算上下文召回率
const score =
allRes.reduce((score, cur) => {
score += +cur.score;
return score;
}, 0) / allRes.length;
return {
score,
data: allRes,
};
}
上下文相關性評估器實現
衡量檢索到的上下文與問題之間的相關性,避免包含無關冗余內容。取值在 0 到 1 之間,數值越高表示檢索到的上下文相關性越高。計算公式:上下文相關性 = 上下文中與問題相關的片段數量 / 上下文中片段總數量。例如,檢索到的上下文總共有 5 個片段,與問題相關的片段有 4 個,則上下文相關性為 0.8。
逐個分析每個上下文片段(retrievedContext)是否與問題(question)相關并計算上下文相關性(contextRelevance)。
舉個例子:
{
"question":"預計2024年少兒編程教育市場規模是多少?未來潛力如何?",
"retrievedContext":[
"2016 年,美國教育部撥款40億\n美元用以計算機科學教育普及\n五年計劃。\n英國:滲透率9.3%\n2024 年,英國把編程納入5-12 歲\n少兒的必修課。\n芬蘭\n2016年,芬蘭將編程納入小學教學大綱",
...
],
"contextRelevance":{
"score":0.7,
"data":[
{
"score":0,
"context":"2016 年,美國教育部撥款40億\n美元用以計算機科學教育普及\n五年計劃。\n英國:滲透率9.3%\n2024 年,英國把編程納入5-12 歲\n少兒的必修課。\n芬蘭\n2016年,芬蘭將編程納入小學教學大綱"
},
...
]
}
}
代碼實現如下:
/**
* 上下文相關性評估器
* 實現步驟:
* 1. 逐個分析每個上下文片段是否與問題相關;
* 2. 根據每個上下文片段的得分,計算上下文相關性。
* @param evaluateData 評估數據
* @param evaluateLLM 評估 LLM
* @returns
*/
asyncfunctioncontextRelevanceEvaluator(evaluateData, evaluateLLM) {
// question 問題
// retrievedContext 檢索到的上下文
const { question, retrievedContext } = evaluateData;
const newRetrievedContext = [...retrievedContext];
const allRes = [];
// 逐個分析每個上下文片段是否與問題相關
while (newRetrievedContext.length > 0) {
const context = newRetrievedContext.shift();
const prompt = `
你是一個語言專家,你的任務是確定上下文是否與問題有關。
說明:
1. 如果上下文與問題無關,則得分為0;
2. 如果上下文與問題有關,則得分為1;
3. 嚴格按以下JSON格式返回:{"score": "得分"},不能輸出其他無關內容。
問題:
${question}
上下文:
${context}
回答:
`;
const llmRes = await evaluateLLM.invoke(prompt);
const data = formatToJson(llmRes);
allRes.push({
score: data?.score ? +data.score : 0,
context,
});
console.log('contextRelevanceEvaluator: ', allRes);
}
// 根據每個上下文片段的得分,計算上下文相關性
const score =
allRes.reduce((score, cur) => {
score += +cur.score;
return score;
}, 0) / allRes.length;
return {
score,
data: allRes,
};
}
答案忠實度評估器實現
衡量實際答案是否嚴格基于檢索到的上下文,避免幻覺。取值在 0 到 1 之間,數值越高表示實際答案越嚴格基于檢索到的上下文。計算公式:答案忠實度 = 上下文能夠推斷出事實的數量 / 實際答案拆解出的事實總數量。例如,實際答案拆解出 5 個事實,若檢索到的上下文覆蓋其中 4 個事實,則答案忠實度為 0.8。
將實際答案(answer)拆分成多個句子(answerStatements)。
舉個例子:
{
"answer": "根據提供的上下文信息,2024年少兒編程教育市場的規模約為488億元人民幣。從長遠來看,隨著越來越多家長認同編程教育的重要性以及其逐漸滲透進入教學體系內,該市場有望在未來5到10年內發展成為一個千億級別的大市場。這表明少兒編程教育具有巨大的發展潛力和空間。",
"answerStatements": [
"2024年少兒編程教育市場的規模約為488億元人民幣。",
"從長遠來看,隨著越來越多家長認同編程教育的重要性以及其逐漸滲透進入教學體系內,該市場有望在未來5到10年內發展成為一個千億級別的大市場。",
"這表明少兒編程教育具有巨大的發展潛力和空間。"
]
}
代碼實現如下:
/**
* 將文本拆分為多個句子(關鍵信息)
* @param text 待拆分的文本
* @param evaluateLLM 拆分的 LLM
* @returns
*/
async function statementSplit(text, evaluateLLM) {
const prompt = `
你是一個語言專家,你的任務是將以下文本拆分為多個獨立的句子,每個句子獨立表達一個完整含義,同時保留原意的邏輯連貫性。
說明:
1. 嚴格按以下JSON格式返回:["句子1", "句子2", ...],不能輸出其他無關內容。
文本:
${text}
回答:
`;
const res = await evaluateLLM.invoke(prompt);
const data = formatToJson(res) || [];
console.log('statements: ', data);
return data;
}
逐個分析每個句子(answerStatements)是否可歸因于給定的上下文(retrievedContext)并計算答案忠實度(faithfulness)。
舉個例子:
{
"retrievedContext":[
"2016 年,美國教育部撥款40億\n美元用以計算機科學教育普及\n五年計劃。\n英國:滲透率9.3%\n2024 年,英國把編程納入5-12 歲\n少兒的必修課。\n芬蘭\n2016年,芬蘭將編程納入小學教學大綱",
...
],
"answer":"根據提供的上下文信息,2024年少兒編程教育市場的規模約為488億元人民幣。從長遠來看,隨著越來越多家長認同編程教育的重要性以及其逐漸滲透進入教學體系內,該市場有望在未來5到10年內發展成為一個千億級別的大市場。這表明少兒編程教育具有巨大的發展潛力和空間。",
"answerStatements":[
"2024年少兒編程教育市場的規模約為488億元人民幣。",
"從長遠來看,隨著越來越多家長認同編程教育的重要性以及其逐漸滲透進入教學體系內,該市場有望在未來5到10年內發展成為一個千億級別的大市場。",
"這表明少兒編程教育具有巨大的發展潛力和空間。"
],
"faithfulness":{
"score":1,
"data":[
{
"score":1,
"statement":"2024年少兒編程教育市場的規模約為488億元人民幣。"
},
{
"score":1,
"statement":"從長遠來看,隨著越來越多家長認同編程教育的重要性以及其逐漸滲透進入教學體系內,該市場有望在未來5到10年內發展成為一個千億級別的大市場。"
},
{
"score":1,
"statement":"這表明少兒編程教育具有巨大的發展潛力和空間。"
}
]
}
}
代碼實現如下:
/**
* 答案忠實度評估器
* 實現步驟:
* 1. 將實際答案拆分成多個句子(事實);
* 2. 逐個分析每個句子(事實)是否可歸因于給定的上下文;
* 3. 根據每個句子(事實)的得分,計算答案忠實度。
* @param evaluateData 評估數據
* @param evaluateLLM 評估 LLM
* @returns
*/
asyncfunctionfaithfulnessEvaluator(evaluateData, evaluateLLM) {
// retrievedContext 檢索到的上下文
// answer 實際答案
// answerStatements 實際答案拆分出的多個句子(事實)
const { retrievedContext, answer, answerStatements } = evaluateData;
let newStatements = [];
if (!answerStatements) {
newStatements = awaitstatementSplit(answer, evaluateLLM);
} else {
newStatements = [...answerStatements];
}
const allRes = [];
// 逐個分析每個句子(事實)是否可歸因于給定的上下文
while (newStatements.length > 0) {
const statement = newStatements.shift();
const prompt = `
你是一個語言專家,你的任務是分析句子是否可歸因于給定的上下文。
說明:
1. 如果句子不能歸因于上下文,則得分為0;
2. 如果句子能夠歸因于上下文,則得分為1;
3. 嚴格按以下JSON格式返回:{"score": "得分"},不能輸出其他無關內容。
句子:
${statement}
上下文:
${retrievedContext.join('\n')}
回答:
`;
const llmRes = await evaluateLLM.invoke(prompt);
const data = formatToJson(llmRes);
allRes.push({
score: data?.score ? +data.score : 0,
statement,
});
console.log('faithfulnessEvaluator: ', allRes);
}
// 根據每個句子(事實)的得分,計算答案忠實度
const score =
allRes.reduce((score, cur) => {
score += +cur.score;
return score;
}, 0) / allRes.length;
return {
score,
data: allRes,
};
}
答案相關性評估器實現
衡量實際答案是否直接完整回答用戶問題,排除冗余或跑題。取值在 0 到 1 之間,數值越高表示實際答案更直接完整回答用戶問題。計算公式:答案相關性 = 與實際問題相關的模擬問題數量 / 實際答案推導出的模擬問題總數量。例如,實際答案推導出 5 個模擬問題,若其中 4 個與實際問題相關,則答案相關性為 0.8。
根據實際答案(answer)推導出多個模擬問題(simulationQuestions)。
舉個例子:
{
"answer": "根據提供的上下文信息,2024年少兒編程教育市場的規模約為488億元人民幣。從長遠來看,隨著越來越多家長認同編程教育的重要性以及其逐漸滲透進入教學體系內,該市場有望在未來5到10年內發展成為一個千億級別的大市場。這表明少兒編程教育具有巨大的發展潛力和空間。",
"simulationQuestions": [
"2024年少兒編程教育市場的規模是多少?",
"未來五年到十年,少兒編程教育市場規模預計能達到多少?",
"為什么說少兒編程教育有巨大的發展空間?"
]
}
代碼實現如下:
/**
* 根據答案推導出多個模擬問題
* @param text
* @param evaluateLLM
* @returns
*/
async function simulationQuestion(text, evaluateLLM) {
const prompt = `
你是一個語言專家,你的任務是根據以下答案的核心內容來生成3個用戶可能問的問題。
說明:
1. 嚴格按以下JSON格式返回:["問題1", "問題2", ...],不能輸出其他無關內容。
答案:
${text}
回答:
`;
const res = await evaluateLLM.invoke(prompt);
const data = formatToJson(res) || [];
console.log('questions: ', data);
return data;
}
逐個分析每個模擬問題(simulationQuestions)是否與原問題(question)相似并計算答案相關性(answerRelevance)。
舉個例子:
{
"question":"預計2024年少兒編程教育市場規模是多少?未來潛力如何?",
"answer":"根據提供的上下文信息,2024年少兒編程教育市場的規模約為488億元人民幣。從長遠來看,隨著越來越多家長認同編程教育的重要性以及其逐漸滲透進入教學體系內,該市場有望在未來5到10年內發展成為一個千億級別的大市場。這表明少兒編程教育具有巨大的發展潛力和空間。",
"simulationQuestions":[
"2024年少兒編程教育市場的規模是多少?",
"未來五年到十年,少兒編程教育市場規模預計能達到多少?",
"為什么說少兒編程教育有巨大的發展空間?"
],
"answerRelevance":{
"score":1,
"data":[
{
"score":1,
"simulationQuestion":"2024年少兒編程教育市場的規模是多少?"
},
{
"score":1,
"simulationQuestion":"未來五年到十年,少兒編程教育市場規模預計能達到多少?"
},
{
"score":1,
"simulationQuestion":"為什么說少兒編程教育有巨大的發展空間?"
}
]
}
}
代碼實現如下:
/**
* 答案相關性評估器
* 實現步驟:
* 1. 根據實際答案推導出多個模擬問題;
* 2. 逐個分析每個模擬問題是否與原問題相似;
* 3. 根據每個模擬問題的得分,計算答案相關性。
* @param evaluateData 評估數據
* @param evaluateLLM 評估 LLM
* @returns
*/
asyncfunctionanswerRelevanceEvaluator(evaluateData, evaluateLLM) {
// question 問題
// answer 實際答案
// simulationQuestions 根據實際答案推導出的多個模擬問題
const { question, answer, simulationQuestions } = evaluateData;
let newSimulationQuestions = [];
if (!simulationQuestions) {
newSimulationQuestions = awaitsimulationQuestion(answer, evaluateLLM);
} else {
newSimulationQuestions = [...simulationQuestions];
}
const allRes = [];
// 逐個分析每個模擬問題是否與原問題相似
while (newSimulationQuestions.length > 0) {
const simulationQuestion = newSimulationQuestions.shift();
const prompt = `
你是一個語言專家,你的任務是分析模擬問題和實際問題是否相似。
說明:
1. 如果模擬問題與實際問題不相似,則得分為0;
2. 如果模擬問題與實際問題相似,則得分為1;
3. 嚴格按以下JSON格式返回:{"score": "相似度"},不能輸出其他無關內容。
模擬問題:
${simulationQuestion}
實際問題:
${question}
回答:
`;
const llmRes = await evaluateLLM.invoke(prompt);
const data = formatToJson(llmRes);
allRes.push({
score: data?.score ? +data.score : 0,
simulationQuestion,
});
console.log('answerRelevanceEvaluator: ', allRes);
}
// 根據每個模擬問題的得分,計算答案相關性
const score =
allRes.reduce((score, cur) => {
score += +cur.score;
return score;
}, 0) / allRes.length;
return {
score,
data: allRes,
};
}
答案正確性評估器實現
衡量實際答案的準確性,需與參考答案對比。取值在 0 到 1 之間,數值越高表示實際答案與參考答案匹配度越高,準確性也就越高。計算公式:答案準確性 = 實際答案覆蓋的關鍵信息數量 / 參考答案中關鍵信息總數量。例如,參考答案需要 5 個關鍵信息,若實際答案覆蓋其中 4 個關鍵信息,則答案正確性為 0.8。
將參考答案(referenceAnswer)拆分成多個句子/關鍵信息(referenceAnswerStatements)。
舉個例子:
{
"referenceAnswer": "2024年市場規模預計約488億人民幣,未來有望達到千億級別,顯示行業持續增長的潛力。",
"referenceAnswerStatements": [
"2024年市場規模預計約488億人民幣。",
"未來有望達到千億級別,顯示行業持續增長的潛力。"
]
}
代碼實現如下:
/**
* 將文本拆分為多個句子(關鍵信息)
* @param text 待拆分的文本
* @param evaluateLLM 拆分的 LLM
* @returns
*/
async function statementSplit(text, evaluateLLM) {
const prompt = `
你是一個語言專家,你的任務是將以下文本拆分為多個獨立的句子,每個句子獨立表達一個完整含義,同時保留原意的邏輯連貫性。
說明:
1. 嚴格按以下JSON格式返回:["句子1", "句子2", ...],不能輸出其他無關內容。
文本:
${text}
回答:
`;
const res = await evaluateLLM.invoke(prompt);
const data = formatToJson(res) || [];
console.log('statements: ', data);
return data;
}
逐個分析每個句子/關鍵信息(referenceAnswerStatements)是否可歸因于給定的實際答案(question)并計算答案正確性(answerCorrectness)。
舉個例子:
{
"question":"預計2024年少兒編程教育市場規模是多少?未來潛力如何?",
"referenceAnswer":"2024年市場規模預計約488億人民幣,未來有望達到千億級別,顯示行業持續增長的潛力。",
"referenceAnswerStatements":[
"2024年市場規模預計約488億人民幣。",
"未來有望達到千億級別,顯示行業持續增長的潛力。"
]
"answerCorrectness":{
"score":1,
"data":[
{"score":1,"statement":"2024年市場規模預計約488億人民幣。"},
{
"score":1,
"statement":"未來有望達到千億級別,顯示行業持續增長的潛力。"
}
]
}
}
代碼實現如下:
/**
* 答案正確性評估器
* 實現步驟:
* 1. 將參考答案拆分成多個句子(關鍵信息);
* 2. 逐個分析每個句子(關鍵信息)是否可歸因于給定的實際答案;
* 3. 根據每個句子(關鍵信息)的得分,計算答案正確性。
* @param evaluateData 評估數據
* @param evaluateLLM 評估 LLM
* @returns
*/
asyncfunctionanswerCorrectnessEvaluator(evaluateData, evaluateLLM) {
// answer 實際答案
// referenceAnswer 參考答案
// referenceAnswerStatements 參考答案拆分出的多個句子(關鍵信息)
const { answer, referenceAnswer, referenceAnswerStatements } = evaluateData;
let newStatements = [];
if (!referenceAnswerStatements) {
newStatements = awaitstatementSplit(referenceAnswer, evaluateLLM);
} else {
newStatements = [...referenceAnswerStatements];
}
const allRes = [];
// 逐個分析每個句子(關鍵信息)是否可歸因于給定的實際答案
while (newStatements.length > 0) {
const statement = newStatements.shift();
const prompt = `
你是一個語言專家,你的任務是分析句子是否可歸因于給定的實際答案。
說明:
1. 如果句子不能歸因于實際答案,則得分為0;
2. 如果句子能夠歸因于實際答案,則得分為1;
3. 嚴格按以下JSON格式返回:{"score": "得分"},不能輸出其他無關內容。
句子:
${statement}
實際答案:
${answer}
回答:
`;
const llmRes = await evaluateLLM.invoke(prompt);
const data = formatToJson(llmRes);
allRes.push({
score: data?.score ? +data.score : 0,
statement,
});
console.log('answerCorrectnessEvaluator: ', allRes);
}
// 根據每個句子(關鍵信息)的得分,計算答案正確性
const score =
allRes.reduce((score, cur) => {
score += +cur.score;
return score;
}, 0) / allRes.length;
return {
score,
data: allRes,
};
}
基礎版 RAG 系統評估
至此,我們基于 LLM-as-judge 自己實現了一套 RAG 系統評估系統,下面我們通過該評估系統來評估基礎版 RAG 系統。
以下每一步的詳細代碼就不貼了,評估詳細代碼地址[2]:https://github.com/laixiangran/ai-learn/blob/main/src/app/rag/03_rag_evaluator/route.ts
準備 QA 測試數據
這里我準備了 20 個 測試 QA(含參考答案),QA 測試數據文件地址[3]:https://github.com/laixiangran/ai-learn/blob/main/src/app/data/qa_test_20.json
[
...
{
"question": "預計2024年少兒編程教育市場規模是多少?未來潛力如何?",
"referenceAnswer": "2024年市場規模預計約488億人民幣,未來有望達到千億級別,顯示行業持續增長的潛力。"
},
...
]
知識庫構建
文檔切分配置:
- 用于分割文本的字符或字符串(separator):["\n\n", "\n", " ", ""];
- 每個文本塊的最大字符數(chunk_size):500;
- 文本塊之間的重疊字符數(chunk_overlap):50。
Embedding 模型:
- nomic-embed-text。
代碼實現如下:
// 1. 文件解析
const docs = awaitloadPdf(
'src/app/data/2024少兒編程教育行業發展趨勢報告.pdf'
);
// 2. 文件切分
const texts = awaitsplitDocuments(docs);
// 3. 初始化向量模型和向量數據庫,并將文檔存儲到向量數據庫
awaitaddDocuments(texts);
指標評估
檢索上下文 TopK: 3;
評估 LLM: qwen2.5:14b(Ollama 部署)。
代碼實現如下:
// 單個指標評估
asyncfunctionbathEvaluator(indexName: string) {
const evaluatorMap = {
contextRecall: contextRecallEvaluator,
contextRelevance: contextRelevanceEvaluator,
faithfulness: faithfulnessEvaluator,
answerRelevance: answerRelevanceEvaluator,
answerCorrectness: answerCorrectnessEvaluator,
};
const evaluator = evaluatorMap[indexName];
const inputPath = 'src/app/data/qa_test_20.json';
const outputPath = 'src/app/data/qa_test_20_base_evaluate.json';
const qaDatas = awaitreadJsonFile(inputPath);
const llm = initOllamaLLM('qwen2.5:14b');
const res = [];
while (qaDatas.length > 0) {
const data = qaDatas.shift();
if (!data.answer) {
const { retrievedContext, answer } = awaitllmAnswerByQaData(
data.question
);
data.retrievedContext = retrievedContext;
data.answer = answer;
}
const evaluateRes = awaitevaluator(data, llm);
res.push({
...data,
[indexName]: evaluateRes,
});
}
// 將 LLM 回答結果保存到文件中
awaitsaveJsonFile(JSON.stringify(res), outputPath);
// 計算最終指標數據
const score =
res.reduce((score, cur) => {
score += cur[indexName].score;
return score;
}, 0) / res.length;
return { [indexName]: +score.toFixed(1) };
}
// 指標評估
const indexs = [
'contextRecall',
'contextRelevance',
'faithfulness',
'answerRelevance',
'answerCorrectness',
];
constdata: any = {};
while (indexs.length > 0) {
const indexName = indexs.shift() || '';
const indexRes = awaitbathEvaluator(indexName);
data[indexName] = indexRes[indexName];
}
基礎版 RAG 系統(V1.0)各指標的評估結果如下:
版本 | 優化描述 | 上下文召回率(contextRecall) | 上下文相關性(contextRelevance) | 答案忠實度(faithfulness) | 答案相關性(answerRelevance) | 答案正確性(answerCorrectness) |
基礎 RAG 系統(V1.0) | 無 | 0.6 | 0.5 | 0.8 | 0.5 | 0.6 |
結語
至此,我們基于 LLM-as-judge 自己實現一套 RAG 系統評估系統,用該評估系統評估了基礎版 RAG 系統(V1.0)的 5 個評估指標的表現,可以看到基礎版 RAG 系統(V1.0)這 5 個指標的值都是偏低的,所以后面我將通過講解不同的優化方法來提升基礎版 RAG 系統(V1.0)這 5 個指標,敬請期待。
引用鏈接
[1]
本文完整代碼地址: https://github.com/laixiangran/ai-learn/blob/main/src/app/rag/03_rag_evaluator/route.ts
[2]
評估詳細代碼地址: https://github.com/laixiangran/ai-learn/blob/main/src/app/rag/03_rag_evaluator/route.ts
[3]
QA 測試數據文件地址: https://github.com/laixiangran/ai-learn/blob/main/src/app/data/qa_test_20.json