RAG 比你想的简单
文章摘要
作者 Rafael Pierre 在自己的 Lighthouse 通讯里写的一篇「RAG 架构菜谱」。核心主张只有一句:现在大多数人都在过度设计自己的 RAG 技术栈——一上来就是嵌入向量、向量数据库、重排管线,而用户其实只是想找到那篇写着「如何重置密码」的文档。
文章先给出六个决策因子:数据新鲜度(实时更新的新闻、社媒适合易于重建索引的方案,语料稳定、月度或季度更新才适合预嵌入)、语料特征(日变更超过 10% 就别做全量预嵌入;长尾分布——90% 的文档从没被访问过——则更适合按需嵌入)、查询模式(关键词型查询从全文检索起步,语义型和对话型查询才受益于嵌入)、规模与性能(每天不到 1000 次查询用最简单的方案就够,1K–10K 做选择性优化,超过 10K 才值得全面优化)、团队能力(没有 ML 经验就停在全文检索加查询改写)。然后是六道「菜谱」,作者反复强调:从上往下读,只有在拿到数据证明确实需要时才往下走一格。
方案一:只用全文检索。 就是老实巴交的 BM25、Elasticsearch、Postgres 全文检索——「在『嵌入』变成一个动词之前就存在的那些东西」。适用于刚起步、用户写关键词式查询(比如 pandas merge dataframe)、精确匹配很重要(发票号 #12345)、语料里有大量专有术语的场景。优点是零 API 成本、10ms 以内、易于调试(能看清一篇文档为什么被命中)、不需要分块策略、没有评估复杂度、没有模型弃用风险。缺点是搞不定同义词(car 和 automobile)、答不了语义型问题、理解不了关键词之外的意图。作者强调:一旦跳过这步直接上嵌入,你立刻要面对分块大小选 512 还是 1024、重叠多少、用语义分块还是定长分块、以及怎么评估分块好不好这一整套问题;而全文检索里,你的文档就是你的文档。
方案二:智能体式查询改写。 用 LLM 把用户凌乱的问句改写成干净的关键词查询。作者认为这是全文的核心洞见:大多数所谓「语义搜索」问题,其实是查询表述问题。LLM 可以去停用词、加同义词、把领域术语翻译过来(「加速代码」→「优化性能」)、把复合查询拆解开,还能从系统提示词里的术语表中学习。成本约每次查询 0.001 美元(用 GPT-4o-mini 做改写)。它比嵌入灵活的地方在于:嵌入方案效果不好时你得调分块策略、重新嵌入整个语料、跑回归测试,然后祈祷有改善;查询改写效果不好时你只需改系统提示词,立刻就能测。作者还给了一个多轮智能体改写的循环示例——改写、检索、评估质量、不达标就根据反馈继续调整,全程不需要重新嵌入任何东西。
他用一个「专有术语问题」把这点讲透:假设你公司有个叫 Atlas 的 Python 框架,通用嵌入模型训练自互联网,它眼里的 Atlas 指向希腊神话、地图、地理,和你那些讲数据处理的 Atlas 文档相似度只有 0.15。而查询改写只要在系统提示词里声明这些专有词不得修改、必须当作精确关键词保留,BM25 就能完美命中。结论是:对专有术语,精确关键词匹配胜过语义理解。
方案三:混合检索(稀疏 + 稠密重排)。 用 BM25 取回前 50–100 条候选,再用嵌入重排出前 10 条。适用于用户提语义型问题、且你已有数据证明 BM25 加查询改写不够用、能容忍 100–500ms 延迟、语料相对稳定的情况。作者算了笔账:按 OpenAI text-embedding-3-small 每百万 token 0.02 美元计,每次查询嵌入 50 篇文档(平均 500 token)约 0.0005 美元,每天 1000 次查询一个月大约 15 美元——成本其实很合理。真正的代价是延迟:临时嵌入 50 篇文档要多花 200–500ms,对面向用户的搜索来说这是能被感知的。他提醒,引入嵌入的同时,分块问题就回来了。
方案四:按需嵌入(On-The-Fly)。 如果数据变得很勤,为什么要一遍遍重新嵌入全部内容?适用于文档日变更超过 10%、实时内容、正在试验不同嵌入模型、数据新鲜度至关重要、重排的 K 值较小(20–50 篇)的场景。特征是:嵌入成本每月约 15 美元(持续支出)、存储成本 0(只存文本)、延迟 200–500ms、新鲜度完美、换模型只需改一行代码。作者特别拿模型弃用说事:OpenAI 弃用 text-embedding-ada-002 换成 text-embedding-3,如果你用旧模型预嵌入了一千万篇文档,你要重新嵌入这一千万篇、更新向量库、跑回归测试、验证质量没退化、处理切换期、应对 API 变更;而按需嵌入的方案,字面意义上只改一行代码。
方案五:预嵌入 + 冷热分层。 高频访问的文档预嵌入(热层),低频文档按需嵌入(冷层),依据是访问模式的帕累托分布——20% 的文档吃掉 80% 的流量。实现上是 BM25 取候选、按热/冷拆分、热的走向量库、冷的现场嵌入打分,再合并排序;同时周期性地把访问次数超阈值的新文档提升到热层,只嵌入新晋热文档。适用于访问模式分化明显、语料在 10 万篇以上、内容有稳有变、常见查询需要好延迟的场景。换模型时的账很直观:全量预嵌入要重嵌 100 万篇花 1 万美元还带停机,冷热分层只需重嵌 20 万篇花 2000 美元且几乎不停机,按需嵌入则是改一行代码、0 美元、零停机。
方案六:全量预嵌入。 全部提前嵌入、存进向量库、用近似最近邻检索。适用于每天超过 1 万次查询、需要 50ms 以下延迟、语料极稳定(月变更低于 5%)、访问模式很宽(没有长尾)、且有 ML 团队维护基础设施的场景。100 万篇文档的账是:一次性嵌入 10 美元,存储 1M × 1536 维 × 4 字节 = 6GB(每月 10–30 美元),检索 50ms 以内,新鲜度取决于上次重建索引。作者的判断是「这对多数系统都是杀鸡用牛刀——我见过团队花几个月优化向量库配置,而查询改写本可以解决他们 90% 的问题」;但如果你是 Pinterest、Shopify 这个量级又有稳定语料,那你最终会落到这里。
文章后半段讲多意图查询问题。前面讨论的都是单意图查询,但真实用户会问「我怎么读取 CSV 文件、清洗缺失数据、再把结果画出来」——这是三个意图,当成一条查询去搜,就像找一家同时供应披萨、寿司和塔可的餐厅。他把 Perplexity 这类现代智能体式 RAG 的做法拆成三步:查询理解智能体把问题拆成子查询并标出依赖顺序;并行自适应处理,给每条子查询选最合适的路径(简单的走去停用词加词形还原再 BM25,成本 0、15ms;复杂的才走 LLM 改写加多路检索,成本 0.001 美元、250ms),因为是并行,总延迟取最大值而不是求和;最后合成结构化的答案。成本对比是:不做分解要 0.03 美元,分解后只要 0.002 美元,便宜 15 倍且质量更好。
最后是决策树和一条 80/20 规则。决策树的起点是:你有搜索吗?没有就先去搭 BM25,「说真的,别读了,去搭」。有了就先测基线——跑 2 到 4 周收集用户反馈,用户满意就到此为止,去发布别的功能。不满意就看主要抱怨是什么:「明明存在的文档找不到」先试查询改写,每次 0.001 美元且不用重建索引,A/B 测两周;「结果还行但不够好」再 A/B 测混合检索,然后按数据变更频率、是否有明显热文档、语料是否稳定来选具体实现。他给出的分布是:60% 的系统应该停在全文检索加查询改写,25% 需要混合方案(按需嵌入或冷热分层),10% 需要全量预嵌入,5% 需要定制方案。「别做那个为 60% 的问题去建 5% 解法的人。」
HN 评论精华
这条帖子拿到 507 分、213 条评论。讨论几乎分成三股互不相干的洪流:一股是真正的技术争论(全文检索到底被低估还是被高估),一股是关于文章标题里的缩写 RAG 该不该展开的漫长跑题(意外地成了最长的子线程之一),还有一股是「这篇文章一看就是 AI 写的」的集体挑刺。作者 j0selit0 全程在评论区回复。
支持作者论点的一派:
- usernametaken29 的发言最有分量:他做过大规模 RAG 系统,「人们严重低估全文检索、严重高估嵌入」。全文检索简单、可移植、可扩展,靠 80/20 法则就能走得很远;嵌入看着神奇,真做进去就会发现语义相似度没你想的那么好,也不可能让所有人满意。你会不可避免地为了越来越精确的嵌入检索而反复重新嵌入不同的分块,然后为了最后一公里再加上重排,与此同时还得承担向量检索的运维负担。
- bob1029 说得更直接:在老实巴交的 Lucene 上加智能体式查询改写就是终局,这几乎能提供语义方案的全部魔力,而让智能体迭代式地查询文档库才是能力真正打开的地方。他的核心论据是:「嵌入和语义搜索是在非确定性之上再加一层非确定性,这从根上就是被诅咒的。词法检索更容易控制、迭代和调试,工具链极其成熟,你的用户大概也更喜欢它。」
- trivet 一句话总结全文:从 BM25 开始,只有在关键词搜索真的不管用时再加嵌入,能省掉很多痛苦。mmargenot 补充说现在这么多技术栈都白送 BM25,虽然他仍喜欢针对特定语料调语义搜索,但 BM25 很难被超越。comandillos 提供了实测:把几千份文档索引进 SQLite 的 FTS5,接上 DeepSeek v4 Flash,效果远好过他公司之前试过的所有商业方案。jankovicsandras 贴出了他用 PL/pgSQL 实现的 BM25(含与 pgvector 做 RRF 倒数排名融合的混合检索版本,公有领域授权),给只有 Postgres 的人。
- waximabbax 贡献了全场最有说服力的反面实证:他们把检索从自家编码智能体里整个拿掉了,而促成这个决定的不是跑分——他们发现检索路径因为一个技术 bug 已经长期返回零结果,却没有任何人察觉,实际上效果反而更好。做完严格的 A/B 测试后他们放弃了索引。他的解释是:代码仓库本身就是可搜索的,import、调用点、文件名、测试名,grep 便宜又可靠地提供了索引能做的事,而且智能体能读命中处的上下文来验证;而分块检索递给模型的东西「看起来对」,模型就倾向于相信它,不再去找真正的源码。他还观察到 Opus 5 和 Fable 这类最聪明的模型本来就大多会忽略那些分块,猜测它们被训练成不信任代码库上的相似度匹配。他补充说带文档的超大代码库是另一回事——「你没法 grep 一个你叫不出名字的概念」,那种情况他仍会用检索。
- jillesvangurp 给了一个很平衡的框架:RAG 本质上就是老派信息检索,只是查询方从人换成了 LLM,可以包含向量检索但没有它也能跑;把向量检索当成能不费力就让搜索变好的魔法粉末,往往不奏效,还会带来成本和复杂度。RAG 的关键是用尽量少的查询把正确的信息塞进上下文,这要求好的召回率和好的精确率。AI 时代之前搜索团队做的那些事——ETL 预处理、测试和评测搜索质量——仍然是优化 RAG 体验的最好办法,垃圾进垃圾出的原则照样成立,「搞砸了这些,多少 AI 也补不回来,或者只能用大量 token 和时间去补」。
反对或补充的一派:
- 7734128 打的是头阵:这类博客这几年出了太多了。嵌入确实算力开销大,但一点都不复杂,收益却很大,90% 的文档型 RAG 项目应该把嵌入语义搜索当作主力手段——它非常强大又极易实现,与其提前预判性能会不会成问题,不如直接试一下再看。akshay_akula 附议:嵌入试错成本低、也很难搞砸。
- MarkMarine 给出了最具体的反例:这完全取决于你的用户和语料。在金融文档上、用户大量使用行话和缩写时,BM25 会在最平常的查询上失手——「你最后等于要把一整个经济学硕士学位编码进查询改写逻辑里」。碰上私募市场的客户,他们对「常用词」还有自己的一套定义,难度翻倍。他试过好几次替换嵌入/摄取管线,在大规模文档上没有一次比原方案更好:性能很差,智能体反复改写、反复重试才能找到想要的东西,而向量检索只要前期做一点功课就好得多(尽管贵)。
- kaon_2 提了一个 BM25 派没有正面回答的问题:他们的技术人员用不同语言检索,知识库本身也是多语种的,全文检索怎么可能行得通?mdp2021 从原理上补刀:子串匹配找不到同义词、找不到迂回表述,也识别不出形近实异的词。timedude 对 KaseyKim「用户只记得描述、不知道确切名称」的场景给的答案也是嵌入更合适。
- jameshart 提了一个被忽略的角度:当发起查询的是 LLM 时,人们其实也高估了全文检索的必要性。如果底层是结构化记录(比如客户数据库),人类可能没时间或没能力弄清「按电话号搜要从 contacts 表 join 到 users 表再规范化号码」,所以才不得不把电话号也塞进全文索引;但只要给智能体一份 SKILLS.md 和 schema,它很乐意手写正确的 SQL。「把模糊搜索转成精确的数据库查找,是 LLM 增强用户的一个绝佳方式。」
- piterrro 认为 RAG 只有在让 LLM 复核检索结果、挑出最相关的、必要时再发一轮查询迭代下去时才有意义;直接把向量检索结果(哪怕加了重排)原样倒出来,就是在自找「为什么这堆垃圾会出现在结果里」这类奇怪的用户提问。_the_inflator 的看法更偏工程哲学:「RAG 是路由和决策」,他做的是高度模块化的编排系统,根据上下文和所需输出决定调度哪些专用模块;他还举了一个关键场景——在受监管的行业里,某些信息(比如价格)必须永远准确,这时向量检索就成了负债,你必须重新考虑如何混合事实性内容与概率性内容。jmutex 则把矛头指向另一个变量:以他的经验,分块大小比检索模型重要得多,这个搞错了别的都救不回来。freakynit 提出了分块方案的老问题:跨分块的指代关系怎么办——后面的分块用「它」指代两块之前的东西,查询时那个分块根本匹配不上。
- simianwents(simianwens) 提了一个被广泛认同的现象:今天的编码 harness 几乎都不用嵌入,就用最朴素的 grep,「我原本不会预测到这一点」。anthonypasq 反驳说 Cursor 仍在用嵌入并且认为比纯 grep 效果更好。andai 贴出了 Anthropic 两年前的 contextual retrieval 文章,认为可能仍是当前最优。
关于「文章像 AI 写的」:
- apavlinovic 开了这一枪:短促的断句、古怪的行话、「Recipe 4: On-The-Fly Embedding (The Fresh Data Play)」这种小标题,都是可预测的破绽;他抱怨大多数句子不知所云、只有一串「是什么」而没有「为什么」。dsego 补充说注意到「Real talk」和「Why this is underrated」之后就再也无法忽视了。紧接着 7734128 用一句完美的模仿收尾:「他们说得对——而这正是为什么这是一个承重的观察,它切中了问题的核心。」seamossfet 给了更结构化的批评:这类 AI 写的文章有个固定套路,先讲想法一,再讲想法二,最后想法三是前两者的某种缝合;Claude 尤其喜欢抛出混合方案和折中方案来回避做选择,然后把这个缝合体包装成「两全其美」,哪怕它近乎讲不通。ShinyLeftPad 最刻薄:「你直接问你的 LLM 就能得到这篇文章,这本该是一条提示词。」
- Planktonne 提供了不同视角:你的大脑极擅长模式识别,它不盯着 LLM 生成的文字看,跟它不会盯着墙纸看是同一个道理。cpdomina 认为最大的破绽其实不是文风而是内容。Alifatisk 承认自己粗读时觉得还行,读到评论说这是 LLM 写的之后就失去了兴致。
关于缩写要不要展开:
- Angostura 起头说他特别反感懒得在首次出现时展开缩写的文章,并贴了 RAG 的维基链接。dotancohen 反驳说这篇的读者早就熟悉 RAG,「讨论 OLED 屏的文章要是先解释 OLED 是什么,那就是这篇文章的层次远低于我需要的层次」。vaylian 提出折中:加个维基链接就解决了。ninkendo 给了这条线程最好的反驳:「我总想象成去餐厅跟服务员要菜单,对方说『自己 Google 去』——不是我不会,而是网站本该链接到它认为相关的背景信息,这就是为什么它叫『网』,链接是核心概念。」他还补了一句诛心的:文章标题是《RAG 比你想的简单》,他点进去时想的是「我不知道 RAG 是啥,好,我愿意学点东西」,结果文章完全没解释它——一篇主旨是「讲清楚某样东西有多简单」的文章却不解释那样东西,多少有点误导。arjie 站在另一边打了个有力的比方:对熟悉这个领域的人来说,那等于每篇硬件文章都得写成「英特尔中央处理器(CPU)配现代双倍数据率第五代(DDR5)随机存取存储器(RAM),加上英伟达图形处理器(GPU),来运行存储在固态硬盘(SSD)上的大语言模型(LLM)」,很快就没法读了。triceratops 给了最合理的中间地带:有歧义的缩写要展开(rag 本身是个英语单词),无歧义的不必(oled 只有一个常用含义)。AshleyGrant 则把话题拉到了门槛问题上,坚持「对真心想学新东西的人降低门槛,永远不是坏事;爬上梯子之后把梯子踢掉是必须被制止的行为」。
作者 j0selit0 的几次回复也值得一提:Tycho 说没看懂方案四「按需嵌入」,作者解释说流程是第一步用稀疏索引(FTS/BM25)取回比如 10 条记录,第二步用嵌入对这 10 条重排,区别在于第二步是在跑检索管线时才把文本转成向量,因此不需要把整个语料预先嵌入进向量库。gabosarmiento 建议给每道菜谱配上对应的评测结果排名,作者说这是个绝妙的主意,但那样文章会长到离谱,也许该写成一个系列。