跳转至

05. RAG 工程

五、RAG 工程

导读:把 RAG 当成“受控检索系统”和“生成系统”的分工合作

RAG 经常被讲成一句很轻飘的话:把文档喂给模型,让模型基于文档回答。这样说不算错,但太容易误导。真正的 RAG,不是“多贴一点资料”,而是把“找资料”和“组织答案”拆成两条职责不同的链路,让系统少靠猜,多靠查证。你如果把这个主线抓稳了,后面切块、embedding、hybrid search、rerank、引用、评测这些词都会自然落回各自该在的位置。

对企业场景来说,RAG 最核心的价值不是“让模型知道更多常识”,而是让模型在面对企业私有知识、频繁更新的制度、带权限边界的内部文档时,不必依赖训练时的记忆,而是能先把相关资料找出来,再在这些资料之上组织回答。换句话说,RAG 不在替模型“增强记忆”,而是在替系统“补充查证能力”。这一区别非常重要,因为只要你把问题理解成“让模型记住更多”,后面就容易一路跑偏到堆上下文、堆 Prompt,而忽略资料生命周期、权限过滤和引用可信这些真正决定上线质量的东西。

RAG pipeline 为什么一定要拆成离线和在线两条链路

RAG 的完整流程最好分两段来看。离线链路负责把原始资料变成可检索资产,在线链路负责把用户问题变成一次受控的检索和回答过程。离线链路通常是上传、解析、清洗、切块、embedding、写索引;在线链路通常是鉴权、召回、必要时 rerank、组织上下文、生成答案、附上引用。很多学习资料把这些步骤堆在一起写,读者就会误以为它们是一个连续的“模型调用前处理”。其实不是。离线和在线的目标、性能要求、错误处理方式和监控方式都完全不同。

离线侧更像数据加工系统。它关心的是文档摄取是否稳定,解析器能不能处理 PDF、Word、网页和扫描件,切块是否保留了文档结构,embedding 是否批量化,索引更新是否安全。在线侧更像典型服务链路。它关心的是权限是否收住,延迟是否可接受,检索质量是否稳定,引用是否可信,断连后能不能取消。你把这两条链路拆开,很多原本混在一起的问题就会自动分层,系统设计题也会一下子清晰很多。

切块为什么会成为高频考点

切块会被面试官反复追问,不是因为它多么“高深”,而是因为它几乎直接决定 RAG 的下限。切块的本质,是在做信息组织,而不是做字符串截断。块太大,噪声多、排序难、模型上下文被无关文字占满;块太小,条件和结论容易被切散,系统最后只召回局部事实,漏掉真正重要的限制条件。

这件事在制度、流程、代码和表格文档里体现得非常明显。制度类文档里,一个条款往往不是一句话,而是“适用范围 + 规则主体 + 例外条件 + 数值上限 + 生效日期”的组合。你如果切得太碎,模型可能只看见“可报销”,却漏掉“单次上限 300 欧元”和“商务舱不适用”。技术手册也是一样,配置项说明和示例代码如果被拆开,最后检索到的往往是半截解释。表格文档更麻烦,如果解析器把表头、行值和备注打散,后面无论向量检索还是关键词检索,都会变得很脆。

所以更成熟的切块策略,通常不是只盯一个固定 chunk size,而是先尊重文档结构。制度类资料优先按标题、条款、段落切,再用窗口长度和 overlap 微调;FAQ 适合按问答对切;代码和配置块尽量保持完整;表格类数据必要时做结构化抽取,不要直接让解析器把它打成纯文本碎片。真正稳的 chunk size,从来不是背出来的,而是拿真实问题集和引用命中率跑出来的。

embedding、向量检索、hybrid search 和 rerank 各自负责什么

embedding 解决的是“用户问法和文档原文不完全一样,但意思可能很接近”这个问题。它把文本映射成向量,让系统能按语义距离来找相关内容,而不只靠关键词匹配。向量检索的任务则是第一轮粗筛,尽量别漏掉真正相关的候选片段。你可以把它理解成“高召回、先把人捞上来”,而不是“这一步就直接得到最终答案”。

但只做向量检索通常并不稳。真实业务里,很多问题同时包含语义表达和精确锚点。比如“EXP-20311 这张报销单为什么被退回”,这里的单号就是强关键词;如果只靠语义相似,未必能把那条记录稳稳排到前面。hybrid search 的价值,就在于把关键词检索和向量检索结合起来,让系统既不容易漏掉语义相近内容,也不轻易丢掉编号、错误码、配置项这类精确匹配信号。

rerank 处理的是第二轮精排。第一轮召回的目标是别漏掉,第二轮 rerank 的目标是别排错太多。很多系统之所以“能搜到一些东西但最后答案还是不准”,往往不是完全没召回到正确资料,而是正确资料淹没在大量“沾边但不关键”的候选里。rerank 在这里就像复筛,成本比第一轮高,所以更适合在一个较小候选集上使用,而不是整库都拿去做重排。

RAG 为什么会慢,为什么会错,真正应该先查哪里

RAG latency 的优化不该只停留在“换更快模型”。真正有用的排查方式,是沿整条链路看时间花在哪。解析和 embedding 如果没批量化,离线构建就慢;查询改写、召回、rerank、生成如果全串行,在线路径就慢;检索 topK 一味拉大,rerank 和上下文组装就会跟着变重;流式输出如果很晚才开始,用户体感就会变差。更成熟的优化手段,通常包括过滤前置、候选集裁小、rerank 只做在小集合上、热门 query 缓存、embedding 缓存、并行查询和尽早流式返回。

RAG 出错时也有稳定的排查顺序。先看文档处理是不是坏了,再看召回和 rerank 有没有把正确片段带回来,再看权限和版本过滤有没有做错,最后才看模型是不是在已有依据上总结偏了。很多人一看到错答就说“模型幻觉”,其实不少所谓幻觉,根本是检索链路失真。比如用户问“试用期离职提前几天申请”,系统答成正式员工规则,这种错很可能是试用期条款被切散了、旧版本制度排在前面了,或者根本没做文档类型过滤。

多租户、权限、引用和版本,为什么决定 RAG 能不能真正上线

企业 RAG 和公开问答最大的不同,就是它几乎一定带着租户、权限和版本边界。也就是说,系统不是在“全库里找最相似内容”,而是在“当前用户有权看到的那部分资料里,再找最相似内容”。这会直接影响你的索引设计、查询组织和缓存策略。很多系统一上来就只盯向量检索性能,最后上线时真正卡住的却是租户过滤、部门权限、生效版本和文档状态。

引用更不能交给模型自由发挥。用户想要的不是一句“来源于差旅制度”,而是能点开、能回跳、能确认当前版本的真实出处。所以更稳的做法通常是:检索阶段就保留 chunk ID、文档标题、章节位置、页码、版本号,最终由后端把这些真实命中元信息返回给前端。只要引用仍然让模型自由生成,系统就会出现一种很危险的假象:答案看起来更可信了,实际上只是包装更像回事了。

版本管理也是很多 demo 一开始完全没有、上线后一定会补的一层。制度会更新,旧版会失效,embedding 模型可能升级,文档可能被删除或下线。只要系统真实运行一段时间,这些问题都会集中浮出来。RAG 之所以比很多人想象中更像后端系统,正是因为它天然带着数据生命周期管理,而不是“建完索引就永远不动”。

如何评估一个 RAG 系统到底有没有变好

RAG 的评估不能只看最终答案像不像,还要看检索链路本身。更成熟的评估方式,通常会把指标拆成三层。第一层是检索层,例如正确片段是否进入候选集,错误召回主要集中在哪类问题。第二层是生成层,例如答案是否忠于给定资料,是否遗漏关键条件,引用是否错位。第三层是业务层,例如用户追问率、人工兜底率、平均响应时间和单位问题成本。

离线评测和线上评测也必须分开。离线评测用来做稳定回归,所以要有固定问题集、标准引用和历史失败样本;线上评测则更看真实用户行为,例如追问率、点击引用率、人工接管率和成本结构。系统是否真正变好,最终不是靠主观感觉,而是看这两套反馈回路能不能稳定闭合。

一个完整案例:为什么 Postgres + pgvector 会成为很多团队第一版的现实选择

如果岗位偏 Go 后端,Postgres + pgvector 很值得准备,因为它很能体现工程取舍。它最大的优势不是“检索能力天下无敌”,而是“把文档元信息、权限字段、版本状态、业务表和向量数据放在同一套体系里”的工程便利。对于第一版企业知识库来说,最稀缺的通常不是极限检索性能,而是低复杂度下的可维护性。你如果能把这种取舍讲清楚,面试官通常会立刻感觉到你是在按工程现实思考,而不是在背热词。

0. 先把 RAG 想成一件很朴素的事

RAG 这个词很容易把人带偏。很多资料一上来就讲向量、召回、rerank,结果读完之后只记住一堆名词,没有真正理解系统在干什么。更稳的理解方式其实很简单:用户提了一个问题,模型自己并不知道你公司最新的制度、内部 SOP 和私有文档,所以系统要先去找资料,再让模型基于这些资料回答。RAG 做的就是这件事。

如果只说到这里,理解还是不够。因为真正的难点并不在“找资料再回答”这句口号,而在这条链路前后有很多工程步骤。文档得先被系统吃进去,吃进去之后还要整理,整理之后要变成可搜索的形式;用户提问时,系统不能把整本手册都丢给模型,而是要挑出最相关的几段;模型回答完以后,系统最好还要把依据一起带出来。只有把这一整条链路看清楚,你才会明白为什么 RAG 不是“把文档复制进 prompt”这么简单。

继续拿企业知识库主项目来说,RAG 的离线链路通常是“上传文档 -> 解析正文 -> 清洗噪声 -> 切块 -> 算 embedding -> 写入索引”;在线链路则是“用户提问 -> 鉴权 -> 召回 -> rerank -> 组织上下文 -> 生成答案 -> 返回引用”。你把这两条链路分开看,很多原本混在一起的问题就会自动分层。

1. 先解释几个核心词,再讲整条链路

RAG 里最容易让人一眼看晕的几个词,先用工程语言讲清楚。

所谓文档清洗,就是把原始文档中不适合进入检索链路的内容先处理掉。比如 PDF 页眉页脚反复重复公司名称,扫描件里有 OCR 识别错误,网页抓下来的文档带了导航栏和广告,这些内容如果不清掉,后面检索时就会把噪声当成正文。切块则是把一篇很长的文档拆成一段一段更适合搜索的片段,因为真正问答时你不可能每次都把整本制度手册送给模型。Embedding 这个词可以先理解成“把一段文字变成便于比较语义接近程度的数字表示”。它的工程意义不是数学炫技,而是让系统能找到“意思相近但字面不完全一样”的内容。召回是第一轮找资料,也就是先从资料库里捞出一批可能相关的候选片段;rerank 是第二轮排序,用更精细的方法在候选片段里重新排先后;引用则是把真正命中的原文片段和它的出处带回来,让用户知道答案不是模型瞎编的。

这些词一旦翻译成人话,RAG 的主链路就很清楚了。离线阶段,系统负责把文档变成可搜索资产;在线阶段,系统负责把用户问题变成检索请求,把命中的片段整理成模型能用的上下文,再把模型输出包装成带依据的回答。你可以把它想成企业内部的“有查证能力的回答系统”,而不是“让模型临场发挥的聊天框”。

如果你去看 pgvector 这种工程方案,会发现它真正强的不是某个数学细节,而是方便你把这条链路里的业务元数据一起带住。文档表、chunk 表、租户、权限、版本、embedding 都能放在同一体系里,这样召回结果就不只是“相似文本”,而是“带业务边界的候选材料”。

2. 为什么 RAG 真正难在前半段,而不是最后那次生成

很多人一看 RAG 效果不好,第一反应是调 prompt。现实里,真正的根因经常在生成之前。因为模型最后那次回答,本质上只是在利用你给它的上下文。如果上下文本身就是错的、旧的、碎的、越权的,模型再强也只是在错误材料上做合理发挥。

这也是为什么成熟团队做 RAG 时,花时间最多的往往不是“最后那条提示词怎么写”,而是文档入库和检索阶段怎么做好。比如制度文档有旧版和新版共存,如果更新机制没处理好,模型很可能会答出去年已经废弃的规则;再比如同一个问题在不同部门有不同答案,如果租户和权限过滤没做对,系统就可能检索到别的部门资料。表面看像模型胡说,实质上是前面给错了材料。

从这个角度看,RAG 更像一个数据工程加检索工程问题,而不是一个单纯的模型调用问题。面试时如果你能把这层逻辑讲出来,通常会比只会说“向量检索 + LLM”更有工程味。

3. 切块为什么总被追问

切块会被反复追问,不是因为它技术上多么花哨,而是因为它几乎直接决定 RAG 的下限。切块的本质是在做一个平衡:你既不能把块做得太大,也不能做得太小。

块太大时,系统虽然不容易把上下文切碎,但每一块里往往混入很多无关信息。这样一来,召回时精度会下降,送给模型的上下文也会更贵。块太小时,问题又反过来了。看起来检索粒度更细,实际上语义会被切断,尤其在制度、合同、技术文档里,一句话的限制条件、适用对象和例外条款常常分散在前后几行。如果你只把一句“可以报销火车票”召回出来,却把紧跟着的“上限 300 欧元,仅限 P4 及以上差旅等级”切到别的块里,模型就很容易基于半条规则作答。

一个很典型的业务例子是企业报销制度。正文里可能先写“德国境内火车票可报销”,后面又写“商务舱、头等舱需审批”“超额部分需主管确认”。如果你切块只按固定长度粗暴切开,很容易把资格条件和报销规则拆散。最后出现的问题不是模型“不会读文档”,而是系统交给它的资料本来就不完整。

所以切块不是越细越好,也不是越大越稳。你要根据文档形态去定策略。FAQ 文档往往适合一问一答式切块,制度手册和技术规范更适合按章节、标题、段落甚至语义边界来切。很多团队前期先用简单策略起步完全没问题,但一定要保留后续调优空间,因为切块一旦做糙,后面的召回、rerank 和生成都会跟着吃亏。

实际落地时,文档类型不同,切块策略也应该不同。员工手册、报销制度这种层级分明的文档,通常更适合按标题、章节和段落边界切;FAQ 更适合按一问一答切;接口文档和技术手册则常常需要把代码块、表格说明和前后约束一起保住。你在面试里如果能把切块讲成“跟文档形态一起设计”,而不是只报一个固定 chunk size,整体质感会好很多。

4. Embedding、召回和 rerank 到底各自负责什么

Embedding 负责把文本变成便于比较语义的向量,解决的是“哪些内容意思接近”。召回负责第一轮把候选集拉出来,目标是尽量别漏掉可能有用的材料。rerank 负责第二轮在候选集里重新排序,目标是把最值得进入最终上下文的几段放到前面。三者的分工是:embedding 负责表示,召回负责覆盖,rerank 负责精排。

真正理解 rerank,要把它放回完整链路。它处理的不是整库,而是“已经召回到的一批候选”。一条比较完整的 rerank 流程通常有七步:

  1. 先准备查询。输入是用户问题和过滤条件;处理上会补齐缺失限定词、做 query rewrite 或保留关键词;输出是更适合检索的查询。
  2. 做第一轮召回。输入是查询和索引;输出是 top-k 候选,常见规模是 20 到 100 条。
  3. 清理候选。先去掉越权文档、失效版本、明显重复块和明显无关的噪声,避免 rerank 给脏数据打分。
  4. 构造打分单元。把“问题 + 候选片段”组成一对输入,必要时再带上标题、章节、版本、时间等元数据。
  5. 计算相关性分。规则打分会看关键词、版本、字段权重;cross-encoder 或专用 rerank model 会联合看问题和片段后直接给分;LLM rerank 则会判断这段内容是否真的回答了问题、是否带上关键限制条件。
  6. 重排并做去重或多样性控制。只看分数容易把同一段附近的重复 chunk 都排到前面,所以通常还要压重复、保留不同证据面。
  7. 装配最终上下文。最后进生成模型的通常不是 top-50,而是 top-3 到 top-8 的证据集,外加必要的标题、版本和引用信息。

rerank 的关键,不是“再跑一次模型”,而是“更认真地判断这段材料能不能回答当前问题”。它常见有三种做法。规则型 rerank 成本低、可控,适合把版本、文档类型、字段命中、标题权重这些规则直接带进来。专用 rerank model 或 cross-encoder 是在线精排的主力,准确率通常比只看向量相似度高,但成本和延迟也更高。LLM rerank 最灵活,适合判断复杂限制条件、表格规则或长条款,但代价最高,一般只会放在很小的候选集上,或者放到离线评测里。

拿制度问答看最直观。用户问“德国境内火车票报销上限是多少”,第一轮召回可能同时拉出差旅总则、交通规则、商务舱审批条款、旧版政策和新版政策。rerank 真正要做的,不是证明这些都“有点相关”,而是把“当前生效版本里真正写了上限和适用范围的那一段”排到前面,把只沾边的总则、已经失效的旧版、只谈审批例外但不回答上限的问题段压下去。只有讲到这一步,读者才知道 rerank 在系统里到底干什么。

这部分常见难点也很固定。第一,候选片段彼此非常像,只差一个限制条件,例如“可报销”和“仅限 P4 以上可报销”。第二,新版和旧版同时进入候选,文本很像,但业务上必须优先当前版本。第三,用户问题本身缺少限定词,导致 rerank 在高频关键词上打高分。第四,chunk 本身切坏了,关键前提和结论不在同一块里,rerank 再强也救不回来。第五,候选里重复块太多,前几名全在说同一件事,最终上下文反而不完整。

优化 rerank 时,不要先上更大的模型,先按顺序收链路。先看切块和元数据,确认版本、租户、文档类型、章节标题这些信号有没有保住;再看召回候选集是不是过大或过脏;然后再决定是否引入 hybrid search、规则分、cross-encoder 或 LLM rerank;最后才是调 top-k、去重策略和上下文装配。很多团队以为自己在调 rerank,实际真正修好的问题是过滤条件、chunk 完整性或 query rewrite。

排查顺序也要固定。先看正确材料是否已经进入 top-k 候选;如果没进,问题在召回前;如果进了却被压到后面,问题才在 rerank;如果排前了但最终答案仍然不对,再看上下文装配、引用选择和生成阶段。评估时至少看三层:召回层看 recall@k,排序层看 MRR 或 NDCG,答案层看最终命中率、引用命中率和人工追问率。这样你才能知道问题到底出在哪一段,而不是把所有锅都扔给“模型不准”。

5. RAG 为什么会答错,应该沿哪条链路排查

RAG 出错时,最容易犯的错是直接改 prompt。真正有效的做法,是沿数据链路往前查。一个完整 RAG 至少经过八个环节:文档来源、清洗、切块、索引、召回、rerank 与过滤、上下文装配、生成与引用。哪个环节出错,最终都表现成“模型答错了”,但解决办法完全不同。

可以按这个顺序排查:

  1. 看源文档是不是对的。制度是否是当前版本,附件是否漏入库,OCR 或清洗有没有把关键表格、编号、条件删掉。
  2. 看切块是不是把前提和结论切散了。很多“模型没读懂”的问题,本质是系统给它的片段本来就不完整。
  3. 看过滤条件是不是对的。租户、部门、版本、权限范围一旦错,后面回答再流畅也没意义。
  4. 看第一轮召回。正确材料有没有进入候选;如果没进,问题在 embedding、hybrid search、query rewrite 或索引。
  5. 看 rerank 和去重。正确材料进了候选,却被旧版或重复块压下去,才是精排问题。
  6. 看上下文装配。进模型的片段太多、顺序太乱、标题和版本信息没带上,都会让模型在证据足够时仍然答偏。
  7. 看生成和引用。模型有没有忠实依据证据作答,引用是不是来自真实命中片段,而不是自由生成。

拿“试用期员工的离职规则”举例最典型。如果系统总回答成正式员工规则,可能有几种根因:试用期条款根本没入库;切块把“适用对象”切掉了;召回只抓到了“离职流程总则”;rerank 把试用期例外压到后面;或者最终上下文里根本没把“试用期”这个限定词保住。你只有顺着链路查,才知道该改哪一层。

所以 RAG 幻觉更准确的拆法,通常是源数据错误、上下文缺失、权限或版本过滤失败、排序失败、引用失败和最后的生成偏差。把这几类分清,优化才不会停留在“让模型再仔细一点”这种无效动作上。

6. 为什么 Postgres + pgvector 在很多团队里很实用

如果岗位偏 Go 后端,Postgres + pgvector 是很值得准备的一条路径。原因并不神秘,它最大的优势不是“向量检索最强”,而是“工程整合成本很低”。很多团队原本就有 Postgres,用它存业务表、权限表、租户表、文档元数据表都很自然。这时候再把 embedding 和向量检索放进同一套体系,能大幅降低系统复杂度。

这条方案特别适合第一版企业知识库。因为第一版系统真正要解决的问题,往往不是上亿级向量的极限检索性能,而是文档、权限、租户、版本、引用和业务元数据能不能放在一条稳定链路里。pgvector 在这里的好处非常直接:你可以把文档原文、chunk 元信息、更新时间、租户标识、可见角色、embedding 放在同一数据库里管理,很多过滤条件和业务查询也更容易一起写。

当然,它不是没有代价。数据规模非常大、索引调优要求很细、检索形态复杂到需要专用引擎时,专门的向量数据库或检索系统会更合适。但对大量实际团队来说,第一版最稀缺的不是极限性能,而是低复杂度下的可维护性。面试时如果你能把这个取舍讲清楚,会比简单背“pgvector 很常见”强很多。

在 Go 后端里,这条路线尤其顺手。你可以用一张 documents 表存文档元信息,用一张 document_chunks 表存 chunk 内容、embedding、租户和版本,再用普通 SQL 把业务过滤和向量排序组合起来。这样当用户只允许看到自己部门和当前生效版本的制度时,检索逻辑不会散在两套系统里,而是还能被放回熟悉的数据库边界中。

7. 索引、过滤和权限,为什么要放在一起看

很多学习材料把向量索引讲得很热闹,却很少提醒你:企业场景里,向量相似度往往不是唯一条件。真实系统常常还要同时考虑租户过滤、部门过滤、文档类型过滤、时间范围过滤和权限过滤。也就是说,系统不是在全库里无差别找最相近的片段,而是在“当前用户有权看到的资料范围内”再去找相近内容。

这会直接影响你的索引和调优思路。比如某个租户只有几千到几万条 chunk,查询时又必须严格带上租户 ID 和文档类型过滤,那么最先拖慢系统的可能不是向量距离计算,而是过滤条件没设计好、元数据组织太乱、缓存和分页策略不合理。很多人一看到检索慢,就急着上更复杂的近似索引,结果收益并不明显,因为真正的瓶颈根本不在那。

所以讲 pgvector 或任何检索引擎时,都不要把它只讲成数学问题。它首先是个工程问题:你的文档如何建模,你的权限如何约束,你的查询如何组织,你的索引如何配合业务过滤。

8. 引用为什么必须由后端兜住

企业场景里,带引用回答几乎已经不是加分项,而是基本可信度要求。用户真正想知道的不是“模型说得像不像”,而是“这句话到底依据哪份文档、哪个位置、是不是当前有效版本”。如果引用只是让模型自由写一句“来源于差旅制度”,这在工程上几乎等于没有引用。

更稳的做法是:系统在召回阶段就保留好片段 ID、文档标题、章节信息、页码或段落位置,最终返回答案时由后端把这些真实命中的元信息一并返回。这样前端展示的引用条目来自真实检索结果,而不是模型临时编造的出处。你甚至可以进一步要求模型只能在后端提供的候选引用里做选择,而不能自由创造新来源。

这件事为什么重要?因为一旦引用也由模型自由生成,系统会出现一种很危险的错觉:答案看起来更可信了,实际上只是包装更像回事了。对于企业制度、法务、合规、财务这类场景,这种“像真的一样的假引用”比没有引用更糟。

前端体验也会直接受益。因为引用一旦由后端兜住,你就可以在 UI 上放心做“点击跳到原文段落”“展开上下文前后几段”“按版本查看来源”这类能力。反过来,如果引用只是模型生成的自由文本,前端哪怕长得再像企业产品,底层也没有真正可核验的依据。

9. 更新、失效和版本管理,决定 RAG 能不能长期可用

很多 demo 里,文档入库一次就不再变化,所以 RAG 看起来像静态系统。真实业务恰好相反。制度会改版,FAQ 会补充,旧政策会废弃,组织架构变更后权限也会变。只要这些变化真实存在,RAG 系统就必须处理更新和失效。

这意味着文档不是“建完索引就完事”,而是有完整生命周期。文档更新后,旧 chunk 和新 chunk 怎么替换;embedding 模型升级后,是否要重算历史向量;被删除或下线的文档,怎样从索引和缓存里清掉;前端正在看的引用,是否还指向当前有效版本。这些问题平时不提,系统上线一段时间后一定会浮出来。

你完全可以把它理解成传统搜索系统里的索引刷新问题,只不过现在多了一层模型生成。也正因为如此,版本管理和数据刷新其实不是 RAG 的补充功能,而是它能否长期可信的必要条件。

这类更新工作通常也应该走后台任务。比如差旅制度改版后,你不太可能同步重算几万条 chunk 的向量;更合理的做法是把新版本文档入库,启动重新切块和重 embedding 任务,等新索引准备好后再切流量,并把旧版本标成失效。这样用户不会在更新窗口里看到半新半旧的答案。

10. 用一个完整案例把整条链路串起来

假设你在做一个企业差旅知识库,用户问:“德国境内火车票的报销上限是多少?” 一个成熟系统不会直接把问题丢给模型,而是先做几件非常具体的事。它先根据登录用户确认租户和权限范围,确保只能检索本部门、本地区、当前版本有效的制度;然后在这个范围内做第一轮搜索,找出和“火车票”“德国境内”“报销上限”最相关的几个片段;如果候选片段比较多,再做第二轮排序,把真正提到上限规则的段落排在前面;接着把这些片段连同问题一起交给模型,让模型在已有依据上组织语言;最后由后端把答案和真实引用位置一起返回。

如果这条链路里任意一环做得不对,最后都会表现成“答错了”。没有权限过滤,可能答到别的部门制度;切块太碎,可能只召回“可报销”而漏掉“上限 300 欧元”;更新机制没做好,可能答出旧版政策;引用没兜住,可能附上一条看似专业、其实并不存在的出处。你会发现,RAG 的难点从来不是最后那一行模型调用,而是前后整条链路是否扎实。

很多人第一次学 RAG,很容易形成一个误解,好像“既然已经有 embedding 了,关键词搜索是不是就过时了”。真实业务里恰恰相反。纯向量检索擅长找语义相近内容,但它对精确字符串、编号、术语缩写、版本号、产品名、岗位级别这类内容并不总是稳定。比如用户问“EXP-123 这张报销单为什么被拒了”“P4 差旅等级能不能坐商务舱”“SOC2 报告在哪个库”,这些问题里都有很强的关键词特征。只用向量检索,常常会把语义接近但关键标识不一致的片段排到前面。

Hybrid search 的思路其实很朴素,就是把向量检索和关键词检索结合起来。关键词部分保证精确符号、编号、专有名词不容易丢,向量部分保证语义近义表达也能被召回。然后再在候选集上做合并、去重、打分和 rerank。企业知识库里之所以常见 hybrid search,不是因为大家喜欢把系统做复杂,而是因为真实文档同时包含两类信息:一类是靠语义理解的自然语言,一类是靠精确匹配的制度编号、角色等级、工单号、接口名、错误码。只靠其中一类信号,另一类问题就会掉精度。

如果你用的是 Postgres + pgvector,这件事也并不神秘。完全可以把全文检索或倒排搜索和向量相似度一起做,再结合租户、版本、文档类型这些元数据过滤。第一版系统不一定要把打分公式做得很复杂,但你至少要知道什么时候 hybrid search 值得上。一个非常实用的判断方法是:如果你的问题里经常出现编号、精确术语、缩写、产品名、版本号、岗位级别,那么单纯向量检索大概率不够稳。

这里也顺手能回答“embedding 模型怎么选”这类高频题。第一看语言覆盖和领域匹配,至少要确认它对你主要语种和文本形态是否稳定;第二看向量维度、延迟和成本,因为文档量一大,重建索引和在线查询都会受影响;第三看你主要是在做长文段检索、短问句匹配,还是多语种跨语言检索。第一版系统通常不需要为了榜单差一点点而盲目追最重模型,能先保证语言覆盖、召回稳定和工程成本可控,往往更重要。

12. RAG latency 怎么优化,先优化哪几段

RAG 慢的时候,最忌讳的做法是只盯着最后的模型生成。因为一次 RAG 请求通常至少包含问题预处理、查询 embedding、召回、可能的 rerank、文档抓取、上下文拼接和最终生成这几段。真正优化时,你要先把每段耗时拆出来。很多团队一开始主观感觉“是模型太慢”,拆 trace 以后才发现时间大头其实落在文档过滤、过大的候选集、串行 rerank 或数据库 I/O 上。

比较常见的优化顺序通常是这样的。先做元数据预过滤,尽量别在全库里无差别召回;再控制候选集规模,让 rerank 只处理真正可能相关的那几十条,而不是上百上千条;能并行做的检索尽量并行,比如关键词召回和向量召回一起跑;对稳定文本做 embedding 缓存,对热门问题做召回缓存;必要时把很重的文档预摘要、表格预解析、FAQ 结构化处理放到离线链路里,别把所有负担留给在线查询。你会发现,很多 latency 优化看上去在做“AI 调优”,实际上做的还是熟悉的后端工作:减少不必要的 I/O、缩小工作集、把重计算前移、避免串行等待。

一个很常见的误区,是为了提高准确率无限扩大 chunk 数和上下文数。这样做有时会让召回看起来更全,实际却把 rerank、上下文拼接和最终生成一起拖慢。更稳的做法通常是让召回负责“别漏掉关键候选”,让 rerank 负责“只把最值得看的几条排上去”,然后让模型在相对干净的上下文里回答。RAG latency 题真正想考的,不是你会不会背几个优化词,而是你能不能把“哪一段最慢、为什么最慢、该优先动哪里”讲成一条清楚的分析链路。

13. RAG 系统效果应该怎么评估,才不只是“感觉变好了”

RAG 的评估最容易答虚,因为很多人会把它直接等同于“最终答案看起来准不准”。更稳的做法,是把评估拆成检索层、答案层和线上运行层三段来看。检索层先回答一个很硬的问题:真正有用的材料有没有在前几条候选里出现。因为如果正确材料根本没被召回,后面的生成再怎么调都只是补锅。答案层则要看模型有没有忠实使用这些材料,是否漏掉了限制条件,引用能不能对上真实来源。线上运行层再补延迟、成本、追问率、人工接管率和引用点击率,判断系统是不是在真实使用中也更稳。

一个很实用的评估集,通常不会只收“标准问答对”。它至少应该覆盖几类样本:高频问题、历史失败案例、边界条件、容易召回到旧版文档的问题、权限敏感问题,以及那些看起来很像、实际上答案不同的问题。比如“正式员工离职提前几天申请”和“试用期员工离职提前几天申请”,字面很近,但适用规则完全可能不同。你如果不把这种样本放进评估集,系统改版以后即使把一堆普通问题答得更顺,也可能把真正危险的边界问题做坏。

更成熟的团队还会把评估和 trace 连起来看。比如你把 chunk 策略从按固定长度切改成按章节切,离线结果显示引用更完整了,但线上却发现平均延迟上升,人工追问率没有下降,这就说明改动不是“绝对变好”,而是发生了取舍。RAG 评估真正想建立的,不是一个单一分数,而是让你知道自己到底提升了哪一段,牺牲了哪一段。只要你能把这层取舍讲清楚,面试官通常就会把你当成在经营系统,而不是在调一个黑盒模型。

更具体一点,一个够用的最小闭环通常是这样的:先挑 20 到 50 个高价值问题,给每题至少标出期望命中的文档或片段,再记录答案要点和禁止犯的错误。以后每次改 chunk、embedding、rerank、过滤或 prompt,都先看检索层的 recall 和引用命中,再看答案层是否真的更稳,最后再看延迟和成本有没有一起恶化。这样你改的是哪一段,评测就能对到哪一段,而不是每次都只看一个含糊的“整体感觉更好了”。

另外,别把所有问题混在一起看一个总分。对企业知识库来说,至少要把编号与术语型问题、规则例外型问题、权限敏感型问题、版本更新型问题分开看。因为系统最容易出事故的,往往不是平均问题,而是这些边界样本。只要你把维度拆开,评测结果才真的能指导工程取舍,而不是停留在漂亮但没法行动的数字上。