RAG应用从零搭建实战:检索增强生成、向量数据库与LLM调用避坑指南
发布时间:2026/10/4 1:21:23 锦皓数字建站

RAG 这个词这两年出现的频率太高了高到很多刚入行的朋友以为它是个新框架或者新工具其实它更像是一种给大模型外挂大脑的工程思路。我最早接触 RAG 是在做一个内部文档问答的需求当时天真地以为把 PDF 丢给模型就能问出答案结果被现实按在地上摩擦——模型要么答非所问要么一本正经地胡说八道。后来才明白问题的根子不在模型本身而在于它压根不知道你那些私有文档里写了什么。RAG 要解决的就是这件事让模型在回答问题之前先去你的知识库里把相关资料捞出来再基于这些资料组织答案。这篇文章我想聊的不是RAG 是什么这种概念科普而是把一个 RAG 应用从零搭起来的过程中那些真正会卡住你的地方。包括文档怎么切、向量库怎么选、检索为什么不准、召回和生成的边界在哪、以及我踩过的那些坑。适合已经了解 LLM 基本用法、想动手做一个能用的 RAG 应用的开发者也适合正在被检索效果折磨、想找优化方向的朋友。全文会围绕检索增强生成、向量数据库、LLM 调用这几条主线展开尽量把每一步的为什么讲清楚。1. 先把 RAG 的数据流想明白再动手很多人一上来就装 LangChain、连向量库、调 API代码跑通了但效果一塌糊涂根本原因是没搞清楚数据在系统里到底是怎么流动的。RAG 的本质是一条流水线任何一个环节出问题最终答案都会崩。我习惯在动手前先把这条链路画在纸上确认每个节点的输入输出。1.1 一条完整的 RAG 链路到底经过哪些环节从用户敲下问题到看到答案中间其实经过了六个阶段。第一阶段是文档加载把 PDF、Word、Markdown、网页这些异构数据读成纯文本。第二阶段是文本切分因为模型和向量库都有长度限制必须把长文档切成小块。第三阶段是向量化用 Embedding 模型把每个文本块转成一串浮点数。第四阶段是存储与索引把这些向量存进向量数据库并建立索引结构。第五阶段是检索用户提问时把问题也向量化去库里找最相似的若干块。第六阶段是生成把检索到的文本块拼进 Prompt交给 LLM 生成最终答案。这六个阶段里前四个是离线预处理通常只在文档更新时跑一次后两个是在线查询每次用户提问都要走一遍。区分离线在线很重要因为优化手段完全不同。离线阶段你可以慢慢跑、可以重试、可以用大模型做增强在线阶段对延迟敏感每一步都要抠时间。我见过不少人把 Embedding 和 LLM 混为一谈以为用了 GPT 做生成就不需要单独的 Embedding 模型了。这是两码事。Embedding 模型负责把文本映射到向量空间追求的是语义相似度计算准确LLM 负责根据上下文生成自然语言追求的是表达流畅和逻辑合理。它们可以来自不同厂商甚至本地部署的 Embedding 配云端 LLM 也是常见组合。1.2 为什么检索才是 RAG 的真正瓶颈刚做 RAG 的时候我把大量精力花在调 Prompt 和换模型上觉得答案不好是模型不够聪明。后来做了一组对照实验才发现把检索结果直接打印出来看很多时候压根就没召回正确的文档块。模型再强你给它喂的是错的资料它也变不出正确答案。这就是所谓的garbage in, garbage out。检索的瓶颈体现在三个层面。第一是语义鸿沟用户问怎么退款文档里写的是申请售后返还货款字面完全不重叠纯关键词匹配就废了必须靠向量语义匹配。第二是粒度问题一个文本块太大里面混了好几个主题向量就被平均掉了相似度算不准块太小又丢失了上下文模型拿到手也不知道在说什么。第三是多义与歧义同一个词在不同语境下含义不同向量空间里却可能挤在一起。所以我的经验是RAG 项目里 70% 的调试时间应该花在检索上而不是生成上。检索准了哪怕用个小模型生成答案也能看检索不准上再贵的模型也是白搭。这个认知转变是我做 RAG 的第一个分水岭。1.3 离线索引和在线查询的边界怎么划划清离线在线的边界直接决定了你的系统架构。离线部分我通常做成一个独立的索引构建任务输入是原始文档目录输出是向量库里的数据。这个任务可以定时跑也可以手动触发关键是它和在线服务解耦。在线部分就是一个查询服务接收用户问题走检索加生成返回答案。这样划分的好处是文档更新不会影响在线服务的稳定性索引重建时可以慢慢跑不怕超时。而且离线阶段可以做很多重操作比如用 LLM 给每个文本块生成摘要、抽取关键词、打标签这些增强信息能显著提升后续检索质量但在线阶段根本没时间做。有个细节容易被忽略离线索引和在线查询必须用同一个 Embedding 模型。我踩过一次坑离线用 A 模型建的库上线时手滑换成了 B 模型结果检索出来的全是无关内容因为两个模型的向量空间根本不兼容。这个错误排查起来很费劲因为代码不报错只是效果差很容易误以为是别的问题。2. 文档切分决定 RAG 上限的第一道关卡如果只能给 RAG 新手一条建议我会说把文档切分做好你的 RAG 就成功了一半。切分策略直接决定了检索的最小单元单元切得不好后面所有环节都在为这个错误买单。我在这上面交的学费最多也最有心得。2.1 固定长度切分为什么经常翻车最简单的切分方式是按固定字符数切比如每 500 字一块块之间留 50 字重叠。这个方法实现简单但问题很明显。它完全不管语义边界经常把一句话从中间劈开或者把一个小节的标题和内容分到两块里。用户问的问题如果正好落在这个边界上检索就会召回半截内容模型看了也懵。重叠窗口是为了缓解这个问题让相邻块共享一部分内容保证语义连续性。但重叠比例不好定太小没效果太大又造成冗余检索时可能召回好几个高度相似的块浪费上下文窗口。我一般把重叠设在块大小的 10% 到 20% 之间具体看文档的句子平均长度。固定长度切分不是不能用它适合那种结构松散、没有明显章节的文本比如聊天记录、日志。但只要文档有标题层级、有段落结构就应该优先用结构化切分。2.2 按语义和结构切分的实操思路结构化切分的核心思想是顺着文档本身的组织方式来切。Markdown 有#、##、###HTML 有标签层级PDF 虽然麻烦但也能通过字体大小和加粗推断标题。我通常先按最高级标题切大块如果某块还是太长再按次级标题切直到每块落在合理长度区间。这里有个关键参数是目标块大小。太小检索准但上下文不足太大上下文全但噪声多。我的经验值是中文 300 到 800 字英文 200 到 500 词。这个区间是权衡的结果足够容纳一个完整论点又不至于混入太多无关信息。当然这要看你的文档密度技术文档信息密度高可以小一点叙述性文档可以大一点。对于代码和技术文档我还会做特殊处理。代码块尽量保持完整不要从中间切断因为半截代码毫无意义。表格也尽量整块保留或者转成文字描述。这些细节看起来琐碎但对检索质量影响很大。2.3 给每个文本块补上上下文身份证这是我个人觉得最有价值的一个技巧给每个切分后的文本块附加元数据。光有一段文字向量化后丢失了它在原文中的位置信息。如果我在块前面加上本文档标题XXX所属章节YYY本块内容……检索时这段前缀也会参与向量计算能显著提升相关性。元数据还能用于过滤。比如用户问的是某个产品的问题我可以在检索时先按产品名过滤只在相关文档里找大大缩小搜索范围。这在多产品、多版本的文档库里特别有用。元数据字段我一般会存文档来源、章节路径、块序号、创建时间、文档类型。这些信息在后续做引用溯源时也用得上用户能看到答案是从哪份文档的哪一节来的信任度会高很多。提示元数据不要塞太多否则会稀释正文的向量表达。前缀控制在 50 字以内比较合适只放最关键的定位信息。3. 向量数据库选型别被参数表忽悠向量数据库这个赛道卷得厉害各种产品参数表看得人眼花。但选型不是比谁的功能多而是看你的场景需要什么。我做过几个不同规模的项目从单机小工具到百万级文档的服务选型逻辑差别很大。3.1 从数据规模和部署方式倒推选型选型第一步是搞清楚你的数据量级和部署环境。如果只是本地跑个小知识库文档几百上千个那根本不需要专门的向量数据库用 FAISS 这种内存索引库就够了甚至 numpy 手写余弦相似度都能扛。这个阶段追求的是简单别给自己找麻烦。数据量到了十万级、需要持久化和并发查询就该上真正的向量数据库了。这时候要考虑是自建还是用云服务。自建的话 Milvus、Qdrant、Weaviate 都是成熟选择云服务则省去了运维但成本高。我的判断标准是如果团队没有专职运维且数据量不是特别大优先考虑云服务或者轻量级自建方案。百万级以上、对延迟和可用性有硬要求才需要认真对比分布式能力和索引算法。这个阶段 HNSW、IVF、PQ 这些索引结构的取舍就变得重要了因为它们直接决定召回率和查询速度的平衡。3.2 几个主流方案的实测对比我把用过的几个方案列个表说说真实感受参数表上查不到的那种。方案适合场景实测优点实测坑点FAISS本地小规模、离线实验快、零依赖、纯内存无持久化、无并发、重启即丢Qdrant中小规模自建部署简单、过滤功能强大规模下内存占用偏高Milvus大规模生产生态全、扩展性好组件多、运维门槛高pgvector已有 Postgres 的团队复用现有数据库、事务一致超大规模性能不如专用库云托管服务无运维团队开箱即用、弹性扩容成本随规模线性上涨这张表里我最想强调的是 pgvector。很多团队本来就在用 Postgres加个扩展就能存向量省去了引入新组件的麻烦数据一致性也好保证。对于中小规模应用这是被低估的选择。我有个项目就是 pgvector 扛的几十万条向量查询延迟完全可接受。3.3 索引参数调优召回率和速度的跷跷板向量索引本质上是在做近似最近邻搜索用一点点召回率的损失换取查询速度的大幅提升。HNSW 是目前最常用的索引它有两个关键参数M控制每个节点的连接数ef_construction控制建索引时的搜索范围。M 越大、ef_construction 越大索引质量越高但建索引越慢、内存占用越大。查询时还有个ef_search参数它控制查询时的搜索范围。这个参数可以在运行时调是调节召回率和延迟最直接的旋钮。我的做法是先设一个较高的 ef_search 保证召回测出延迟再逐步往下调找到满足延迟要求的最低值。这里有个反直觉的点索引不是越精确越好。有时候稍微降低召回率把省下来的时间用于召回更多候选块再重排整体效果反而更好。这就是后面要讲的召回加精排两阶段检索的思路。4. 检索质量优化从能查到到查得准检索是 RAG 的心脏。前面把数据和索引准备好了这一节聊怎么让检索真正准起来。我把它分成三个层次查询改写、混合检索、重排序。这三招叠加使用效果提升非常明显。4.1 查询改写用户的问题往往不是好的检索词用户提问的方式和文档写作的方式经常对不上。用户问这个功能怎么收费文档里可能写的是计费规则说明。直接拿用户原话去检索命中率堪忧。解决办法是在检索前先对查询做改写或扩展。最简单的是同义词扩展把收费扩展成收费、计费、价格、费用。稍微高级点的是用 LLM 做查询改写让模型把口语化的问题改写成更适合检索的关键词组合或者生成多个不同角度的查询。我常用的一招是让 LLM 基于用户问题生成三个改写版本分别检索后合并结果召回率提升立竿见影。还有个技巧叫HyDE思路是先让 LLM 根据问题编一个假想的答案然后用这个假想答案去检索。因为假想答案的措辞更接近文档风格检索效果往往比原问题好。这个方法听起来有点玄但实测确实有效尤其适合问答式文档。4.2 混合检索向量加关键词才是稳的纯向量检索擅长语义匹配但对精确匹配不敏感。比如用户搜一个特定的错误码ERR_5023向量检索可能召回一堆语义相近但错误码不同的内容。这时候关键词检索BM25就派上用场了它能精确匹配这些专有名词。混合检索就是把向量检索和关键词检索的结果融合。融合方式有加权的也有用 RRF倒数排名融合的。RRF 不需要调权重对两路结果按排名做融合鲁棒性好我一般优先用它。实测下来混合检索在专有名词、代码、编号这类查询上比纯向量检索强太多。实现上很多向量数据库已经内置了混合检索能力比如同时支持稠密向量和稀疏向量。如果没有也可以分别查两个库再在应用层融合只是多一次查询开销。4.3 重排序把最相关的顶到最前面检索召回 Top-K 之后顺序其实不一定对。向量相似度高不代表真的相关因为 Embedding 模型的能力有限。这时候引入一个重排序模型Reranker对召回的候选块做精细打分重新排序把真正相关的顶上来。Reranker 通常是交叉编码器结构它把问题和候选块拼在一起过一遍模型输出相关性分数。它比向量点积准得多但慢得多所以只适合对少量候选做精排。典型流程是向量检索召回 50 个Reranker 精排出 Top 5再交给 LLM。这个粗召回加精排序的两阶段结构是我目前认为性价比最高的检索方案。选 Reranker 时要注意它和你的语言、领域是否匹配。通用 Reranker 在专业领域可能表现一般有条件的话可以用领域数据微调。不过对大多数应用现成的开源 Reranker 已经够用。5. 生成环节Prompt 设计和上下文管理检索把资料找齐了最后一步是让 LLM 基于资料生成答案。这一步看似简单其实 Prompt 的写法直接决定答案质量。我见过太多人随便拼个 Prompt 就上线然后抱怨模型不听话。5.1 把检索结果喂进 Prompt 的正确姿势拼接上下文时我习惯给每个文本块加上编号和来源比如[1] 来源产品手册第三章……。这样模型在生成时可以引用编号用户也能追溯。块与块之间用明确的分隔符隔开避免模型把它们混在一起理解。Prompt 里必须明确告诉模型只根据提供的资料回答资料里没有的信息就说不知道。这句话能大幅减少幻觉。如果不加这句模型很容易用自己的先验知识补充而这些补充可能是错的。我还会要求模型在答案里标注引用了哪几块资料方便验证。上下文长度要控制。虽然现在模型支持很长的上下文但塞太多无关内容反而会干扰模型。我的做法是精排后只取 Top 3 到 Top 5宁可少而精不要多而杂。如果确实需要更多信息就分多轮检索而不是一次性全塞进去。5.2 处理检索不到和资料冲突的情况真实场景里检索不到相关资料是常态。这时候系统应该明确告诉用户没有找到相关信息而不是让模型硬编一个答案。我在 Prompt 里会设定一个兜底逻辑如果资料与问题无关直接回复未找到并建议用户换个问法或联系人工。资料冲突也很常见尤其是文档有多个版本时。我的处理方式是让模型指出存在冲突并分别列出不同来源的说法把判断权交给用户。这比模型自作主张选一个要诚实得多。元数据里的时间戳在这里就有用了可以优先采用较新的版本但要明确告知用户。5.3 引用溯源让答案可验证引用溯源是我认为 RAG 相比纯 LLM 最大的优势之一。答案不是凭空来的而是有据可查。实现上我在生成时要求模型输出引用的块编号前端再把编号映射回原文位置用户点击就能跳转。这个功能对内部知识库、客服系统特别重要因为用户需要确认答案的权威性。溯源做得好还能反过来帮你调试。当用户反馈答案错误时你能快速定位是检索错了还是生成错了。如果引用的是无关块那是检索问题如果引用正确但答案错那是生成问题。这个区分能省下大量排查时间。6. 那些文档里不会写的踩坑记录前面讲的都是方法论这一节聊点实在的都是我实际踩过的坑希望能帮你少走弯路。6.1 中文分块和英文分块的差异中文没有空格分词按字符数切和按词切效果差别很大。我一开始用英文那套按空格切词的方法处理中文结果切出来的块惨不忍睹。中文切分要么用专门的分词工具要么按标点符号切句子再组合。标点切分简单有效句号、问号、感叹号、分号都是天然边界。还有个坑是中文的标点全角和半角混用正则匹配时容易漏。我写切分逻辑时会把全角半角都考虑进去否则会出现句子切不断的情况。这些细节很小但不处理就会影响切分质量。6.2 Embedding 模型的维度陷阱不同 Embedding 模型输出的向量维度不同从 384 维到 1536 维甚至更高都有。维度高不代表效果好有些高维模型在特定领域反而不如低维的。选模型要看它在你的语言和领域上的表现最好用一批真实查询做评测别只看榜单。另一个坑是归一化。有些模型输出的向量已经归一化了有些没有。如果混用余弦相似度计算会出错。我一般统一做一次归一化确保一致性。这个错误很隐蔽因为不报错只是相似度算出来偏大或偏小。6.3 增量更新时的一致性维护文档会更新索引也得跟着更新。最简单的做法是删掉旧块、插入新块但这里有个坑如果文档只是小改重新切分可能导致块边界变化旧块的 ID 和新块对不上删除时容易漏。我的做法是给每个块一个基于文档 ID 加内容哈希的稳定 ID更新时按文档 ID 批量删除再重建保证不残留。还有个问题是更新期间的查询一致性。如果一边删一边插用户可能查到半新半旧的结果。生产环境我会用双索引切换新索引建好后再原子切换避免中间态。这个成本高一点但对用户体验值得。6.4 评测没有评测就没有优化最后强调一个最容易被忽略的环节评测。没有评测你所有的优化都是凭感觉改了半天不知道有没有变好。我建议一开始就建一个小规模的评测集几十条真实问题和对应的标准答案每次改动后跑一遍看召回率和答案准确率的变化。评测指标我主要看两个检索的召回率正确文档块有没有被召回和生成的准确率答案对不对。这两个指标分开看才能定位问题出在哪一环。评测集不用大但要覆盖典型场景和边界情况。随着项目迭代评测集也要不断补充把线上发现的 bad case 加进去。注意评测集里的标准答案要人工确认不能直接用模型生成否则就是拿模型评模型没有意义。7. 从 Demo 到生产还差哪些工程化工作跑通一个 Demo 可能只要一下午但要让它在生产环境稳定服务还有不少工程活要干。这一节聊聊那些容易被低估的部分。7.1 缓存策略省下的是真金白银RAG 每次查询都要调 Embedding 和 LLM成本不低。缓存能省很多钱。我一般做两层缓存一层是查询结果的缓存相同问题直接返回另一层是 Embedding 的缓存相同文本不重复计算。查询缓存要注意失效策略文档更新后相关缓存要清掉。语义缓存是个进阶玩法不是精确匹配问题而是判断新问题和缓存问题语义是否相近相近就复用答案。这能提高命中率但要用向量相似度判断阈值设不好会返回错误答案。我一般把阈值设得保守一点宁可多算一次也不返回错答案。7.2 延迟优化用户等不了太久RAG 的延迟主要来自三块Embedding 计算、向量检索、LLM 生成。Embedding 和检索通常很快几十毫秒级别大头在 LLM 生成。优化生成延迟可以用流式输出让用户先看到部分答案感知上快很多。检索阶段如果用了 Reranker也会增加延迟。我的做法是给 Reranker 设个超时超时就跳过精排直接用粗排结果保证响应时间可控。这种降级策略在生产环境很重要宁可效果差一点也不能让用户干等。7.3 监控与反馈闭环上线不是终点。我会监控几个关键指标查询量、平均延迟、检索召回率、用户反馈。用户反馈是最宝贵的信号点赞点踩都要记录定期分析踩的案例找出共性问题。我还会记录每次查询的完整链路原始问题、改写后的问题、召回的块、重排后的块、最终答案。这些日志在排查问题时价值巨大。有了这些数据你就能持续迭代让系统越用越准。8. 关于 RAG 能力边界的一些实话做了这么多 RAG 项目我对它的能力边界有了比较清醒的认识这里说几句实话可能不太中听但有用。RAG 不是万能的。它擅长的是从已有资料里找答案如果你的需求是推理、计算、创意生成RAG 帮不上太多忙。有些团队把 RAG 当成解决一切问题的银弹结果发现效果不达预期其实是场景选错了。RAG 的效果高度依赖文档质量。如果原始文档本身就混乱、过时、自相矛盾RAG 只会把这些混乱放大。我见过一个项目文档是十年前写的里面很多信息已经失效RAG 检索出来照样喂给模型答案自然错得离谱。这种情况下先整理文档比优化 RAG 更有效。还有一个现实是RAG 的维护成本不低。文档在变、模型在更新、用户需求在变系统需要持续投入。它不是搭好就一劳永逸的东西。如果团队没有持续维护的准备不如先用简单方案顶着。我个人在实际操作中的体会是RAG 项目成功的关键不在技术选型多先进而在对业务场景的理解有多深。你得知道用户真正会问什么问题文档里真正有什么内容两者之间的鸿沟在哪。把这些想清楚了技术方案自然就出来了。反过来如果只是堆技术再花哨的架构也解决不了实际问题。
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。