RAG 深入详解
一、RAG 完整流程
┌─────────── 离线阶段(提前准备)──────────┐
│ │
│ 文档 → 分块 → Embedding → 存入向量数据库 │
│ │
└─────────────────────────────────────────┘
┌─────────── 在线阶段(用户提问时)─────────┐
│ │
│ 用户提问 → Embedding → 向量检索 │
│ │ │
│ 取出 Top-K 相关文档 │
│ │ │
│ (可选)重排 Reranking │
│ │ │
│ 拼接到 Prompt 中 │
│ │ │
│ LLM 生成回答 │
│ │
└─────────────────────────────────────────┘
二、分块(Chunking)
为什么要分块
黄帝内经全文约 20 万字
不分块 → 塞不进 Prompt,也找不到重点
分块后 → 只检索出最相关的 3-5 段,精准高效
四种分块策略
1. 固定大小分块
每 500 字切一刀,不管语义
优点:简单快速
缺点:可能切断句子
"气虚者应当注意饮" | "食调理,多食补气之品"
**2. 固定大小 + 重叠(最常用) **
每 500 字切一刀,相邻块重叠 100 字
chunk 1: 第 1-500 字
chunk 2: 第 400-900 字 ← 重叠了 100 字
优点:切断概率大幅降低
缺点:存储量增加约 20%
3. 按语义分块
根据标题、段落、句号来切
## 气虚证 → 一个 chunk
## 阳虚证 → 一个 chunk
优点:语义完整
缺点:chunk 大小不均匀
**4. 递归分块(LangChain 默认) **
先按标题(##)切 → 太大就按段落(\n\n)切 → 还大就按句子(。)切
逐级递归,优先保留语义完整性
分块大小选择
太大(2000+ 字)→ 检索不精准,浪费 Token
太小(50 字) → 丢失上下文,全是碎片
经验值:
一般文档:300-500 字/chunk
代码:按函数/类切
中医经典:按条文/方剂切(天然分块单位)
overlap:chunk 大小的 10-20%
三、Embedding(向量化)
什么是 Embedding
把文字变成一组数字(向量),语义相近的文字数字也相近:
"气虚" → [0.12, 0.85, 0.33, 0.67, ...] ← 1536个数字
"气血不足" → [0.13, 0.83, 0.35, 0.65, ...] ← 跟"气虚"很接近
"Python" → [0.91, 0.02, 0.78, 0.15, ...] ← 跟"气虚"很远
关键区分:
Embedding 模型:文字 → 向量(数字) 用于检索
聊天模型(LLM):文字 → 文字 用于生成回答
这是两个不同的模型,不要搞混
Embedding 模型选型
| 模型 | 维度 | 特点 |
|------|------|------|
| text-embedding-v3 (阿里) | 1536 | 中文效果好 |
| text-embedding-3-small (OpenAI) | 1536 | 便宜,英文强 |
| text-embedding-3-large (OpenAI) | 3072 | 更准但更贵 |
| bge-large-zh (开源) | 1024 | 免费,中文专用 |
| jina-embeddings-v3 (开源) | 1024 | 多语言,免费 |
选型原则:
中文为主 → text-embedding-v3 或 bge-large-zh
英文为主 → OpenAI text-embedding-3
省钱 → 开源模型自己部署
图省事 → 用 API
四、向量存储与检索
三种相似度计算
余弦相似度(Cosine)← 最常用
衡量方向是否一致,不管长度
值域:-1 到 1(1 = 完全一样)
内积(Dot Product)
同时考虑方向和长度
适合:文档长度差异大的场景
欧氏距离(L2 Distance)
衡量空间中两点的直线距离
值越小越相似(跟前两个相反)
大部分场景用余弦相似度就够。Embedding 输出通常是归一化的,
余弦相似度和内积结果等价。
向量数据库对比
| 数据库 | 特点 | 适合场景 |
|--------|------|---------|
| pgvector | PostgreSQL 扩展,无需额外部署 | 数据量 < 100 万,已有 PG |
| Pinecone | 全托管云服务,开箱即用 | 不想运维,直接付费 |
| Weaviate | 开源,支持混合搜索 | 需要向量+关键词混合 |
| Milvus | 开源,高性能 | 数据量大,性能要求高 |
| Chroma | 轻量级 | 本地开发、小项目 |
| FAISS (Meta) | 库,不是数据库 | 嵌入到应用代码中 |
ivfflat vs HNSW 索引
ivfflat:
原理:把向量分成 N 个桶,查询时只搜最近的几个桶
优点:构建快,占内存少
缺点:精度略低
适合:数据更新频繁
HNSW:
原理:构建多层图结构,跳跃式搜索
优点:精度高,查询快
缺点:构建慢,占内存大
适合:数据相对稳定、要求高精度
一般推荐:
数据 < 10 万条 → ivfflat
数据 > 10 万条 + 要求高精度 → HNSW
五、重排(Reranking)
为什么需要重排
向量检索返回 Top-10(粗筛):
1. "气虚者少气懒言" ← 相关 ✅
2. "补气方剂:四君子汤" ← 相关 ✅
3. "气功修炼方法" ← ❌ "气"字相关但语义无关
4. "气候变化对养生的影响" ← ❌ 完全无关
问题:向量检索是粗筛,会混入不相关结果
重排流程
向量检索 Top-10(粗筛,快但粗)
│
▼
Reranker 模型(精排)
把问题和每个chunk拼在一起,让模型判断"到底相不相关"
│
▼
重排后 Top-3(精准结果)
向量检索:分别编码,比方向 → 快但粗
Reranker:拼在一起,整体判断 → 慢但准
常用 Reranker
Cohere Rerank → API 服务,效果好
bge-reranker → 开源,可本地部署
Jina Reranker → 开源,多语言
LLM 当 Reranker → 直接问 LLM "这段话跟问题相关吗?"
六、拼接 Prompt 并生成
基础方式
System: "你是中医助手,根据参考资料回答。资料中没有答案就说不知道。"
User: "参考资料:
1. 气虚者,少气懒言,肢体倦怠...
2. 补气方剂:四君子汤,由人参、白术、茯苓、甘草组成...
用户问题:气虚怎么调理?"
高级:带引用来源
User: "参考资料:
[1] 《黄帝内经·素问》: 气虚者,少气懒言...
[2] 《伤寒论》第34条: 补气方剂四君子汤...
请根据以上资料回答,并标注引用来源编号。
用户问题:气虚怎么调理?"
模型回答:
"气虚调理建议:
1. 可多食黄芪、山药、红枣[1]
2. 方剂可参考四君子汤[2]"
七、高级 RAG 策略(面试加分)
1. 混合检索(Hybrid Search)
向量检索:语义相关("气血不足" 能匹配 "气虚")
关键词检索:精确匹配("四君子汤" 必须包含这四个字)
两种结合:
results = 向量检索(query) + 关键词检索(query)
合并去重,加权排序
弥补各自短板:
向量检索找不到专有名词 → 关键词补上
关键词搜不到同义词 → 向量补上
2. 查询改写(Query Rewriting)
用户原始问题:"我总是没力气" ← 口语化,检索效果差
让 LLM 先改写:
"我总是没力气" → "气虚 乏力 疲劳 调理方法"
→ 用改写后的关键词检索,效果好很多
类似中医项目中 analyzeSymptoms 的症状标准化:
"容易疲劳" → "疲劳", "乏力"
3. 多跳检索(Multi-hop)
用户问:"气虚体质吃四君子汤要注意什么禁忌?"
第1跳:检索"四君子汤" → 成分:人参、白术、茯苓、甘草
第2跳:检索"人参禁忌" → 高血压慎用
第3跳:检索"气虚+高血压" → 替代方案
单次检索只能回答一部分,多跳才能回答完整
4. Self-RAG(自我判断)
普通 RAG:检索了就一定用
Self-RAG:检索后让 LLM 判断"这个结果有用吗?"
流程:
检索 Top-5
→ LLM 判断:#1 相关 ✅ #2 相关 ✅ #3 不相关 ❌
→ 只用相关的生成回答
→ 生成后自检:"回答有没有超出参考资料范围?"
八、RAG vs Fine-tuning
| | RAG | Fine-tuning |
|---|------|------------|
| 做什么 | 给模型实时喂参考资料 | 把知识训练进模型参数 |
| 类比 | 开卷考试(带资料查) | 闭卷考试(全背下来) |
| 知识更新 | 改数据库就行,秒级 | 重新训练,小时/天级 |
| 成本 | 低(只需向量数据库) | 高(需要 GPU 训练) |
| 幻觉控制 | 好(有出处可查) | 差(可能编造) |
| 适合 | 知识会变、需要引用来源 | 改变模型风格/格式/行为 |
选型话术:
"中医项目选 RAG 原因有三:知识库需要持续更新,RAG 改数据库就行;
RAG 能标注引用来源,用户可验证;微调成本高,不适合早期迭代。
如果需要模型以特定中医话术风格回答,可以 RAG + 微调结合。"
九、面试高频问题
Q: 分块大小怎么选?
一般 300-500 字,overlap 10-20%。太大检索不精准浪费 Token,太小丢失上下文。中医经典按条文/方剂切是天然的最佳分块。
Q: 检索结果不相关怎么办?
四个方向:1. 加 Reranking 精排;2. 混合检索(向量+关键词);3. 查询改写让搜索词更精准;4. 优化分块策略和 Embedding 模型选型。
Q: 怎么减少幻觉?
- Prompt 里明确"资料中没有答案就说不知道";2. 要求标注引用来源;3. Self-RAG 让模型自检回答是否超出参考范围;4. 控制 temperature 参数。
Q: RAG 和微调怎么选?
知识会更新、需要引用来源 → RAG;改变模型风格和行为 → 微调;两者可以结合。
Q: 向量数据库怎么选?
已有 PostgreSQL 且数据量不大 → pgvector;需要高性能大规模 → Milvus;快速原型 → Chroma;不想运维 → Pinecone。
Q: 余弦相似度和内积的区别?
余弦只看方向,内积同时看方向和长度。Embedding 模型输出通常是归一化的,两者结果等价,选余弦即可。