放弃 LangChain 尝试用纯 Python 手撸 RAG 流程后的几个核心发现
很多开发者在构建 RAG(检索增强生成)系统时,习惯性地直接调用 LangChain 或 LlamaIndex。虽然这些框架提供了极高的开发效率,但它们本质上是“黑盒”封装,在实际调试时,当你发现检索结果不准确或者模型回答跑题时,往往需要对着复杂的框架源码发呆,很难快速定位问题。最近我尝试剔除所有重型框架,仅使用基础库重新实现了一遍 RAG 流程,发现这种“回归原始”的方式反而让性能调优变得直观。
整个流程的核心其实只有三个纯技术环节:文档切片、向量检索和 Prompt 增强。
首先是文档切片与向量化。在实际操作中,由于大模型的上下文窗口限制,不能直接将长文档塞入,必须进行 Chunking(切片)。我在这里采用了最简单的字符长度切分法,并调用 sentence-transformers 库中的 all-MiniLM-L6-v2 模型将文本转换为向量。这个模型虽然轻量,但足以验证整个链路。代码实现非常精简:通过 model.encode(texts) 即可将文本列表转化为对应的 Embedding 向量。
接下来的关键环节是构建向量索引并实现检索。为了避免引入复杂的数据库,我直接使用了 FAISS(Facebook AI Similarity Search)。在构建索引时,需要先获取 Embedding 向量的维度(例如 embeddings.shape[1]),然后使用 faiss.IndexFlatL2 创建一个 L2 距离索引。当用户发起查询时,系统会将查询词同样通过 all-MiniLM-L6-v2 向量化,随后调用 index.search(query_vector, k=1)。这里 k=1 表示只检索最相关的一个片段,通过返回的索引 I 即可在原文本列表中定位到具体内容。
最后一步是构造 Prompt 并调用 LLM。这其实就是一个简单的字符串拼接过程:将检索到的 context 文本直接嵌入到 Prompt 的上下文区域,明确告知模型“基于以下内容回答问题”。
通过这次从零构建的实战,我意识到 RAG 的性能瓶颈其实非常透明。当你不用框架时,你会发现所谓的“检索不准”,本质上只有两种可能:要么是切片粒度太粗,导致检索到的片段包含了过多噪音,干扰了模型的判断;要么是 Embedding 模型的语义空间偏差,导致计算出的余弦相似度与实际语义相关性不匹配。
在这种环境下进行调优,比在框架里修改某个深层的 retriever_config 参数要直观得多。你能够清晰地看到每一个阶段的输入和输出,从而快速决定是该更换一个更高维度的 Embedding 模型,还是优化切片的重叠度(Overlap)。对于追求极致控制力的开发者来说,先用纯 Python 跑通逻辑,再决定是否引入框架,才是更稳妥的架构路径。

手撸分段长度简直爽翻,比在 LangChain 里死磕参数快了不止一个量级!