返回博客
·AI技术

我在 RAG 系统里栽过的三个坑,希望你现在还来得及避开

从 POC 85% 跌到生产 42% 的 RAG 系统,chunk 策略、embedding 模型和元数据过滤三个关键坑的真实踩坑经验。

#RAG#Embedding#LangChain#ChromaDB

# 我在 RAG 系统里栽过的三个坑,希望你现在还来得及避开

去年做了一个内部知识库的 RAG 系统,选型是 LangChain + ChromaDB + OpenAI embeddings。跑起来之后,准确率从 POC 阶段的 85% 一路跌到生产环境的 42%。

回头看,问题不在模型,不在向量库,而在三个我当初完全没想到的地方。

坑一:chunk 大小不是越大越好

POC 阶段我用的是 1000 token 的 chunk size,结果检索召回率惨不忍睹。调大到 2000、5000,情况更糟。

后来才发现,问题出在 chunk 边界上。中文文本按字符数切分和按 token 数切分,结果完全不一样。我的文档大多是技术文档,里面代码块、表格、章节标题混杂,固定大小的 chunk 经常把一个完整的技术说明切成两半。

解决方案是用 langchain-text-splitters 的 MarkdownHeaderTextSplitter,按标题层级来切:

from langchain.text_splitter import MarkdownHeaderTextSplitter

headers_to_split_on = [

("#", "Header 1"),

("##", "Header 2"),

("###", "Header 3"),

]

splitter = MarkdownHeaderTextSplitter(headers_to_split_on)

docs = splitter.split_text(markdown_content)

这样切出来的 chunk 天然有语义完整性,每个 chunk 都带着自己的标题上下文。召回率从 42% 提到了 71%。

但别高兴太早——这个方法只对 Markdown 格式有效。如果是 PDF 或者 Word 文档,你得先做格式转换,转换过程中的格式丢失会让问题更复杂。

坑二:embedding 模型的语义差距

我用的是 text-embedding-3-small,POC 阶段测下来还行。上了生产数据之后,发现同一份文档的不同部分,embedding 的相似度分布完全不符合预期。

查了半天才发现,embedding 模型的训练数据和我们的领域数据差距太大。我们是医疗行业的内部文档,里面充斥着专业术语和缩写,通用 embedding 模型对这些词的理解和我们的领域专家完全不在一个频道。

换 embedding 模型不是简单的 API 切换。我试了 bge-m3、text-embedding-ada-002,效果各有优劣。最终的选择是:用 bge-m3 做初筛,再用一个微调过的医疗领域 embedding 模型做重排。

# 两阶段检索

# Stage 1: 粗排

vector_db = ChromaCollection(embedding_function=BGE_M3Embeddings())

candidates = vector_db.search(query, top_k=50)

# Stage 2: 精排

reranker = CrossEncoderModel(model_name="BAAI/bge-reranker-v2-m3")

results = reranker.rank(query, candidates)

这一套下来,准确率才真正稳定在 78% 以上。代价是延迟从 200ms 涨到了 800ms,需要做好缓存。

坑三:元数据过滤的陷阱

这是我踩得最狠的一个坑。我们给每个 chunk 都打了元数据标签:部门、文档类型、更新时间、敏感级别。检索的时候根据用户身份做元数据过滤,确保用户只能看到自己有权限的内容。

问题出在过滤逻辑上。我的过滤条件是 AND 关系——用户所属的所有部门标签必须全部匹配。但实际业务中,一个文档可能属于多个部门,用 AND 过滤会把大量相关文档排除掉。

改成 OR 关系之后,召回量上去了,但噪声也多了。最后用的是加权评分:匹配部门数量越多,分数越高,然后取 top-k。

def score_with_metadata(doc, user_departments):

base_score = doc["vector_score"]

dept_match = sum(1 for d in doc["metadata"]["departments"] if d in user_departments)

meta_score = dept_match / max(len(doc["metadata"]["departments"]), 1)

return base_score * 0.7 + meta_score * 0.3

总结

RAG 系统的坑不在技术选型,而在数据理解和业务适配。三个坑的共同点是:POC 阶段看不出来,一上生产就暴露。

如果你正在做 RAG 项目,我的建议是:

  • **chunk 策略要跟着文档结构走**,别用固定大小一刀切
  • 2. **embedding 模型要匹配领域**,通用模型在专业场景下会掉链子

    3. **元数据过滤要用加权评分**,AND/OR 的简单逻辑不够用

    最后说一句,RAG 系统的准确率永远不可能 100%,但把坑提前踩完,比上线后再补救强得多。

    ENDOFPPOST

    echo "Posts written successfully"

    wc -c /tmp/post1.md /tmp/post2.md