RAG 系统为什么检索不到答案?一个被忽略的细节
RAG 检索失败不一定是 embedding 的问题,更可能是你忽略了 chunk 边界的语义断裂。
背景
我们在做一个内部知识库问答系统,用的 RAG 架构:文档分块 → embedding → 向量检索 → LLM 生成答案。
上线两周,用户反馈率很高——很多问题答非所问,或者直接说"不知道"。
我们的第一反应是:embedding 模型不行?换 model?
调了三种模型,效果提升不超过 5%。后来冷静下来重新看检索日志,才发现真正的问题不在 embedding 本身。
问题:Chunk 边界的语义断裂
假设你的文档里有这样一段:
> "2024年Q3,公司营收达到1.2亿美元,同比增长18%。其中海外市场贡献了4200万美元,主要来自东南亚地区。东南亚市场的增长驱动力是..."
如果我们用固定 500 字符来分块,很可能在"东南亚地区的。"这里切断,下一块从"东南亚市场的增长驱动力是"开始。
问题在于:检索时用户问"Q3营收主要增长来源是哪里?"
embedding 会把 query 和 chunk1 的向量对齐(提到了 Q3、营收、增长),但 chunk1 里没有完整答案——它被切断了。chunk2 有答案但缺少 Q3 的上下文,语义上关联度不够高。
结果:检索回来的 chunk 要么没答案,要么答案不完整。
解决方案:语义感知的分块
我们后来改成基于段落的分块,同时保留上下文重叠:
import re
def semantic_chunk(text, chunk_size=400, overlap=80):
paragraphs = re.split(r'\n\s*\n', text)
chunks = []
current = ""
for para in paragraphs:
para = para.strip()
if not para:
continue
if len(current) + len(para) < chunk_size:
current += para + "\n\n"
else:
if current:
chunks.append(current.strip())
current = para + "\n\n"
if current:
chunks.append(current.strip())
# 添加重叠
overlapped = []
for i, chunk in enumerate(chunks):
if i > 0:
prev_tail = chunks[i-1][-overlap:]
chunk = prev_tail + "\n\n" + chunk
overlapped.append(chunk)
return overlapped
同时,检索时做 query expansion——把用户问题扩展成多个角度:
# 用 LLM 把问题扩展成 3 个相关查询
expanded_queries = llm_chat(
f"围绕这个问题给出3个不同的检索角度:{user_question}",
max_tokens=200
)
# 对每个查询分别检索,合并结果去重
results = [retrieve(q) for q in [user_question] + expanded_queries]
另一个坑:metadata 丢失
很多 RAG 框架在分块时会丢弃原文档的元数据(章节标题、作者、日期)。检索回来的 chunk 没有任何上下文,LLM 即使拿到了相关内容,也无法判断时效性。
我们在 chunk 里加了一行前缀:
[来源: 产品手册 v2.3 | 章节: 计费规则 | 更新: 2025-03-15]
原始内容...
效果立竿见影——LLM 能基于元数据判断内容是否过时,回答准确度提升了约 20%。
总结
RAG 检索效果差,先别急着换 embedding 模型。检查一下你的分块策略和元数据保留情况,这两个地方往往是性价比最高的优化点。