RAG系列:系統評估 - 構造QA測試數據集
引言
在 RAG系列:基于 DeepSeek + Chroma + LangChain 開發一個簡單 RAG 系統 中,我們搭建了一個基礎版的 RAG 系統,實現了文檔解析和切分 -> 文檔向量化存儲 -> 用戶輸入問題 -> 根據問題檢索相關知識 -> 將檢索到的知識和原問題重新組合成 prompt -> 最后 LLM 根據增強后的 prompt 給出答案。
構造一個基礎版的 RAG 系統是非常簡單的,借助 LangChain 等框架可快速搭建出完整流程,代碼也不會很多,但基礎版的問答效果往往比較差,無法直接在實際業務中應用。
在 RAG系列:一文讓你由淺到深搞懂RAG實現 中,我們將 RAG 系統主要分為問題理解、檢索召回以及答案生成這三個模塊。
要做出一個問答效果更好的 RAG 系統,往往需要結合實際業務場景對這三個模塊進行獨立優化。
下面這張圖是 OpenAI 介紹的 RAG 優化經驗,這個準確率隨不同的數據集會有不同,但基本上優化后的準確率比優化前有顯著提升這個基本上是一致的。
RAG 系統的問答效果是好還是壞是需要經過評估的,而評估就需要高質量的 QA 測試數據集,所謂的 QA 測試數據集,就是一份包含“問題-回答”的測試數據集合。部分系統(如客服系統)是有這方面數據的,但絕大多數情況下是沒有的,這時就需要首先構造一批問答數據,這是后續對每一步優化進行驗證的基礎。
本文將會使用多鯨教育研究院所發布的《2024少兒編程教育行業發展趨勢報告》作為文檔來構造 QA 測試數據集 ,后續也會圍繞針對這個文檔的問答效果優化展開。
本文使用通過 ollama 部署 qwen2.5:14b 模型來對《2024少兒編程教育行業發展趨勢報告》這個文檔進行 QA 測試數據集的構造和質檢。
QA 測試數據集構造包含如下3個步驟:
- 文檔解析與切分:這部分就是對文檔進行解析和切分;
- 文檔片段 QA 構造:這部分就是讓 LLM 根據文檔片段構造 QA;
- QA 質量打分:這部分就是讓 LLM 再次對構造的 QA 進行質量評估。
QA 測試數據集已經構造好放在代碼庫里,大家可以直接使用:
https://github.com/laixiangran/ai-learn/blob/main/src/app/data/qa\_test.xlsx
本文的代碼地址:
https://github.com/laixiangran/ai-learn/blob/main/src/app/rag/02\_qa\_extraction/route.ts
下面分步驟講解下代碼實現:
文檔解析和切分
這一步做了一個巧妙的設計,就是將每個文檔生成一個 uuid,這個 uuid 后續會作為元數據存到向量數據庫中,也會和 QA 測試數據集綁定在一起,這樣后續在驗證檢索效果的時候,就可以知道這個問題是否檢索到正確的哪個文檔。
// 文件解析
const loader = newPDFLoader(
'src/app/data/2024少兒編程教育行業發展趨勢報告.pdf',
{
splitPages: false,
}
);
const docs = await loader.load();
// 文件切分
const textSplitter = newRecursiveCharacterTextSplitter({
chunkSize: 500,
chunkOverlap: 50,
});
const texts = await textSplitter.splitDocuments(docs);
texts.forEach((text) => {
// 給每個文檔生成uuid
text.metadata.uuid = uuidv4();
});
// 文檔持久化
const baseTextPath = 'src/app/data/doc_base.xlsx';
awaitsaveToExcel(
texts.map((text) => {
return {
doc: JSON.stringify(text),
};
}),
baseTextPath
);
LLM 進行 QA 構造
這一步通過 LLM 進行 QA 構造,然后將構造好的 QA 與原始文檔和原始文檔 uuid 進行綁定。
// 讀取切分后的文檔
const baseTextData = awaitreadFromExcel(baseTextPath);
baseTextData.forEach((text) => {
text.doc = JSON.parse(text.doc);
});
// QA 數據集抽取
const allData = [];
const errorData = [];
const baseQaPath = `src/app/data/qa_base.xlsx`;
const errorTextPath = `src/app/data/doc_error.xlsx`;
// 本地資源有限,因此單個處理完再處理下一個
while (baseTextData.length > 0) {
const baseText = baseTextData[0];
try {
const { pageContent: document, metadata } = baseText.doc;
const prompt = `
我會給你一段文本,你需要閱讀這段文本,分別針對這段文本生成8個問題、用戶回答這個問題的上下文,和基于上下文對問題的回答。
說明:
1. 問題要與這段文本相關,不要詢問類似“這個問題的答案在哪一章”這樣的問題;
2. 上下文必須與原始文本的內容保持一致,不要進行縮寫、擴寫、改寫、摘要、替換詞語等;
3. 答案請保持完整且簡潔,無須重復問題。答案要能夠獨立回答問題,而不是引用現有的章節、頁碼等;
4. 返回結果以JSON形式組織,格式為[{"question": "...", "context": ..., "answer": "..."}, ...];
5. 如果當前文本主要是目錄,或者是一些人名、地址、電子郵箱等沒有辦法生成有意義的問題時,可以返回[]。
文本:
${document}
回答:
`;
const ollamaLLM = initOllamaLLM();
const res = await ollamaLLM.invoke(prompt);
const regex = newRegExp('\\[(.*?)\\]', 's');
const match = res.content.match(regex);
if (match) {
const data = JSON.parse(`[${match[1]}]`);
data.forEach((item) => {
item.doc = document; // 保留原始文檔
item.uuid = metadata.uuid; // 保留原始文檔 uuid
});
allData.push(data);
// 保存QA數據集
awaitsaveToExcel(allData.flat(), baseQaPath);
baseTextData.shift();
}
} catch (error) {
console.log('error', error);
errorData.push({ doc: JSON.stringify(baseText.doc) });
awaitsaveToExcel(errorData, errorTextPath);
baseTextData.shift();
}
}
QA 構造樣例如下:
LLM 進行 QA 質檢
這一步通過 LLM 進行 QA 質量檢查,給每個問題和答案進行打分(1-5分),并給出打分的理由。
// QA 數據集質量檢查
const checkQaPath = 'src/app/data/qa_grade.xlsx';
const baseQaData = awaitreadFromExcel(baseQaPath);
const allQas = [];
while (baseQaData.length > 0) {
const baseQa = baseQaData[0];
const prompt = `
你是一個優秀的教師,你的任務是根據問題和參考答案來進行打分。
說明:
1. 你需要根據所出的問題以及參考答案進行打分,并給出打分理由,分值是一個int類型的值,取值范圍為1-5;
2. 好的問題,應該是詢問事實、觀點等,而不是類似于“這一段描述了什么”;
3. 好的答案,應該能夠直接回答問題,而不是給出在原文中的引用,例如“在第3頁中”等;
4. 結果請以JSON形式組織,格式為如下:{"score": ..., "reason": ...}。
問題:
${baseQa.question}
參考答案:
${baseQa.answer}
請打分:
`;
const ollamaLLM = initOllamaLLM();
const res = await ollamaLLM.invoke(prompt);
const regex = newRegExp('\\{(.*?)\\}', 's');
const match = res.content.match(regex);
if (match) {
const data = JSON.parse(`{${match[1]}}`);
// 保存QA數據集
allQas.push({ ...baseQa, ...data });
awaitsaveToExcel(allQas, checkQaPath);
}
baseQaData.shift();
}
QA 質檢樣例如下:
QA 測試數據集構造
這一步從大于 3 分的記錄中隨機挑選 100 條數據作為后續的 QA 測試數據集。
// 篩選出分數大于3的記錄并分為測試數據集和訓練集
const data = awaitreadFromExcel(checkQaPath);
const testData = [];
const trainData = [];
data.forEach((item, i: number) => {
if (item.score > 3) {
if (i % 2 === 0 && testData.length < 100) {
testData.push(item);
} else {
trainData.push(item);
}
}
});
saveToExcel(testData, 'src/app/data/qa_test.xlsx');
saveToExcel(trainData, 'src/app/data/qa_train.xlsx');
QA 測試數據集樣例如下:
至此,QA 測試數據集就構造完成,后續 RAG 系統的全鏈路效果以及各個模塊的優化效果,都將基于這個 QA 測試數據集進行。