RAG应用从零到落地:文本切分、向量检索与重排实战笔记 1. 内容整体设计与思路拆解先交代背景今天这篇笔记源于我近期一直在跟的一个实际项目——给某公司内部的资料库做一个“问答机器人”。项目本身不复杂但真正做起来才发现把大模型接进业务系统并不是“调个接口”那么简单。我把2026年1月19日这一天的学习重点和实操记录整理成了这篇笔记主要围绕检索增强生成RAG应用从零到落地的过程。1.1 为什么要把“学习笔记”定成RAG主题很多人看到“RAG”会想到一堆名词向量库、embedding、chunking、rerank……但笔记里如果能把这些词串成一条清晰的决策链才算真正学明白。我选择RAG作为这一天的学习主题是因为它正好覆盖了大模型应用开发中最核心的痛点模型不知道私有知识、回答容易胡编、知识更新成本高。RAG的思路很朴素先把你自己的文档切碎转成向量存起来用户提问时先去医院侧做语义检索找出相关内容再让大模型基于这些内容总结回答。这么一来模型不需要记住你的内部资料每次只要拿着“小抄”回答问题就行。这种模式适用于企业知识库、产品说明书问答、员工入职助手等场景也是目前大模型落地最稳的一条路。1.2 整体架构如何选型做RAG之前先别急着写代码得把架构摸清楚。我这一天的学习笔记里第一件事就是画了一张“数据流向图”离线阶段原始文档 - 解析 - 清洗 - 文本切分 - 向量化 - 写入向量数据库在线阶段用户问题 - 向量化 - 向量检索 - 重排 - 拼接Prompt - 大模型生成 - 答案返回这个链路看着直白但每一步都有不少隐藏的坑。例如文本解析如果用了不合适的解析器表格和PDF里的内容会乱成一团切分策略不对语义被拦腰截断向量检索召回了一堆无关文档最后大模型就容易被“带偏”。所以我的笔记里专门分成了“索引构建”和“查询推理”两条线后面逐步拆解。在选型层面我坚持一个原则能用成熟组件就不要自己写但也不能被封闭生态绑死。向量数据库、Embedding接口、LLM接口这三块尽量做成可插拔的哪怕前期多写几个包装类也值得。这样后续换供应商、换模型版本只要改一层配置不用动业务代码。2. 核心细节解析与实操要点2.1 文本切分chunk_size 与 overlap 怎么定文本切分是RAG里最能“决定成败”的细节。如果切得太粗一个chunk里塞了多个主题检索时相关性容易被稀释切得太细语义不完整模型连上下文都读不通。我在项目里试过从200到2000字的多个chunk_size最终稳定在500字左右overlap设成50字这个组合对中文技术文档的效果最好。overlap的作用是防止语义被切断。想象一下如果你把一段话从中间剪开恰好把“虽然天气很好”和“但是风特别大”分到两段检索“天气”时只能看到前半句判断就失真了。overlap相当于让相邻片段重叠一小段保住边界上的上下文。切分时还要尽量按标题、段落边界切不能机械地按字符数硬切。我用的方案是优先用正则找段落标记再在段落内部按句号、分号切成短句最后拼成500字左右的块。这里有三个实操要点统一换行符别让Windows和Linux的换行差异污染切分结果。对英文和数字要保留原格式避免把“C”切成了“C ”和“ ”。表格类内容最好单独处理转成Markdown表格文本后再切否则检索时常常丢列名。2.2 向量化与索引Embedding 模型选择选Embedding模型时我的笔记里记了三个对比维度向量维度、语义能力、更新成本。向量维度越高存储和计算开销越大但不见得效果更好语义能力要看你的语料是不是能覆盖专业术语更新成本则决定了你多久能换一次模型。实际测试中我发现通用中文场景下用常见的“基础Embedding接口”就能达到不错的召回率。但如果是医疗、法律、机械这种术语密集的领域必须额外用领域语料做微调或者至少在项目里先跑一批业务样本验证。我在某医疗项目里把通用向量模型换成领域微调后的版本后召回率提升了15%左右这个差距很悬殊。索引层面我重点记录了三类向量索引的取舍索引类型优点缺点适用场景暴力检索精度最高实现简单数据量大时速度慢百级文档、离线验证HNSW查询快、扩展性强内存消耗大、参数敏感万级到千万级向量IVF节省内存、速度快召回精度略低海量向量、对精度不敏感我现在的默认选择是HNSW因为RAG场景里query时延敏感HNSW能在毫秒级返回TopK结果。但需要提醒别一上来就用新手阶段先用暴力检索跑通再切换否则参数调不明白容易得出错误结论。2.3 检索链路重排与混合检索单靠向量检索并不总能解决问题因为向量相似度不等于语义相关度。比如用户问“这个产品的保修期是多久”如果你文档里写着“保修期为购机之日起一年”向量距离可能很近但排在前面的可能是很多“保修、期限、产品”相关的高频片段。为了解决这个问题我加了重排阶段。重排的方案很多可以使用专门的Cross-Encoder模型也可以用一个更强的大模型对召回结果做粗筛。我一般先向量召回50个候选再用重排模型选出Top5。这个“粗召回细排”的结构能够显著提升最终生成效果虽然没有精确统计过但至少减少了30%的答非所问。混合检索也是我强烈建议在笔记里写一笔的点。很多场景下关键词精确匹配比语义匹配更可靠比如型号“X-1000”、日期“2025-12-31”、报错码“E401”。混合检索会把关键词匹配和向量检索的结果做一个加权合并让两类信号互补。我在代码里给向量检索和关键词检索分别设置了权重先用规则从问题里抽关键词再配合向量检索效果稳定不少。3. 实操过程与核心环节实现3.1 环境准备与依赖我这次学习笔记里没有用特别复杂的技术栈整条链路都是基于Python搭建的。环境依赖大致如下语言模型接口一个支持函数调用/流式输出的大模型API客户端Embedding接口与语言模型配套的text-embedding服务向量数据库一个本地可跑的轻量级向量库用于索引和检索文档解析库能够读取PDF、Markdown、Word格式的解析工具这些组件我都用了一层薄封装避免业务代码直接依赖某个供应商SDK。换组件时只需要改工厂类里的一个参数。提示跑通流程之前先把依赖隔离好用单独的虚拟环境。RAG组件版本冲突非常常见我在项目里就遇到过解析库升级后把Markdown标题的层级信息全部丢掉的问题。3.2 核心代码实现下面这段是我整理后的简化版实现重点突出“索引构建”和“查询”两条主链路。# 配置各个组件的统一入口 config { chunk_size: 500, overlap_size: 50, top_k_candidates: 50, top_k_final: 5, vector_db_type: local_hsnw, embedding_dim: 1024, } class DocumentLoader: 读入不同格式文档统一转成纯文本。 def load(self, file_path: str) - str: # 根据扩展名选择对应的解析器 ext file_path.rsplit(., 1)[-1].lower() if ext pdf: text self._parse_pdf(file_path) elif ext md: text self._parse_markdown(file_path) elif ext in (docx, doc): text self._parse_word(file_path) else: text self._parse_plain(file_path) # 清洗去掉多余空行、统一换行符 return clean_text(text) class TextSplitter: 按段落和长度双重约束切分文本。 def split(self, text: str, chunk_size: int, overlap: int) - list[str]: paragraphs re.split(r\n\s*\n, text) chunks [] current for para in paragraphs: # 如果当前块加段落超长则先存下当前块再考虑是否和段落combined if len(current) len(para) chunk_size and current: chunks.append(current) current current[-overlap:] if overlap 0 else current para \n if current.strip(): chunks.append(current) return chunks class VectorStore: 对向量数据库做一层薄封装上层不感知具体实现。 def __init__(self, config): self.config config self._client create_vector_db_client(config[vector_db_type]) def insert(self, embeddings, texts, metadata_list): for vec, text, meta in zip(embeddings, texts, metadata_list): self._client.upsert(vectorvec, payload{text: text, **meta}) def search(self, query_embedding, top_k): hits self._client.query(query_embedding, top_ktop_k) return [h.payload for h in hits] def build_index(doc_files: list[str]): loader DocumentLoader() splitter TextSplitter() store VectorStore(config) for file_path in doc_files: raw_text loader.load(file_path) chunks splitter.split(raw_text, config[chunk_size], config[overlap_size]) embeddings embed_texts(chunks) # 批量调用Embedding接口 metadata [{source: file_path, chunk_index: i} for i in range(len(chunks))] store.insert(embeddings, chunks, metadata) def query(question: str): q_vec embed_texts([question])[0] km_matches keyword_search(question) # 并行做关键词检索 vec_matches store.search(q_vec, top_kconfig[top_k_candidates]) merged merge_results(km_matches, vec_matches, weight_vector0.8, weight_keyword0.2) reranked rerank_by_score(question, merged)[:config[top_k_final]] context \n---\n.join([m[text] for m in reranked]) prompt build_prompt(question, context) answer llm_generate(prompt) return answer上面这段代码是学习笔记的骨架但真实项目里还需要补充很多异常处理Embedding接口超时、向量库连接断开、文档解析失败等等。我在笔记里把这些异常记录成了独立的“坑位”后面专门展开。3.3 工程化落地的参数调整代码跑通只是第一步真正要上生产还得做参数调优。我整理了表格记录不同参数设置下的效果变化参数我常用的初始值出现问题时怎么调chunk_size500回答太碎片就调大到800答非所问就调小到300overlap_size50语义断裂明显时调到100候选召回数50重排后效果不稳定时提高到100最终返回数5上下文太长导致超时就降到3关键词检索权重0.2专业型号查询多时调到0.5还有一个容易被忽略的参数相似度阈值。并不是所有召回结果都值得进Prompt。如果某条候选的相似度低于0.35我通常会直接丢掉避免无关内容污染生成。当然阈值也要基于实际分布调整不能照搬。4. 常见问题与排查技巧实录4.1 检索“召回差”怎么调召回差是RAG应用里最常见的问题具体表现就是“用户问A你检索出来的都是一堆B”。排查时我按固定顺序来看切分是不是chunk太大导致一个块里混入了多个主题看Embedding是不是模型对领域术语不理解用几个高相关问题做召回测试。看检索方式只用了向量检索忘了关键词检索看重排Top10候选里有正确答案但排到后面去了调低向量权重调高关键词权重。有一次所有答案都检索不到我查到最后发现是PDF解析时的表格数据被解析成了乱码根本没有进索引。所以排查时别一头扎进算法里先从最上游的数据质量检查起。4.2 幻觉问题怎么治即使RAG链路很顺大模型还是可能“脑补”内容。我的经验是在Prompt里强制加上“如果上下文没有明确答案直接回答我不知道”。这看似简单却能大幅减少无中生有的回答。另外在输出前端加一层“引用校验”要求模型在关键结论后面标注来源chunkID我再用脚本检查答案里的关键实体是否真的出现在对应chunk中。如果有疑点就把答案打回重生成或降级回复。我还在笔记里记录了一个心得不要只依赖大模型“自觉”。应该把上下文来源列表原样交给用户让用户也能自己核对。这不仅是技术方案也是产品设计上的信任策略。4.3 延迟与成本优化RAG应用上线后最大的压力往往不是准确率而是时延和费用。我优化的方向有三个缓存对重复问题做语义缓存命中后直接返回历史答案能省掉大半查询开销。并发把Embedding和LLM调用改成异步并发而不是串行循环。模型分级简单问题用轻量模型难问题走满血大模型。做一个简单的路由判断就能把成本降到原来的三分之一。这里有个小技巧是把“问题分类器”做成一个只输出标签的极短模型跳过复杂Prompt判断。我用这个方案之后平均响应时间从4秒降到了1.5秒而且答案质量没有明显下降。最终说一个我在踩过多次坑之后的体会做RAG学习笔记别光盯着“模型有多聪明”真正要死磕的是“上下文有多准”。把文本切分、检索融合、重排这三件事做扎实哪怕模型版本老一点效果也差不到哪去。反过来如果只堆模型调用检索结果乱七八糟再强的模型也是浪费时间。希望这份笔记能让你在迭代自己的RAG应用时少走几条弯路。