大模型生成 QA Pairs 提升 RAG 应用测试效率的实践
一、RAG 应用测试的痛点
在测试大模型应用(如 RAG 系统)时,QA 对(Question-Answer Pairs)数据集是第一道防线。它用于验证模型的事实准确性、防范"幻觉"输出以及测试上下文理解能力。
手工生成 QA 对的瓶颈
手动构造 QA 对虽然精准,但当测试规模从数十对跃升到上千甚至上万时,会面临以下问题:
- 时间成本飙升:人工标注耗时严重
- 团队精力分散:重复劳动占用高阶测试活动时间
- 人为偏差风险:可能遗漏隐秘的模型痛点
- 多样性不足:难以覆盖复杂场景和边缘案例
自动生成 QA 对的价值
借助大模型批量生成 QA 对,能够实现:
- 基于现有文档或知识库自动产出多样化问答对
- 融入智能验证机制确保质量
- 将测试工程师从琐碎标注中解放出来
- 专注于更高阶的测试活动和问题诊断
二、整体架构:输入-生成-过滤-输出流水线
采用模块化流水线设计,针对中文 LLM 测试的痛点(事实准确性低、幻觉输出多、上下文理解偏差)提供高效解决方案。
文档输入 → 文本分块 → QA 生成 → 质量验证 → 过滤去重 → JSON 输出
核心设计原则
| 原则 | 实现方式 |
|---|---|
| 高效性 | 自动化分块和批量生成,无需人工干预 |
| 准确性 | 规则关键词匹配 + 语义相似度验证双保险 |
| 灵活性 | JSON 配置文件调整阈值和模型,适应不同场景 |
三、文档预处理与 QA 生成
文本分块策略
长文档直接喂入易导致上下文丢失或生成不准,采用滑动窗口算法分块:
python
from langchain.text_splitter import RecursiveCharacterTextSplitter
def _split_documents(self, docs: List[Document], chunk_size: int, chunk_overlap: int):
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=chunk_size, # 每块大小,默认 4000
chunk_overlap=chunk_overlap, # 重叠大小,默认 200
separators=["\n\n", "\n", "。", "!", "?"], # 中文友好分隔符
keep_separator=True
)
return text_splitter.split_documents(docs)
QA 生成核心逻辑
使用 LangChain LCEL 链实现稳健的 JSON 输出解析:
python
PROMPT_TEMPLATE = """
You are an expert assistant tasked with generating question-and-answer pairs from a given text.
Based on the following text, please generate a list of QA pairs.
The output should be a single, valid JSON object containing a single key "qa_pairs",
which holds a list of dictionaries. Each dictionary must have a "question" key and an "answer" key.
Do NOT output any other text, explanations, or markdown formatting.
Here is the text:
--- TEXT ---
{text}
--- END TEXT ---
JSON_OUTPUT:
"""
class QAGenerator:
def __init__(self, llm: Any):
self.llm = llm
prompt = ChatPromptTemplate.from_template(PROMPT_TEMPLATE)
parser = JsonOutputParser()
self.chain = prompt | self.llm | parser
def generate_from_document(self, doc_path: str) -> List[Dict[str, str]]:
docs = load_document(doc_path)
chunks = self._split_documents(docs, chunk_size=4000, chunk_overlap=200)
qa_pairs = []
for doc in chunks:
try:
result = self.chain.invoke({"text": doc.page_content})
if isinstance(result, dict) and "qa_pairs" in result:
qa_pairs.extend(result["qa_pairs"])
except Exception as e:
# 处理无法返回 JSON 的大模型异常
result = extract_json(e.llm_output)
if isinstance(result, dict) and "qa_pairs" in result:
qa_pairs.extend(result["qa_pairs"])
return qa_pairs
四、质量验证与过滤机制
生成的 QA 对需要经过多维度验证才能投入使用。
验证策略优先级
验证策略按优先级配置,互斥执行:
- 语义相似验证(最高优先级)
- 长度检查
- 关键词匹配验证
- 唯一性/去重验证
1. 语义相似验证
将源文档、问题和答案分别向量化,计算余弦相似度:
python
def _validate_similarity(self, doc_content: str, qa_pair: Dict[str, str]) -> bool:
threshold = self.config['similarity_threshold'] # 推荐 0.5
model_name = self.config['similarity_model'] # 推荐 'paraphrase-multilingual-MiniLM-L12-v2'
model = SentenceTransformer(model_name)
doc_embedding = model.encode(doc_content, convert_to_tensor=True)
q_embedding = model.encode(qa_pair['question'], convert_to_tensor=True)
a_embedding = model.encode(qa_pair['answer'], convert_to_tensor=True)
q_sim = util.cos_sim(doc_embedding, q_embedding).item()
a_sim = util.cos_sim(doc_embedding, a_embedding).item()
return q_sim > threshold and a_sim > threshold
2. 长度检查
确保问题和答案长度在合理范围内:
| 参数 | 推荐值 | 说明 |
|---|---|---|
| question_min_length | 5 | 问题最短长度 |
| question_max_length | 100 | 问题最长长度 |
| answer_min_length | 10 | 答案最短长度 |
| answer_max_length | 500 | 答案最长长度 |
3. 关键词匹配验证(中文优化)
使用 jieba 提取关键词,确保 QA 对包含原文核心信息:
python
import jieba
import jieba.analyse
def _extract_keywords_chinese(self, documents) -> list:
keywords = []
top_n = self.config['keyword_top_n'] # 推荐 10
for doc in documents:
doc_keywords = jieba.analyse.extract_tags(doc, topK=top_n, withWeight=True)
keywords.extend(doc_keywords)
return keywords
def _validate_keywords(self, doc_content: str, qa_pair: Dict[str, str]) -> bool:
keywords = self._extract_keywords_chinese([doc_content])
keyword_set = {word for word, _ in keywords}
question_words = set(jieba.lcut(qa_pair['question']))
answer_words = set(jieba.lcut(qa_pair['answer']))
question_matched = question_words.intersection(keyword_set)
answer_matched = answer_words.intersection(keyword_set)
return len(question_matched) > 0 and len(answer_matched) > 0
4. 唯一性验证(去重)
移除内容重复或语义高度相似的 QA 对,增加多样性:
python
def _validate_duplicates(self, qa_pairs: List[Dict[str, str]]) -> List[Dict[str, str]]:
threshold = self.config['uniqueness_distance_threshold'] # 推荐 0.1
model_name = self.config['similarity_model']
model = SentenceTransformer(model_name)
questions = [p['question'] for p in qa_pairs]
embeddings = model.encode(questions, convert_to_tensor=True, normalize_embeddings=True)
# 计算距离矩阵
distance_matrix = 1 - util.cos_sim(embeddings, embeddings).cpu().numpy()
distance_matrix = np.clip(distance_matrix, 0, None)
# 层次聚类
clustering = AgglomerativeClustering(
n_clusters=None,
distance_threshold=threshold,
metric='precomputed',
linkage='average'
).fit(distance_matrix)
# 每个聚类保留一个代表性 QA 对
unique_indices = []
seen_labels = set()
for i, label in enumerate(clustering.labels_):
if label not in seen_labels:
unique_indices.append(i)
seen_labels.add(label)
return [qa_pairs[i] for i in sorted(unique_indices)]
五、配置示例
通过 JSON 配置文件灵活选择验证策略:
json
{
"similarity_threshold": 0.5,
"similarity_model": "paraphrase-multilingual-MiniLM-L12-v2",
"question_min_length": 5,
"question_max_length": 100,
"answer_min_length": 10,
"answer_max_length": 500,
"keyword_top_n": 10,
"uniqueness_distance_threshold": 0.1,
"uniqueness_check_enabled": true
}
六、总结
借助大模型生成 QA Pairs 是提升 RAG 应用测试效率的有效手段。通过"输入-生成-过滤-输出"的模块化流水线,实现了:
- 自动化:从文档到测试数据集的全流程自动化
- 高质量:多维度验证机制确保 QA 对准确性
- 中文优化:针对中文场景的专门处理(jieba 分词、中文分隔符等)
- 灵活性:配置驱动,适应不同测试场景
这种方法不仅提升了效率,更是测试策略的升级------让测试工程师从琐碎标注中解放,专注于更高阶的测试设计和问题诊断。