🧠 图解记忆:先找正确证据在哪一层丢失,再修改对应环节;点击图片可查看原图。
💡 答案要点
问题分析:
召回率低 = 相关文档没检索到
可能原因:
1. 分块策略不合理
2. Embedding模型不适配
3. 只用单一检索方式
4. Query表达不清晰诊断步骤:
Step 1: 建立评估数据集
python
# 构建100条标准问答对
test_dataset = [
{
"question": "员工请假超过3天需要什么审批?",
"ground_truth_docs": ["doc_123", "doc_456"], # 应该检索到的文档ID
"ideal_answer": "超过3天需要部门经理+HR审批"
},
# ... 100条
]
# 计算Recall@K
def evaluate_recall(retriever, dataset, k=5):
total_recall = 0
for item in dataset:
retrieved = retriever.search(item["question"], k=k)
retrieved_ids = [doc.id for doc in retrieved]
# 计算召回了多少ground_truth
hits = len(set(retrieved_ids) & set(item["ground_truth_docs"]))
recall = hits / len(item["ground_truth_docs"])
total_recall += recall
return total_recall / len(dataset)
# 基线: Recall@5 = 0.68Step 2: 逐项优化
(1) 优化分块 (+11% Recall)
python
# Before: 固定长度
splitter_old = RecursiveCharacterTextSplitter(chunk_size=512)
# After: 语义分块 + 元数据
from langchain.text_splitter import MarkdownTextSplitter
splitter_new = MarkdownTextSplitter(
chunk_size=800,
chunk_overlap=200,
# 保留标题层级
add_start_index=True
)
# 添加元数据
for chunk in chunks:
chunk.metadata = {
"source": doc.source,
"title": extract_title(chunk),
"section": extract_section(chunk),
"page": chunk.page
}
# Recall: 0.68 → 0.79 (+11%)(2) 混合检索 (+8% Recall)
python
# Before: 只用向量检索
vector_results = vector_db.search(query)
# After: BM25 + Vector + RRF
def hybrid_retrieval(query, k=20):
# BM25擅长关键词
bm25_results = bm25.search(query, k=k)
# Vector擅长语义
vector_results = vector_db.search(
embed(query), k=k
)
# RRF融合
return rrf_fusion([bm25_results, vector_results], k=60)
# Recall: 0.79 → 0.87 (+8%)(3) Query改写 (+5% Recall)
展开 Python 代码示例(33 行)
python
# Before: 直接用用户原始问题
query = "请假流程" # 太简短,语义不明
# After: LLM扩展query
def expand_query(query):
prompt = f"""
原始问题: {query}
请生成3个相关的扩展查询,帮助检索:
1. 同义词替换
2. 补充上下文
3. 细化问题
示例:
原始: "请假流程"
扩展:
1. "员工请假审批流程"
2. "如何申请休假"
3. "请假需要哪些步骤"
"""
expanded = llm.generate(prompt)
return [query] + expanded # 返回原始+扩展
# 用多个query检索,合并结果
all_results = []
for q in expand_query(query):
all_results.extend(hybrid_retrieval(q, k=10))
# 去重+排序
final = deduplicate_and_rerank(all_results)
# Recall: 0.87 → 0.92 (+5%)(4) Rerank精排 (+3% 保持top-5质量)
python
from sentence_transformers import CrossEncoder
reranker = CrossEncoder('BAAI/bge-reranker-large')
# 先召回20个候选
candidates = hybrid_retrieval(query, k=20)
# Rerank精选top-5
scores = reranker.predict([
[query, doc.content] for doc in candidates
])
top5 = sorted(
zip(candidates, scores),
key=lambda x: x[1],
reverse=True
)[:5]
# Precision@5: 0.73 → 0.89 (+16%)最终效果:
优化路径:
基线 (0.68)
→ 语义分块 (0.79, +11%)
→ 混合检索 (0.87, +8%)
→ Query扩展 (0.92, +5%)
→ Rerank (精确率+16%)
总提升: Recall@5 从 68% → 92% (+24个百分点)成本分析:
优化成本:
- 语义分块: 0 (只是代码改动)
- 混合检索: +100ms延迟, $0成本
- Query扩展: +$0.002/次 (LLM调用)
- Rerank: +200ms延迟, $0成本
投入产出比: 极高面试话术:
"召回率低我先建立评估数据集量化问题,然后逐项优化:语义分块+11%,混合检索+8%,Query扩展+5%,Rerank保证精确率。最终Recall@5从68%提升到92%,代价只是+300ms延迟和每次$0.002的Query扩展成本。"
