RAG 深度学习笔记

RAG 入门学习笔记

学习起点:[[20260522.SearchToolDesign(Private)]] 中的资产语义搜索项目
目标:理解 RAG 原理 → 在本地图片数据上跑通最小 Demo


一、什么是 RAG?

RAG = Retrieval-Augmented Generation(检索增强生成)

用一句话说:先从知识库里找到相关内容,再用这些内容辅助 AI 回答问题。

1.1 为什么需要 RAG?

问题 RAG 如何解决
LLM 训练数据有截止日期,不知道新信息 检索阶段可以实时查询最新数据
LLM 不知道私有数据(公司内部资产库) 把私有数据做成索引,检索后注入给 LLM
LLM 会”幻觉”,凭空捏造事实 提供检索到的真实文档作为依据
LLM token 窗口有限,无法塞入全部资料 只检索最相关的少数文档片段

二、RAG 的完整流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
╔══════════════════════════════════════════════════════════╗
║ 索引阶段(离线,一次性构建) ║
║ ║
║ 原始数据(文档 / 图片 / 资产描述) ║
║ ↓ ║
║ Embedding 模型 → 将内容转成固定长度的向量 ║
║ ↓ ║
║ 向量数据库(FAISS / ChromaDB)存储向量,支持快速检索 ║
╚══════════════════════════════════════════════════════════╝

╔══════════════════════════════════════════════════════════╗
║ 检索阶段(在线,每次查询触发) ║
║ ║
║ 用户问题 → Embedding 模型 → 问题向量 ║
║ ↓ ║
║ 向量数据库:计算相似度,找 Top-K 个最近邻 ║
║ ↓ ║
║ 返回对应原始内容(文档片段 / 图片路径 / 资产信息) ║
╚══════════════════════════════════════════════════════════╝
↕(可选)
╔══════════════════════════════════════════════════════════╗
║ 生成阶段(可选,传统 RAG 才需要) ║
║ ║
║ [用户问题] + [检索到的内容] → LLM → 有据可查的回答 ║
╚══════════════════════════════════════════════════════════╝
我们的项目是"只检索,不生成"

资产搜索工具只做前两个阶段——把美术的查询转成向量,找最相似的资产,直接返回结果。这是 RAG 的检索子集,学术上叫 语义检索(Semantic Search)Dense Retrieval


三、核心概念解释

3.1 Embedding(向量化)

把任意内容(文字、图片)压缩成一个固定长度的数字列表(向量),使得:

  • 语义相似的内容 → 向量在空间中相近
  • 语义不同的内容 → 向量在空间中远离
1
2
3
"一棵柳树"   → [0.12, -0.34, 0.87, ..., 0.05]  (768 维)
"江南垂柳" → [0.11, -0.31, 0.89, ..., 0.06] ← 很接近!
"一块岩石" → [-0.45, 0.72, -0.13, ..., 0.91] ← 差很远

常用 Embedding 模型:

模型 输入类型 维度 特点
BGE-M3 (BAAI) 文字 1024 中文最强,本地部署友好
CLIP (OpenAI) 图片 + 文字 512 图文共享同一向量空间 ⭐
SigLIP (Google) 图片 + 文字 768 CLIP 改进版,零样本更好
GTE-Qwen2 (阿里) 文字 768 多语言,商用 Apache 2.0

3.2 FAISS(向量相似度搜索库)

FAISS = Facebook AI Similarity Search(Meta 开源)

为什么需要它?
当你有 20000 个向量,要找和查询向量最相似的 Top-10,如果逐一比较需要 20000 次计算。FAISS 用特殊的数据结构,把这个过程加速几十到几千倍。

类比:

  • 暴力搜索 = 图书馆里一本一本翻,找和你兴趣最接近的书
  • FAISS = 图书馆按主题分区,直接去”植物类”书架精确查找

三种主要索引类型:

类型 原理 精度 速度 适合规模 我们的选择
IndexFlatL2 精确,逐一比较 100% 中(但绝对够用) < 100K ✅ 首选
IndexIVFFlat 先分群,再搜索 ~98% 100K–10M 将来扩展用
IndexHNSWFlat 图结构导航 ~99% 最快 任意 过度设计
20000 资产用 `IndexFlatL2` 即可

精确、代码最简单,单次查询 <1ms,完全满足需求。

3.3 相似度度量

度量方式 公式含义 何时用
L2 距离(欧式) 向量空间中的直线距离,越小越相似 FAISS 默认,图片检索
余弦相似度 两向量夹角的余弦值,越接近 1 越相似 文本检索更常用
内积(点积) 方向 + 长度共同决定 CLIP 官方推荐

四、图片 RAG(CLIP 的特殊能力)

CLIP 的核心创新:让图片和文字共享同一个向量空间

1
2
3
4
"一棵柳树"(文字)→ CLIP 文本编码器 → 向量 A
🌳(柳树图片) → CLIP 图片编码器 → 向量 B

向量 A ≈ 向量 B ← 这是 CLIP 的训练目标!

这意味着你可以:

  • 文字 → 图片:输入”给我一个流程图”,在图片库里找最相似的
  • 图片 → 图片:上传参考图,找风格相近的资产
  • 图片 → 文字:输入图片,找最相关的描述文档

这正是资产搜索工具「以文搜图」功能的核心原理。


五、本地 Demo 计划

5.1 目标

D:\Project\UGit\MyPicGo\Images\ 里的博客图片(PNG/JPG),跑通一个最小的图片语义检索 Demo

  • 输入:一段文字描述(如”代码截图”、”流程图”、”UI 界面”)
  • 输出:图片库里最相似的 Top-5 图片路径

5.2 技术栈

1
2
3
4
5
6
7
D:\Project\UGit\MyPicGo\Images\  (博客图片,约 100+ 张 PNG/JPG)
↓ 批量读取
CLIP ViT-B/32 → 视觉编码器,每张图 → 512 维向量
↓ 建索引
FAISS IndexFlatL2 → 内存索引(不需要 GPU,不需要服务器)
↓ 查询
用户文字 → CLIP 文本编码器 → 512 维向量 → Top-K 检索

5.3 实战过程

  1. 建项目目录 D:\Project\UGit\PicGoRAGDemo\ + Python venv
  2. 装依赖:torch / transformers / faiss-cpu / modelscope / Pillow / numpy
  3. build_index.py(索引阶段)+ search.py(查询阶段)
  4. python build_index.py:225 张图编码成 225 × 512 维向量,建 FAISS 索引落盘(CPU 上 ~15s)
  5. python search.py "query" 验证检索(单次查询 <100ms)

3 个关键踩坑:

  • transformers 5.x 改了 CLIP APIget_image_features(...) 现在返回 BaseModelOutputWithPooling(不再是裸 Tensor),要 .pooler_output 取 512 维向量
  • HuggingFace 国内连不上:直连超时、镜像也不稳,换 ModelScope(阿里魔搭,国内服务器无障碍)
  • ModelScope 命名不同:HF 上的 OFA-Sys/chinese-clip-vit-base-patch16 在 MS 上对应 AI-ModelScope/chinese-clip-vit-base-patch16AI-ModelScope 是 HF 格式镜像专用命名空间,文件结构和 HF 完全一致,transformers.AutoModel 可以直接加载)

5.4 测试发现:中英文 query 效果对比

实测发现英文 query 略准

Query 模型 检索效果
"code screenshot" OpenAI CLIP ✅ Top-3 全是代码截图
"flowchart" OpenAI CLIP ✅ Top-3 全是流程图
"代码截图" Chinese-CLIP ✅ Top-3 全是代码截图
"流程图" Chinese-CLIP ⚠️ Top-3 有流程图但混入少量无关图

可能原因:博客图片以英文技术截图为主(代码、终端、UI 文字大多为英文)。OpenAI CLIP 同时识别英文文字 + 视觉特征,命中更稳;Chinese-CLIP 训练数据偏中文生活 / 资讯,对”流程图”这种偏技术的视觉概念边界不够锐利。

这正印证 §6.1 的核心观点:embedding 模型在你领域的表现取决于训练数据分布——选型要看场景

5.5 完整代码

build_index.py(索引阶段)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
"""build_index.py — 把 N 张图编成 N 个 512 维向量,建 FAISS 索引

产物:
index.faiss FAISS 索引(行 i = 第 i 张图的 512 维向量)
paths.json 行号 → 原图路径
"""
from __future__ import annotations
import json
from pathlib import Path
import faiss, numpy as np, torch
from PIL import Image
from modelscope import snapshot_download
from transformers import AutoModel, AutoProcessor

# ===== 配置 =====
IMAGE_DIR = Path(r"D:\Project\UGit\MyPicGo\Images")
INDEX_PATH = Path(__file__).parent / "index.faiss"
PATHS_PATH = Path(__file__).parent / "paths.json"
# AI-ModelScope 是 ModelScope 上的 HF 格式镜像命名空间,国内可直连
MODEL_ID = "AI-ModelScope/chinese-clip-vit-base-patch16"
BATCH_SIZE = 16
IMG_EXTS = {".png", ".jpg", ".jpeg", ".webp", ".bmp"}


def collect_images(root: Path) -> list[Path]:
"""递归扫描所有图片"""
return sorted(p for p in root.rglob("*") if p.suffix.lower() in IMG_EXTS)


def main() -> None:
device = "cuda" if torch.cuda.is_available() else "cpu"

# === 1. 加载模型 ===
# snapshot_download 首次下载 ~700MB 到 ~/.cache/modelscope/,之后直接返回本地路径
model_dir = snapshot_download(MODEL_ID)
model = AutoModel.from_pretrained(model_dir).to(device).eval()
processor = AutoProcessor.from_pretrained(model_dir)

# === 2. 扫描图片库 ===
paths = collect_images(IMAGE_DIR)
if not paths:
raise SystemExit(f"no images found under {IMAGE_DIR}")

# === 3. 批量编码 ===
embeddings, ok_paths = [], []
for i in range(0, len(paths), BATCH_SIZE):
batch = paths[i:i + BATCH_SIZE]
imgs, ok = [], []
for p in batch:
try:
imgs.append(Image.open(p).convert("RGB"))
ok.append(p)
except Exception as e:
print(f" skip {p.name}: {e}")
if not imgs:
continue
inputs = processor(images=imgs, return_tensors="pt").to(device)
with torch.no_grad():
# transformers 5.x: get_image_features 返回 BaseModelOutputWithPooling
# 512 维投影向量在 .pooler_output 字段里
feats = model.get_image_features(**inputs).pooler_output
# L2 归一化:归一化后 L2 距离 ↔ cosine 相似度排名等价
feats = feats / feats.norm(dim=-1, keepdim=True)
embeddings.append(feats.cpu().numpy().astype("float32"))
ok_paths.extend(ok)

# === 4. 建 FAISS 索引并落盘 ===
mat = np.vstack(embeddings)
# IndexFlatL2: 精确暴力检索;< 100K 向量足够,单次查询 <1ms
index = faiss.IndexFlatL2(mat.shape[1])
index.add(mat)
faiss.write_index(index, str(INDEX_PATH))
PATHS_PATH.write_text(
json.dumps([str(p) for p in ok_paths], ensure_ascii=False, indent=2),
encoding="utf-8",
)


if __name__ == "__main__":
main()

search.py(查询阶段)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
"""search.py — 文字 query → CLIP 文本编码 → FAISS Top-K → 打印路径

用法:
python search.py "代码截图"
python search.py "flowchart" --topk 10
python search.py "blue UI" --open # 顺便打开 Top-1
"""
from __future__ import annotations
import argparse, json, os
from pathlib import Path
import faiss, torch
from modelscope import snapshot_download
from transformers import AutoModel, AutoProcessor

INDEX_PATH = Path(__file__).parent / "index.faiss"
PATHS_PATH = Path(__file__).parent / "paths.json"
# 必须和 build_index.py 一致——不同模型的向量空间不通用
MODEL_ID = "AI-ModelScope/chinese-clip-vit-base-patch16"


def main() -> None:
parser = argparse.ArgumentParser()
parser.add_argument("query", type=str, help="自然语言查询")
parser.add_argument("--topk", type=int, default=5)
parser.add_argument("--open", action="store_true", help="打开 Top-1")
args = parser.parse_args()

if not INDEX_PATH.exists():
raise SystemExit(f"找不到 {INDEX_PATH.name},先跑 python build_index.py")

# === 1. 加载模型 + 索引 ===
device = "cuda" if torch.cuda.is_available() else "cpu"
model_dir = snapshot_download(MODEL_ID)
model = AutoModel.from_pretrained(model_dir).to(device).eval()
processor = AutoProcessor.from_pretrained(model_dir)
index = faiss.read_index(str(INDEX_PATH))
paths = json.loads(PATHS_PATH.read_text(encoding="utf-8"))

# === 2. 文字编码为向量 ===
inputs = processor(text=[args.query], return_tensors="pt", padding=True).to(device)
with torch.no_grad():
# 同样取 .pooler_output 拿 512 维投影向量
feat = model.get_text_features(**inputs).pooler_output
feat = feat / feat.norm(dim=-1, keepdim=True)
query_vec = feat.cpu().numpy().astype("float32")

# === 3. FAISS Top-K 检索 ===
distances, indices = index.search(query_vec, args.topk)

# === 4. 打印结果 ===
for rank, (idx, dist) in enumerate(zip(indices[0], distances[0]), 1):
# 对 L2-normalized 向量:cos_sim = 1 - L2_dist^2 / 2
sim = 1.0 - dist / 2.0
print(f" #{rank} sim={sim:.3f} {paths[idx]}")

if args.open and len(indices[0]) > 0:
os.startfile(paths[indices[0][0]])


if __name__ == "__main__":
main()

六、学习资源

6.1 入门文章(由浅入深)

资源 类型 适合阶段
IBM Technology: What is RAG? 5分钟视频 零基础入门,概念最清晰
Retrieval-Augmented Generation - LangChain 官方文档 图文教程 理解流程,有代码示例
Building RAG from Scratch - Towards Data Science 博客 不依赖框架,直接用 FAISS + Python
LlamaIndex 官方教程 实践 工程化框架,生产级 RAG
OpenAI Cookbook - RAG 代码示例 进阶,含评估指标

6.2 必读论文(可选)

论文 年份 为什么读
RAG for Knowledge-Intensive NLP Tasks(Lewis et al.) 2020 RAG 概念起源
CLIP: Learning Transferable Visual Models(Radford et al.) 2021 图文对齐的基础原理
SigLIP: Sigmoid Loss for Language Image Pre-Training 2023 CLIP 改进版,了解进展

6.3 中文资源

  • BAAI BGE 系列文档:中文 embedding 模型的官方说明,直接指导模型选型
  • 知乎「RAG 实践」:国内工程师踩坑笔记,实用
  • B站 @跟李沐学AI:深度学习基础,理解 embedding 原理的必要背景

七、笔记

模块一:RAG Overview

1. RAG体系结构:

image.png
在 RAG 系统内部,首先会有可以访问数据库的 retriever,它会发动一次查询(类似数据库)。然后 retriever 会得到一个查询后的 (被认为最相关的一些信息)。接着,它会把这写得到的可能最相关的信息,生成一个增强的提示词。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
# ============================================================
# 阶段 1:定义用户提问(原始 Prompt)
# ============================================================
# 这是用户最初提出的问题,包含时效性信息("this weekend"),
# 大模型自身的训练数据无法覆盖实时情况。
prompt = "Why are hotel prices in Vancouver super expensive this weekend?"


# ============================================================
# 阶段 2:朴素生成(Naive Generation,作为对照基线)
# ============================================================
# 直接把原始 prompt 丢给 LLM 生成答案。
# 缺点:模型不知道"本周末"温哥华正在发生什么事件,
# 只能凭训练数据给出泛泛的猜测(旅游旺季、汇率等),容易出现幻觉。
generate(prompt)


# ============================================================
# 阶段 3:检索相关文档(Retrieval)
# ============================================================
# 用 prompt 去外部知识库(向量数据库、搜索引擎、内部文档等)
# 检索与问题相关的实时/权威信息,例如:
# - 本周末温哥华正在举办的演唱会、展会、体育赛事
# - 酒店行业的供需新闻
# 这些文档是后续"增强"环节的关键素材。
retrieved_documents = retrieve(prompt)
print(retrieved_documents)


# ============================================================
# 阶段 4:构造增强提示(Augmented Prompt)
# ============================================================
# 把"原始问题" + "检索到的文档"拼接成一个新的 prompt,
# 显式地把外部知识喂给模型,让它在有据可依的前提下作答。
augmented_prompt = f"""Respond to the following prompt: {prompt}

using the following retrieved information to help you answer {retrieved_documents}"""


# ============================================================
# 阶段 5:基于增强 Prompt 重新生成(Augmented Generation)
# ============================================================
# 此时 LLM 拿到的是"问题 + 上下文证据",
# 输出会更准确、更具时效性,也能减少幻觉。
# 这就是 RAG(Retrieval-Augmented Generation)的完整闭环:
# Retrieve → Augment → Generate
generate(augmented_prompt)

2. LLMs上理解RAG

LLMs 本质上是在不停地预测下一个即将出现的值的可能性,RAG 是改变了这个可能性的分布。

3. 信息检索:RAG Retrieve vs 搜索引擎 vs 数据库查询

三者都在”找东西”,但底层匹配逻辑完全不同:

维度 搜索引擎(BM25/TF-IDF) 数据库查询(SQL) RAG Retrieve(向量检索)
匹配方式 关键词词频统计 精确字段匹配 语义相似度(向量距离)
查询语言 自然语言词袋 结构化 SQL 任意内容(文字 / 图片 / 音频)
能否理解同义词 ❌ 部分(需要词典) ❌ 完全不能 ✅ 天然支持
能否跨模态 ❌ 文字只能找文字 ✅ 文字找图片(CLIP)
结果排序依据 TF-IDF 分数 无排序(精确匹配 / 过滤) 向量空间距离(余弦 / L2)
数据结构要求 需要倒排索引 需要严格 Schema 只需向量,原始结构无要求
适合的问题 “找包含这些词的文档” “找符合这些条件的记录” “找语义上最相关的内容”

一句话区分三者本质:

1
2
3
数据库查询  ── "完全相等吗?"   (精确)
搜索引擎 ── "有没有这个词?" (词频)
RAG Retrieve── "意思像不像?" (语义)
实际工程:混合检索(Hybrid Search)

RAG 并非”完美替代”前两者,生产环境常将向量检索 + BM25结合使用,取并集后再用 Re-ranker 重新排序——向量检索负责语义泛化,BM25 负责关键词锚定,两者互补。

模块二:信息检索和搜索技术

1. 检索器架构概述

Metadata Filtering(元数据过滤)

在向量检索前/后,用结构化字段对候选集进行硬性筛选,缩小检索范围

  • 原理:文档在入库时附带元数据字段(如来源、日期、类别),查询时先过滤再做向量相似度排序
  • 优势:大幅减少无关文档的干扰,提升精度和检索效率。
  • 局限:依赖入库时的元数据质量,字段缺失或分类不准会导致有效文档被误过滤。不会理解内容,且几乎不会单独使用。
Keyword Searching(关键词搜索)

基于精确词语匹配来查找文档,是最经典的搜索方式。主要包含 TF-IDF 和 BM25 两种算法。

  • 原理:将查询拆成词条,统计词频(如 BM25 算法),返回包含这些词的文档
  • 优势:速度快、结果可解释、对专有名词(代码、ID、型号)命中精准
  • 局限:无法理解同义词或语义近似表达,换个说法就可能找不到
TF-IDF vs BM25

两者都用词频打分,但 BM25 是 TF-IDF 的改进版:TF-IDF 词频越高得分线性增长;BM25 加入词频饱和(高频词收益递减)和文档长度归一化,短文档中出现一次与长文档出现多次权重更合理,实际检索效果更好。

Semantic Searching(语义搜索)

将文本编码为向量,通过计算向量相似度来匹配”意思相近”的内容。

  • 原理:用 Embedding 模型把查询和文档都映射到高维向量空间,用余弦相似度等方式度量距离
  • 优势:能捕捉语义泛化,即使措辞不同也能找到相关内容
  • 局限:计算开销更高,对精确词语(如特定 ID)反而不如关键词搜索可靠

2. 两种常见的关键词检索算法详解

TF-IDF

核心公式:Score = TF(词, 文档) × log(文档总数 / 含该词的文档数)

  • TF(词频):词在当前文档出现越多,说明越相关
  • IDF(逆文档频率):词在越多文档里出现,说明越”废话”,权重越低;罕见词权重越高
  • 结果:"的" "is" 等高频虚词接近 0 分,"量子纠缠" "BM25" 等专有词得高分

BM25(TF-IDF 的工业级改进版,Elasticsearch 默认算法)

核心改进点:

问题 TF-IDF BM25
词频线性叠加 出现 100 次 = 1 次的 100 倍 词频饱和,多了收益递减(参数 k1,默认 1.2–2.0)
长文档天然优势 惩罚过重 按平均文档长度归一化(参数 b,默认 0.75)

k1:控制词频饱和速度,越高代表”多说几遍越重要”;越低代表”说 3 遍和说 100 遍差不多”
b:控制长度惩罚力度,b=0 忽略长度,b=1 严格按密度打分,0.75 是工业经验值

3. 语义搜索与关键词搜索的异同

1. 共同点:数字化
  • Prompt and documents each get a vector(提示词和文档各获得一个向量):
    无论是哪种搜索,计算机都无法直接“读懂”文字。第一步都是将用户的提问和数据库里的文档转化成一串数字列表,这串数字就叫向量。
  • Vectors compared to generate scores(对比向量以生成评分):
    一旦变成了数字,计算机就可以通过数学公式(如余弦相似度)计算两个向量之间的“距离”。距离越近,分值越高,代表两者越相关。
2. 核心区别:向量是如何生成的?
关键词搜索:统计单词数量
  • 原理: 这种方式生成的叫稀疏向量
  • 逻辑: 向量里的每一个位置代表一个特定的单词。如果文档里出现了这个词,就在对应位置标上分数(基于词频,如 BM25)。
  • 特点: 字面匹配,它只认“长得一样”的词。
    局限: 如果你搜“医生”,它找不到包含“医师”但没写“医生”的文档,因为它不理解词义。
语义搜索:使用嵌入模型
  • 原理: 这种方式生成的叫稠密向量 (Dense Vector)
  • 逻辑: 它不直接数单词,而是把文本输入到一个预训练好的深度学习模型(Embedding Model,如 BERT)。模型会把文本“映射”到一个多维的语义空间里。
  • 特点: 理解含义,向量里的数字代表的是抽象的“特征”或“概念”。
    优势: 它能识别同义词。即使单词不匹配,只要“意思”接近(比如“猫”和“小猫”,“医生”和“医师”),它们的向量在数学空间里的距离就会非常近。

3. RRF 算法(平衡关键词和语义)

它是 混合检索 中的核心技术。当你在 RAG 系统中同时运行”关键词检索”和”语义检索”时,会得到两份完全不同的评分列表。RRF(Reciprocal Rank Fusion,倒数排名融合)的作用就是将这两份列表公正地合并成一份最终的排名列表

1. 核心问题:”苹果和橙子”的对比困境

混合检索面临一个根本性困难:

  • 关键词检索(BM25) 的分数可能是 15.412.8 等。
  • 语义检索(向量搜索) 的分数(余弦相似度)通常在 0.80.9 之间。
  • 问题:这两种分数单位不同,无法直接相加比较。

RRF 的解法:完全忽略原始分数,只看文档在列表中的位次(排名)。

2. 核心机制
  • 奖励”共识”文档: 如果一个文档在关键词搜索和语义搜索中都排名靠前,RRF 会给它极高的最终得分。
  • 标准化两种搜索的权重: 提供了一种跨策略的公平比较方式,不让任何一种算法因分值范围大而主导结果。
  • 得分 = 排名的倒数(算法名称由来):
    • 第 1 名 → 1/1 = 1.0
    • 第 2 名 → 1/2 = 0.5
    • 第 10 名 → 1/10 = 0.1
    • 逻辑:名次越靠前分值越高,且名次越往后分值下降越快。
  • 汇总得分: 把同一文档在所有列表中的倒数分数相加,总分最高者赢得最终排名。
3. 公式

$$RRF(d) = \sum_{i=1}^{n} \frac{1}{k + rank_i(d)}$$

  • rank_i:文档 d 在第 i 个检索列表中的排名(从 1 开始)。
  • k平滑常数,工业界通常默认设为 60

为什么需要 k

若不加 k,第 1 名(1.0 分)与第 100 名(0.01 分)差距极大,会导致排名第一的文档拥有统治级权力。k 作为”减震器”,能压缩极端差异,削弱偶然排第一的噪声文档的影响。

参数值 效果 风险
k = 0(极度敏感) 第一名拥有绝对统治力 某个算法偶然将噪声文档排第一,会干扰整个 RAG 结果
k = 60(平滑稳健,工业默认) 单列表的高排名不再独占优势 无明显风险;要求多个检索策略共同认可才能胜出
4. RRF 的核心优势:只在乎排名
  • 无需分数归一化: 不需要考虑 BM25 的 20 分和向量搜索的 0.9 分如何换算,直接比较位次即可。
  • 跨策略无缝合并: 无论是 2 种还是 5 种不同的检索技术,只要各自给出一份排名列表,RRF 就能公平融合。
5. 调节语义 vs 关键词权重的参数

标准 RRF 本身没有直接的”语义 vs 关键词”权重参数——k 只是平滑常数,不控制两者比重。
加权 RRF(Weighted RRF) 在公式中为每个检索策略引入独立权重 w

$$RRF(d) = \sum_{i=1}^{n} \frac{w_i}{k + rank_i(d)}$$

  • 提高 w_semantic → 结果更偏向语义理解(找近义、概念相关)
  • 提高 w_keyword → 结果更偏向精确字面匹配

在工程实现中,这个权重在哪里调?
LangChainEnsembleRetriever 为例,最为直观:

1
2
3
4
5
6
7
8
9
10
11
12
from langchain.retrievers import BM25Retriever, EnsembleRetriever
from langchain_community.vectorstores import FAISS

# 两种检索器
bm25_retriever = BM25Retriever.from_documents(docs)
vector_retriever = FAISS.from_documents(docs, embedding_model).as_retriever()

# weights 就是公式里的 w_i,两个值相加通常为 1.0
ensemble_retriever = EnsembleRetriever(
retrievers=[bm25_retriever, vector_retriever],
weights=[0.3, 0.7] # 关键词 30%,语义 70%
)

weights=[0.3, 0.7] 即公式中的 w_keywordw_semantic。调大语义权重 → 更擅长理解意图;调大关键词权重 → 更擅长精确匹配专有名词。

4. 评估指标

用量化数字衡量检索器好不好用,是系统调优的科学基础。

三大核心指标
指标 核心目标 一句话解释
召回率 Recall@K “找得全” 前 K 个结果里,成功找到了多少个真正相关的文档
精确率 Precision & MAP “找得准 + 排得好” Precision 衡量返回结果中有多少是噪音;MAP 进一步评估相关文档是否排在最前面
平均倒数排名 MRR “首位命中” 第一个相关文档出现的位置越靠前,得分越高
用一个具体例子理解 Precision 和 Recall

场景: 知识库共 100 个文档,其中 10 个真正相关(Ground Truth),检索器返回了 8 个,其中 6 个真正相关。

$$Precision = \frac{\text{返回的且相关的}}{\text{总返回数}} = \frac{6}{8} = 75%$$

$$Recall = \frac{\text{返回的且相关的}}{\text{库中所有相关文档数}} = \frac{6}{10} = 60%$$

Precision:站在”你返回的文档”角度 —— 有多少是真货,有多少是噪音?
Recall:站在”知识库”角度 —— 10 个正确答案,你找到了几个?

Precision 与 Recall 的内在张力

两者天然互相拉扯:

操作 Precision Recall 原因
K 调大(如 8 → 50) ⬇️ 下降 ⬆️ 上升 捞得多,噪音增多,但遗漏减少
K 调小(如 8 → 3) ⬆️ 上升 ⬇️ 下降 只选最有把握的,精准但遗漏多
指标的实际用途
  • 评估基准表现:给当前系统打分,明确现状水平
  • 验证优化效果:换 Embedding 模型、调整 Chunking 大小、修改混合检索权重时,通过对比指标前后变化来确认改动是否真的有效
最关键的前提:Ground Truth

所有指标都依赖于”基准真相”数据集

  • 什么是 Ground Truth? 人工标注好的数据集。例如:针对问题 A,提前标注知识库里”文档 1”和”文档 5”是唯一正确答案。
  • 为什么重要? Recall、Precision、MAP、MRR 的所有计算都需要对比”系统找出的答案”与”标准答案”。没有 Ground Truth,就无法进行科学调优。

5. Embedding model深入探讨

对比训练(Contrastive Training Process)

训练目标:让相似内容的向量靠近,不相似内容的向量远离

正样本:来自”互联网的自然配对”

人类在互联网上的自然行为,天然就产生了海量配对数据:

数据源 正样本配对方式 谁”标注”的?
网页图片的 alt 属性 <img alt="夕阳下的柳树"> → (图片, “夕阳下的柳树”) 网页作者写 alt 文字时,无意识地创造了配对
论坛问答(StackOverflow、知乎) (问题, 最佳答案) 用户提问和回答时,无意识地创造了配对
维基百科 (文章标题, 正文第一段) 人类写百科,结构天然就是配对
新闻图片 (配图, 图说文字) 编辑写图说时,无意识地创造了配对

核心思想:互联网本身就是一个巨大的”隐式标注数据集”。人类日常创作内容时,已经在无意识地为 AI 标注数据了。

负样本:由算法自动生成
负样本类型 来源 需要人工吗?
In-batch 负样本 Batch 内其他样本自动充当 ❌ 完全自动
随机负样本 从数据集随机采样 ❌ 完全自动
Hard Negatives 用弱模型先检索”接近但错误”的样本 ⚠️ 部分需要人工验证
LLM 生成的 Hard Negatives 让 GPT-4 生成语义相近但不同的句子 ❌ AI 生成
极少数需要人工标注的场景

只有在需要高精度 Benchmark(用来测试模型好不好)时,才会动用人工标注:

1
2
3
STS(Semantic Textual Similarity)数据集:
人类给两个句子的相似度打 1-5 分
但这是用来"评测"模型的,不是"训练"模型的主力数据

训练过程:前向传播生成向量 → 构建相似度矩阵 → InfoNCE Loss 惩罚”正样本排名不靠前”的情况 → 反向传播更新权重。

CLIP 的 4 亿训练对几乎全部自动获取。互联网本身就是隐式标注数据集,人工标注只用于评测 Benchmark,不是主力训练数据。

另外我感觉这个训练的过程很像我个人网站上的节点图,这个节点图会有斥力和吸力,整个节点图系统需要保持一个稳定的状态。因此,需要保持斥力和吸力的稳定,它需要有一个调整的过程。而这个调整的过程,我觉得可能就是 embedding 这个过程。

一个常见的误区——大模型参数 vs Embedding维度:参数是神经网络内部的权重总量(CLIP 约 1.5 亿个),是训练中学到的知识本身;维度是输出向量的长度(如 512 维),是设计时固定的规格。参数是模型学到的知识总量(越多越”聪明”),维度是输出向量的长度(影响表达能力的上限)。两者不是同一回事。

模块三:使用向量数据进行信息检索

1. 从暴力检索到 ANN

向量检索的所有算法,都有一个关键变量 K——指定返回与 query 最相近的向量数量(Top-K)。围绕”如何高效找到这 K 个向量”,演化出了两大流派:精确检索近似检索(ANN)

最基础的算法:暴力检索(Brute Force / Flat Search / Linear Scan)

最朴素直观的做法——计算 query 向量与数据库中每一个向量的距离(余弦/欧氏),全排序后取 Top-K。

在 FAISS 里它叫 IndexFlatL2,在学术界叫 Exact KNN,在工程上常被称为 Brute ForceLinear Scan。本质都是同一件事:不建任何索引,硬算到底。

  • 优点:实现极简(一行 numpy 就能写完);Recall = 100%,是所有 ANN 算法的精度天花板与评测基准
  • 致命问题:复杂度为 O(N×D)(N=向量数,D=维度)。数据库越大,单次查询越慢——百万向量已经吃力,亿级规模直接不可用
  • 适用场景:小数据集(<10万)、离线评测、为 ANN 提供 ground truth
解决方案:ANN(Approximate Nearest Neighbor,近似最近邻搜索)

核心思想:预先建索引,跳过大部分不可能相关的向量,只在小范围内精细比对——通过牺牲极小的精度,换取查询速度数个数量级的提升。

主流实现:

  • HNSW(分层可导航小世界图):基于”跳表+小世界网络”,复杂度降至 O(log N),精度高、查询快,工业界主流
  • IVF(倒排文件索引):先用 K-means 聚类,查询时只扫描最近的几个簇,适合超大规模
  • PQ(乘积量化):把高维向量切段后用聚类编号代替,向量压缩 64 倍以上,显著节省内存

工程上需在 精度(Recall)↔ 速度(QPS)↔ 内存 三者间权衡,常见组合如 IVF+PQ、HNSW+PQ。

2. 向量数据库

向量数据库是专门为存储、管理和检索高维向量设计的数据库系统。普通数据库存的是结构化行列,向量数据库存的是 Embedding 向量——并在其上内置 ANN 索引,让语义检索变成一等公民。

与传统数据库的核心区别
维度 关系型数据库(MySQL) 向量数据库(Qdrant/Milvus)
核心数据 结构化行列 高维浮点向量
查询方式 SQL 精确匹配 ANN 近似相似度检索
索引类型 B-Tree、Hash HNSW、IVF、PQ
典型问题 “找 id=42 的记录” “找最语义相似的 Top-10”

向量数据库通常同时存向量 + 元数据,支持先用元数据硬过滤,再做向量检索——即上文提到的 Metadata Filtering 与语义检索的结合。

主流向量数据库对比
  • Qdrant:Rust 编写,性能强,REST/gRPC 接口,支持 payload 过滤,开源可自部署
  • Milvus:专为超大规模设计(亿级),云原生架构,适合生产级分布式场景
  • ChromaDB:最轻量,几行代码启动,开发调试首选,不适合大规模生产
  • pgvector:PostgreSQL 插件,已有 PG 数据库直接加向量检索,无需引入新系统
  • FAISS:严格来说是库而非数据库——无持久化、无 CRUD,但是所有向量数据库的底层算法来源
创建向量数据库的基础流程

Step 1 — 数据库初始化(Database Setup)
创建集合(Collection)并定义 Schema——指定存哪些字段、向量维度是多少、用什么距离度量(余弦 / L2 / 内积)。这是后续所有操作的容器,相当于建表。

Step 2 — 文档加载(Loading Documents)
将原始数据(文本、PDF、图片等)读入内存,按需做分块(Chunking)——把长文档切成适合 Embedding 的小段,避免单段过长导致语义稀释。

Step 3 — 稀疏向量(Sparse Vectors,关键词检索用)
用 BM25 等算法为每个文档块生成稀疏向量——向量大多数位置为 0,只有出现过的词对应位置有权重值。专为关键词精确匹配设计,是混合检索(Hybrid Search)的关键词侧。

Step 4 — 稠密向量(Dense Vectors,语义检索用)
将文档块送入 Embedding 模型(如 BGE、CLIP),输出每个块的稠密向量——每一维都有值,承载语义信息。这是语义搜索的核心,能理解同义词和意图。

Step 5 — 构建 HNSW 索引(HNSW Index)
在稠密向量上建立 HNSW(分层可导航小世界图)索引。预先在向量之间织好”导航网络”,查询时沿图跳跃定位,将检索复杂度从 O(N) 降至 O(log N),实现毫秒级 ANN 搜索。这个也可以不做,如果不做的话就是暴力检索。

3. Chunk(分块技术)

将长文档切成适合 Embedding 的小段,是 RAG 索引阶段的关键预处理步骤。块太大语义稀释、块太小上下文丢失,切得好不好直接影响检索质量。

为什么要分块?

Embedding 模型有 Token 上限(如 BERT 系列 512 token、BGE-M3 8192 token)。把整本书塞进去只会得到一个模糊的”平均语义”,检索时很难命中具体段落。分块后每段各自有独立向量,检索精度大幅提升。

主要分块策略
  • 固定大小(Fixed-size Chunking):按字数 / token 数硬切,简单粗暴。缺点是可能从句子中间截断,损失上下文。常配合 Overlap(重叠) 使用——相邻块共享若干 token,缓解截断问题。
  • 语义分块(Semantic Chunking):按句子边界、段落、标题层级切分,保留自然语义完整性。适合结构化文档(Markdown、PDF)。
  • 递归字符分割(Recursive Character Splitting):LangChain 默认策略,按 \n\n → \n → 句号 → 空格 优先级依次尝试,尽量在自然边界切割,兼顾简单与语义完整。

4. 一些更高级的 chunk 技术

利用 LLM 进行语义分块

传统分块靠规则(段落、标题、固定大小),而”LLM 语义分块”让模型真正理解内容的语义边界后再决定怎么切,质量更高但成本也更高。


① 基于 Embedding 相似度的语义分块(SemanticChunker)

原理:先按句子拆开文本 → 对每个句子计算 Embedding → 计算相邻句子的余弦相似度 → 当相似度骤降时,说明话题发生了转变,在此处切割。

1
2
3
句1 → 句2 → 句3 ↘↘骤降↘↘ 句4 → 句5

切割点(语义断裂)
  • 工具:LangChain 的 SemanticChunker 直接封装了这个逻辑
  • 优点:无需调用 LLM 推理,成本低;真正按语义断点切,而不是按字数
  • 缺点:需要设定相似度阈值,阈值选不好会切太碎或切太少

② 命题分块(Proposition Chunking)

原理:用 LLM 将每个文本段进一步提炼为若干原子命题——每条命题是一个独立、完整、可单独理解的最小事实单元。

例:原文”爱因斯坦于 1905 年发表了狭义相对论,并因光电效应获诺贝尔奖”
→ 命题1:「爱因斯坦于 1905 年发表了狭义相对论」
→ 命题2:「爱因斯坦因光电效应研究获得了诺贝尔奖」

  • 优点:检索时 query 与命题一一对应,精准度极高;每条命题自包含,不依赖上下文也能理解
  • 缺点:每个 chunk 都要调用 LLM 提炼,token 消耗大、速度慢,索引构建成本高
  • 适用:对知识库检索质量要求极高的场景(医疗、法律、精确问答)

③ Agentic 分块

直接将整段文档交给 LLM,让它自主判断语义边界、输出分割点或直接输出分好的块。最灵活,能处理复杂非结构化文档(如对话记录、混排文档),但成本最高,一般只用在离线预处理管道中。


三种方式对比
方式 是否调用 LLM 成本 精度 适用场景
Embedding 相似度 只用 Embedding 模型 通用场景,快速构建
命题分块 是(提炼命题) 精确问答、知识库
Agentic 分块 是(理解+切割) 最高 最高 复杂非结构化文档

实际项目中最常见的折中方案:先用递归字符分割做粗切,再用Embedding 相似度做语义边界校正,只在核心知识库段落上启用命题分块

5.查询解析

用户的原始问题往往口语化、模糊、包含多个子意图——直接用来检索,向量相似度会偏移,漏掉真正相关的文档。”查询解析”这一步的目标,就是在检索前用 LLM 把问题变得更适合检索

核心思路:在 Retrieval 之前,先用一次(或多次)LLM 调用对 Query 做变换,再去检索。


Query Rewriting —— 查询改写

原理:直接用 LLM 将用户的原始问题改写为一个(或多个)措辞更精准、更接近知识库文档语言的新 Query。

例:用户问”这个 bug 咋修” → 改写后:”如何修复 IndexError 数组越界异常?”

为什么有效:Embedding 模型是对称相似度——用户说的词和文档里写的词越像,向量越接近。改写填补了”口语 ↔ 文档语言”的词汇鸿沟(Vocabulary Mismatch)。

1
2
3
4
# 改写 Prompt 示意
system = "你是一个专业搜索助手,将用户问题改写为适合文档检索的精准查询语句,只输出改写后的句子。"
rewritten = llm.chat(system, user_query)
results = vector_db.search(embed(rewritten), top_k=5)
  • 优点:实现简单,一次 LLM 调用;显著改善词汇不匹配问题
  • 缺点:改写有可能偏离原意;增加一次 LLM 延迟

Query Decomposition —— 查询分解

原理:遇到包含多个子问题的复杂 Query,让 LLM 将其拆解为若干独立子问题,每个子问题单独去检索,最后汇总答案。

例:用户问 “Python 和 JavaScript 在 Web 开发中各有什么优缺点,该选哪个?”
→ 子问题 1:「Python Web 开发的优点是什么?」
→ 子问题 2:「Python Web 开发的缺点是什么?」
→ 子问题 3:「JavaScript Web 开发的优点是什么?」
→ 子问题 4:「JavaScript Web 开发的缺点是什么?」

每条子问题分别检索,检索结果合并后一起交给 LLM 生成最终答案。

为什么有效:一个复杂问题往往在知识库里对应多处分散的信息。不分解则很难用一条 Query 同时命中所有相关段落;分解后每条 Query 更聚焦,检索精度大幅提升。

  • 优点:特别适合多跳推理(Multi-hop)和比较类问题
  • 缺点:子问题数量不可控;多次检索成本倍增;汇总逻辑复杂

HyDE —— 假设性文档嵌入(Hypothetical Document Embeddings)

原理:不直接对 Query 做 Embedding,而是先让 LLM 生成一段假设性的”理想答案”,再对这段假设答案做 Embedding 去检索。

1
用户 Query → LLM 生成"假设答案" → Embed(假设答案) → 向量检索

例:用户问”HNSW 为什么查询速度快?”
→ LLM 生成假设答案:”HNSW 基于分层图结构,查询时从高层稀疏图快速定位大致区域,再逐层精细搜索……”
→ 对这段假设答案做 Embedding → 比直接对问题句做 Embedding,向量更接近知识库里真实文档

直觉理解:问题的 Embedding 和答案的 Embedding 在语义空间里本就不在同一个位置。假设答案更像一段”真实文档”,因此和知识库里的文档向量距离更近,检索 Recall 更高。

  • 优点:对”知识密集型问答”效果显著;不依赖关键词,纯语义驱动
  • 缺点:假设答案可能包含幻觉,但检索阶段不依赖答案的正确性,只用它的向量,所以幻觉不直接影响结果
  • 适用:专业领域问答、文档措辞与问题措辞差异大的场景

Multi-Query —— 多角度提问

原理:让 LLM 从多个角度生成同一问题的 N 种变体,分别检索,再对检索结果去重合并(常与 RRF 联用)。

例:原问题”RAG 的局限性”
→ 变体 1:「RAG 在什么场景下效果不好?」
→ 变体 2:「Retrieval-Augmented Generation 的缺点有哪些?」
→ 变体 3:「RAG 系统的失败案例」

三条 Query 各自检索,合并结果后用 RRF 重排序,最终 Top-K 覆盖面远超单一 Query。

  • 优点:弥补单条 Query 的覆盖盲区;与 RRF 天然搭配
  • 缺点:N 次 Embedding 检索 + LLM 调用,延迟和成本线性增长

总结/各方案对比
方式 核心思路 适用场景 额外 LLM 调用 风险
Query Rewriting 改词,更像文档语言 口语/专业词汇不匹配 1次 改偏原意
Query Decomposition 拆子问,分别检索 多跳/比较类复杂问题 1次(拆分)+ N次检索 子问题爆炸
HyDE 先生成假设答案再检索 问题与文档措辞差异大 1次 幻觉影响向量方向
Multi-Query 生成多变体覆盖盲区 召回率不足、覆盖面窄 1次(生成)+ N次检索 成本高

工程实践中:最常见的轻量方案是 Query Rewriting + Multi-Query(N=3)+ RRF——只多 1 次 LLM 调用,检索质量提升明显。HyDE 和 Decomposition 留给对质量要求极高的场景(如法律文书问答)。

6. Reranker 与精排策略

背景:向量检索(常规的编码两次的成为Bi-Encoder)快但粗糙——它把 Query 和 Document 分别压成单个向量再做点积,损失了大量细粒度的交互信息。Reranker 就是在粗检索之后,对 Top-N 候选做精细打分、重新排序的第二阶段模块。

两阶段检(Bi-Encoder)索管道
1
2
3
4
5
用户 Query

[第一阶段] 向量检索(Bi-Encoder + FAISS) → Top-100 候选 ← 快,但粗

[第二阶段] (Cross-Encoder)Reranker 精细打分 → Top-10 最终结果 ← 慢,但准
交叉编码器(Cross-Encoder)

原理:把 Query 和 Document 拼在一起输入同一个 Transformer,让两段文字的所有 token 互相做注意力——直接输出一个相关性分数。

1
2
输入: [CLS] Query tokens [SEP] Document tokens [SEP]
输出: 一个 0~1 的相关性分数
  • 优点:全量交互,打分最准确,是精度天花板
  • 缺点:Document 向量无法预计算——每次查询都要把每个候选文档和 Query 重新跑一遍,100 个候选 = 100 次模型推理,延迟高
  • 适用:候选集不大(Top-100 以内)、对精度要求极高的场景
ColBERT(Contextualized Late Interaction over BERT)

image.png
如上图,问题的每一个词都会和文章里的每一个词产生呼应,从而获得更精确的结果。

原理:Query 和 Document 依然分开编码(所以 Document 可预计算),但不压缩成单个向量——保留每个 token 的向量。相关性分数用 MaxSim 计算:对 Query 的每个 token(逐字切分),找 Document 所有 token 里最相似的那个,累加求和。

1
2
3
4
Query   → [q₁, q₂, q₃, ...]     每个 token(逐字切分) 一个向量
Document → [d₁, d₂, d₃, ...] 每个 token(逐字切分) 一个向量(可离线存储)

Score = Σ max_j sim(qᵢ, dⱼ) ← "Late Interaction"
  • 优点:Document 可以离线预计算并存储;交互比单向量 Bi-Encoder 丰富得多
  • 缺点:存储开销大(每个 token 存一个向量,文档越长越占空间);比 Bi-Encoder 慢,比 Cross-Encoder 快
  • 定位:精度与速度的中间档,适合候选集较大(Top-1000)的场景
三者横向对比
Bi-Encoder ColBERT Cross-Encoder
编码方式 Query/Doc 各一个向量 Query/Doc 各保留 token 向量 拼接后联合编码
Doc 预计算
交互粒度 粗(单向量点积) 中(token-level MaxSim) 细(全量注意力)
速度 最快 最慢
精度 最低 最高
典型用途 第一阶段粗检索 中等规模 Reranking 小候选集精排

7. ReRanking

精排不是一种具体模型,而是一个管道位置的概念——在粗检索之后,对候选集重新打分排序。
具体实现方式有多种,§6 已详解 Cross-Encoder 与 ColBERT,这里补充另外两条路线。

精排的三条路线(概览)
路线 核心机制 详见
Cross-Encoder Query+Doc 拼接联合编码,token 全量交互 §6 ①
ColBERT 保留 token 向量,MaxSim 延迟交互 §6 ②
RRF 合并多路检索的排名,无需打分 下方 ↓
LLM 直接评分 让大模型判断相关性 下方 ↓

① RRF(互惠排名融合,Reciprocal Rank Fusion)

适用场景:你同时运行了多路检索(如稀疏 BM25 + 稠密 CLIP),需要把两份排名列表合并成一份。

核心公式

1
2
RRF_score(doc) = Σ  1 / (k + rank_i)       k 通常取 60
i

对每路检索,用文档的排名位置(而非分数)计算贡献,再累加。

具体例子

1
2
3
4
5
6
7
8
9
10
11
Query: "覆有青苔的石头"

BM25 排名(关键词): CLIP 排名(视觉):
stone_moss_03 → rank 1 stone_moss_03 → rank 1
stone_moss_01 → rank 2 rock_wet_01 → rank 2
rock_wet_01 → rank 8 stone_moss_01 → rank 5

RRF 合并:
stone_moss_03 = 1/(60+1) + 1/(60+1) = 0.0328 ← 两路都认可 ✅
stone_moss_01 = 1/(60+2) + 1/(60+5) = 0.0315
rock_wet_01 = 1/(60+8) + 1/(60+2) = 0.0308

为什么不直接把两路分数相加?

BM25 分数(词频统计)和 CLIP 余弦相似度的量纲完全不同,直接相加没有意义。
RRF 只关心排名位置,不看具体数值,天然绕开了量纲对齐问题。


② LLM 直接评分

将候选结果的文本描述喂给 LLM,让模型直接判断与 Query 的相关性:

1
2
3
4
5
6
7
8
9
Prompt 示例:
查询需求:"覆有青苔的石头"
候选资产:"灰褐色石块,表面密布绿色苔藓,风化纹理明显,适合古风场景"

请给这个资产与查询需求的相关程度打分(0-10),并给出一句理由。

LLM 输出:
分数:9
理由:石块与青苔两个核心要素完全吻合,风化纹理进一步强化古风相关性
说明
优点 语义理解能力最强;可自定义评分标准(风格、场景适配度等)
缺点 每条候选都要调一次 LLM,成本高、延迟高
适用 候选集极小(Top-5 以内)、或需要可解释的评分理由

选型速查
候选集规模 推荐策略
全量 10 万+ 只能 Bi-Encoder(向量检索)
Top-1000 ColBERT 或 RRF 合并多路结果
Top-100 Cross-Encoder(推荐,精度/延迟最佳平衡)
Top-10 LLM 直接评分(可选,需要极致精度或可解释性时)
游戏资产库项目对应策略
  • 已有(M1):FTS5 关键词检索(稀疏)
  • 已有(M4):CLIP FAISS 视觉检索(稠密)
  • 进行中(M5):Cross-Encoder 精排,Top-100 → Top-10
  • 可选升级:将 FTS5 与 CLIP 的结果先用 RRF 合并,再送入 Cross-Encoder——
    稀疏路线补精确关键词命中,稠密路线补语义覆盖,两者互补

模块四:大语言模型与文本生成

前三个模块都在讲 Retrieve(检索)——把最相关的内容找出来。本模块进入 RAG 的最后一环 Generate(生成):把检索到的内容交给大语言模型(LLM),生成有据可查的回答。

本模块在 RAG 流程中的位置
1
2
用户问题 →【检索】Top-K 相关片段 →【拼装 Prompt】→【LLM 生成】→ 回答
↑ 模块一~三 ↑ 模块四

我们的资产搜索工具是”只检索不生成”,所以模块四对它是可选项;但本地的 starting-ragchatbot-codebase(问答机器人)是完整 RAG,生成环节由 Claude 承担。

Transformer架构简介

一句话:Transformer 是当今几乎所有 LLM、Embedding 模型、Reranker 的共同底座。本笔记反复出现的 BERT、CLIP、BGE、Cross-Encoder、GPT/Claude——全部是 Transformer 的变体。

1. 为什么是 Transformer?(对比 RNN)

先说命名:为什么叫 Transformer,而不叫 Attention?

  • Attention 是零件,Transformer 是整机。 Attention 早在 2014 年(Bahdanau)就作为 RNN 的配件用于机器翻译,到 2017 年已是通用技术——新架构若也叫 Attention,会和”RNN+attention”撞名。
  • 论文标题是论点,架构名是产品名。《Attention Is All You Need》的潜台词是”以前 RNN 配 attention,把 RNN 拿掉、只留 attention 就够”;标题负责喊口号,新架构另起名 Transformer 与 RNN 时代切割。
  • “Transform” = 逐层变换表示。 模型把 token 的向量表示通过堆叠多层一层层重写,最终把”孤立词向量”变成”饱含上下文语义的向量”。Attention 只描述单层的某个操作(Q·K→加权 V),Transformer 描述整机在做什么——抽象层级不同。

那为什么需要它? 2017 年论文《Attention Is All You Need》之前,处理文本主要靠 RNN/LSTM (RNN(Recurrent Neural Network,循环神经网络)),它按顺序一个词一个词读,有两个硬伤:

问题 RNN/LSTM Transformer
并行能力 必须顺序计算,无法并行 整句一次性并行处理 ⭐
长距离依赖 距离越远信息衰减越严重 任意两词直接建立联系
训练速度 快(能吃满 GPU)

Transformer 用**自注意力(Self-Attention)**一举解决了这两点,这也是 LLM 能”规模化”到千亿参数的前提。

2. 核心机制:自注意力(Self-Attention)

自注意力让句子里的每个词,都去”看”句子里所有其他词,按相关性加权吸收信息。

关键三元组 Q / K / V:

符号 含义 类比(像不像检索?)
Query(查询) 我现在想找什么 用户的搜索词
Key(键) 我能提供什么”标签” 文档的索引
Value(值) 我实际的内容 文档正文

计算流程:

1
2
3
4
对每个词:
1. 用 Query 和所有词的 Key 做点积 → 相关性分数
2. softmax 归一化 → 注意力权重(加起来=1)
3. 用权重对所有 Value 加权求和 → 该词的新表示

具体例子——理解句子”它”指代谁:

1
2
3
4
5
6
句子: 这块石头覆满青苔,因为它很潮湿

"它"这个词的 Query 去匹配全句的 Key:
"石头" → 注意力 0.7 ← 权重最高,模型据此判断"它=石头"
"青苔" → 注意力 0.2
"潮湿" → 注意力 0.1
关键洞察:注意力本质上就是一次"软检索"

Query·Key→softmax→对 Value 加权求和——这套机制和模块二、三讲的稠密检索(向量相似度→Top-K→取回内容)是同一个数学思想,只不过它发生在模型内部,对每个 token 实时进行。理解了向量检索,你已经理解了注意力的一半。

3. 多头注意力 + 位置编码 + 整体结构
  • 多头注意力(Multi-Head):并行跑多组 Q/K/V,每个”头”关注不同角度(一个头看语法、一个头看指代、一个头看语义……),最后拼接。类比:多个专家从不同维度同时审阅同一句话。
  • 位置编码(Positional Encoding):自注意力本身不区分词序(”狗咬人”和”人咬狗”会被看成一样),因此要额外注入每个词的位置信息。
  • 残差连接 + 层归一化 + 前馈网络(FFN):每一层的标准配件,保证深层网络(几十上百层)能稳定训练。
4. 三种架构变体(决定模型用途)
架构 代表模型 擅长 在本笔记中的角色
Encoder-only(编码器) BERT、BGE-M3 理解、向量化 Embedding 模型(§3.1)、Cross-Encoder(§6)
Decoder-only(解码器) GPT、Claude、Qwen、Llama 生成文本 本模块的主角,负责”生成”
Encoder-Decoder T5、BART 翻译、摘要 较少用于聊天
一张图串起整本笔记
  • 你用 CLIP/BGE(Encoder)把资产转成向量 → 这是 Transformer
  • 你用 Cross-Encoder(Encoder)做精排 → 这是 Transformer
  • 你用 Claude/Qwen(Decoder)生成答案 → 还是 Transformer
    三者血缘相同,区别只在”怎么搭、用哪半边、怎么训练”。

大语言模型采样策略

检索决定”喂什么内容”,采样策略决定”模型怎么把内容说出来”——同样的上下文,参数不同,输出可以严谨也可以天马行空。对 RAG 来说这一节直接关系到幻觉率

1. LLM 是怎么”吐字”的?

Decoder 模型每一步只做一件事:预测下一个词的概率分布

1
2
3
4
5
6
7
输入:"覆有青苔的石头通常出现在"
模型输出(下一个词的概率):
潮湿 0.45
阴暗 0.25
森林 0.15
沙漠 0.02
...(词表里几万个词,每个都有概率)

“采样策略”就是从这个概率分布里挑一个词的规则。挑完接到输入后面,再预测下一个,循环往复。

2. 确定性解码
策略 做法 特点
贪心解码 Greedy 每步都选概率最高的词 稳定但呆板,易陷入重复
Beam Search 同时保留 N 条候选路径,最后选整体最优 质量高但慢,多用于翻译/摘要
3. 随机采样(聊天/创作主流)

① Temperature(温度)——调节分布的”陡峭/平缓”:

1
2
3
4
5
logits 经过 softmax 前先除以 T:

T = 0(极冷):只剩最高分,等价于贪心,最保守
T = 0.7(温): 高分词更可能,但偶有惊喜 ← 通用默认
T = 1.5(热): 分布被拉平,低分词也有机会,更随机/有创意

② Top-k:只在概率最高的 k 个词里采样(如 k=40),砍掉长尾。

③ Top-p / 核采样(Nucleus):从高到低累加概率,凑够 p(如 0.9)就停,只在这个动态集合里采样。比 Top-k 更聪明——分布陡时候选少、分布平时候选多。

1
2
候选(按概率排序): 潮湿0.45 阴暗0.25 森林0.15 苔藓0.08 ...
Top-p=0.9:累加 0.45+0.25+0.15+0.08=0.93 ≥0.9 → 只在这4个里采样

④ min-p(较新):以最高概率词为基准,按比例设一个动态下限,鲁棒性比 Top-p 更好。

⑤ 重复惩罚(repetition / frequency penalty):对已经出现过的词降权,避免”复读机”。

4. RAG 场景该怎么调?
RAG 的核心诉求是"忠于检索内容",不是"创意"

生成阶段如果温度太高,模型容易脱离检索到的事实、自由发挥 → 幻觉

场景 Temperature Top-p 说明
RAG 事实问答 0 ~ 0.3 0.9 最大化忠实度,少编造
摘要/改写 0.3 ~ 0.5 0.9 略有灵活但不离谱
创意写作/起名 0.8 ~ 1.2 0.95 鼓励多样性
代码生成 0 ~ 0.2 要确定性
资产库项目的启示

如果将来给资产搜索加一个”自然语言解释为什么推荐这个资产”的生成功能,温度应设到 0.2 左右——让它老老实实根据资产的 metadata 描述说话,而不是脑补不存在的属性。


选择适合的大语言模型

没有”最好”的模型,只有”最适合当前任务+预算+合规要求”的模型。下面是一套可复用的选型框架。

1. 七个评估维度
维度 关键问题 对 RAG 的影响
能力等级 推理/复杂指令够不够强 决定答案质量上限
上下文窗口 能塞下多少检索片段 RAG 直接相关,见下 ↓
成本 输入/输出每百万 token 价格 高频调用时是主要开销
延迟/吞吐 首字时间、每秒 token 数 影响用户体验
隐私/合规 数据能否出本地 私有资产/代码的红线 ⭐
中文能力 中文理解与生成 中文场景必看
结构化输出/工具调用 能否稳定输出 JSON、调函数 Agentic RAG 必备
2. 闭源 API vs 开源本地
闭源 API(Claude / GPT / Gemini) 开源本地(Qwen / Llama / DeepSeek …)
能力 通常最强 头部开源已逼近闭源
部署 调 API,零运维 自己用 GPU 跑,需运维
成本 按量付费,无前期投入 硬件前期投入,边际成本近乎 0
隐私 数据出本地 ⚠️ 数据不出本地
定制 有限 可微调/量化,完全可控
3. 2026 年中主流模型速览
数据来源与时效

下表数据交叉核对自 Anthropic 官方 claude-api 参考(缓存 2026-06-04)与公开比价站(morphllm LLM API,验证于 2026-06-09LLM API Pricing 2026)。模型与价格变动极快,以官网为准。

闭源 API(价格=输入/输出,每百万 token):

模型 定位 上下文 价格(in/out)
Claude Fable 5 最强、长程 Agent 1M $10 / $50
Claude Opus 4.8 旗舰、编码/Agent 强 1M $5 / $25
Claude Sonnet 4.6 速度/智能平衡 1M $3 / $15
Claude Haiku 4.5 快而省 200K $1 / $5
GPT-5.5(OpenAI) 推理/数学强 ~256K ~$5 / $30
Gemini 3.1 Pro(Google) 超长上下文、多模态、性价比 巨大(≥1M) ~$2 / $12
DeepSeek V4 极致性价比 ~$0.14 起

开源/可本地部署(适合私有数据):

模型族 特点 备注
Qwen 3.5(通义千问) 中文最强梯队、尺寸齐全 小尺寸 8GB 可跑,本地首选 ⭐
Llama 4(Meta) 生态最大 中大尺寸
DeepSeek V4 推理强、开源 满血版需大显存
GLM-5(智谱) 中文友好
Mistral / Gemma / Phi 欧系/谷歌/微软小模型 Phi 适合边缘端
4. 上下文窗口与 RAG 的关系

RAG 要把 [系统提示] + [Top-K 检索片段] + [对话历史] + [用户问题] 一起塞进上下文。窗口越大,能放的检索证据越多。

但"窗口大"不等于"可以无脑塞"
  • 塞太多无关片段会稀释有用信息、抬高成本、还可能触发”中间遗忘”(见下一节)。
  • 正解仍是:先精排取 Top-5~10 高质量片段(模块三 §6/§7 的 Reranker),而不是把 Top-100 全丢进去。好的检索 > 大的窗口。
5. 选型速查
你的情况 推荐
私有美术资产 / 私有代码,数据不能出本地 本地 Qwen3 系列(用你的 DGX Spark 节点跑,128GB 统一内存可加载量化后的中大模型) ⭐
追求最高答案质量、可接受 API Claude Opus 4.8claude-opus-4-8)或 Fable 5
高频、对成本敏感 Claude Haiku 4.5 / Gemini Flash / DeepSeek
需要超长上下文 Gemini 3.1 Pro / Claude(1M 窗口)
两个项目的对应选择
  • 资产库项目(私有美术资产)→ 隐私优先 → 本地 Qwen on DGX Spark
  • starting-ragchatbot-codebase(课程问答机器人)→ 已用 Claude(Anthropic 官方推荐默认 claude-opus-4-8 + 自适应思考 thinking:{type:"adaptive"}),调 API 即可,无需运维

提示词工程

模型选定、检索完成后,提示词(Prompt)是你在推理阶段唯一的控制杆——不重新训练,仅靠组织输入就能大幅改变输出质量。对 RAG 而言,提示词工程的核心目标是:让模型严格基于检索内容作答,拒绝幻觉

1. 提示词的基本结构
1
2
3
4
5
6
7
┌── System(系统提示):设定角色、规则、语气、边界
│ "你是资产库助手,只依据提供的资产信息回答..."
├── Context(上下文):注入检索到的片段
│ "【资产1】灰褐色石块,密布青苔... 【资产2】..."
├── User(用户提问):本轮问题
│ "推荐适合古风场景的石头类资产"
└──(可选)Few-shot 示例 / 输出格式约束
2. 通用技巧
技巧 做法 何时用
角色设定 “你是资深美术指导……” 几乎总是
Few-shot 少样本 给 1~3 个”输入→理想输出”示例 输出格式/风格要稳定
思维链 CoT “请一步步推理后再给结论” 复杂推理(注:RAG 事实问答通常不需要太多发散)
结构化输出 要求输出 JSON / 指定字段 下游程序要解析、Agentic RAG
3. RAG 专用提示词模板(重点)

一个合格的 RAG 提示词必须包含三道”防幻觉”指令:①只用提供的内容 ②找不到就说不知道 ③标注来源

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
你是游戏资产库的检索助手。请严格遵守:
1. 只根据下面【检索结果】中的信息回答,不要使用你自己的知识。
2. 如果检索结果里没有答案,直接回答"未找到相关资产",不要编造。
3. 回答时用 [资产ID] 标注你依据的是哪条资产。

【检索结果】
[stone_moss_03] 灰褐色石块,表面密布绿色苔藓,风化纹理明显,适合古风场景
[rock_wet_01] 湿润岩石,深色,无苔藓

【用户问题】
有没有适合古风场景、带青苔的石头?

【期望输出】
推荐 [stone_moss_03]:灰褐色石块且密布青苔、风化纹理,契合古风场景。
([rock_wet_01] 无苔藓,相关性较低未推荐。)
这三条指令直接决定 RAG 系统的可信度

缺了第 2 条,模型在检索为空时会”自信地胡说”;缺了第 3 条,用户无法核查,答案不可追溯。

4. “中间遗忘”现象(Lost in the Middle)

研究发现:当上下文很长时,模型对开头和结尾的信息记得最牢,中间的内容最容易被忽略——召回率曲线呈 U 形。

1
2
3
4
5
模型注意力 ▲
│\ /
│ \ ____ / ← 中间塌陷
└──────────────▶ 片段位置
开头 中间 结尾
对 RAG 的直接启示

最相关的检索片段放在 Prompt 的最前面或最后面,不要埋在中间一大堆片段里。这也再次印证了精排(Reranking)的价值:与其塞 50 条让关键信息淹没在中间,不如精排出 5 条放在显眼位置。

5. 好 vs 坏 提示词对比
❌ 差 ✅ 好
角色 (无) “你是资产库助手”
防幻觉 (无,放任发挥) “只用检索结果,找不到就说没有”
来源 (不要求) “用 [资产ID] 标注依据”
片段顺序 随便堆 最相关的放首/尾
输出 “随便答” 指定格式/字段
提示词工程 vs 微调 vs RAG——三种"喂知识"的方式
  • 提示词工程:临时、零成本,靠当前输入引导 → 本节
  • RAG:动态注入外部知识,知识可实时更新 → 本笔记全篇
  • 微调(Fine-tuning):把知识/风格”焊进”模型权重,成本高、更新慢
    三者常组合使用:RAG 提供事实 + 提示词约束行为 + (可选)微调统一风格

幻觉抑制处理(Hallucination Suppression)

前三节讲的是”如何让模型少胡说”,但当幻觉不可避免地出现时,我们还需要事后手段来检测、定位、评估、归因。下面四个概念构成 RAG 系统幻觉治理的完整闭环。

(1) Citation / Context Cite(上下文引用)

Citation 直译”引用”,在 RAG 语境里特指:让模型把答案中每一个事实”钉”回原文的具体片段,让用户能一键追溯到出处。

两种实现层次:

层次 做法 代表
事后归因 (Attribution) 用 LLM 给答案中的每个事实反查”出处是哪个片段” Citation Generation / Cite-and-Explain
原生内嵌 (Native Citations) 让模型在生成的同时直接产出引用,无需二次加工 Anthropic Citations API、LongCite

Anthropic Citations API 示例官方文档)——把文档标记为 citations: {"enabled": True},模型就会自动返回带 char_start / char_end 区间的引用块:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
response = client.messages.create(
model="claude-opus-4-8",
max_tokens=1024,
messages=[{
"role": "user",
"content": [
{"type": "document",
"source": {"type": "text", "media_type": "text/plain",
"data": "草地是绿色的。天空是蓝色的。"},
"title": "我的文档",
"citations": {"enabled": True}},
{"type": "text", "text": "草地和天空是什么颜色?"}
]
}]
)
# response.content 里会带 citations 字段,
# 直接告诉用户"天空是蓝色的"出自原文第 13~24 个字符

它解决什么痛点?

没引用 有引用
“草是绿的” “草是绿的”〔引用自《我的文档》§1, char 0–6〕
模型编造 → 用户无法察觉 引用对照 → 用户可秒判真假
对资产库项目的启示

即便现在只做检索不做生成,将来若加 LLM 总结,把引用机制从一开始就塞进 prompt 是几乎零成本的”防御工事”。


(2) Citation Generation(引用生成)

Citation Generation = 让 LLM 主动”产出”带引用的答案。

它是上面 Citation 的”生成端”实现策略,强调训练/提示模型把”输出 + 引用”打包在一起写出来。

主流做法分两路:

路线 代表 思路
训练式 LongCite(THU, 2024) 构造长文 QA 训练对,让模型学会”答到哪句、引到哪段”,产出细粒度句级引用
提示式 Anthropic-style Citations 改用提示词约束输出格式,例如:「请输出 JSON: {answer, citations: [{doc_id, span}]}

LongCite 例子(虚构示意):

1
2
3
4
5
问:CLIP 是什么时候、由谁发布的?
答:CLIP 由 OpenAI 于 2021 年 1 月发布。
引用:
[1] 文档《CLIP 概述》§1.1 "OpenAI 于 2021 年 1 月公开发布 CLIP..."
[2] 文档《多模态模型发展史》§3 "2021 年 1 月:CLIP、ALIGN..."
Citation 生成 ≠ 完全消除幻觉

CiteGuard(2025) 指出:模型可能生成”长得像引用”但实际错位”的引用(recall 只有 16–17%)。所以生成引用本身仍需要被验证——它比无引用强,但不是银弹


(3) Evaluating Criterion Quality in LLMs

直译:评估 LLM 中”评估标准”的质量。

这一节讲的不是”评估 LLM”,而是评估”用来评估 LLM 的那些打分标准(criterion / rubric)本身靠不靠谱”

为什么重要? 当我们用 LLM-as-a-Judge(让 LLM 当裁判打分)评价 RAG 输出时,裁判其实只看到了你写的 rubric(评分标准)。rubric 写得糊,裁判打得就糊。

1
2
3
4
5
6
7
✅ 好 rubric(一条一维度 + 锚点 + justify):
"答案忠实性 (1–5):
1=完全编造 3=部分基于上下文 5=完全基于上下文
请基于检索片段逐句核对,引用支持该分数的句子。"

❌ 差 rubric:
"答案是否好?(1–5)"

量化”rubric 质量”要看几件事:

维度 含义 如何测
判别力 Discriminability 不同质量的答案能不能拉开档次? 同 query 多份候选,看分数分布
一致性 Inter-judge Agreement 同一份答案,不同裁判(人/LLM)打分是否一致 Cohen’s κ、% 一致率
与人类对齐 Human Alignment 裁判分是否接近真人打分 跟人工标注集求 Pearson/Spearman
稳定性 Rubric Drift 同一 rubric 用久了分数是否漂移 定期回测金标集
抗偏性 Bias Resistance 不受位置偏差 / 长度偏差 / 自家偏好影响 ABX 配对测试

实操做法(行业主流):

  1. 先建一个金标集(几百条问题 + 人工标注的”标准答案 + 标准分数”)。
  2. 多版 rubric 对照打同一份金标,看哪版跟人类分数最接近。
  3. 定期回测——模型升级、领域变化时重跑金标,看分数曲线是否漂移(rubric drift)。
  4. 记录 justification——每个分数要求裁判给出理由,方便事后复盘”为什么这个答案拿 4 分不是 3 分”。
对 RAG 评估的直接影响

模块五要讲的 RAG 评估三大指标——Context Relevance / Groundedness / Answer Relevance(即 TruLens 提出的 RAG Triad)——能否真的衡量质量,完全取决于为这三个指标写的 rubric 写得清不清楚。这一节就是为那块内容做铺垫。


(4) 四个概念如何拧成”幻觉治理闭环”
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
                ┌─────────────────────────┐
│ Citation / Context Cite │ ← "答案能溯源"
│ (生成时内嵌 / 事后归因)│
└────────────┬────────────┘

Citation Generation ────→ 训练/提示 LLM 主动产出引用

┌─────────────────────────┐
│ Evaluating Criterion │
│ Quality in LLMs │ ← "裁判标准本身要可靠"
│ (rubric 质量评估) │
└────────────┬────────────┘

RAG 评估打分
(更准的分数)

上线后持续监控 + 反馈回检索 / 生成

对 RAG 的总结性建议:

阶段 用上哪些手段
离线开发 Good prompt(§3)+ 精排 Top-K(§模块三)+ Citation Generation(训练/提示)
上线前 RAG Triad 评估 + Evaluating Criterion Quality(人评对齐)
线上 原生 Citations(如 Anthropic API)+ 用户反馈回流
事后归因 Context Cite 反查 + 监控 Groundedness 分数
一句话记住

好的 RAG 不只”答得对”,还要”答得可以查”——生成(Citation)+ 评估(Criterion)才是完整的幻觉治理。

RAG vs 微调(Fine tuning)

RAG 与微调经常被一起讨论,但其实是两条不同的路线,解决的问题不一样:

维度 RAG(检索增强生成) Fine-tuning(微调)
做什么 检索外部知识,拼进 prompt 改模型权重,让模型”记住”知识
知识更新 改知识库即可,实时生效 必须重新训练,耗时耗钱
适用场景 私有数据 / 时效性强 / 要引出处 学特定风格 / 固定输出格式 / 注入专业表达
推理成本 每次多一次检索(向量查询) 训练贵,推理不变
可解释性 答案可追溯到具体文档 黑箱,模型说啥就是啥
幻觉风险 低(有据可查) 仍可能幻觉(遗忘 / 编造)
数据需求 文档 + 嵌入索引 高质量标注问答对(通常上千条起步)
怎么选?
  • 数据在变、要溯源、合规要求高 → 优先 RAG
  • 要改风格、固定输出格式、学特定表达 → 优先 Fine-tuning
  • 最难的任务 → RAG + 微调组合(先精调基底,再外挂知识)
一句话记住

RAG 是给模型”开卷考试”,Fine-tuning 是让模型”在家背书”。开卷永远比背书保鲜。

常见微调方式

方式 一句话说明 适合场景 数据量级
指令微调(SFT) 用”指令-回答”对训练,让模型听懂人话、学格式 教新任务、改语气风格 几千~几万条
持续预训练(Continued Pre-training) 在专业语料上继续预训练,让模型”懂行” 法律、医疗、代码、金融等垂直领域 大规模无标注语料
LoRA / QLoRA(PEFT) 只训练少量参数(低秩适配器),冻结原权重 资源不够 / 多任务并行 / 快速迭代 几百~几千条
DPO(直接偏好优化) 用”好回答 vs 坏回答”对直接优化策略 想要 RLHF 效果但嫌流程复杂 几千条偏好对
RLHF 先训奖励模型,再用 PPO 强化学习对齐人类偏好 ChatGPT 式的”听话 + 有用 + 安全”对齐 几万条人类标注
Prefix / Prompt / Adapter Tuning 在模型上加一个可训练的小模块/前缀 一个基座服务多任务 几百条
工程上最常见的"三段式"路径

持续预训练(学领域知识)→ 指令微调 SFT(学格式/风格)→ DPO 或 RLHF(对齐偏好)——这也是 Llama、Qwen、DeepSeek 这类开源模型的标准训练流程。

但是课程的老师也说,微调并不是让AI去学习新知识的一种好办法。AI通常经过微调之后,通常它在提示词上会有比较大的变化。但是对于信息面的影响会比较小。
所以这就引出了 RAG 和微调的适合场景:RAG 比较适合于注入新知识,而微调比较适合于为特定领域做适配

一句话记住

不是所有微调都要”改全量”——LoRA/QLoRA 这类 PEFT 用 1% 的参数就能拿到 90% 的效果,才是工程界的性价比之王。


模块五 生产环境中的RAG系统

1. 生产环境面临哪些挑战?

RAG 走出 demo 后,会撞上一连串 demo 里看不到的”墙”:

维度 Demo 状态 生产现实
数据 几十条干净文档 百万级脏文档(重复、过期、权限敏感)
用户 自己人测试 真实用户问法千奇百怪、夹带错别字/口语
召回 “差不多就行” 必须可追溯、可解释、有 SLA
成本 一张卡跑跑 QPS、成本、延迟三者拉扯
反馈 没有 需要日志、监控、A/B、可观测性
本质变化

Demo 是”能不能答出来“,生产是”答得稳不稳、贵不贵、快不快、安不安全、能不能量化改进“。

2. 实施 RAG 评估策略

评估 = 回答”我的 RAG 现在到底有多好?”。三步走:

1
2
3
离线评估集(黄金集) → 自动化打分 → 回归对比
↑ ↓
└──── 收集线上反馈,扩充集 ──────┘
  • 黄金集:人工标注 question + 期望答案 / 相关片段
  • 指标:检索(Recall@k、MRR)、生成(忠实度、答案相关性)、端到端(命中率)
  • 工具:RAGAS、ARES、LangSmith Evaluation、DeepEval
评估要"可重复"

同一份数据 + 同一份代码 = 同一份分数。否则版本一改,谁也说不清是”模型进步”还是”prompt 改了”。

3. 日志记录、监控与可观测性

生产 RAG 是黑盒流水线,没有日志 = 没有调试能力。三个层面:

层面 记录什么 用来干嘛
Trace(链路) 每次 query 的 retriever / reranker / LLM 调用、入参出参、耗时 复现问题
Metric(指标) QPS、延迟分位、token 用量、命中率、错误率 大盘告警
Feedback(反馈) 用户 👍/👎、人工抽检、显式评分 灌回评估集,迭代模型
三件套参考

LangSmith / Langfuse / Arize Phoenix / MLflow —— 任选一套,别裸跑。

4. 定制化评估

通用指标(如”语义相似度”)往往对不上业务。业务说答得好,才算真的好

  • 领域专家打分:法务/医生/客服主管按业务标准 1~5 分
  • 规则化校验:必须出现某字段、必须引用某类来源、不能出现某些词
  • 行为代理指标:点赞率、复制率、追问率(用户继续问 = 没答清)
  • LLM-as-a-Judge:用强模型当裁判,但必须用黄金集校准,否则裁判自己也在飘

5. 量化(Quantization)

把模型/向量的”高精度数值”换成”低精度表示”,省显存、省钱、省延迟

对象 方法 效果
Embedding 模型 int8 / binary 向量库内存 ↓ 4~32x,召回略降
LLM GPTQ / AWQ / GGUF 显存 ↓ 2~4x,速度 ↑,质量微损
向量存储 Product Quantization (PQ) 千万级向量单机可跑
量化的代价

召回率/生成质量会有 1~3% 的下降,必须在黄金集上回归,别拍脑袋上生产。

6. 成本 VS 响应质量

1
2
3
4
5
6
7
质量 ▲
│ ╭──── 大模型(贵)
│ ╱
│ ╱
│╱
└──────────────▶ 成本
小模型 路由

常见省钱策略:

  • 模型路由:简单问题 → 小模型/规则,复杂问题 → 大模型
  • 缓存:相同 query 命中直接返回,省一次 LLM
  • 减 token:精排压到 Top-3、截断长文档、用更小 context
  • 批处理 / 异步:非实时任务合并请求
不是越贵越好

把”质量提升 1% 要多花 10 倍钱”这种点找到,就是工程优化的 ROI 关键。

7. 延迟 VS 响应质量

1
2
3
4
5
首字延迟 ▲
│ ╭─ 长上下文 + 大模型
│ ╱
│╱───── 流式 + 小模型 + 精排
└──────────────▶ 质量

生产对延迟的硬要求:

场景 目标首字延迟
聊天/搜索 < 1s
客服自动回复 < 2s
离线分析 不敏感

手段:流式输出(SSE)、并行检索(向量+BM25 并发)、精排前置、speculative decoding

8. 安全性

RAG 的攻击面 = 检索器 + LLM,两头都要防:

风险 例子 防御
Prompt Injection 用户问题里塞”忽略上面所有指令” 输入清洗 + 指令/数据严格分段
数据泄露 检索返回了别人私有文档 元数据 ACL 过滤 + 租户隔离
越权回答 内部 wiki 答给了外部用户 权限网关 + 来源水印
幻觉 编造不存在的资产 强制引用 + “找不到就说没有”
有毒输出 检索到恶意文本被 LLM 复述 内容审核 + 输出过滤
提示词注入没有银弹

只能在 输入清洗 + 输出审查 + 权限隔离 三层一起做,单点防御都会被绕。

9. 多模态检索增强生成

RAG 不止能喂文字。能检索/生成的模态越多,应用场景越广:

1
2
3
4
5
6
文本 ←─────► 文本   (最常见,问答/搜索)
图片 ←─────► 文本 (以图搜文,CLIP)
文本 ←─────► 图片 (文生图 / 文搜图)
图片 ←─────► 图片 (以图搜图)
音频 ←─────► 文本 (会议录音 → 摘要)
视频 ←─────► 文本 (长视频 → 关键帧 + 字幕检索)

关键技术:

  • 统一 Embedding:CLIP / SigLIP / BGE-M3,把多模态映射到同一向量空间
  • 多模态 LLM:GPT-4o、Gemini、Qwen-VL,能看图、能听音、能读文档
  • 结构化解析:PDF 表格、扫描件、Chart → 用专用模型转 Markdown/JSON 再进 RAG
一个常见落地姿势

先把 PDF/PPT/截图全部 OCR + 结构化 → 转成文本 + 图块 → 进同一个向量库。一个 RAG 系统吃下企业全部知识资产。

模块五小结

生产 RAG = 评估体系 + 可观测性 + 工程权衡 + 安全合规 + 多模态扩展。技术之外,工程化能力决定了它能不能真正”活下去”。

八、参考资料


关联文档

我本地的仓库位置 D:\Project\UGit\starting-ragchatbot-codebase