Skip to content
Swil
  • Writing
  • Work
  • Vibe
  • Lab/lab3D scenes, a playable piano, the solar system.
  • 3D Models/modelsModels you can turn and look around.
  • Music/musicA listening room, an aespa archive and a piano.
  • Terminal/terminalA small command line in the browser.

Things to click, turn and listen to.

  • Planner/tools/workA week of tasks with a focus timer.
  • Image editor/tools/image-editCrop, resize and adjust in the browser.
  • Typing/tools/typingA typing trainer.
  • All tools →/tools

Small tools I use myself, all in the browser.

  • Finuwell↗finuwell.comAn atlas of how the body works.(opens in a new tab)
  • Swil News↗news.supwil.comAI and tech news, updated daily.(opens in a new tab)
  • SwirlRead↗swirl-read.vercel.appA Markdown reader for a notes folder.(opens in a new tab)

Separate apps I built, each on its own site.

  • Resources/resourceTools and reading I keep going back to.
  • Perf/perfThis site's own measurements.
  • Changelog/changelogWhat changed on the site, and when.

Reading I go back to, and how this site is measured and changed.

Writing/blogWork/projectVibe/vibe

Explore

Lab/lab3D scenes, a playable piano, the solar system.3D Models/modelsModels you can turn and look around.Music/musicA listening room, an aespa archive and a piano.Terminal/terminalA small command line in the browser.

Planner/tools/workA week of tasks with a focus timer.Image editor/tools/image-editCrop, resize and adjust in the browser.Typing/tools/typingA typing trainer.All tools →/tools

Finuwell ↗(opens in a new tab)finuwell.comAn atlas of how the body works.Swil News ↗(opens in a new tab)news.supwil.comAI and tech news, updated daily.SwirlRead ↗(opens in a new tab)swirl-read.vercel.appA Markdown reader for a notes folder.

Resources/resourceTools and reading I keep going back to.Perf/perfThis site's own measurements.Changelog/changelogWhat changed on the site, and when.

Site

  • Home
  • Writing
  • Work
  • Vibe
  • RSS

Playground

  • Lab
  • 3D Models
  • Music
  • Terminal

Tools

  • Planner
  • Image editor
  • Typing
  • All tools→

Apps

  • Finuwell↗
  • Swil News↗
  • SwirlRead↗

More

  • Resources
  • Perf
  • Changelog

Elsewhere

  • GitHub
  • LinkedIn
  • Bilibili
  • Email

Huahao Shang, 2026 · Privacy

Built with Next.js · measured at /perf

Writing/Essay

对于RAG技术的原理性拆解与个人思考

RAG是什么?它怎么和大模型相辅相成?从浅入深有多少种RAG的开发方式?怎么做优化?优势和劣势怎么看?

6 Jul 202611 min readUpdated5 Aug 2026Ai · Rag

Contents

  1. RAG 简述
  2. RAG 的两个阶段:索引阶段(Indexing)与 查询阶段(Querying)
  3. 索引阶段:把知识变成可检索的向量
  4. 查询阶段:从向量库中捞出最相关的内容
  5. 一些高级的 RAG 技术
  6. 内容更新要如何应对?
  7. Fitnuhealth 的 RAG 技术
  8. 构建个人 RAG 知识库

RAG 简述

RAG (Retrieval Augmented Generation,检索增强生成)是与大语言模型 LLM 和智能体 Agent 等技术密不可分的一项技术。当你有一批已经整理好的内容,比如十几万字甚至百万字的规范描述,知识内容,内部文档等等,单单堆积提示词已经难以做到目标效果。

即便你有 50k-1M token context,将几十万字内容作为输入让大模型全量处理已经不太适合,后续导致的注意力缺失问题和计算成本是难以招架的。但通过符合场景的 RAG 技术可以大幅缓解这种场景的痛苦。

通过对海量文本的向量化存储和优化查询,在最终发给大模型 Prompt 输入之前对 Prompt 提示词做内容补全。这样可以既保持一个合理的输入 size,同时让大模型拿到基于用户原 Prompt 有关的特定内容,让大模型有机会做准确回复。

说到这里,你可能会有一个直觉:不去纠结 RAG 技术中的各种细节和花哨方法,只专注于它解决的核心问题 ——RAG 是不是本质上就是一种 "高级的 Prompt 拼接",属于 Prompt Engineering 的一部分?

这个问题我思考了很久。在做 fitneheal 这个项目的过程中,我越来越觉得:表层看是 Prompt 拼接,深层看是一整套工程体系。接下来我会拆解 RAG 的具体原理,再结合我在 fitneheal 中的实战经验,看看这个说法到底站不站得住脚。

https://fitnuhealth.supwil.com/zh/assistant


RAG 的两个阶段:索引阶段(Indexing)与 查询阶段(Querying)

RAG 可以清晰地拆成两个阶段:建索引和查索引。前者是离线、一次性的(或增量的),后者是在线的、每次查询都要走的。

索引阶段:把知识变成可检索的向量

原始数据 -> 清洗与解析

原始数据可能是 Markdown、PDF、Word、HTML..... 第一步是把它们统一解析成干净的文本结构。这一步看似简单,实则是很多 RAG 系统翻车的地方,PDF 表格解析错乱、HTML 标签残留、中英文混排格式混乱,都会直接影响后续 embedding 的质量。

在 fitneheal 中,我们的内容是结构化的 TypeScript 数据(@fitnuhealth/content 包),所以这一步几乎是免费的。但对于大多数项目来说,这是一个需要认真对待的工程问题。

文本分割(Chunking)

这是 RAG 里最被低估、也最影响效果的一步。把长文本切成多大的块?按什么规则切?

常见的几种切法:固定字符窗口(每 500 字一刀切,简单但经常切断语义)、语义边界切分(按段落 / 章节切,语义完整但大小不均)、重叠窗口(块之间留一部分重叠,防止关键信息掉在切割线上)。

我个人的经验是:最好的 chunking 是让内容作者来帮你做。 如果你的内容本身就是结构化的(比如一篇篇文章、一个个知识点),直接用作者定义的边界来切,效果远好于任何自动切分算法。fitneheal 就是这么干的 —— 一个 scene 就是一个 chunk,天然语义完整。

向量化(Embedding)

把每个 chunk 的文本送入 embedding 模型,转换成一个固定维度的向量。这个向量就是这段文本的 "语义指纹"—— 意思相近的文本,在向量空间里的距离也近。

选模型的时候注意两点:一是语言适配性,中文语料就选中⽂强的模型(bge-m3、Qwen3-Embedding 这类),别默认上 OpenAI 的英文模型;二是维度和成本的 trade-off,维度越高表达力越强,但存储和计算也越贵。

存储

向量 + 原始文本 + 元数据一起存进向量数据库。常见选择:pgvector(PG 扩展,适合已经在用 PG 的团队)、Pinecone / Weaviate(专门的向量库)、Chroma / FAISS(轻量本地方案)。

fitneheal 用的是 pgvector on Neon,索引类型 HNSW,cosine 相似度。选它的原因很实际:我们已经在用 Neon 做主库,没必要为了向量再引入一套新基础设施。

查询阶段:从向量库中捞出最相关的内容

输入向量化

把用户的问题也用同一个 embedding 模型转成向量。很快,因为只有一句话。

检索

拿查询向量去向量库里做相似度搜索,找出最接近的 top-K 个 chunk。这一步叫粗排—— 速度快,但精度有限,毕竟 embedding 是对整段文本的压缩表示,细粒度信息丢了不少。

(重排)

这步很多人跳过,但我觉得只要预算允许就一定要加。粗排回来的 20-50 个候选,再用一个交叉编码器(Cross-Encoder)重新打分。

为什么需要重排?因为 embedding 模型是双编码器(query 和 document 分别编码再算距离),快但精度有限;交叉编码器是把 query 和 document 拼在一起直接算相关性,精度高得多,但慢也贵得多。所以策略是:双编码器粗排召回大量候选,交叉编码器精排选 top-N。

fitneheal 的配置:pgvector 先召回 20 个,取前 12 个送重排器,最后取 5 个进 Prompt。重排器的分数还兼任了可回答性门槛—— 最高分都低于阈值,说明库里真没有,直接告诉用户没找到,别让 LLM 硬编。

提示词增强

把检索到的 chunk 格式化后塞进 Prompt,和用户问题一起发给大模型。这就是大家最熟悉的 "拼接" 环节。但怎么塞、用什么格式、怎么告诉模型哪些能用哪些不能用,这里面的门道也不少。后面讲 fitneheal 的时候细说。

模型生成

大模型基于增强后的 Prompt 生成回答。理想状态下,模型只用检索到的内容回答,每个事实都能追溯到具体来源。


一些高级的 RAG 技术

基础的 "索引、检索、生成" 只是入门。真正到了生产环境,你会遇到各种问题,于是有了各种高级技术。

混合检索(Hybrid Search)

纯向量检索有个盲点:专有名词、缩写、罕见术语。比如用户搜 "MTHFR 基因",向量检索可能召回不好,但关键词匹配(BM25)反而能精准命中。混合检索就是同时跑语义检索和关键词检索两路,再用 RRF 之类的算法融合结果。

但说句实话 ——不要上来就加混合检索。fitneheal 曾经实现过 dense + sparse 双臂 + RRF 融合,然后在专有名词 query 上做了 A/B,结果是稀疏臂零增益。dense embedding 已经把正确 chunk 放进了 top-12,交叉编码器也已经把它提上来了。

所以我们后来把稀疏臂整个删了。不是混合检索不好,是当你的 embedder 足够强、语料足够规整时,第二臂的边际收益可能是零。别为了 "高级" 而高级,测了再说。

查询改写(Query Rewriting)

用户的提问往往不是最优检索 query—— 太口语化、太宽泛、指代不清。查询改写就是用 LLM 把问题改写成更适合检索的形式。常见的有 HyDE(先幻想一篇答案再用答案去检索)、多查询生成(一个问题拆成几个子问题分别检索)。

fitneheal 有个 broaden 机制:对特别宽泛的问题(比如 "维生素 B 族都有什么用"),自动拆成几个子问题分别检索再合并,本质就是多查询生成。

重排之后的重排

重排不是终点。fitneheal 重排之后还有几步纯逻辑后处理,不花一分钱 API 费用:

  • 去冗余:如果一个 story 摘要排在了它自己某个 scene 的后面,说明摘要是多余的,删掉

  • 同场景限制:同一个 scene 最多 2 个 page chunk 进 Prompt,防止一个超长场景占满整个上下文

  • 个性化加权:用户最近读过的内容、当前正在看的营养素,相关 chunk 会得到额外 boost

这些都是 "重排之后的重排"—— 纯逻辑,零成本,效果还不错。


内容更新要如何应对?

这是 RAG 从 demo 走向生产绕不开的问题。知识库不是一成不变的 —— 内容会加、会改、会删,向量库怎么跟内容保持同步?

几种常见方案:

全量重建—— 最简单但最笨。每次内容变了就删掉重建。小数据量没问题,内容多了之后又慢又贵。

增量更新—— 给每个 chunk 算个内容哈希,更新时对比哈希,没变的跳过,变了的重新 embedding。fitneheal 就是这么做的,能省大量 embedding API 费用。

还有一个细节:纯元数据变更不需要 re-embed。比如只是修正了一个 citation 的 id,正文没变,哈希也没变 —— 不需要重新调用 embedding,只更新元数据就行。fitneheal 有个 --reconcile 模式专门干这个,快得多也便宜得多。

实时更新—— 内容频繁变化的场景(新闻、文档协作)需要实时索引,通常用 webhook 事件驱动或 CDC 监听数据库变更。

fitneheal 的策略总结下来就是三句话:增量构建哈希驱动、元数据独立更新、CI 无 key 自动 dry-run(保证 CI 不会意外产生费用或写入生产库)。


Fitnuhealth 的 RAG 技术

前面讲了很多通用原理,来看看 fitneheal 里 RAG 是怎么落地的。

整体流程长这样:

plaintext

code
用户提问
  ↓
红旗预拦截 / 拒答检查(Guard)
  ↓
Query Embedding (Qwen3-Embedding-8B, 1024-dim)
  ↓
Dense Search (pgvector HNSW, cosine, top-20)
  ↓
Rerank (Qwen3-Reranker-8B, top-8)
  ↓
后处理:去冗余 roll-up + 同场景页数限制 + 用户上下文加权
  ↓
取 top-5 进 Prompt
  ↓
LLM 生成 (DeepSeek-V4-Flash)
  ↓
机械忠实度检查 (Faithfulness Check)
  ↓
返回给用户

挑几个我觉得有意思的设计决策聊聊。

机械忠实度检查 —— 这是我最满意的设计

大多数 RAG 系统的 "忠实度" 靠另一个 LLM 来评判,这本身就很讽刺 ——LLM 评判 LLM,你信谁?

fitneheal 的做法是机械的、可验证的:每个 chunk 都带着它的 citationIds 列表,检索回来的所有 chunk 的 citationIds 并集,就是本次回答的引用白名单。模型生成的每个 <Cite id="..."> 都必须在这个白名单里,不在的要么是越界引用要么是幻觉引用。

这是一个纯函数就能完成的检查,不需要再调一次 LLM,快、确定、可写单元测试。

当然也发现了漏洞:如果模型干脆不引用任何东西,忠实度分数就是满分(0 个无效 / 0 个总数 = 1)。所以我们加了个检测 —— 回答很长但一个引用都没有,标记出来,这通常意味着模型在自由发挥。

内容作者即 chunk 作者

前面提过,fitneheal 的 chunk 边界就是内容作者定义的内容单元。一个 scene 一个 chunk,一个 story 一个 chunk,一个 rule 一个 chunk。好处是每个 chunk 天然语义完整,chunk 有明确类型可以做过滤,chunk 带着丰富的元数据可以做更精细的检索策略。

代价是你得有结构化的内容。如果你的内容是一堆散乱的 PDF,那就享受不到这个红利了。

用户上下文增强检索

fitneheal 的检索不是千人一面的。同一个问题,不同用户问,结果可能不一样 —— 我们会根据用户的个人上下文调整排序:报告触发了某个规则 → 对应 rule chunk 1.5 倍 boost;最近读过某个 scene → 1.2 倍 boost(回调式复习,不从头讲基础);当前正在浏览某个营养素 → 1.6 倍 boost(最强,因为用户明确在关注这个话题)。

RAG 从 "通用知识库检索" 变成了 "个性化知识检索"。


构建个人 RAG 知识库

最后,聊聊如果你想自己搭一个 RAG 知识库,应该怎么入手。

先跑通,再优化。 别一开始就追求 "最先进的 RAG"。先把最基础的流程跑通:选一个 embedding 模型、选一个向量库、选一个 LLM、写最简单的 "切分→向量化→检索→生成"。跑通之后用你的真实问题去测,哪里不行再针对性地优化。

最影响效果的三个因素,按优先级排:

  1. 内容质量 > 一切。知识库本身是混乱的、过时的、不准确的,再牛的 RAG 技术也救不了。RAG 只能检索你已有的知识,不能创造知识。

  2. Chunking 策略 > 模型选择。很多人花大量时间选 embedding 模型、调向量库参数,但效果提升最大的往往是改 chunking 策略。

  3. 重排 > 混合检索。只能加一个高级功能的话,加重排,不要加混合检索。交叉编码器带来的精度提升通常比加一个稀疏臂大得多。

别忽视工程问题:成本监控(embedding + 重排 + LLM,每一项都在烧钱,全量重建时账单可能让你吓一跳)、延迟预算(设个 p95 目标,倒推每一步的预算)、失败降级(API 挂了怎么办?别直接 500)、内容同步(知识库更新了向量库怎么同步?第一天就要想清楚)。


回到最开始的问题:RAG 是高级 Prompt 拼接吗?

我的答案是:从结果上看是,从工程上看不是。

用户看到的最终效果,确实就是 "把相关内容拼进 Prompt 里"。但为了让这个拼接拼得准、拼得对、拼得快、拼得便宜、拼得可验证,背后是一整套工程体系 ——chunking、embedding、向量检索、重排、后处理、忠实度检查、增量更新、评测体系……

Prompt 拼接是冰山露出水面的那一角。水面之下,是 90% 的工程工作。

而这,也正是 RAG 真正有意思的地方。

Filed under
  • Ai
  • Rag
← Previous构建产物超出平台限制后,对nextjs的页面渲染类型的实践优化
Next →MCP vs Function Calling: Who Asks, and Where the Tools LiveMCP 和 Function Calling:谁来发起调用,工具又住在哪
← Back to Writing