用 LangChain 做 RAG,最容易踩的不是向量库


第一次做 RAG 的时候,很容易把注意力放在向量库上。

Milvus、Qdrant、pgvector、Chroma,名字看起来都很关键。实际做完几轮以后会发现,向量库当然重要,但它通常不是最先拖垮效果的地方。真正影响回答质量的,更多是文档怎么切、怎么检索、怎么拼上下文,以及模型不知道的时候能不能老实说不知道。

文档切分是第一个坑。

很多示例代码会直接按固定长度切,比如 500 token 一段,overlap 50。这个办法能跑,但不一定好用。技术文档、会议纪要、产品说明、代码注释,它们的结构不一样。按长度硬切,常常会把一个完整概念拆开,也可能把标题和内容拆散。检索时命中了正文,却丢了标题,模型就少了判断上下文的线索。

我更喜欢先按结构切,再按长度兜底。Markdown 就按标题层级切,网页就先抽正文区域,PDF 至少要处理页眉页脚和目录。切分这一步越认真,后面 prompt 越不用补锅。

第二个坑是只看相似度。

向量检索返回 topK,并不代表这几段就适合直接塞给模型。相似度高可能只是关键词接近,不代表答案在里面。一个常见做法是加 rerank,把粗召回结果再排一次。哪怕不用复杂模型,简单地混合关键词检索和向量检索,也经常比单一路径稳定。

第三个坑是上下文拼装。

很多 RAG 系统失败,不是没检索到,而是把检索结果拼得太乱。模型看到五段互相冲突的内容、没有来源、没有时间、没有标题,回答自然会飘。上下文最好带上来源、章节名和必要的元信息。不是为了好看,是为了让模型知道这段内容在原文里的位置。

LangChain 的好处是这些环节都有现成抽象:loader、splitter、retriever、chain。坏处是抽象多了以后,人容易以为把组件串起来就完成了系统。其实 RAG 的质量更多来自每一步的小判断。

我现在会给 RAG 链路加几条硬规则:

  • 检索不到足够证据时,回答“不确定”
  • 回答必须引用来源片段
  • 上下文里有冲突时,先指出冲突
  • 不允许用常识补全文档没有的信息

这些规则看起来普通,但能显著降低胡编。尤其是内部知识库场景,用户问的不是“你觉得呢”,而是“资料里到底怎么写”。

一个能用的 RAG 系统,不是把文档扔进向量库就结束了。它更像一个搜索系统加一个谨慎的写作者:先尽力找到证据,再基于证据组织答案。

LangChain 提供的是工具箱,真正的质量还是取决于你怎么处理材料。