RAG 比你想的简单

查看原文 HN 讨论

文章摘要

作者 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 全程在评论区回复。

支持作者论点的一派

反对或补充的一派

关于「文章像 AI 写的」

关于缩写要不要展开

作者 j0selit0 的几次回复也值得一提:Tycho 说没看懂方案四「按需嵌入」,作者解释说流程是第一步用稀疏索引(FTS/BM25)取回比如 10 条记录,第二步用嵌入对这 10 条重排,区别在于第二步是在跑检索管线时才把文本转成向量,因此不需要把整个语料预先嵌入进向量库。gabosarmiento 建议给每道菜谱配上对应的评测结果排名,作者说这是个绝妙的主意,但那样文章会长到离谱,也许该写成一个系列。