RAG:让 AI 只说有据可查的话
从原理到落地,一篇搞懂检索增强生成
LLM 什么都敢说,但你只想让它说有根据的话。RAG 就是给 LLM 加一道「先查资料再开口」的规矩。
🎯 LLM 的三大硬伤
在聊 RAG 之前,先看看纯 LLM 在实际场景中会碰到什么问题。
硬伤一:幻觉——一本正经地胡说八道
你问 GPT:"我们公司的报销流程是什么?"
它会非常流畅地给你一套流程——问题是,全是编的。它从来没见过你公司的制度,但它不会告诉你"我不知道",而是用训练数据里的通用知识拼凑出一个看起来很合理的答案。
这就是 Hallucination(幻觉):模型生成了看似正确但实际无据的内容。在闲聊场景无所谓,但在医疗、法律、金融、企业知识等领域,幻觉是致命的。
硬伤二:过时——训练截止日期之后的世界它不知道
每个 LLM 都有训练数据截止日期。2024 年训练的模型不知道 2025 年发生了什么,更别提你上周更新的产品文档。
模型不会自动"跟上"你的知识更新。你今天发了一篇新的技术文档,GPT 明天并不会自动知道。
硬伤三:不懂你的私有数据
你的内部 Wiki、代码仓库、客户数据、业务规则——这些 LLM 在训练时从未见过。它可以帮你写 Python,但它不知道你公司内部的 OrderService 是怎么实现的。
三条路:怎么让 LLM "懂你的数据"
| 方案 | 做法 | 成本 | 数据更新 | 适用场景 |
|---|---|---|---|---|
| Prompt 塞入 | 把资料直接塞进 prompt | 几乎为零 | 实时 | 资料很少(< 几千字) |
| Fine-tuning | 用你的数据重新训练模型 | 高(GPU + 工程) | 每次更新要重新训练 | 需要改变模型风格/能力 |
| RAG | 检索你的数据,塞进 prompt | 中(向量库 + Embedding) | 数据更新后重新索引即可 | 大部分"基于私有数据回答"的场景 |
Prompt 塞入适合数据量极小的情况;Fine-tuning 适合你想改变模型本身的行为(比如让它说特定风格的话);而 RAG 是最通用、最灵活、最具性价比的方案——数据更新了,不用重新训练模型,只要更新索引就行。
🧠 RAG 是什么?一张图讲清楚
RAG = Retrieval-Augmented Generation = 检索增强生成。
拆开来看:
- Retrieval(检索):在你的知识库里,找到和用户问题最相关的几段内容。
- Augmented(增强):把这些内容塞进 prompt,作为模型回答时的"参考资料"。
- Generation(生成):模型基于这些参考资料生成答案。
┌─────────────────────────────────────────────────────────────────┐
│ RAG 全流程 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 【离线:建索引】 │
│ │
│ 你的文档 ──→ 分块 ──→ Embedding ──→ 存入向量库 │
│ (Markdown, (按标题/段落 (文本变成 (pgvector, │
│ PDF, HTML) 切成小段) 数字向量) Qdrant 等) │
│ │
│ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ │
│ │
│ 【在线:问答】 │
│ │
│ 用户提问 ──→ 问题 Embedding ──→ 向量库检索 Top-K │
│ │ │
│ ▼ │
│ 拼成 Prompt 上下文 │
│ ┌─────────────────┐ │
│ │ System: 规则 │ │
│ │ Context: 检索结果│ │
│ │ User: 用户问题 │ │
│ └────────┬────────┘ │
│ ▼ │
│ LLM 生成 │
│ │ │
│ ▼ │
│ 答案 + 引用来源 │
│ │
└─────────────────────────────────────────────────────────────────┘
和搜索引擎有什么区别?
搜索引擎给你一堆链接,你自己点进去读。RAG 帮你读完了,然后用自然语言告诉你答案,还附上来源。
本质上,RAG 就是在 LLM 面前摆了一本参考书,然后说:"只看这本书回答,不准自己编。"
⚙️ RAG 五步流水线
RAG 的核心就是五步。每一步都有不同的技术选型和 trade-off。
Step 1:分块(Chunking)
为什么要分块?
你有一篇 5000 字的文章,用户问的问题可能只和其中一段有关。如果把整篇文章塞进 prompt:
- 浪费 token,贵
- 噪声大,模型可能被无关内容干扰
- 向量化整篇文章,语义会被"稀释",检索不准
所以要把长文档切成一块一块的(chunk),每块是一个语义相对完整的段落,检索时按块来找。
怎么切?
| 策略 | 做法 | 优点 | 缺点 |
|---|---|---|---|
| 固定大小 | 每 500 token 切一刀 | 简单粗暴 | 可能切断句子和段落 |
| 按段落/换行 | 遇到双换行就切 | 保留段落完整性 | 段落大小不一 |
| 按标题 | 遇到 ## 就切 | 语义最完整 | 依赖文档有结构 |
| 递归切分 | 先按标题,再按段落,再按句子 | 灵活 | 实现较复杂 |
| 滑动窗口 | 固定大小 + 前后重叠 N 个 token | 不丢上下文 | chunk 之间有冗余 |
最佳实践:
- 优先利用文档本身的结构(标题、章节),语义完整性最好
- 设一个上限(比如 1500 字),超过的 section 二次切分
- 太短的块(比如只有一句话)可以跳过,信息量不够
- 每个 chunk 带上元数据(标题、来源、标签、日期),方便后面展示引用来源
Step 2:向量化(Embedding)
分好块之后,要把每块文本变成一个向量(一串数字),这样才能做语义相似度计算。
Embedding 的详细原理可以参考《给 AI 装一个大脑:长期记忆系统解析》中的「记忆存储:向量化的艺术」章节。这里简要回顾核心概念。
Embedding 做了什么事?
把一段文字通过模型压缩成一个固定长度的数字列表(比如 1536 个浮点数)。语义相近的文本,Embedding 出来的向量在空间中距离更近。
- "如何设置 Nginx 反向代理" 和 "Nginx 代理配置教程" → 向量很近
- "如何设置 Nginx 反向代理" 和 "今天天气真好" → 向量很远
这就是为什么 RAG 能做语义检索而不只是关键词匹配——用户问"怎么配代理",即使你的文档标题是"Nginx 反向代理设置指南",也能匹配上。
模型怎么选?
| 模型 | 维度 | 特点 | 适用 |
|---|---|---|---|
OpenAI text-embedding-3-small | 1536 | 性价比高,效果好 | 大部分场景的首选 |
OpenAI text-embedding-3-large | 3072 | 更精准,更贵 | 对精度有极高要求 |
Cohere embed-v3 | 1024 | 多语言好 | 多语言混合场景 |
bge-large-zh-v1.5 | 1024 | 开源、中文优化 | 中文为主 + 想自托管 |
all-MiniLM-L6-v2 | 384 | 开源、轻量、快 | 资源有限、对延迟敏感 |
注意:一旦选定了 Embedding 模型,后续所有的 chunk 和查询都必须用同一个模型。换模型 = 重新建索引。
Step 3:存储与索引
向量算出来了,要存到能做高效相似度搜索的地方。
选型:专用向量库 vs 数据库扩展
| 方案 | 代表 | 优点 | 缺点 | 适用规模 |
|---|---|---|---|---|
| PG + pgvector | PostgreSQL 扩展 | 复用已有 PG、运维简单、SQL 生态 | 百万级以上性能下降 | < 100 万向量 |
| Qdrant | 专用向量库 | 高性能、丰富过滤、Rust 编写 | 多一个组件要运维 | 百万到亿级 |
| Milvus | 分布式向量库 | 水平扩展、企业级 | 重、部署复杂 | 亿级以上 |
| Pinecone | 全托管 SaaS | 零运维 | 贵、数据在别人手里 | 想省事 + 预算够 |
| Chroma | 轻量嵌入式 | 零配置、开发方便 | 不适合生产 | 本地开发/原型 |
怎么选?一个简单决策树:
你已经有 PostgreSQL 了吗?
├─ 是 → 数据量 < 100 万条?
│ ├─ 是 → pgvector(最省事)
│ └─ 否 → 考虑 Qdrant / Milvus
└─ 否 → 是否想自托管?
├─ 是 → Qdrant
└─ 否 → Pinecone
向量索引(加速检索):
数据量小(几千到几万条)时,顺序扫描(逐条比较)就够快。数据量大了需要建向量索引:
| 索引类型 | 原理 | 特点 |
|---|---|---|
| IVFFlat | 先聚类,查询时只搜最近的几个聚类 | 需要先有数据才能建、精度可调 |
| HNSW | 基于图的近邻搜索 | 查询更快、精度更高、占内存更多 |
一般建议:先不建索引跑起来,等数据量上来或查询变慢了,再加 HNSW。
Step 4:检索(Retrieval)
用户问了一个问题,现在要从向量库里找到最相关的 K 个 chunk。
基本流程:
用户问题: "Temporal 怎么做中断恢复?"
│
▼
Embedding 模型 → 问题向量 [0.12, -0.34, 0.56, ...]
│
▼
向量库: 计算和每个 chunk 向量的距离
│
▼
返回距离最近的 Top-K 个 chunk
(附带相似度分数,如 0.87, 0.82, 0.79 ...)
相似度怎么算?
| 算法 | 公式直觉 | 常用于 |
|---|---|---|
| 余弦相似度(Cosine) | 向量方向越一致,越相似 | 最常用,OpenAI 推荐 |
| L2 距离(欧氏距离) | 向量越近,越相似 | pgvector 默认 |
| 内积(Dot Product) | 综合方向和长度 | 归一化后等价于余弦 |
对于大部分场景,余弦相似度是默认选择,不需要纠结。
三种检索策略:
| 策略 | 做法 | 优点 | 缺点 |
|---|---|---|---|
| 纯语义检索 | 只用向量相似度 | 理解同义词、自然语言 | 精确关键词可能漏掉 |
| 纯关键词检索(BM25) | 经典全文搜索 | 精确词匹配好 | 不懂同义词、自然语言 |
| 混合检索(Hybrid) | 向量 + BM25,结果合并排序 | 兼得两家之长 | 实现稍复杂 |
举个例子说明差异:
用户问:"k8s pod 重启了怎么办"
纯语义检索 → 能找到标题为"Kubernetes 容器频繁重启排查"的 chunk ✅
纯关键词 → 搜 "k8s" 和 "pod",但文档里写的是 "Kubernetes",漏了 ❌
用户问:"ERROR: ORA-12541"
纯语义检索 → 不理解错误码,可能找到无关的"数据库连接"chunk ❌
纯关键词 → 精确匹配 "ORA-12541",命中 ✅
所以很多生产系统会做 Hybrid Search:两路都跑,结果合并。
Step 5:生成(Generation)
检索到了 Top-K 个 chunk,最后一步是拼 prompt 让 LLM 回答。
Prompt 的典型结构:
┌──────────────────────────────────────────────────┐
│ System Prompt(规则) │
│ ┌──────────────────────────────────────────────┐ │
│ │ 你是一个知识助手。 │ │
│ │ 只根据下面的「参考段落」回答。 │ │
│ │ 如果参考段落里没有,就说"没有找到相关内容"。 │ │
│ │ 回答末尾列出引用的来源。 │ │
│ └──────────────────────────────────────────────┘ │
│ │
│ Context(检索结果) │
│ ┌──────────────────────────────────────────────┐ │
│ │ --- 参考段落 1 --- │ │
│ │ 文章:《Temporal 工作流指南》 │ │
│ │ 章节:中断恢复机制 │ │
│ │ (chunk 原文内容...) │ │
│ │ │ │
│ │ --- 参考段落 2 --- │ │
│ │ 文章:《分布式任务调度》 │ │
│ │ (chunk 原文内容...) │ │
│ └──────────────────────────────────────────────┘ │
│ │
│ User Message(用户问题) │
│ ┌──────────────────────────────────────────────┐ │
│ │ Temporal 怎么做中断恢复? │ │
│ └──────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────┘
防幻觉的关键在 System Prompt 里:
- "只根据参考段落回答"——限制模型的知识边界
- "没有就说没有"——鼓励模型承认无知,而不是编造
- "列出来源"——强制模型关联答案和具体段落,可追溯
温度(temperature)怎么设?
| 值 | 效果 | 适用 |
|---|---|---|
| 0.0 - 0.3 | 几乎只选最高概率的 token,输出稳定 | RAG 问答(推荐) |
| 0.5 - 0.7 | 有一定随机性,表达更自然 | 创意写作 |
| 0.8 - 1.0 | 输出多样,容易跑偏 | 头脑风暴 |
RAG 场景要的是准确和稳定,所以温度建议 0.1 ~ 0.3。
🏛️ RAG 能干什么?典型用途
RAG 不只是"问答机器人"。下面是几个典型场景,帮你理解它的适用范围。
| 场景 | 痛点 | RAG 怎么解决 | 例子 |
|---|---|---|---|
| 企业知识库 | 员工查内部文档靠搜索关键词,找不到、读不完 | 索引全部内部文档,用自然语言提问直接得到答案 | "新员工入职需要准备什么?" |
| 客服系统 | 客服翻 FAQ 手册回答,效率低、质量不一致 | 索引产品文档 + 历史工单,自动生成回答建议 | "我的订单物流一直不更新怎么办?" |
| 代码问答 | 新人看不懂项目,README 过时 | 索引代码仓库 + 设计文档,问"这个服务怎么部署" | GitHub Copilot 的 @workspace 就是这个思路 |
| 法律/合规 | 法规文件海量,靠人逐条查 | 索引法规库,自然语言检索相关条款 | "跨境数据传输需要满足哪些条件?" |
| 医疗辅助 | 医生需要快速查最新诊疗指南 | 索引最新指南和论文,辅助诊断建议 | "2 型糖尿病的最新用药方案?" |
| 个人知识管理 | 笔记越来越多,找不到以前写的东西 | 索引自己的笔记/博客,自然语言问答 | "我之前写过关于 Redis 分布式锁的内容在哪?" |
| 产品文档 | 用户在文档站翻了半天找不到想要的 | 在文档站加"问一下"入口,RAG 直接回答 | Stripe、Vercel 等产品的 AI 文档助手 |
| 教育/培训 | 学员对课件内容有疑问,不知道去哪找 | 索引课件,学员直接提问 | "上节课讲的设计模式里,工厂模式和抽象工厂有什么区别?" |
一个核心判断标准:如果你的场景是**"基于一堆已有文档回答问题"**,那大概率适合 RAG。
什么不适合 RAG?
| 场景 | 为什么不适合 | 更好的方案 |
|---|---|---|
| 需要实时计算 | "今天股票涨了多少" → 需要调 API,不是查文档 | Function Calling / Agent |
| 需要多步推理 | "对比 A 公司和 B 公司过去三年的财报" → 要多次查询 + 计算 | Agent + Tools |
| 需要改变模型风格 | "用周杰伦的风格写歌词" → 不是知识问题 | Fine-tuning |
| 数据量极小 | 只有几段话 → 直接塞 prompt 就行 | Prompt Engineering |
⚠️ RAG 的难点与挑战
RAG 的原理不难,但要做好并不容易。以下是业界公认的几大难点。
难点一:分块质量——切得不好,后面全废
分块是 RAG 的第一步,也是最容易被低估的一步。
- 切太碎:一块只有一句话,检索到了也没有上下文,模型没法回答
- 切太大:一块包含多个主题,向量被"稀释",检索不准
- 切断结构:表格、代码块、列表被拦腰切断,信息残缺
- 丢失关联:section A 里说"如上所述"指的是 section B,切开后这个关联就丢了
这也是为什么"按文档结构切"比"固定字数切"好很多的原因——利用人类写作时已有的语义边界。
难点二:检索质量——找到的不一定是对的
这是 RAG 效果好坏的关键。
- 语义鸿沟(Semantic Gap):用户的提问方式和文档的写法不一致。用户说"怎么让服务不挂",文档写的是"高可用架构设计"
- 长尾查询:冷门问题、专业术语、缩写,向量模型可能没见过
- 否定意图:"怎么避免死锁" vs "怎么实现死锁"——语义很像,意图相反
- 多意图:一个问题包含多个子问题,一次检索只能覆盖一部分
难点三:幻觉——模型说"有"但其实"没有"
即使你在 System Prompt 里写了"只根据参考段落回答",模型仍然可能:
- 混入训练数据里的知识,而不是从你提供的段落里来的
- 回答正确,但标错了来源
- 检索结果不相关,但模型"硬答"了
完全消除幻觉目前还是业界难题。降低幻觉的方法更多是工程手段:低温度、约束 prompt、答案校验、人工抽查。
难点四:评估困难——怎么知道 RAG 好不好?
和传统软件不同,RAG 的质量很难用简单的单元测试衡量。
| 环节 | 要评估什么 | 指标 |
|---|---|---|
| 检索 | 找到的是不是该找的? | Recall@K、MRR、nDCG |
| 生成 | 回答是否忠于检索结果? | Faithfulness(忠实度) |
| 生成 | 回答是否真正解答了问题? | Relevance(相关度) |
| 端到端 | 用户满意吗? | 人工评分、用户反馈 |
要做好评估,需要一批标注好的 QA 对(问题 + 期望答案 + 期望命中的段落),然后持续跑评估。大部分项目初期没有这个,只能靠"试一试,感觉对不对"。
难点五:规模与性能
| 规模 | 典型延迟 | 瓶颈 |
|---|---|---|
| 千级 chunk | < 100ms | 无瓶颈 |
| 万级 chunk | 100-500ms | 需要向量索引(HNSW) |
| 百万级 chunk | 500ms-2s | 需要专用向量库 + 优化 |
| 千万级以上 | 要架构设计 | 分片、缓存、异步 |
对于大部分个人项目和中小企业,千级到万级就够了。百万级以上才需要真正操心性能。
难点六:多跳推理——一次检索不够
有些问题不是一次检索就能回答的:
用户: "对比 Temporal 和 Cadence 的错误处理机制"
需要:
1. 先检索 Temporal 的错误处理 → 拿到相关段落
2. 再检索 Cadence 的错误处理 → 拿到相关段落
3. 综合两组段落,生成对比回答
简单的单轮 RAG 只做一次检索,如果库里没有一个 chunk 同时覆盖两个话题,就答不好。这需要多步检索甚至 Agent 来编排整个流程。
🚀 进阶:让 RAG 更好的实践
基础 RAG(分块 → Embedding → 检索 → 生成)已经能跑起来了。以下是业界常用的进阶手段,每个都解决一个特定的痛点。
Query 改写(Query Rewriting)
解决的问题:用户的提问可能很口语、很模糊,直接用它检索效果不好。
做法:在检索之前,先让 LLM 把用户问题"翻译"成更适合检索的形式。
用户原始问题: "那个东西上次挂了怎么搞的来着"
↓ LLM 改写
改写后的查询: "服务故障排查流程和恢复步骤"
↓
用改写后的查询去检索 → 命中率更高
HyDE(Hypothetical Document Embeddings)
解决的问题:问题和文档的表达方式天然不同(一个是疑问句,一个是陈述句),向量相似度不够高。
做法:让 LLM 先假设性地写一段答案,然后用这段答案的向量去检索。因为"假设答案"和文档的表达方式更接近。
用户问题: "怎么配置 Nginx 反向代理?"
↓ LLM 生成假设答案
假设答案: "Nginx 反向代理的配置需要在 server 块中设置
proxy_pass 指向后端服务..."
↓ 用假设答案的向量去检索
检索结果 → 和真实文档的向量更接近 → 命中率更高
Hybrid Search(混合检索)
解决的问题:纯语义检索漏精确关键词,纯关键词不懂同义词。
做法:同时跑向量检索和 BM25 全文搜索,用 RRF(Reciprocal Rank Fusion) 等算法合并两路结果的排名。
Reranker(重排序)
解决的问题:向量检索是"粗筛",Top-K 里可能混入不太相关的结果。
做法:先用向量检索拿一个较大的候选集(比如 Top-20),然后用一个更精准的 Cross-encoder 模型对每个候选逐一打分,重新排序,取真正最相关的 Top-5。
向量检索 Top-20(粗筛,快但不够准)
↓
Reranker 逐一精排(慢但准)
↓
最终 Top-5(又快又准)
Cross-encoder 之所以更准,是因为它同时看问题和文档,而不是分别编码后比较距离。代价是慢——所以先粗筛再精排。
上下文压缩(Context Compression)
解决的问题:检索到的 chunk 里只有一小部分和问题相关,其它都是噪声。
做法:检索后、生成前,用 LLM 对每个 chunk 做一轮"压缩":只保留和问题相关的句子,删掉无关内容。这样塞进 prompt 的全是精华。
Self-RAG / CRAG(自反思 RAG)
解决的问题:模型不知道检索结果够不够好,"硬答"导致幻觉。
做法:让模型在回答之前先判断:
- 这个问题需要检索吗?(有些常识题不需要)
- 检索到的段落和问题相关吗?(不相关就重新检索或拒绝回答)
- 我的答案忠于检索结果吗?(自我检查)
相当于给 RAG 加了一层"自省"能力。
Agentic RAG(Agent 驱动的 RAG)
解决的问题:复杂问题需要多步检索、多数据源、甚至调用工具。
做法:不再是固定的"检索一次 → 生成"流水线,而是让 Agent 自己决定:
- 要不要检索?检索哪个知识库?
- 检索结果够不够?要不要再检索一次?
- 需要调 API 补充实时数据吗?
- 多个子问题分别检索,最后合并回答
这已经不是纯 RAG 了,而是 RAG 作为 Agent 的一个工具。
📝 总结
RAG 的核心价值
一句话:让 LLM 基于你的数据回答问题,有理有据、可追溯、可更新。
不需要重新训练模型,不需要 GPU 集群,一套向量库 + Embedding + 检索逻辑 + Prompt 工程就能跑起来。
你的场景适合 RAG 吗?
| 条件 | 适合 RAG | 不适合 RAG |
|---|---|---|
| 数据形态 | 文本文档(PDF、Markdown、HTML、Wiki) | 结构化数据(数据库表)、实时数据流 |
| 交互方式 | 自然语言问答 | 精确查询(SQL)、批量分析 |
| 数据更新 | 定期或按需更新 | 每秒级实时变化 |
| 答案来源 | 需要可追溯、可引用 | 不关心出处 |
| 团队规模 | 1 人也能搞起来 | — |
从简单开始
不要一上来就追求 Hybrid Search + Reranker + Self-RAG + Agent。
基础 RAG 已经能解决 80% 的问题。 先跑通「分块 → Embedding → 检索 → 生成」,看看效果,再根据实际问题决定要不要加进阶手段。
就像这个博客的"问博客"功能——最基础的 RAG 流水线,pgvector + OpenAI Embedding + 简单的 Top-K 检索,已经足够让你通过自然语言查询自己写过的所有文章了。
先跑起来,再优化。这是工程的正确姿势。