RAG 系统完全笔记:从检索器到生产部署
- 约 25 分钟读完 - 全文 9852 字RAG 系统完全笔记:从检索器到生产部署
最近重刷了吴恩达 DeepLearning.ai 的《Retrieval Augmented Generation》课程,整理了一份从检索器到生产部署的完整笔记。RAG 不只是「把文档塞进向量库然后问大模型」,它的每个环节都有值得细究的工程决策。本文在每个算法之外,也补充了对应的落地工具与最佳实践。
一、RAG 是什么?为什么需要它?
大语言模型(LLM)有两个天然缺陷:
- 知识有截止日期:训练数据之后发生的事件、私有文档,模型都不知道。
- 容易 hallucination(幻觉):没有可靠依据时,模型会一本正经地编造答案。
RAG 的解决思路很简单:在生成答案前,先从外部知识库中检索相关文档,把检索结果作为上下文喂给 LLM。这样模型既能利用最新、最专有的知识,又能基于检索到的内容生成,显著降低幻觉风险。
一个完整的 RAG 系统分为离线索引和在线检索生成两条流水线:
flowchart LR
subgraph 离线索引
A[原始文档] --> B[文档分块 Chunking]
B --> C[Embedding 编码]
C --> D[(向量数据库)]
end
subgraph 在线检索生成
E[用户查询] --> F[查询改写]
F --> G[检索器 Retriever]
D -.-> G
G --> H[重排序 Re-ranking]
H --> I[LLM 生成]
I --> J[最终答案]
end
落地框架:搭建整条 pipeline 不必从零写,LangChain 和 LlamaIndex 是最主流的两个 RAG 编排框架,检索器、分块器、重排序器、提示词模板都有现成实现;需要精细控制流程状态(如 Agentic RAG)时可以用 LangGraph。入门建议先用 LlamaIndex 快速跑通,再根据瓶颈逐环节替换。
二、检索器:找到正确的文档
检索器是 RAG 系统的第一道关卡。常见检索方式有三种,以及它们的组合。
2.1 元数据过滤
在向量搜索之前,先用结构化条件缩小范围:
- 作者、发布时间、信源
- 地区、类别、权限标签
例如,企业知识库中只允许查询「本部门」文档,就可以通过 department_id 元数据过滤。
最佳实践:主流向量库都支持「过滤 + 向量搜索」的混合查询——Elasticsearch 的 filter 子句、Qdrant 的 payload filter、pgvector 配合普通 WHERE 条件都可以。权限过滤一定要放在检索阶段做,而不是检索出来再在应用层过滤,否则 Top-K 里可能挤掉有权限的文档,还会把不该出现的文档内容带进 LLM 上下文。
2.2 关键词搜索
基于词频的检索,计算查询词与文档的匹配程度。
TF-IDF
- TF(词频):查询中的词在一篇文档里出现了多少次。
- IDF(逆文档频率):查询中的词在所有文档中出现的频率的倒数。词越稀有,权重越高。
写成公式:
$$ \mathrm{tf}(t, d) = \frac{f_{t,d}}{\sum_{t’ \in d} f_{t’,d}}, \qquad \mathrm{idf}(t, D) = \log \frac{N}{1 + |{d \in D : t \in d}|} $$
公式中各符号的含义:
- $t$(term):一个词项(term),通常指查询中的某个词。
- $d$(document):一篇文档,即被评分的单篇文档。
- $D$(document collection):整个文档集合(语料库),包含全部 $N$ 篇文档。
- $f$:词频计数函数(frequency)。$f_{t,d}$ 表示词 $t$ 在文档 $d$ 中出现的次数,例如「检索」在某文档中出现 3 次,则 $f_{t,d} = 3$。
- $\sum_{t’ \in d} f_{t’,d}$:文档 $d$ 中所有词项的出现次数之和,即文档总词数(长度)。分子除以它,就把词频归一化成「占比」,避免长文档天然占优。
- $N$:文档集合 $D$ 中的文档总数。
- $|{d \in D : t \in d}|$:包含词 $t$ 的文档篇数(document frequency)。分母加 1 是平滑处理,防止词在所有文档中都不出现时除以零。
直观理解:$\mathrm{tf}$ 衡量「这个词在本文档中有多重要」(出现占比越高越重要),$\mathrm{idf}$ 衡量「这个词在整个语料库中有多稀有」(越稀有的词越能区分文档,权重越高),两者相乘就是词的综合权重。
文档 $d$ 对查询 $q$ 的得分即查询中各词的 TF-IDF 之和:
$$ \mathrm{score}(q, d) = \sum_{t \in q} \mathrm{tf}(t, d) \cdot \mathrm{idf}(t, D) $$
BM25
BM25 是 TF-IDF 的改进版,引入了两个可调参数:
$$ \mathrm{score}(q, d) = \sum_{t \in q} \mathrm{idf}(t) \cdot \frac{f_{t,d} \cdot (k_1 + 1)} {f_{t,d} + k_1 \cdot \left(1 - b + b \cdot \dfrac{|d|}{\mathrm{avgdl}}\right)} $$
- $k_1$:控制词频对评分的影响(词频饱和曲线),常见取值 $1.2 \sim 2.0$。
- $b$:控制文档长度归一化的强度,$|d|$ 是文档长度,$\mathrm{avgdl}$ 是平均文档长度,常见取值 $0.75$。
BM25 在短查询、精确匹配场景中仍然非常有效,比如查找包含特定术语的技术文档。
工业实现:Elasticsearch
BM25 不是纸面算法,它就是 Elasticsearch 的默认评分器(自 ES 5.0 / Lucene 6.0 起,替代了旧的 TF-IDF)。最普通的 match 查询,底层走的就是 BM25:
GET /my-index/_search
{
"query": {
"match": {
"content": "检索增强生成"
}
}
}$k_1$ 和 $b$ 两个参数可以在索引设置里自定义(注意修改 similarity 需要关闭索引或重建索引):
PUT /my-index
{
"settings": {
"similarity": {
"my_bm25": {
"type": "BM25",
"k1": 1.2,
"b": 0.75
}
}
},
"mappings": {
"properties": {
"content": {
"type": "text",
"similarity": "my_bm25"
}
}
}
}调试时还可以加 "explain": true,ES 会返回每个文档 BM25 打分的详细拆解(tf、idf、长度归一化各自贡献了多少),对排查「为什么这篇文档排在前面」非常有用。
不想维护 ES 集群时,轻量替代方案有 OpenSearch(同源于 Lucene)、纯 Python 的 rank_bm25 库,以及 LangChain 内置的 BM25Retriever。注意中文 BM25 的效果强依赖分词器:ES 建议装 IK Analyzer,并针对领域维护自定义词典(产品名、缩写、术语)。
2.3 语义搜索
将查询和文档都编码成向量,通过 余弦相似度(cosine similarity) 找到语义相近的文档。即使关键词不同,语义接近也能召回。
$$ \cos(\mathbf{q}, \mathbf{d}) = \frac{\mathbf{q} \cdot \mathbf{d}}{|\mathbf{q}| , |\mathbf{d}|} = \frac{\sum_i q_i d_i}{\sqrt{\sum_i q_i^2} \cdot \sqrt{\sum_i d_i^2}} $$
余弦相似度取值在 $[-1, 1]$ 之间,越接近 $1$ 表示语义越相近。实际工程中通常先对向量做 L2 归一化,此时余弦相似度就退化为简单的内积,检索更高效。
落地工具:向量由 Embedding 模型生成,选型参考 Hugging Face 的 MTEB / C-MTEB 榜单:
- API 方案:OpenAI
text-embedding-3系列、Cohere Embed、阿里 DashScope 通用文本向量。 - 自托管方案:BGE-M3(BAAI,中英文效果好,支持稠密 + 稀疏 + 多向量三种输出)、
gte-Qwen系列、E5 系列,均可用 sentence-transformers 或 FlagEmbedding 库本地加载。
最佳实践:查询和文档必须用同一个 Embedding 模型(部分模型对查询和文档有不同前缀,如 E5 的 query: / passage:,不要混用);更换 Embedding 模型意味着全量重建索引,选型时先在自有数据上做小规模评测再定。
2.4 混合搜索
实际工程中,通常把多种检索方式组合起来:
flowchart LR
Q[用户查询] --> K[关键词搜索 BM25]
Q --> S[语义搜索 向量]
K --> M[结果融合]
S --> M
M --> F[元数据过滤]
F --> R[重排序]
R --> O[最终上下文]
两路结果分数量纲不同,直接相加不合理,标准做法是 RRF(Reciprocal Rank Fusion):只看排名不看分数。
$$ \mathrm{RRF}(d) = \sum_{r \in \text{各路检索}} \frac{1}{k + \mathrm{rank}_r(d)} $$
其中 $k$ 是平滑常数,通常取 $60$。文档在任一路排得靠前,融合后得分就高。
落地工具:这套组合在 Elasticsearch 8.x 里一个引擎就能实现——BM25 + kNN 向量检索(底层 HNSW),原生支持 RRF 融合;代码侧则可以用 LangChain 的 EnsembleRetriever(内部同样是 RRF)把 BM25Retriever 和任意向量检索器组合起来。
2.5 检索质量评估
- 精准度(Precision):检索出的相关文档 / 检索出的所有文档。反映结果的可信度。
- 召回率(Recall):检索出的相关文档 / 所有相关文档。反映结果的完整度。
$$ \mathrm{Precision} = \frac{|\text{相关文档} \cap \text{检索结果}|}{|\text{检索结果}|}, \qquad \mathrm{Recall} = \frac{|\text{相关文档} \cap \text{检索结果}|}{|\text{相关文档}|} $$
- 平均精准度(MAP):先对单次查询在相关文档出现的每个位置取精准度平均(AP),再对多次查询求平均。
$$ \mathrm{MAP} = \frac{1}{|Q|} \sum_{q=1}^{|Q|} \mathrm{AP}(q), \qquad \mathrm{AP}(q) = \frac{1}{m_q} \sum_{k=1}^{n} P(k) \cdot \mathrm{rel}(k) $$
其中 $m_q$ 是查询 $q$ 的相关文档总数,$P(k)$ 是前 $k$ 个结果的精准度,$\mathrm{rel}(k)$ 表示第 $k$ 个结果是否相关。
- 平均倒数排名(MRR):最相关文档在结果列表中的排名的倒数。排名第一,MRR 就是 1;排名第十,MRR 就是 0.1。
$$ \mathrm{MRR} = \frac{1}{|Q|} \sum_{q=1}^{|Q|} \frac{1}{\mathrm{rank}_q} $$
落地工具:评估的前提是有一批带标注的「黄金测试集」(golden dataset),通常 50-200 条真实用户问题加人工标注的相关文档。标注成本高时可以用 Ragas 的合成数据功能自动生成测试集;Ragas 和 DeepEval 还都提供 context precision / context recall 等无需人工标注、由 LLM 打分的检索评估指标,适合纳入 CI 做回归测试。
三、向量数据库:如何高效存储和检索
向量数据库的核心问题是:给定一个查询向量,如何快速找到最相似的文档向量?
3.1 KNN:精确但慢
KNN(K-Nearest Neighbors)会实时计算查询向量与所有文档向量的距离,结果准确,但当向量数量达到亿级时,计算量不可接受。设向量库有 $N$ 个 $d$ 维向量,暴力检索的复杂度为 $O(Nd)$。
适用场景:向量量在万级以内、或需要精确结果做基准评测时,暴力 KNN 反而最省事——FAISS 的 IndexFlatIP、NumPy 一次矩阵乘法就够,不必引入索引结构。
3.2 ANN:近似但快
ANN(Approximate Nearest Neighbor)通过构建近邻图或索引结构来加速检索,牺牲了极小的准确率,换取了数量级的性能提升。
HNSW(Hierarchical Navigable Small World) 是目前最常用的 ANN 算法之一,采用分层导航图结构,能够在亿级向量库中快速找到近似最近邻:
flowchart TB
Q[查询向量] --> E[顶层入口点]
subgraph HNSW["HNSW 分层图"]
direction TB
L2["Layer 2(最稀疏,快速定位大致区域)"]
L1["Layer 1(中间过渡层)"]
L0["Layer 0(包含全部节点,精确找最近邻)"]
L2 --> L1 --> L0
end
E --> L2
L0 --> R["Top-K 近似最近邻"]
查询时从最稀疏的顶层开始贪心搜索,逐层向下,每层把入口点收敛到更接近目标的区域,最后在最稠密的第 0 层做精细查找。平均查询复杂度约为 $O(\log N)$。
落地工具:
- 向量库选型:Milvus、Qdrant、Weaviate(独立向量数据库)、pgvector(已有 PostgreSQL 时的最简方案)、Chroma(轻量嵌入式)、FAISS(Meta 的索引库,无服务化能力)、Elasticsearch kNN(想复用 BM25 生态时)。
- HNSW 三个关键参数:
M(每个节点的最大连接数,默认 16,越大越准但越耗内存)、ef_construction(建索引时的搜索宽度,默认 100)、ef_search(查询时的搜索宽度,运行时调大它就能在召回率和延迟之间实时权衡,是最常用的调优旋钮)。
四、文档分块:粒度决定召回质量
把长文档切成小块(chunk)是 RAG 的关键步骤。chunk 太大或太小都有问题:
- 太大:一个向量要表达太多内容,指向性弱,容易引入无关信息。
- 太小:上下文不足,语义不明确。
4.1 固定大小分块
- 文本:250 字符左右(例如 225 字符正文 + 25 字符重叠)。
- 代码:500 字符左右,重叠 50-100 字符。
重叠(overlap)能避免关键信息被切分断掉。
落地工具:LangChain 的 CharacterTextSplitter / RecursiveCharacterTextSplitter、LlamaIndex 的 SentenceSplitter。其中 RecursiveCharacterTextSplitter 是事实上的默认选择:按「段落 → 句子 → 词 → 字符」的优先级递归切分,尽量在大小限制内保留自然边界。
4.2 按特殊字符分块
按换行、段落、标题等自然边界切分,保留文档结构。
落地工具:Markdown 文档用 LangChain 的 MarkdownHeaderTextSplitter 按标题层级切;HTML/PDF 等复杂格式先用 Unstructured 做版面解析再分块;代码可以按语法树切分(LangChain 的 Language 参数基于 tree-sitter)。
4.3 语义分块
按句子处理,判断当前句子与下一个句子的语义相似度。如果不相似,就在当前位置切分。即对相邻句子的向量 $s_i$、$s_{i+1}$,当 $\cos(s_i, s_{i+1}) < \tau$($\tau$ 为预设阈值)时切分。这样切出来的 chunk 内部语义更连贯。
落地工具:LangChain 的 SemanticChunker(基于 Embedding,可选百分位/标准差阈值)、LlamaIndex 的 SemanticSplitterNodeParser、专用库 chonkie。
4.4 LLM 分块
直接让大模型根据语义边界做分块,效果通常最好,但成本也最高。实践中一般用小型模型(如 GPT-4o-mini 级别)离线跑一遍建索引,在线查询不再产生额外成本。
4.5 上下文感知分块
在分块后补充描述信息,例如 chunk 所属的章节、文档标题等,帮助提升索引相关性。
最佳实践:Anthropic 提出的 Contextual Retrieval 是这个思路的成熟方案——索引入库前,让 LLM 为每个 chunk 生成一段 50-100 token 的上下文说明(「本段出自 XX 文档第三章,讨论……」)拼在 chunk 前面,再做 Embedding 和 BM25 索引,官方报告可将检索失败率降低约 49%(配合重排序可达 67%)。Embedding 前拼接即可,无需改动检索链路。
五、查询改写:让问题更容易被检索
用户的原始查询往往不适合直接做向量检索。常见优化方式:
5.1 LLM 重写查询
让 LLM 在检索前改写查询:去除无关信息、修正术语、扩展同义词。
落地工具:LangChain 提供现成的实现——MultiQueryRetriever(让 LLM 生成多个改写版本并行检索后去重合并)、RePhraseQueryRetriever;LlamaIndex 有 QueryTransform(如 HyDEQueryTransform)。最佳实践:多轮对话里务必先把「指代消解」做掉——用 LLM 把「那它多少钱?」结合历史改写成独立问题再检索,这一步对客服场景提升最明显。
5.2 命名实体识别(NER)
识别查询中的人名、地名、日期、组织、角色等实体,用作:
- 知识图谱检索
- 精准 BM25 匹配
- 元数据过滤
落地工具:通用场景用 spaCy、中文用 HanLP 或 LTP;实体类型不固定(领域专有名词)时用零样本模型 GLiNER,或直接让 LLM 做结构化抽取(JSON 输出),简单且免去训练。
5.3 假设文档嵌入(HyDE)
HyDE(Hypothetical Document Embeddings)的思路是:
flowchart LR
Q[用户查询] --> L["LLM 生成「理想答案」"]
L --> E[Embedding 编码]
E --> V[(向量数据库)]
V --> D[检索相似的真实文档]
D --> G[LLM 基于真实文档生成最终答案]
- 先用 LLM 生成一个「理想答案」。
- 用这个答案的向量去向量库中检索相似文档。
传统 RAG 搜索的是「与用户查询相似的文档」,而 HyDE 搜索的是「与理想答案相似的文档」,往往召回质量更高。
最佳实践:HyDE 适合开放式、长尾问题,对事实短查询收益有限;生成理想答案时用低 temperature 并生成 2-4 个版本取向量均值,比单个版本更稳;LangChain 的 HypotheticalDocumentEmbedder 和 LlamaIndex 的 HyDEQueryTransform 可直接使用。代价是多一次 LLM 调用的延迟,对时延敏感的场景要权衡。
六、编码与重排序:让相似度更准确
6.1 双向编码(Bi-Encoder)
RAG 默认的编码方式。查询和文档分别编码成向量,然后通过向量相似度匹配:
$$ \mathrm{score}(q, d) = \cos\big(E_q(q), E_d(d)\big) $$
编码可以离线预计算,在线检索只需一次查询编码加向量搜索,速度快,但查询与文档之间没有深度交互。
落地工具:就是 2.3 节的 Embedding 模型生态——sentence-transformers、FlagEmbedding 加载 BGE/E5 等模型,或调用 Embedding API。
6.2 交叉编码(Cross-Encoder)
把查询和每个文档配对,输入一个小型 LLM,输出相关度分数:
$$ \mathrm{score}(q, d) = E_{\mathrm{cross}}([q; d]) $$
准确率更高,但每对 $(q, d)$ 都要过一次模型,计算成本大,通常用于重排序阶段。
落地工具:开源重排序模型首选 bge-reranker-v2 系列(BAAI,中英双语)和 Qwen3-Reranker;API 方案有 Cohere Rerank 和 Jina Reranker。sentence-transformers 的 CrossEncoder 类三行代码即可加载本地模型打分。
6.3 ColBERT
文档中每个词都生成一个向量,而不是整个文档只有一个向量。查询时,把查询词与文档中所有词向量对比,构建相似度矩阵并加和。其核心是 MaxSim 操作:
$$ \mathrm{score}(q, d) = \sum_{i=1}^{|q|} \max_{j=1}^{|d|} ; \mathbf{q}_i \cdot \mathbf{d}_j $$
即查询中每个 token 都在文档里找到最匹配的一个 token,再把最高分相加。兼顾效率和细粒度匹配。
落地工具:训练和使用 ColBERT 模型用官方 ColBERT 仓库或更易用的 RAGatouille;生产检索引擎 Vespa 原生支持张量(多向量)排序;BGE-M3 的 multi-vector 输出也能实现类似的 MaxSim 细粒度匹配。
6.4 重排序(Re-ranking)
先从向量库中粗召回一批候选文档,再用交叉编码或 LLM 做二次排序,过滤掉不相关的结果:
flowchart LR
Q[查询] --> B["Bi-Encoder 粗召回<br>(快,Top 50)"]
B --> C["Cross-Encoder / LLM 精排<br>(准,Top 5)"]
C --> O[送入 LLM 的上下文]
粗召回阶段追求「不漏」,精排阶段追求「排得准」,两个阶段各司其职。
最佳实践:重排序通常是 RAG 系统里性价比最高的单项优化——不动索引、不改 Embedding 模型,只在检索后插一层,召回质量立竿见影。粗召回取 Top 20-50、精排后保留 Top 3-5 是常见配置;用 LangChain 的 ContextualCompressionRetriever 或 LlamaIndex 的 Postprocessor 机制接入,几行代码就能挂在现有链路上。
七、LLM 生成:如何让答案更可靠
7.1 Transformer 基础
LLM 的核心是 Transformer:多头注意力机制 + 前馈网络。缩放点积注意力的公式为:
$$ \mathrm{Attention}(Q, K, V) = \mathrm{softmax}\left(\frac{Q K^{\top}}{\sqrt{d_k}}\right) V $$
其中 $Q$、$K$、$V$ 分别由输入线性变换得到,$d_k$ 是 key 的维度,除以 $\sqrt{d_k}$ 是为了防止点积过大导致 softmax 梯度消失。理解这一点有助于理解上下文窗口、注意力稀释等问题。
7.2 LLM API 关键参数
- temperature:采样温度。0 表示贪婪模式,总是选概率最高的 token;大于 1 会让低概率 token 更有可能被选中。其作用是在 softmax 前对 logits 做缩放:
$$ p_i = \frac{\exp(z_i / T)}{\sum_j \exp(z_j / T)} $$
$T \to 0$ 时分布退化为 one-hot(贪婪),$T$ 越大分布越平坦、越随机。
- top-k:只从概率最高的前 k 个 token 中采样。
- top-p(nucleus sampling):从累计概率达到 p 的 token 集合中采样。
- repetition_penalty:惩罚重复出现的 token。
- logit_bias:调整特定 token 的概率。
最佳实践:RAG 问答场景建议 temperature = 0(或 0.1-0.3)以保证答案稳定、贴合检索内容;top-p 与 temperature 一般只调一个,不要同时大幅改动;需要模型输出严格 JSON 时优先用各家的 structured output / JSON mode,而不是靠提示词约束。
7.3 LLM 选型因素
- 模型大小
- 发布时间
- 成本
- 上下文窗口长度
- 首 token 延迟(Time to First Token)
落地工具:横向对比可以参考 LMArena(真人盲测偏好榜)和 OpenCompass(中文能力评测较全);但最终一定要在自有评估集上跑一次——榜单排名和具体业务场景的表现经常有出入。自建服务推理引擎选 vLLM(高吞吐)或 TGI;本地/边缘部署用 Ollama、llama.cpp。
7.4 提示词工程
一个典型的 RAG 提示词结构:
系统提示词
+ 会话历史
+ 检索到的文档
+ 用户问题
优化技巧:
- 添加回答样例(few-shot)
- 要求模型展示思考链路(chain-of-thought)
- 对过长的上下文做裁剪,保留最相关部分
最佳实践:给每个检索到的 chunk 编号并要求模型在答案中标注引用(如 [1][3]),既能溯源又便于自动校验幻觉;明确写「文档中没有答案时直接说不知道」,比含糊的约束更能压住幻觉;上下文窗口紧张时按重排序分数从高到低填充,而不是按文档原始顺序。
7.5 幻觉处理
- 多次生成对比:让模型多次回答,看是否一致。
- 强制基于检索文档回答:明确要求模型只根据提供的文档作答,并引用来源。
落地工具:自动化检测可用 Ragas 的 faithfulness 指标(答案是否忠于检索上下文)、SelfCheckGPT(多次采样一致性);要求引用编号后,还可以在应用层写一个简单的校验器:答案中引用的编号必须真实存在于本次检索结果里,否则丢弃重答。
八、Agentic RAG 与微调
8.1 Agentic RAG
复杂查询可以交给 Agent:
flowchart LR
Q[复杂查询] --> R[提示词路由]
R --> P["制定计划<br>(拆分多跳子查询)"]
P --> X["执行检索 / 工具调用"]
X --> V{结果是否充分}
V -- 否 --> P
V -- 是 --> S[整合答案]
S --> A[最终回答]
例如,一个多跳问题可以拆分成多个子查询,分别检索后再综合回答;检索不充分时 Agent 还会调整计划继续检索。
落地工具:LangGraph(显式状态机,最可控)、CrewAI / AutoGen(多 Agent 协作)、LlamaIndex 的 AgentWorkflow。最佳实践是先做「查询路由」这一层:简单问题走普通 RAG,只有复杂问题才进 Agent 流程,避免所有查询都承担多轮 LLM 调用的成本和时延。
8.2 微调
当通用模型在某些领域表现不足时,可以用领域数据集微调模型,提升特定任务的准确率和稳定性。
落地工具:参数高效微调用 LoRA / QLoRA(Hugging Face peft 库),一站式训练框架选 LLaMA-Factory(中文社区活跃、零代码 WebUI)或 Axolotl。最佳实践:微调是最后手段——先把提示词工程和检索质量优化到位仍不达标时再考虑;微调 Embedding 模型(用自己业务的正负例对)往往比微调生成模型投入产出比更高。
九、生产部署:从 demo 到可运维系统
9.1 生产挑战
- 高并发
- 预期外的会话
- 数据质量差
- 权限控制
9.2 评估与监控
- 基础监控:流量、日志、AB 测试切换
- 质量监控:组件级和系统级评估
- 用户满意度与回答质量
- LLM 评估与代码评估
工具如 Phoenix 可以做消息跟踪和可观测性。同类常用工具还有 Langfuse(开源自托管、LLM 调用全链路追踪 + 数据标注)和 LangSmith(LangChain 官方,商业化);评估指标侧用 Ragas 做周期性离线评估,监控侧采集用户点踩、会话中断率等业务信号。
9.3 成本优化
- 选择更小的 LLM
- 减小提示词大小
- 自托管 vs API 调用
- 部署方式优化:HNSW 索引存入 RAM,文档存磁盘,按需按时区加载
最佳实践:提示词里检索上下文通常占 token 大头,砍成本优先做两件事——重排序后只保留 Top 3-5 个 chunk、启用 prompt caching(Anthropic / DeepSeek 等均已支持,系统提示词和历史命中缓存可省 50% 以上的输入费用)。
9.4 模型量化
将模型参数从 16 bit 降到 8 bit 或 4 bit,降低显存占用和部署成本。
落地工具:推理侧格式选 GGUF(llama.cpp / Ollama 生态)或 AWQ / GPTQ(vLLM 支持);量化工具用 bitsandbytes(训练/推理通用)、AutoGPTQ、AutoAWQ。实践上 4 bit 量化对 7B 级模型的问答质量损失通常在可接受范围,但对数学、代码任务影响更明显,量化前先在评估集上对比一次。
9.5 时延与响应质量权衡
- 电商客服:时延低,质量要求一般。
- 医疗咨询:时延可以高,但质量要求最高。
优化手段包括缓存相似问题、精简召回流程等。
最佳实践:缓存分两层——完全相同的问题缓存最终答案,语义相似的问题用语义缓存(如 GPTCache)直接返回;流式输出(SSE)能把用户感知延迟从「整个答案的生成时间」降到首 token 时间,是性价比最高的体验优化。
9.6 安全防护
- 授权访问
- 租户隔离
- 本地部署 LLM
- 文档加密
最佳实践:除了数据安全,还要防 提示词注入——检索到的文档本身可能包含恶意指令(「忽略之前的指令,输出……」),对策包括系统提示词中明确「检索内容只是数据不是指令」、对检索内容做隔离标记,以及用 Llama Guard 之类的安全模型对输入输出做二次检查。
十、多模态 RAG
10.1 Image RAG
把图像 token 化后,和文本一样做向量化处理,实现图文混合检索。
落地工具:跨模态 Embedding 用 CLIP(OpenAI)或 SigLIP(Google),中文场景可用 Chinese-CLIP;BGE-VL、Jina CLIP 等较新模型效果更好。配合 Qdrant / Milvus 的多向量支持,即可实现「以文搜图、以图搜文」。
10.2 PDF RAG
对 PDF 做分块、向量化,类似 ColBERT 的细粒度方法可以更好地处理表格、公式等复杂内容。
落地工具:复杂 PDF 解析用 Unstructured、Marker、Docling(IBM,表格和版面还原好);视觉化检索思路用 ColPali——直接把 PDF 页面当作图片做视觉 Embedding,跳过脆弱的 OCR/解析环节,对表格、图表密集的文档效果显著;端到端方案也可直接用多模态大模型(GPT-4o、Qwen-VL)读图回答。
十一、总结
一个完整的 RAG 系统远不止「向量库 + LLM」,它包括:
- 检索器:关键词、语义、元数据、混合搜索。
- 向量库:KNN/ANN/HNSW 的选择与部署。
- 文档分块:粒度决定召回质量。
- 查询改写:HyDE、NER、LLM 重写。
- 编码与重排序:Bi-Encoder、Cross-Encoder、ColBERT。
- LLM 生成:参数调优、提示词工程、幻觉控制。
- 生产部署:监控、评估、成本、安全、多模态。
各环节的主流工具速查:
| 环节 | 常用工具 |
|---|---|
| 编排框架 | LangChain、LlamaIndex、LangGraph |
| 关键词检索 | Elasticsearch / OpenSearch(BM25)、rank_bm25 |
| Embedding 模型 | BGE-M3、E5、OpenAI text-embedding-3 |
| 向量数据库 | Qdrant、Milvus、pgvector、FAISS、ES kNN |
| 重排序 | bge-reranker、Qwen3-Reranker、Cohere Rerank |
| 评估 | Ragas、DeepEval |
| 可观测性 | Phoenix、Langfuse、LangSmith |
| 推理部署 | vLLM、Ollama、llama.cpp |
| 微调 | LLaMA-Factory、peft(LoRA/QLoRA) |
RAG 的每一个环节都可以单独调优,也都有成熟的工程实践。真正影响系统效果的,往往不是某个单点技术,而是整个 pipeline 的协同设计。
参考:吴恩达 DeepLearning.ai《Retrieval Augmented Generation (RAG)》课程