返回博客
·AI技术

RAG 系统为什么检索不到答案?一个被忽略的细节

RAG 检索失败不一定是 embedding 的问题,更可能是你忽略了 chunk 边界的语义断裂。

#RAG#LLM#Embedding#检索

背景

我们在做一个内部知识库问答系统,用的 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 模型。检查一下你的分块策略和元数据保留情况,这两个地方往往是性价比最高的优化点。