返回博客
·AI技术

别再给 AI 画饼了:一个 RAG 踩坑一年的血泪总结

RAG 系统从搭建到上线,半年调 chunk 半年治幻觉。本文总结了一个 RAG 项目的真实踩坑经验。

#RAG#LLM#Embedding#检索增强

# 别再给 AI 画饼了:一个 RAG 踩坑一年的血泪总结

去年开始搞 RAG 项目,我以为就是把文档丢进向量库,写个 retrieval 然后丢给 LLM。结果呢?半年时间在调 chunk size,又半年在治 hallucination。

这篇文章把踩过的坑都摊开,希望能让后来者少掉几根头发。

Chunk 大小:没有银弹,只有权衡

这是 RAG 里最大的玄学。

文档分割太细,上下文丢失严重——你切到单句级别,语义完整性直接崩盘。切太粗,噪声太多,embedding 质量暴跌。

我们初期用 500 token 的固定 chunk,召回率看着还行,但 answer 质量差得离谱。LLM 拿到一堆碎片信息,根本拼不出完整逻辑。后来换到 1000 token + 重叠 100 token,情况才好转。

但 1000 也不是万能值。对于技术文档,代码块和自然语言混在一起,固定 chunk 会把代码截断在中间,embedding 完全失效。

**我的解法:语义感知分割**

import re

def smart_split(text, max_tokens=1000):

# 先按代码块分割

code_pattern = r'(``[\s\S]*?``)'

parts = re.split(code_pattern, text)

chunks = []

for part in parts:

if part.startswith('```'):

# 代码块整体保留,不分割

chunks.append(part)

else:

# 自然语言按段落分割

paragraphs = re.split(r'\n\n+', part)

current = ""

for para in paragraphs:

if len(current) + len(para) > max_tokens * 3:

chunks.append(current)

current = para

else:

current += para + "\n\n"

if current:

chunks.append(current)

return chunks

代码块单独处理是关键。代码的语义边界就是 `` 标记,不要动它。自然语言部分按段落切,比按固定长度切靠谱得多。

Embedding 模型选型:越大越好?

刚开始我用 text-embedding-3-small,成本低,响应快。召回率也就那样,但能接受。

后来项目要求提高,换到了 text-embedding-3-large。效果确实提升明显,但成本翻了 4 倍,延迟也上来了。

中间还试过 Cohere 的 embed-english-v3.0,在特定领域(技术文档)的召回率比 OpenAI 的还好一些。

**建议:先用 small 跑通 pipeline,确认瓶颈在召回质量再换模型。**

很多人一上来就砸大模型,结果发现瓶颈根本不在 embedding 质量,而在 chunk 策略或者检索逻辑。

混合检索:BM25 + 向量,才是正道

纯向量检索有个致命问题:对精确匹配无能为力。

比如你搜 "TypeError: undefined is not a function",向量库可能给你召回一堆语义相近但关键词不匹配的结果。而 BM25 对这种精确关键词匹配很敏感。

我们把两种检索结果融合,用 Reciprocal Rank Fusion(RRF)合并:

def rrf_fuse(vector_results, bm25_results, k=60):

"""Reciprocal Rank Fusion 合并"""

scores = {}

for i, doc in enumerate(vector_results):

doc_id = doc['id']

scores[doc_id] = scores.get(doc_id, 0) + 1 / (k + i + 1)

for i, doc in enumerate(bm25_results):

doc_id = doc['id']

scores[doc_id] = scores.get(doc_id, 0) + 1 / (k + i + 1)

# 按融合分排序

return sorted(scores.items(), key=lambda x: x[1], reverse=True)

RRF 的好处是不需要调权重参数,两种检索结果天然融合。实测下来,混合检索的 NDCG@10 比纯向量检索高了 15-20%。

提示词里的 RAG 套路

检索到文档后,怎么让 LLM 正确使用这些信息才是关键。

一个经常被忽视的细节:**让 LLM 说出它的信息来源**。

根据以下参考资料回答问题。如果资料中没有相关信息,

请明确说明"资料中未找到",不要编造答案。

回答时请标注信息来源的文档编号。

参考资料:

{context}

问题:{question}

这段 prompt 做了两件事:

  • 明确告诉 LLM 不要 hallucinate
  • 2. 要求标注来源,方便后续追溯

    我们的经验是,加上"标注来源"这个要求后,LLM 的幻觉率明显下降。因为它知道自己要 accountability,会更谨慎。

    最后说几点踩坑体会

  • **先做评估再优化**。用一套固定测试集评估你的 RAG pipeline,没有基准就是瞎折腾。
  • 2. **不要迷信重排序**。Reranker 确实有效,但成本也高。先确保基础检索质量好,再加 reranker 事半功倍。

    3. **监控比调优更重要**。记录每次检索的召回结果、LLM 的回答质量,建立反馈闭环。没有监控的 RAG 就是在黑盒里瞎猜。

    4. **chunk 策略要适配场景**。技术文档、产品手册、FAQ,每种内容的最佳 chunk 策略都不一样。别指望一套参数打天下。

    RAG 不是一个"装好就能用"的系统,它需要持续迭代。但只要你踩够足够的坑,它确实能work。