RAG 链路设计¶
RAG 链路与分块策略¶
RAG 的完整链路,以及它和“直接 Prompt 塞文档”的根本区别¶
完整的 RAG 链路,本质上是一条“离线索引 → 在线检索 → 生成答案”的工业流水线。
很多初学者会认为,把文档扔进 Prompt 就算 RAG 了。但这两者之间的差距,就像“把整本书丢给一个人让他找答案”和“建好索引的图书馆”。

离线索引阶段:把文档变成可检索的“卡片”。
-
清洗:去掉页眉页脚、特殊字符,保证文本干净。
-
分块:将长文档切成语义相对完整的小段落(chunk),这是 RAG 效果的核心基础。
-
向量化:用 Embedding 模型把每个 chunk 转成固定维度的向量。
-
存储:写入向量数据库,同时保留元数据(来源、页码、时间等)。
在线检索生成阶段:
-
用户问题同样向量化。
-
在向量库中做相似度搜索,取出最相似的 K 个 chunk。
-
可选地,用 Reranker(交叉编码器)对这些 chunk 重新精排,提升精度。
-
将最相关的 chunk 与用户问题一起,按照精心设计的 Prompt 模板喂给大模型。
-
模型生成答案,并(最好)附上引用来源。
它与“直接 Prompt 塞文档”的本质区别在哪里?
一个简单的代码对比,比任何文字都直观:
# ❌ 直接塞文档:粗暴、低效、不可控
prompt = f"请根据以下整本手册回答问题:\n{whole_book}\n\n问题:{question}"
answer = llm(prompt)
# ✅ RAG 方式:精准、高效、可追溯
docs = vectorstore.similarity_search(question, k=5)
context = "\n".join([d.page_content for d in docs])
prompt = f"参考资料:\n{context}\n\n问题:{question}"
answer = llm(prompt)
RAG 的完整链路,本质上是在用工程手段,把“大海捞针”变成“按图索骥”。
文档分块策略有哪几种?各自的优缺点是什么?¶
分块是 RAG 里“细节见真章”的地方。切得好,检索准;切不好,后面整个链路都在为碎片信息买单。我习惯把主流策略分成四类,从左到右,智能程度递增,成本也递增。
在生产中,绝大多数情况用“递归字符切分”就能达到不错的效果。 LangChain 和 LlamaIndex 都提供了现成的实现。
from langchain.text_splitter import RecursiveCharacterTextSplitter
splitter = RecursiveCharacterTextSplitter(
chunk_size=500, # 每个 chunk 最大字符数
chunk_overlap=50, # 相邻 chunk 重叠长度,防止关键信息被切断
separators=["\n\n", "\n", "。", ".", " ", ""],
length_function=len,
)
chunks = splitter.create_documents([full_text])
Chunk Size 和 Overlap 的经验值:
-
chunk_size:通常设在 256~1024 token 之间。越小检索越精准,但可能丢失上下文;越大上下文越全,但检索粒度粗。对于中文,512 字符是个安全的起点。 -
chunk_overlap:取chunk_size的 10%~20%。目的是在边界处形成“语义重叠带”,防止答案的关键部分正好被截断在两个 chunk 的连接处。
分块策略选型,最重要的是看文档的“亲妈”是谁——是结构化的 Markdown 还是混乱的 PDF 扫描件?是严谨的法律条款还是随意的聊天记录?没有银弹,只有最适配的刀。
RAG 系统召回的文档相关性很差,如何系统性排查和优化?¶
召回差是 RAG 系统最常见的线上故障,而根因往往不止一个。我习惯用一条“顺藤摸瓜”式的排查路线,从问题本身倒推,找到病根再下药。
排查路线图:

具体排查步骤与优化手段:
第一步:先看 Query 有没有“带病上岗”
很多召回问题,根源在用户。用户问“那个红色的按钮是干嘛的?”,而知识库里写的是“紧急制动开关功能说明”。这种表达鸿沟靠单纯的向量相似度很难跨越。
- 解决方案:引入 Query 改写。在检索前,用 LLM 把口语化、模糊化的问题,改写成更精准、更像文档语言的检索词。例如用 Multi-Query 生成多个角度的查询,或用 HyDE 先生成一篇假设文档再检索。
# 简单的 Query 改写示例:把口语变专业
rewrite_prompt = f"将以下用户问题改写成更适合检索的关键词:{question}"
search_query = llm(rewrite_prompt)
docs = vectorstore.search(search_query)
第二步:看检索结果,判断是“没召回”还是“召回了没用”
把检索返回的 Top-5 文档片段打印出来,和问题放在一起对比。
-
如果完全不沾边:问题出在“语义鸿沟”或“专有名词”。 → 上混合检索(Hybrid Search):向量检索擅长语义,但对专有名词、缩写不敏感。加上 BM25 关键词检索,两路结果用 RRF(倒数排名融合)合并,能极大改善专有名词的召回。
-
如果部分相关,但最准的那条排在后面:问题出在“初排不够精”。 → 引入 Reranker(精排模型):用 Cross-Encoder 对召回的候选(比如 top-50)重新打分排序,把真正相关的提到前几位。这是成本低、效果提升最显著的一步。
-
如果检索到的片段像是“半句话”:问题出在分块切断了关键信息。 → 增大 chunk_overlap,或用“父子文档索引”:用小 chunk 做精准检索,但返回给 LLM 时,自动展开成包含该 chunk 的整个大段落(父文档)。
第三步:如果检索结果明明很准,但 LLM 还是答非所问
根因在“生成侧”。常见问题:
-
上下文过长,模型注意力分散:精简上下文,只保留 Reranker 给出的 Top-3。
-
Prompt 指令不清晰:强化“只能根据提供的资料回答,不知道就明确说不知道”的指令。
-
模型能力不足:从 GPT-3.5 换成 GPT-4o,或从 7B 小模型换成更强的模型。
第四步:做一个端到端的排查脚本
我习惯在生产日志里挂一个诊断钩子,当用户点“踩”时,自动抓取当时的 question、检索 docs 和最终 answer,然后跑一遍分析。
def diagnose_relevance(question, docs, answer):
print(f"用户问题: {question}")
for i, doc in enumerate(docs):
print(f"检索结果 {i+1}: {doc.page_content[:200]}...")
# 用 LLM 辅助判断
judge_prompt = f"问题: {question}\n检索片段: {docs}\n答案: {answer}\n分析召回差的原因。"
diagnosis = llm(judge_prompt)
print(f"诊断意见: {diagnosis}")
优化不是一锤子买卖。每次改进分块、改写、混合检索后,都应该用同一批测试集(比如 100 条真实用户查询)来对比前后的召回率和最终答案的准确率。用数据说话,而不是凭感觉调参。
4.2 向量数据库与检索优化¶
向量数据库设计¶
🧱 1. 什么是向量数据库的 Namespace 隔离?¶
在向量数据库中,Namespace(命名空间)就像数据库里的 Schema 或文件夹——它在同一个 Collection(类似表)内部,将数据在逻辑上划分成多个相互独立的“隔间”。每个隔间里的向量只能被搜索该隔间的查询看到,不同 Namespace 之间互不干扰。

查询时的行为:你必须在查询请求中显式指定 namespace,如果不指定,有些向量库会返回错误,有些会查全部。指定 namespace="tenant_A" 后,检索就只会在这个隔离区里进行,绝不会跑到隔壁去。
它的核心价值:
-
逻辑隔离:同一个 Collection,一套索引结构,但数据检索互不穿透。
-
成本低:不需要为每个租户单独建 Collection(每个 Collection 都有独立的索引、内存和管理开销)。
-
管理简单:批量操作(如备份、删除)可以按 Namespace 执行。
示例代码(以 Chroma 为例,也适用于 Milvus 的 partition key 等类似概念):
import chromadb
client = chromadb.PersistentClient(path="./db")
collection = client.get_or_create_collection("knowledge_base")
# 为 tenant_A 插入文档
collection.add(
documents=["文档内容..."],
metadatas=[{"source": "HR手册"}],
ids=["doc_1"],
namespace="tenant_A" # 关键:指定命名空间
)
# 查询时也指定 namespace
results = collection.query(
query_texts=["年假政策"],
n_results=5,
namespace="tenant_A" # 只在这个租户的数据里搜
)
Namespace 隔离的实质:它是数据层面的“软隔断”,不是物理上的强隔离。如果代码里不小心写错了 namespace,或者被恶意注入,就可能发生跨租户访问。所以它通常需要配合应用层的权限校验一起使用。
⚖️ 2. SaaS 场景下,多租户 RAG 的隔离方案与权衡¶
在 SaaS 里,隔离绝不是“越强越好”,而是要在安全性、成本、性能、运维复杂度四者之间找到平衡。从上到下,隔离等级递增,成本也递增。

权衡核心:
-
免费用户 / 小微企业:文档少,付费意愿低,用 Namespace 或 Metadata Filter 足以。给他们独立 Collection 就像给路边的共享单车配私人飞机——浪费且没必要。
-
付费企业客户:文档多,查询 QPS 高,对响应时间和数据安全性有合同要求。必须上 独立 Collection 甚至 独立实例,既能保证性能不相互干扰,也能满足审计和合规需求。
-
中间层:可以用 Namespace 作为平滑过渡。Namespace 在 Milvus、Chroma、Pinecone(通过 index 区分)中都有支持,它比 filter 更安全,比独立 Collection 更省资源。
一个常见的错误:初期为了省事,全部用 metadata filter 一把梭。当租户量上来后,查询时 WHERE tenant_id=xxx 会在海量向量中做过滤,导致响应时间飙升,而且一旦 filter 写错就是安全事故。架构上,隔离策略必须与客户等级绑定,动态可配。
🏗️ 3. 5000 租户,分层付费场景下的隔离架构设计¶
场景重述:
-
4950 个免费用户,每人 200 条文档,总共约 100 万条向量。
-
50 个付费企业,每人 5 万条文档,总共 250 万条向量。
-
免费用户要求低成本、基本可用;付费用户要求高性能、强隔离。
设计目标:在满足隔离需求的前提下,最小化资源消耗和运维负担,并为未来扩展留好口子。
推荐架构:分级隔离 + 动态路由

免费用户:使用独立 Collection + Namespace 或 Metadata Filter?
这里有一个资源权衡。4950 个免费用户如果每人一个 Namespace,管理上很清晰,底层仍是共享索引,成本可控。但 4950 个 Namespace 的管理本身也有开销(元数据、连接数)。更极致的省钱方案:所有免费用户扔进一个 free_tenants Collection,使用 tenant_id 做元数据过滤,查询时强制加 filter。虽然安全性稍弱,但对于免费用户的数据敏感度,这个风险是可接受的。并且可以通过代码层严格封装 filter,避免遗漏。
付费企业:独立 Collection
为每个企业客户创建一个独立的 Collection,名字如 ent_tenant_123。这样做的好处:
-
性能隔离:大客户的频繁查询不会挤占免费用户的资源,也不会被其他大客户干扰。
-
独立调优:可以为不同企业配置不同的索引参数(如 HNSW 的
M、efConstruction)。 -
精细运维:备份、恢复、迁移都可以针对单个 Collection 进行,不影响其他客户。
-
安全合规:合同里承诺的“数据物理隔离”可以轻松满足。
路由层实现(伪代码):
from enum import Enum
class TenantTier(Enum):
FREE = "free"
ENTERPRISE = "enterprise"
# 根据租户 ID 解析其等级和对应的 Collection/Namespace
def get_tenant_context(tenant_id: str) -> dict:
info = tenant_service.get_info(tenant_id) # 从缓存或数据库查
if info.tier == TenantTier.FREE:
return {
"collection_name": "free_tenants",
"filter": {"tenant_id": tenant_id} # 元数据过滤
}
else:
return {
"collection_name": f"ent_tenant_{tenant_id}",
"filter": None # 独立 Collection,无需额外过滤
}
# 检索接口
def search_for_tenant(tenant_id: str, query: str):
ctx = get_tenant_context(tenant_id)
collection = get_collection(ctx["collection_name"])
if ctx["filter"]:
# 免费用户:走 metadata filter
results = collection.query(
query_texts=[query],
n_results=5,
where=ctx["filter"]
)
else:
# 付费用户:直接查专属 Collection
results = collection.query(
query_texts=[query],
n_results=5
)
return results
使用 Milvus 的 Partition Key 优化免费用户隔离
如果向量库是 Milvus,可以用 Partition Key 替代 metadata filter。Partition Key 在写入时根据 tenant_id 哈希分区,查询时指定分区键,能实现与 Namespace 类似的逻辑隔离,且查询效率远高于通用的 scalar filter。
from pymilvus import Collection, connections
# 免费用户共享 Collection,用 tenant_id 作为 partition key
connections.connect()
schema = ... # 定义向量字段 + tenant_id (VARCHAR, partition_key=True)
collection = Collection("free_tenants", schema)
collection.create_index("vector", ...)
# 查询时指定分区键
results = collection.search(
data=[query_vec],
anns_field="vector",
expr=f'tenant_id == "{tenant_id}"', # Milvus 会根据 partition key 优化
limit=5
)
未来扩展性预留:
-
如果某个免费用户成长为付费企业,只需将其数据从
free_tenantsCollection 中迁移到独立的ent_tenant_xxxCollection。迁移可以通过向量库的 export/import 或自研脚本完成,该过程对用户几乎无感知。 -
定期对
free_tenants做数据清理,删除僵尸用户,回收存储空间。 -
在应用层加一个容量监控,当免费用户的数据量接近上限时,提示升级。
成本估算:
-
免费用户:共享 Collection,索引内存占用 ≈ 向量维度 × 向量总数。100 万条 1536 维向量,约需 6GB 内存(加上索引开销,10GB 以内)。
-
付费企业:50 个独立 Collection,每个 5 万条向量,各自索引。总内存占用会比共享模式略高(每个 Collection 的索引有固定开销),但换来的是绝对的性能隔离和灵活的 SLA 管理。
-
运维:免费用户只需维护一个 Collection,备份简单;付费企业可用自动化脚本批量管理。
一句话总结:5000 租户的架构,不是在每一个细枝末节上追求“最高安全”,而是用分层隔离的策略,把好钢用在刀刃上——免费用户用逻辑隔离降本,付费客户用物理隔离提质,路由层屏蔽所有复杂度,让租户永远看不到底层的隔离逻辑。 这是 SaaS 架构最经典的“分级服务”思维在 RAG 时代的延伸。
数据更新权衡¶
1、基础题:为什么 RAG 系统的知识库更新不能简单地删掉旧数据再全量重新导入?¶
难度级别:⭐(增量更新必要性)
全量重建会对所有文档重新做 Embedding,成本随文档数量线性增长,对于大型知识库(几万到几十万文档)成本和时间都难以接受。全量重建期间知识库可能处于不可用状态,影响服务连续性。增量更新只处理有变化的文档,大幅降低成本,同时可以做到对用户无感知的滚动更新。
2、进阶题:当知识库的源文档发生变更时,如何设计 RAG 系统的增量更新机制?如何避免全量重建带来的性能问题?¶
难度级别:⭐⭐(文档变更检测、增量 Embedding、Upsert 幂等设计、删除同步)
1️⃣ Common Answer
RAG 系统的知识库需要随着源文档更新而更新。全量重建太慢成本也高。增量更新的思路是只处理有变化的文档,可以用 MD5 或者 SHA256 给每个文档算一个哈希值存起来,下次处理时先算新文档的哈希,和存储的哈希比对,只有哈希不一样的才需要重新处理。向量数据库支持 Upsert 操作,可以根据文档 ID 更新已有的向量,不需要先删再插。如果源文档被删除了,也需要同步把对应的向量从数据库里删掉。
2️⃣ Impressive Answer
我会把增量更新拆成四个核心步骤来设计,每个步骤都有容易踩的坑。
-
变更检测用内容哈希,不用文件时间戳。时间戳可能因为 touch 操作、系统迁移等原因不可靠,内容哈希(SHA256)才能准确反映文件是否真的变了。同时要维护一个文档注册表,记录每个文档的哈希值和对应的 chunk_id 列表,检测时做三路对比:新增(在源目录但不在注册表)、删除(在注册表但不在源目录)、更新(哈希值变化)。
-
更新文档要先删旧 chunk 再插新 chunk,不能直接 Upsert。因为文档内容变化可能导致分块数量和边界都发生变化,如果直接 Upsert 旧的 chunk_id,会出现旧 chunk 残留的问题——比如原来 10 个 chunk,更新后变成 7 个,直接 Upsert 只会更新前 7 个,后 3 个旧 chunk 会永久残留在向量库里污染检索结果。
-
chunk_id 用确定性设计保证 Upsert 幂等性。推荐用
文件路径 + chunk 序号做 MD5 生成 chunk_id,同一文件同一位置的 chunk ID 永远一样,这样即使因为网络中断重试处理,也不会产生重复数据。 -
删除同步是最容易被忽视的环节。源文档删除时必须同步清理向量库里的所有相关 chunk,否则检索时会召回已删除文档的内容,造成信息污染。工程上还要注意:批量处理时合并 Embedding API 请求降低成本;大批量更新要支持断点续传,单个文档失败不影响整体;把更新任务安排在低峰期执行,防止 Embedding API 请求过于集中。
3️⃣ Key Differences
3、场景题:知识库有 10 万份文档,每天有约 1000 份文档更新,更新期间服务不能中断,如何设计更新流程?¶
难度级别:⭐⭐⭐(滚动更新、服务连续性、流量切换)
1️⃣ Common Answer
可以用异步更新,把更新任务放到队列里,后台慢慢处理,不影响前台服务。每天 1000 份文档,如果处理速度够快就不会有问题。可以在低峰期(比如凌晨)做批量更新。
2️⃣ Impressive Answer
这道题的关键约束是"服务不能中断",意味着更新过程中检索必须始终可用,不能有 dirty read(读到更新了一半的数据)。
核心设计是写时隔离,切换生效。新文档的 chunk 写入时先打上 status=pending 标记,检索查询强制过滤 status=active 的 chunk,这样更新中的文档不会污染线上检索结果。一批文档的所有 chunk 全部写入完成后,原子地把状态从 pending 改为 active,同时把旧版本 chunk 标记为 deprecated,异步清理。
流量控制也很重要:1000 份文档同时 Embedding 会把 API 配额打满,要做速率限制(比如每分钟处理 50 份),均匀分散在低峰时段(凌晨 1-5 点)处理。同时维护一个更新队列,记录每份文档的处理状态(pending/processing/done/failed),支持失败重试和断点续传。
监控上要关注两个指标:更新队列积压量(防止更新速度跟不上)和 pending chunk 的年龄(超过 1 小时未切换 active 要告警,说明更新流程卡住了)。
3️⃣ Key Differences
4、容易一起考的题¶
Milvus / Qdrant 与检索策略¶
1、基础题:向量相似度检索常用哪几种距离度量?各自适合什么场景?¶
难度级别:⭐⭐(余弦相似度、欧氏距离、内积、归一化影响)
2、进阶题:HNSW 索引的原理是什么?为什么它比暴力搜索快?¶
难度级别:⭐⭐⭐(分层图结构、贪心搜索、构建参数 M/efConstruction、精度与速度权衡)
1️⃣ Common Answer
HNSW 是一种图结构的索引,把向量都连成图,查询的时候从图上跳着找,不用和所有向量都比较,所以比暴力搜索快。它有多层结构,上层稀疏下层密,先在上层快速定位,再在下层精细查找。M 参数控制每个节点的连接数,越大越准但越慢。
2️⃣ Impressive Answer
HNSW(Hierarchical Navigable Small World)的核心思想是分层跳跃 + 局部贪心搜索,我从结构和查询两个维度来说:
-
分层图结构:构建时将节点随机分配到多层,底层包含所有节点(全量),上层节点数指数级减少。每个节点与邻居建立双向连接,连接数由参数 M 控制(通常 16-64)
-
查询过程:从最高层入口节点开始,贪心地朝查询向量方向移动,逐层降落到底层;底层用 ef(动态候选列表大小)控制搜索范围,最终返回 Top-K 结果
-
核心参数权衡:
M:每节点最大连接数,越大精度越高,内存和构建时间成比例增加efConstruction:构建时的候选列表大小,越大索引质量越好,但构建越慢-
ef(查询时):搜索时动态候选数,可运行时调整,是精度和速度的实时权衡旋钮 -
复杂度优势:暴力搜索 O(N·d),HNSW 平均 O(log N ·d),百万级向量场景下速度差距可达百倍以上
3️⃣ Key Differences
3、场景题:RAG 系统要同时支持语义检索和关键词过滤(如按部门、时间范围),如何设计?¶
难度级别:⭐⭐⭐(标量过滤 + 向量检索、预过滤 vs 后过滤、Payload 索引、性能影响)
1️⃣ Common Answer
可以先用关键词过滤一下文档,再对过滤后的结果做向量检索。或者向量检索完再根据部门、时间做过滤。用 Milvus 的话,好像可以在查询里加 filter 条件,直接一步到位。
2️⃣ Impressive Answer
向量检索和标量过滤的组合,核心在于过滤时机的权衡,有两种策略:
- 预过滤(Pre-filtering):先用标量条件(部门=XX,时间范围)筛出候选集,再在候选集上做向量检索
- 优点:最终结果一定满足过滤条件
-
风险:若候选集过小,向量检索空间不足,召回率下降,极端情况返回空
-
后过滤(Post-filtering):先向量检索 Top-K,再过滤不满足条件的结果
-
风险:过滤后结果数量不可控,可能所有 Top-K 都被过滤掉
-
生产推荐方案:在向量数据库(Qdrant/Milvus)中对标量字段建 Payload 索引,让数据库在向量检索时同步应用过滤条件,利用索引加速标量过滤,避免全量扫描;同时适当放大初始召回数(如 ef=200)保证过滤后仍有足够结果
-
分区隔离优化:若按部门过滤是高频场景,可以按部门做 Collection 分区(Milvus Partition),直接缩小检索范围,性能提升最显著
3️⃣ Key Differences
4、容易一起考的题¶
4.3 Embedding 模型原理与选型¶
Embedding 模型核心机制¶
基础题:Embedding 模型的作用是什么?好的 Embedding 有什么特征?¶
难度级别:⭐(语义向量化、语义相似度、聚类能力、泛化性)
进阶题:BGE、M3E、OpenAI Ada 这几款 Embedding 模型有什么区别?如何选型¶
难度级别:⭐⭐⭐(中英文能力差异、上下文长度、开源 vs 闭源、领域适配)
1️⃣ Common Answer
BGE 是智源开源的,有中文和英文版本,中文效果不错。M3E 也是开源的,主打中文场景。OpenAI Ada 是 API 调用的,效果好但要花钱,而且中文支持一般。选型的话,中文项目用 BGE 或 M3E,英文或者有预算就用 OpenAI。
2️⃣ Impressive Answer
我从模型能力、使用成本和适用场景三个维度对比:
- BGE(BAAI General Embedding)系列:
- 智源开源,有 BGE-base/large 和 BGE-M3(多语言)多个版本
- 中文 MTEB 榜单第一梯队,最大上下文 512-8192 token(M3 支持长文本)
- 优势:开源免费、中文最强、可本地部署、支持微调
-
适合:中文 RAG、预算有限、数据敏感不能出域的场景
-
M3E 系列:
- 中文专用 Embedding,MTEB 中文榜单常客
- 轻量级(M3E-base 约 110M 参数),推理速度快
- 优势:中文场景精度高、资源消耗低、可蒸馏部署
-
适合:纯中文场景、边缘设备部署、低延迟要求
-
OpenAI Ada-002 / text-embedding-3:
- 闭源 API,text-embedding-3 支持 256-3072 维度可调
- 英文 MTEB 顶尖,中文中等偏上但不如 BGE
- 优势:开箱即用、无需维护、多语言均衡
- 劣势:按 token 计费、数据出域、延迟受网络影响
- 适合:英文 RAG、快速原型、团队无 ML 运维能力
选型决策树:中文优先 BGE-M3 → 纯中文轻量选 M3E → 英文/多语言选 OpenAI → 长文本选 BGE-M3(8K 上下文)
3️⃣ Key Differences
进阶题:准确率到了瓶颈怎么办?有了解模型微调嘛?¶
1️⃣ Common Answer
针对垂直领域数据进行收集,并且对基模做 LoRa,实现准确率提升。
2️⃣ Impressive Answer
"我们在 RAG 召回系统中,对 Qwen2.5-7B 做了 LoRA 微调,主要解决通用模型在 xx 领域的两个问题:一是不熟悉 xx 术语和行业黑话,二是输出格式不符合 xx 报告的规范。具体流程是:先从业务日志里提取了 300 条真实的投研问答,再用 Claude 扩充到 800 条,人工筛选后保留 600 条高质量数据。用 LLaMA-Factory 对 Qwen2.5-7B-Instruct 做 LoRA 微调(rank=8,alpha=16),在单卡 A100 上训练了 3 个 epoch,约 2 小时。效果对比:领域问答准确率从 72% 提升到 89%,格式合规率从 60% 提升到 95%,幻觉率从 18% 降到 7%。微调后的模型集成进 RAG 系统,替换了原来的通用 Qwen3.5,整体答疑质量有明显提升。"
整体流程图
业务需求分析
↓
数据准备(最关键,占 60% 工作量)
↓
选择微调方法(LoRA / QLoRA / 全量)
↓
配置训练参数
↓
启动训练 + 监控 loss 曲线
↓
效果评估(对比微调前后)
↓
部署推理(vLLM / Ollama)
Step 1:数据准备(最重要)
数据格式:Alpaca 格式(最通用)
[
{
"instruction": "分析宁德时代2024年Q3的营收情况",
"input": "营收数据:Q3营收1000亿,同比增长15%,毛利率28%",
"output": "宁德时代2024年Q3营收表现稳健,营收1000亿元同比增长15%,毛利率维持在28%的健康水平,主要受益于储能业务放量和海外市场拓展..."
}
]
数据来源:
-
方案 A(最快):用 GPT-4/Claude 生成种子数据,人工筛选
-
方案 B(最真实):从业务日志里提取真实的用户问题 + 专家回答
-
方案 C(混合):A 生成初稿,B 的真实数据做质量校验
数据量经验值:
-
500-1000 条:能让模型学会领域风格和格式
-
3000-5000 条:能显著提升领域准确率
-
10000+ 条:接近全量微调效果
Step 2:选择微调方法
LoRA 核心原理:
不直接更新原始权重矩阵 W,而是训练两个小矩阵 A(d×r)和 B(r×d),其中 r 远小于 d(如 r=8,d=4096)。推理时 W' = W + BA,参数量从 d² 降到 2dr,当 r=8 时参数量减少 99.6%。
Step 3:用 LLaMA-Factory 训练(推荐工具)
# 安装
pip install llamafactory
# 启动 WebUI(最简单,不用写代码)
llamafactory-cli webui
# 或者用命令行
llamafactory-cli train \
--model_name_or_path Qwen/Qwen2.5-7B-Instruct \
--stage sft \
--do_train \
--finetuning_type lora \
--lora_rank 8 \
--lora_alpha 16 \
--dataset my_finance_data \
--template qwen \
--num_train_epochs 3 \
--per_device_train_batch_size 2 \
--gradient_accumulation_steps 4 \
--learning_rate 1e-4 \
--output_dir ./output/qwen2.5-7b-finance-lora
关键参数说明(面试会问):
-
lora_rank=8:低秩矩阵的秩,越大效果越好但显存越多,通常 8-16 -
lora_alpha=16:缩放系数,通常设为 rank 的 2 倍 -
learning_rate=1e-4:LoRA 的学习率比全量微调大 10 倍(全量通常 1e-5) -
gradient_accumulation_steps=4:梯度累积,等效于 batch_size×4,节省显存
Step 4:监控训练过程
看 loss 曲线判断是否正常:
正常情况:
train_loss: 2.5 → 1.8 → 1.2 → 0.8 → 0.6(平滑下降后趋于平稳)
eval_loss: 2.6 → 1.9 → 1.3 → 0.9 → 0.85(略高于 train_loss,正常)
过拟合信号:
train_loss: 2.5 → 0.3(下降太快,说明数据太少或 epoch 太多)
eval_loss: 2.6 → 1.5 → 2.0 → 2.5(先降后升,已过拟合)
欠拟合信号:
train_loss 在 1.5 以上不再下降(数据太少或 learning_rate 太小)
Step 5:效果评估
# 对比微调前后在测试集上的表现
test_queries = [
"分析新能源行业的投资机会",
"宁德时代的核心竞争力是什么",
... # 50条测试问题
]
# 评估指标
# 1. 领域准确率(人工标注):答案是否符合金融专业标准
# 2. 格式合规率:是否按要求输出结构化格式
# 3. 幻觉率:是否编造不存在的数据
# 4. BLEU/ROUGE:与标准答案的文本相似度
场景题:RAG 系统在专业领域(如医疗、法律)检索效果差,如何优化 Embedding?¶
难度级别:⭐⭐⭐⭐(领域微调、Contrastive Learning、数据构造、Adapter 方案)
1️⃣ Common Answer
领域效果差的话,可能是通用 Embedding 不懂专业术语。可以试试微调 Embedding 模型,用领域数据训练一下。或者用大一点的模型,比如 BGE-large。也可以考虑用领域专用的 Embedding,如果有的话。
2️⃣ Impressive Answer
领域适配的本质是分布对齐,我有三层递进方案:
- 第一层:Prompt 增强(零成本快速验证)
- 在查询和文档前加领域前缀,如"以下是医疗问题:"、"以下是医疗文档:"
-
利用模型预训练中的领域知识,无需训练,可快速验证是否有提升
-
第二层:Contrastive Learning 微调(核心方案)
- 构造三元组数据 (anchor, positive, negative):医疗问题 - 正确答案 - 错误答案
- 用 InfoNCE 或 Triplet Loss 微调,拉近正样本、推远负样本
- 数据构造可借力 LLM:用 GPT 生成相似问题和干扰答案
-
通常 1000-5000 条高质量三元组即可显著提升领域 MRR
-
第三层:Adapter 轻量化方案(资源受限时)
- 冻结 Embedding 主干,只训练少量 Adapter 层(LoRA 或 MLP Head)
- 参数量减少 90% 以上,训练速度快,适合多领域快速切换
- 配合知识蒸馏,可将微调后的模型压缩到 M3E-base 量级
关键点:先评估领域词分布偏移程度(KL 散度),若偏移小则 Prompt 增强足够;若偏移大则必须微调
3️⃣ Key Differences
容易一起考的题¶
4.4 混合检索与 Rerank 机制¶
BM25 + 向量融合策略¶
1、基础题:什么是混合检索?为什么需要结合 BM25 和向量检索?¶
难度级别:⭐⭐(关键词匹配、语义匹配、互补性、Reciprocal Rank Fusion)
2、进阶题:混合检索中 BM25 和向量检索的结果如何融合?RRF 的原理是什么?¶
难度级别:⭐⭐⭐(Reciprocal Rank Fusion、加权融合、归一化、参数调优)
1️⃣ Common Answer
混合检索就是把 BM25 和向量的结果合在一起。融合的话,可以把两个结果列表拼起来去重,或者给每个结果算个加权分。RRF 是一种融合方法,大概是按排名给个倒数分数,然后加起来。具体公式记不太清了,好像是 1/(k+rank) 这种形式。
2️⃣ Impressive Answer
融合策略的核心是排名融合而非分数融合,因为两种检索的分数空间不一致。主流方案有两种:
- RRF(Reciprocal Rank Fusion,倒数排名融合):
- 公式:
RRF_score(d) = Σ 1 / (k + rank_i(d)),其中 i 遍历各检索源,k 是平滑常数(通常 60) - 核心思想:排名越靠前贡献越大,k 控制头部排名的权重集中度
- 优势:无需归一化、对异常值鲁棒、参数少易调优
-
变体:加权 RRF 可为不同检索源分配不同权重,如向量权重 1.5、BM25 权重 1.0
-
归一化加权融合:
- 先将 BM25 分数和向量相似度分别归一化到 [0,1](Min-Max 或 Z-Score)
- 然后线性加权:
final_score = α * bm25_norm + (1-α) * vector_norm - 需要调优 α 参数(通常 0.3-0.7),并用验证集网格搜索最优值
生产实践:RRF 是默认首选,无需调参且效果稳定;若追求极致效果且有标注数据,可用加权融合 + 网格搜索
3️⃣ Key Differences
3、场景题:RAG 召回 Top-20 文档后,如何进一步提升最终回答质量?¶
难度级别:⭐⭐⭐⭐(Rerank 模型、Cross-Encoder、两阶段检索、精度与延迟权衡)
1️⃣ Common Answer
可以多召回一些文档,比如 Top-50,然后选一个更好的模型重新排序。Rerank 模型好像比向量模型更准,但速度更慢。也可以让 LLM 自己判断哪些文档更有用。总之就是加一道排序步骤,把最相关的放前面。
2️⃣ Impressive Answer
这是典型的两阶段检索架构,核心是用 Rerank 模型做精排:
- 架构设计:
- 第一阶段(粗排):用轻量 Embedding(如 BGE-base)+ 向量检索快速召回 Top-50~100
-
第二阶段(精排):用 Cross-Encoder Rerank 模型(如 BGE-Reranker)对 Top-K 逐一计算 query-doc 相关性,重排后取 Top-5 给 LLM
-
Rerank 模型原理:
- Cross-Encoder 架构:将 query 和 doc 拼接后输入 BERT,CLS 向量经 MLP 输出相关性分数
- 相比向量检索的双塔架构(Bi-Encoder),Cross-Encoder 能做细粒度交互,精度通常高 5-15 个百分点
-
代价:需对每个候选逐一推理,复杂度 O(K·d),K 大时延迟显著增加
-
生产权衡:
- 典型配置:粗排 Top-100 → Rerank → Top-5,Rerank 延迟约 100-300ms(可接受)
- 模型选型:BGE-Reranker-base(快)vs large(准),根据 SLA 选择
- 优化手段:Rerank 模型蒸馏到更小模型,或用 LoRA 微调领域数据
3️⃣ Key Differences
4、容易一起考的题¶
4.5 向量索引进阶优化¶
IVF、PQ 与大规模检索¶
1、基础题:除了 HNSW,向量索引还有哪些类型?各自适合什么场景?¶
难度级别:⭐⭐(IVF、PQ、LSH、暴力搜索、内存与速度权衡)
2、进阶题:IVF 索引的原理是什么?nlist 和 nprobe 参数如何影响检索效果?¶
难度级别:⭐⭐⭐⭐(倒排文件、聚类中心、探测列表、精度 - 内存 - 速度三维权衡)
1️⃣ Common Answer
IVF 是倒排文件索引,先把向量聚类成若干簇,每个簇有个中心,查询时先找最近的几个簇,然后在簇内搜索。nlist 是簇的数量,nprobe 是搜索时探测的簇数。nlist 越大越准但越慢,nprobe 也是越大越准。
2️⃣ Impressive Answer
IVF(Inverted File Index)的核心思想是聚类剪枝,我从构建、查询、参数三个维度说:
- 构建过程:
- 用 K-Means 将所有向量聚类成 nlist 个簇,每个簇有一个聚类中心(Centroid)
- 建立倒排索引:每个簇维护一个向量 ID 列表,指向属于该簇的所有向量
-
可选压缩:对簇内向量用 PQ(Product Quantization)压缩,大幅降低内存
-
查询过程:
- 第一步:计算查询向量与所有 nlist 个聚类中心的距离,找出最近的 nprobe 个簇
- 第二步:只在这 nprobe 个簇内的向量中做精确搜索(暴力或 HNSW)
-
剪枝效果:若 nlist=1024、nprobe=8,则只需搜索约 0.8% 的向量
-
参数权衡三角:
nlist:簇数量,经验值 ≈ √N(N 为向量总数)。越大聚类越细,内存增加,构建时间线性增长nprobe:探测簇数,是精度 - 延迟的核心权衡旋钮。nprobe 翻倍,延迟近似翻倍,Recall@10 提升 5-10%- 生产配置:千万级向量通常 nlist=4096、nprobe=16-32,Recall@10 可达 0.9 以上
对比 HNSW:IVF 内存占用更低(尤其加 PQ 压缩),适合亿级超大规模;HNSW 查询更快,适合十亿以下、低延迟场景
3️⃣ Key Differences
3、场景题:亿级向量库(10 亿 +)如何设计索引方案?内存和延迟如何权衡?¶
难度级别:⭐⭐⭐⭐⭐(大规模索引、PQ 压缩、IVF-PQ、多索引融合、SLA 保障)
1️⃣ Common Answer
10 亿向量很大了,可能需要用 IVF 加 PQ 压缩来减少内存。也可以用分布式向量数据库,把数据分到多台机器。或者用 HNSW 但减少连接数。延迟的话,可以加缓存或者用近似检索。具体要看硬件条件和 SLA 要求。
2️⃣ Impressive Answer
10 亿向量是大规模 RAG的典型场景,我从存储、索引、查询三层设计:
- 存储层:PQ 压缩是核心
- 原始向量:10 亿 × 768 维 × 4 字节(float32)≈ 3TB,无法全量驻留内存
- PQ(Product Quantization):将 768 维拆分为 M=32 个子向量,每子向量用 8-bit 量化(256 码本)
- 压缩后:10 亿 × 32 字节 ≈ 32GB,压缩率 100 倍,精度损失约 3-5%
-
变体:OPQ(Optimized PQ)加旋转矩阵提升精度,RQ(Residual Quantization)用残差量化进一步提升
-
索引层:IVF-PQ 组合
- IVF 倒排索引 + PQ 压缩存储,是 FAISS 推荐的大规模方案
- nlist ≈ √10 亿 ≈ 32768,实际取 65536(2^16)便于计算
-
nprobe 根据 SLA 调整:nprobe=64 时 Recall@100≈0.95,延迟约 50-100ms
-
查询层:多索引融合 + GPU 加速
- 若单一 IVF-PQ 精度不足,可用 IVF-PQ + HNSW 双索引,RRF 融合结果
- GPU 加速:FAISS 支持 GPU 暴力搜索,单卡可处理 1 亿向量,多卡并行可扩展到 10 亿
- 延迟优化:批量查询、向量预取、热点缓存(高频查询结果缓存)
生产参考:10 亿向量、IVF-65536 + PQ32、单卡 A100,Recall@100=0.93 时延迟约 80ms
3️⃣ Key Differences
4、容易一起考的题¶
4.6 RAG 幻觉检测与抑制¶
可信 RAG 与事实一致性¶
1、基础题:什么是 RAG 幻觉?产生的原因有哪些?¶
难度级别:⭐⭐(事实编造、检索失败、注意力偏差、训练数据污染)
2、进阶题:如何检测 RAG 系统是否产生了幻觉?有哪些自动化检测方法?¶
难度级别:⭐⭐⭐⭐(NLI 模型、事实一致性评分、Self-Check、引用溯源)
1️⃣ Common Answer
RAG 幻觉就是模型瞎说不真实的内容。检测的话,可以让人工审核一下,或者用另一个模型来判断回答是否正确。也可以让模型自己检查一遍。还可以看模型的回答有没有引用检索到的文档,没引用的可能是幻觉。
2️⃣ Impressive Answer
幻觉检测的核心是事实验证,主流方案有四类:
- NLI(Natural Language Inference)模型检测:
- 用 DeBERTa、RoBERTa-MNLI 等模型,判断"回答"是否被"检索文档"蕴含(Entailment)
- 三分类:Entailment(一致)、Contradiction(矛盾)、Neutral(无关)
-
若 Contradiction 分数高,则判定为幻觉;精度高但需额外模型推理
-
Self-Check 自检:
- 让 LLM 对回答中的每个原子事实逐一提问:"文档中是否提到 X?"
- 例如回答"马斯克 2024 年收购了 Twitter",自检问题:"检索文档是否提到马斯克收购 Twitter 的时间是 2024 年?"
-
无需额外模型,但增加 LLM 调用次数,延迟增加 2-5 倍
-
引用溯源(Citation Grounding):
- 要求模型为每个句子标注引用来源(如 [doc1, para3])
- 验证引用是否真实存在且内容匹配,无引用或引用错配则标记为幻觉
-
生产友好:LangChain、LlamaIndex 均支持引用标注
-
一致性投票(Self-Consistency):
- 对同一问题采样多次生成(temperature=0.7),若关键事实在多数回答中一致则可信
- 适用于事实型问题,对开放性问答效果有限
生产推荐:引用溯源 + NLI 模型双保险,低延迟场景用前者,高可信场景叠加后者
3️⃣ Key Differences
3、场景题:如何抑制 RAG 幻觉?从检索和生成两端分别有哪些优化手段?¶
难度级别:⭐⭐⭐⭐⭐(检索质量、Prompt 约束、事实校验、拒绝回答机制)
1️⃣ Common Answer
抑制幻觉的话,检索端可以多召回一些文档,提高相关性。生成端可以加 Prompt 约束,告诉模型不要瞎说。也可以让模型在不确定时说不知道。
2️⃣ Impressive Answer
幻觉抑制需要检索 - 生成两端协同,我有五层防御体系:
- 第一层:检索质量保障(源头控制)
- 提高检索相关性:用混合检索 + Rerank,确保 Top 文档真正相关
- 添加相关性阈值:若最高相似度<0.7,判定为"无可靠信息",不进入生成阶段
-
文档质量过滤:移除低可信来源(如论坛、未验证内容),只保留权威文档
-
第二层:Prompt 约束(生成时引导)
- 显式指令:"请严格基于以下文档回答,若文档中没有相关信息,请明确说明'根据提供的信息无法回答'"
- 引用强制:"每个论断必须标注引用来源,格式为 [doc_id]"
-
负面约束:"不要编造数据、日期、人名,不要添加文档中没有的信息"
-
第三层:Fact-Checking 模块(生成后验证)
- 用 NLI 模型对"文档 - 回答"对做蕴含判断,矛盾分数>阈值则拦截
-
对关键实体(人名、日期、数字)做抽取验证,与文档逐一比对
-
第四层:拒绝回答机制(边界控制)
- 当检索结果为空或相关性过低时,触发拒绝回答模板
-
明确告知用户:"当前知识库中没有相关信息",而非强行生成
-
第五层:反馈闭环(持续优化)
- 收集用户反馈的幻觉案例,加入负样本训练 Rerank 模型
- 定期用对抗测试(红队测试)主动发现幻觉边界
用户提问
↓
检索层(召回Top10文档)
↓
[检索质量评估] ← 在这里判断召回文档质量
├── Context Relevance < 阈值?
│ ↓ Yes
│ 触发查询改写 → 重新检索
│ ↓ No
└── 继续生成
↓
生成层(LLM 基于召回文档生成答案)
↓
[生成质量评估] ← 在这里判断答案是否有幻觉
├── Faithfulness < 阈值?
│ ↓ Yes
│ 在答案中标注"⚠️ 以下内容可能超出文档范围"
│ ↓ No
└── 正常返回答案 + 引用来源
指标解释
3️⃣ Key Differences
4、容易一起考的题¶
4.7 RAG 评估体系与可观测性¶
RAG 质量评估与监控¶
1、基础题:RAG 系统有哪些核心评估指标?如何量化检索和生成的质量?¶
难度级别:⭐⭐(Recall@K、MRR、NDCG、Faithfulness、Answer Relevancy)
RAG 评估需要检索质量和生成质量两个维度分别度量:
检索质量指标:
-
Recall@K:Top-K 召回结果中包含正确文档的比例,衡量"能不能找到"
-
MRR(Mean Reciprocal Rank):正确文档首次出现的排名倒数的均值,衡量"排得准不准"
-
NDCG(Normalized Discounted Cumulative Gain):考虑排名位置的加权相关性评分,排名越靠前权重越高
生成质量指标:
-
Faithfulness(忠实度):回答中的事实是否都能在检索文档中找到依据,衡量"有没有瞎说"
-
Answer Relevancy(回答相关性):回答是否真正回答了用户的问题,衡量"答没答到点上"
-
Context Precision(上下文精度):检索到的文档中,真正对回答有用的比例
-
Context Recall(上下文召回):回答所需的信息在检索文档中的覆盖程度
核心原则:检索指标和生成指标要联合看——检索好但生成差说明 LLM 没用好上下文,检索差但生成好可能是模型在"编"
2、进阶题:如何搭建 RAG 自动化评估流水线?RAGAS 框架的原理和核心指标是什么?¶
难度级别:⭐⭐⭐(RAGAS、TruLens、LangSmith、Context Precision/Recall、Faithfulness 自动评估)
1️⃣ Common Answer
RAGAS 是一个评估 RAG 的框架,可以自动打分。它有几个指标,比如忠实度、相关性什么的。用的话就把问题、回答、检索文档传进去,它会给个分数。也可以用 LangSmith 来做评估,它有个 dashboard 可以看结果。具体怎么搭流水线,大概就是定期跑一下评估脚本吧。
2️⃣ Impressive Answer
自动化评估流水线的核心是用 LLM 评估 LLM,我从框架原理和流水线设计两个角度来说:
- RAGAS 框架核心原理:
- 无需人工标注,用 LLM 作为评判者(LLM-as-Judge)自动评估
-
四大核心指标:
- Faithfulness:将回答拆解为原子事实,逐一判断是否被检索文档蕴含
- Answer Relevancy:用 LLM 从回答反向生成问题,计算生成问题与原始问题的相似度
- Context Precision:检索文档中排名靠前的是否真正相关(加权精度)
- Context Recall:标准答案中的关键信息是否被检索文档覆盖
-
评估流水线架构设计:
- 数据层:维护 Golden Set(标注的问答对 + 标准检索文档),覆盖高频、长尾、边界场景
- 执行层:CI/CD 集成,每次模型/分块/Prompt 变更自动触发评估
- 指标层:RAGAS 四指标 + 自定义业务指标(如拒绝回答率、引用准确率)
-
告警层:设置基线阈值(如 Faithfulness < 0.85 触发告警),防止质量回退
-
工具选型对比:
- RAGAS:开源免费,指标体系完善,适合离线评估
- TruLens:支持实时追踪每次调用的评估分数,适合线上监控
- LangSmith:LangChain 生态,集成 Trace + 评估 + 数据集管理,适合全链路可观测
3️⃣ Key Differences
3、场景题:RAG 系统上线后如何做持续监控和质量回归?发现质量下降如何排查?¶
难度级别:⭐⭐⭐⭐(A/B 测试、Golden Set 回归、漂移检测、告警策略、反馈闭环)
1️⃣ Common Answer
上线后可以看看用户反馈,如果有人说回答不对就去查一下。也可以定期抽样检查一些回答的质量。如果质量下降了,可能是数据变了或者模型有问题,逐个排查一下。也可以做 A/B 测试,比较新旧版本的效果。
2️⃣ Impressive Answer
线上质量保障需要主动监控 + 被动反馈双通道,我有四层监控体系:
- 第一层:实时指标监控
- 用 TruLens 或自建模块对每次调用计算 Faithfulness 和 Relevancy 分数
- 监控检索相似度分布:若平均相似度持续下降,说明新增文档与查询分布偏移
-
监控拒绝回答率:突然升高可能是索引故障,突然降低可能是阈值被误改
-
第二层:定期回归测试
- 维护 Golden Set(200-500 条标注问答对),覆盖核心业务场景
- 每次变更(Prompt 修改、模型升级、文档更新)自动跑回归,对比基线分数
-
设置红线:任一核心指标下降超过 3% 则阻断上线
-
第三层:漂移检测
- 查询漂移:监控用户查询的 Embedding 分布,用 KL 散度检测是否偏离训练分布
- 文档漂移:新增文档的主题分布是否与索引整体一致,异常文档可能污染检索
-
模型漂移:API 模型(如 GPT-4)的隐式更新可能导致输出风格变化,需定期校验
-
第四层:排查 SOP
- 质量下降时按链路分层排查:检索层(相似度分布)→ 文档层(新增/删除文档)→ 模型层(Prompt/模型变更)→ 数据层(用户查询模式变化)
- 用 Trace 工具(LangSmith/LangFuse)回溯具体 Bad Case 的完整链路
- 建立 Bad Case 库,定期复盘并转化为回归测试用例
3️⃣ Key Differences
4、容易一起考的题¶
4.8 查询理解与改写¶
查询优化与意图理解¶
1、基础题:什么是查询改写?为什么用户原始查询直接检索效果往往不好?¶
难度级别:⭐⭐(口语化查询、语义鸿沟、模糊表述、多意图混合)
查询改写是指在用户原始查询进入检索系统之前,对其进行优化转换,使其更适合检索的过程。
用户原始查询效果差的四个原因:
-
口语化表述:用户说"那个东西怎么弄",但文档写的是"配置方法"——口语和书面语之间存在语义鸿沟
-
模糊和省略:用户省略了关键上下文,如"怎么部署"没说部署什么、用什么环境
-
多意图混合:一个查询包含多个子问题,如"Redis 和 MySQL 的区别以及各自的使用场景",单次检索难以同时覆盖
-
词汇不匹配:用户用的词和文档用的词不同(如"挂了"vs"服务不可用"),导致关键词检索和语义检索都可能失效
查询改写的本质:弥合用户表达和文档表达之间的语义鸿沟,让检索系统"听懂"用户真正想问什么
2、进阶题:HyDE、Multi-Query、Step-Back Prompting 等查询改写技术的原理和区别是什么?¶
难度级别:⭐⭐⭐(假设文档生成、多查询扩展、抽象化提问、Query Decomposition)
1️⃣ Common Answer
HyDE 是让模型先生成一个假的答案,然后用这个答案去检索,因为答案和文档更像。Multi-Query 就是把一个问题改写成多个不同的问法,然后分别检索再合并结果。Step-Back 是让问题变得更抽象一些。这几种方法都是为了提高检索效果,具体用哪个看情况。
2️⃣ Impressive Answer
查询改写的核心思路是缩短查询与文档的语义距离,主流技术有四种,各自解决不同的问题:
- HyDE(Hypothetical Document Embeddings):
- 原理:让 LLM 先生成一个"假设性回答",用这个回答的 Embedding 去检索
- 为什么有效:假设回答的语言风格更接近文档(都是陈述句),比问句检索效果好
-
局限:若 LLM 生成的假设回答方向错误,会导致检索偏移;适合事实型问题,不适合开放性问题
-
Multi-Query(多查询扩展):
- 原理:用 LLM 将原始查询改写为 3-5 个不同角度的查询,分别检索后合并去重
- 为什么有效:不同表述覆盖不同的语义空间,提高召回覆盖率
-
示例:"Redis 缓存穿透怎么解决" → ["缓存穿透的防护方案"、"布隆过滤器防止缓存穿透"、"空值缓存策略"]
-
Step-Back Prompting(后退提问):
- 原理:将具体问题抽象为更高层的问题,先检索通用知识,再结合具体问题回答
- 示例:"BGE-M3 的 efConstruction 参数设多少合适" → 先检索"HNSW 索引参数调优原则"
-
适合:具体参数/配置类问题,文档中可能没有精确匹配但有通用指导
-
Query Decomposition(查询分解):
- 原理:将复杂多跳问题拆解为多个子问题,逐一检索后合成
- 示例:"对比 Milvus 和 Qdrant 在百万级场景下的性能" → ["Milvus 百万级性能基准"、"Qdrant 百万级性能基准"、"向量数据库性能对比维度"]
选型建议:事实型问题首选 HyDE,覆盖率不足用 Multi-Query,具体问题用 Step-Back,复杂问题用 Decomposition
3️⃣ Key Differences
3、场景题:用户输入模糊的对话式查询(如"最近那个政策怎么改的"),RAG 系统如何理解并检索?¶
难度级别:⭐⭐⭐⭐(意图识别、指代消解、时间感知、对话上下文融合、多轮查询改写)
1️⃣ Common Answer
这种模糊查询确实不好处理。可以让用户说清楚一点,或者用 LLM 帮忙改写一下查询。"最近"可以理解为最近一段时间,"那个政策"需要从上下文推断是哪个政策。也可以多召回一些文档,让 LLM 自己判断哪个是用户想要的。
2️⃣ Impressive Answer
模糊对话式查询需要多层理解 + 渐进式改写,我分四步处理:
- 指代消解:
- 结合对话历史,将"那个政策"替换为具体实体(如上文提到的"个人所得税专项扣除政策")
- 实现方式:将最近 3-5 轮对话作为上下文,让 LLM 输出消解后的完整查询
-
输出:"个人所得税专项扣除政策最近怎么改的"
-
时间感知处理:
- "最近"是相对时间,需转换为绝对时间范围
- 策略:默认映射为最近 3 个月(可配置),在检索时加时间过滤条件
-
若文档有时间戳字段,用预过滤限定范围;否则在查询中注入时间关键词
-
意图识别与查询改写:
- 识别用户意图:这是一个"变更查询"(问的是"怎么改的"而非"是什么")
- 改写为检索友好的查询:"个人所得税专项扣除政策 2024 年修订内容 变更要点"
-
可选:用 Multi-Query 生成多个变体覆盖不同表述
-
兜底策略:
- 若消解后仍不确定指代对象,生成澄清问题反问用户:"您是指个人所得税专项扣除政策,还是企业所得税优惠政策?"
- 设置置信度阈值:消解置信度 > 0.8 直接检索,< 0.8 触发澄清
3️⃣ Key Differences
4、容易一起考的题¶
4.9 多模态 RAG¶
多模态文档处理与检索¶
1、基础题:多模态 RAG 和纯文本 RAG 有什么区别?支持哪些模态?¶
难度级别:⭐⭐(图片、表格、PDF、音视频、多模态 Embedding、模态对齐)
多模态 RAG 是将 RAG 的能力从纯文本扩展到图片、表格、PDF、音视频等多种数据模态的技术。
与纯文本 RAG 的核心区别:
-
数据处理层:纯文本只需分块,多模态需要先做模态解析(OCR、表格提取、音频转写等),将非文本内容转化为可检索的形式
-
Embedding 层:纯文本用文本 Embedding,多模态需要跨模态 Embedding(如 CLIP 将图文映射到同一向量空间)或分模态独立 Embedding
-
检索层:纯文本单一索引,多模态可能需要多索引(文本索引 + 图片索引)或统一索引
-
生成层:纯文本用文本 LLM,多模态需要视觉语言模型(如 GPT-4V、Gemini)理解图片内容
支持的模态及处理方式:
-
图片:CLIP/SigLIP Embedding 或 VLM 生成描述文本
-
表格:结构化提取为 Markdown/JSON,或转为自然语言描述
-
PDF:Layout 分析 + OCR + 表格提取 + 图片提取的组合处理
-
音视频:ASR 转写为文本,关键帧提取为图片
2、进阶题:如何对图文混合文档(如技术文档含架构图)做 RAG?有哪些技术方案?¶
难度级别:⭐⭐⭐⭐(多模态 Embedding、CLIP、ColPali、视觉语言模型、图文联合索引)
1️⃣ Common Answer
图文混合文档的话,可以把图片用 OCR 识别出文字,然后和正文一起做 RAG。或者用多模态模型直接理解图片内容。架构图的话,可以让模型描述一下图片内容,把描述文本存进去检索。具体用什么工具看项目需求。
2️⃣ Impressive Answer
图文混合文档的 RAG 有三种主流方案,复杂度和效果递增:
- 方案一:图片转文本(Text-Only Pipeline)
- 对图片用 VLM(如 GPT-4V)生成详细文本描述,与正文一起做纯文本 RAG
- 优点:复用现有文本 RAG 全套基础设施,改造成本最低
- 缺点:描述可能丢失图片细节(如架构图中的箭头方向、颜色含义)
-
适合:快速验证、图片信息密度低的场景
-
方案二:多模态 Embedding 统一索引
- 用 CLIP/SigLIP 将图片和文本映射到同一向量空间
- 查询时用文本 Embedding 检索,可同时命中文本块和图片
- 优点:端到端统一,检索时无需区分模态
- 缺点:CLIP 对复杂图表(如架构图、流程图)的理解能力有限
-
适合:图片以照片、示意图为主的场景
-
方案三:ColPali / 文档视觉检索(最前沿)
- ColPali 将整个文档页面作为图片输入视觉模型,生成 Token 级 Embedding
- 跳过 OCR/Layout 分析,直接从视觉层面理解文档
- 优点:保留完整的视觉布局信息,对表格、图表、公式效果最好
- 缺点:计算开销大,索引构建慢,模型选择有限
- 适合:PDF 报告、学术论文等视觉布局重要的场景
生产推荐:方案一快速上线 → 方案二提升图片召回 → 方案三处理复杂文档,渐进式升级
3️⃣ Key Differences
3、场景题:企业内部有大量 PDF 报表(含表格和图表),如何设计 RAG 系统?¶
难度级别:⭐⭐⭐⭐(PDF 解析、表格结构化、OCR、图表描述生成、混合索引、Layout 分析)
1️⃣ Common Answer
PDF 的话,先把 PDF 转成文本,然后做 RAG。表格可以用工具提取出来,图表的话用 OCR 或者多模态模型识别。然后把所有内容都存到向量数据库里检索。可能需要用 PyPDF 或者 pdfplumber 这些库来解析 PDF。
2️⃣ Impressive Answer
企业 PDF 报表的 RAG 需要分层解析 + 多模态索引,我从解析、索引、检索三层设计:
- 解析层:结构化提取
- Layout 分析:用 LayoutLMv3 或 Unstructured.io 识别 PDF 的版面结构(标题、正文、表格、图片区域)
- 文本提取:对正文区域用 pdfplumber 提取,保留段落和标题层级关系
- 表格提取:用 Camelot/Tabula 提取表格结构,转为 Markdown 格式保留行列关系;复杂合并单元格用 Table Transformer 模型
- 图表处理:提取图片后用 VLM 生成结构化描述(如"柱状图显示 2024 年 Q1-Q4 营收分别为 X、Y、Z、W 亿元")
-
元数据保留:提取页码、章节标题、文档标题作为元数据,用于后续过滤
-
索引层:混合索引设计
- 文本块:正常分块 + 文本 Embedding 索引
- 表格块:整表作为一个块(保留结构),同时生成自然语言摘要辅助检索
- 图表块:VLM 描述文本 + 原始图片双存储
-
元数据索引:对文档名、日期、部门等字段建标量索引,支持过滤
-
检索层:多路召回 + 智能路由
- 判断查询类型:数据查询("Q3 营收多少")路由到表格索引,概念查询路由到文本索引
- 表格查询增强:将用户问题转为 SQL-like 查询,在结构化表格上精确匹配
- 图表查询:用多模态检索匹配相关图表,将图表描述 + 原图一起传给 VLM 生成回答
3️⃣ Key Differences
4、容易一起考的题¶
4.10 RAG 与 Agent 结合(Agentic RAG)¶
自适应检索与推理增强¶
1、基础题:什么是 Agentic RAG?和传统 RAG 有什么区别?¶
难度级别:⭐⭐(主动检索决策、多轮推理、自适应检索、路由选择)
Agentic RAG 是将 Agent 的自主决策能力引入 RAG 系统,让模型主动决定何时检索、检索什么、是否需要再次检索,而非传统 RAG 的"有问必检"。
与传统 RAG 的核心区别:
核心能力:
-
检索路由:根据问题类型选择最合适的数据源
-
自我反思:评估检索结果是否足够回答问题,不够则补充检索
-
查询规划:将复杂问题分解为子问题,逐步检索和推理
2、进阶题:Self-RAG、CRAG(Corrective RAG)的原理是什么?如何让模型自主决定何时检索?¶
难度级别:⭐⭐⭐⭐(检索判断 Token、相关性评估、自我反思、检索-生成交替、Critique Token)
1️⃣ Common Answer
Self-RAG 是让模型自己决定要不要检索,如果觉得自己知道就直接回答,不知道再去检索。CRAG 是检索完之后检查一下结果对不对,不对就修正。具体怎么实现的不太清楚,好像是在训练时加了一些特殊的 token 来控制。
2️⃣ Impressive Answer
Self-RAG 和 CRAG 代表了 Agentic RAG 的两个核心方向:检索前决策和检索后纠正。
- Self-RAG(Self-Reflective RAG):
- 核心机制:在模型中训练特殊的 Critique Token,用于自主决策
- 四种 Critique Token:
[Retrieve]:判断是否需要检索(Yes/No/Continue)[IsRel]:判断检索文档是否与查询相关[IsSup]:判断生成内容是否被文档支持(Fully/Partially/No)[IsUse]:判断最终回答是否有用
- 工作流程:生成过程中动态插入 Critique Token → 若
[Retrieve]=Yes则触发检索 → 用[IsRel]过滤无关文档 → 用[IsSup]验证生成内容 -
训练方式:用 GPT-4 标注 Critique Token,然后微调开源模型(如 Llama)
-
CRAG(Corrective RAG):
- 核心机制:在检索后增加质量评估和纠正环节
- 三级评估:对检索文档评分为 Correct / Ambiguous / Incorrect
- 纠正策略:
- Correct:直接使用,提取关键信息去除噪声
- Ambiguous:结合检索结果和 Web 搜索补充信息
- Incorrect:丢弃检索结果,转为 Web 搜索兜底
-
优势:无需微调模型,用轻量评估器即可实现,工程落地更简单
-
生产选型:
- Self-RAG 精度更高但需要微调,适合有训练资源的团队
- CRAG 无需微调,用 Prompt + 评估器即可实现,适合快速落地
- 两者可组合:Self-RAG 做检索决策 + CRAG 做结果纠正
3️⃣ Key Differences
3、场景题:设计一个能处理复杂多跳问题的 Agentic RAG 系统(如"对比 A 公司和 B 公司最近两年的营收趋势")¶
难度级别:⭐⭐⭐⭐⭐(查询分解、迭代检索、中间推理、结果合成、多源信息融合)
1️⃣ Common Answer
这种对比类问题,可以先分别检索 A 公司和 B 公司的营收数据,然后让 LLM 做对比分析。如果一次检索不够,可以多检索几次。也可以把问题拆成几个小问题分别回答,最后合并。用 Agent 框架的话,可以让 Agent 自己规划检索步骤。
2️⃣ Impressive Answer
复杂多跳问题需要规划-检索-推理-合成的迭代闭环,我设计四阶段架构:
- 阶段一:查询规划(Planner)
- 用 LLM 将复杂问题分解为子任务 DAG(有向无环图):
- Task1:检索 A 公司 2023 年营收数据
- Task2:检索 A 公司 2024 年营收数据
- Task3:检索 B 公司 2023 年营收数据
- Task4:检索 B 公司 2024 年营收数据
- Task5(依赖 1-4):对比分析营收趋势
-
识别可并行的子任务(Task1-4 可并行),减少总延迟
-
阶段二:自适应检索(Retriever Agent)
- 每个子任务独立检索,检索后用 CRAG 评估结果质量
- 若检索结果不足(如只找到年度总营收,缺少季度明细),自动生成补充查询
-
数据源路由:结构化数据(营收数字)优先查数据库/表格,非结构化分析查文档
-
阶段三:中间推理(Reasoner)
- 对每个子任务的检索结果做中间推理,提取关键数据点
- 交叉验证:若 A 公司营收在不同文档中数据矛盾,标记冲突并选择更权威的来源
-
生成中间结论:"A 公司 2023→2024 营收增长 15%,B 公司增长 8%"
-
阶段四:结果合成(Synthesizer)
- 将所有中间结论合成最终回答,确保对比维度一致
- 添加数据来源引用,标注每个数据点的出处
- 若存在数据缺失,明确告知用户哪些信息未找到
关键设计决策:
-
最大迭代深度限制(如 3 轮),防止无限循环
-
每轮检索设置超时,超时后用已有信息生成部分回答
-
用 LangGraph 实现状态机,管理多阶段流转和错误恢复
3️⃣ Key Differences
4、容易一起考的题¶
4.11 RAG 生产工程实践¶
企业级 RAG 架构与运维¶
1、基础题:RAG 系统的数据更新策略有哪些?如何处理文档的增删改?¶
难度级别:⭐⭐(增量索引、全量重建、版本管理、一致性保障、软删除)
RAG 系统的数据更新需要保证索引与源数据的一致性,主要有三种策略:
- 增量更新:
- 新增文档:分块 → Embedding → 写入向量数据库,不影响已有索引
- 修改文档:先删除旧文档的所有向量,再重新分块写入(先删后增)
- 删除文档:软删除(标记为不可检索)或硬删除(物理删除向量)
-
优点:速度快,适合频繁更新;缺点:长期可能产生索引碎片
-
全量重建:
- 定期(如每天凌晨)对全部文档重新分块、Embedding、建索引
- 用蓝绿部署切换:新索引构建完成后原子切换,旧索引保留用于回滚
-
优点:索引质量最高,无碎片;缺点:耗时长,资源消耗大
-
版本化管理:
- 每个文档维护版本号,索引中保留版本信息
- 检索时只返回最新版本的结果,历史版本可按需查询
- 适合:法规、合同等需要追溯历史版本的场景
一致性保障:用消息队列(如 Kafka)异步处理文档变更事件,保证源数据和索引的最终一致性
2、进阶题:RAG 系统如何做成本控制?Token 消耗、Embedding 计算、向量存储的成本如何优化?¶
难度级别:⭐⭐⭐(Semantic Cache、分层检索、按需加载、Token 压缩、批量 Embedding)
1️⃣ Common Answer
成本控制的话,可以少召回一些文档减少 Token 消耗。Embedding 可以用开源模型自己部署,不用付 API 费用。向量存储的话,用 PQ 压缩减少内存。也可以加缓存,相同的问题不用重复检索。总体就是能省则省。
2️⃣ Impressive Answer
RAG 成本主要来自三个环节:LLM Token、Embedding 计算、向量存储,我逐一优化:
- LLM Token 成本优化(占总成本 60-80%):
- Semantic Cache(语义缓存):对相似查询(余弦相似度 > 0.95)直接返回缓存结果,可减少 30-50% 的 LLM 调用
- 上下文压缩:用 LLMLingua 或 Recomp 压缩检索文档,去除冗余信息,Token 减少 50-70% 且精度损失 < 3%
- 分层模型策略:简单问题用小模型(GPT-3.5/Qwen-7B),复杂问题用大模型(GPT-4),通过路由器分流
-
控制召回数量:Rerank 后只取 Top-3~5 而非 Top-10,减少 Context 长度
-
Embedding 计算成本优化:
- 批量处理:文档入库时批量 Embedding(batch_size=256),比逐条调用 API 便宜 50%+
- 本地部署:用 BGE/M3E 本地部署,单卡 A10 可支撑日均百万次 Embedding,成本远低于 API
-
增量 Embedding:只对新增/修改的文档做 Embedding,避免全量重算
-
向量存储成本优化:
- PQ 压缩:768 维 float32(3KB/向量)→ PQ32(32B/向量),压缩 100 倍
- 冷热分层:高频访问的向量放内存(HNSW),低频放磁盘(IVF-PQ),按访问频率自动迁移
- TTL 过期:对时效性文档设置过期时间,自动清理过期向量
成本量化参考:百万文档 RAG 系统,优化前月成本约 $3000,优化后可降至 $800-1200
3️⃣ Key Differences
3、场景题:从 0 到 1 搭建一个企业级 RAG 系统,技术选型和架构设计怎么做?¶
难度级别:⭐⭐⭐⭐⭐(全链路架构、技术栈选型、容量规划、灰度上线、降级方案)
1️⃣ Common Answer
从 0 到 1 的话,先选个框架,比如 LangChain 或 LlamaIndex。然后选个向量数据库,Milvus 或者 Qdrant。Embedding 用 BGE 或 OpenAI。LLM 用 GPT-4 或者开源模型。然后把文档处理、检索、生成串起来就行了。部署的话用 Docker 容器化。
2️⃣ Impressive Answer
企业级 RAG 从 0 到 1 需要分阶段交付,我按四个阶段设计:
- 阶段一:MVP 验证(2-4 周)
- 目标:验证 RAG 在目标场景的可行性,用最小成本跑通链路
- 技术选型:LlamaIndex(快速原型)+ Qdrant(轻量部署)+ BGE-M3(中文最优)+ GPT-4(效果天花板)
- 关键动作:用 50-100 篇核心文档跑通端到端,人工评估 Top-20 问题的回答质量
-
交付物:可行性报告 + 基线指标(Faithfulness、Relevancy)
-
阶段二:工程化落地(4-8 周)
- 架构设计:
- 数据层:文档处理 Pipeline(Unstructured.io)→ 分块 → Embedding → 向量数据库
- 检索层:混合检索(BM25 + 向量)→ Rerank(BGE-Reranker)→ Top-5
- 生成层:Prompt 模板管理 + LLM 调用 + 引用标注 + 流式输出
- 评估层:RAGAS 自动评估 + Golden Set 回归
-
关键决策:
- 向量数据库:< 1000 万向量选 Qdrant(运维简单),> 1000 万选 Milvus(分布式扩展)
- LLM:数据敏感选私有化部署(Qwen/Llama),否则选 API(GPT-4/Claude)
-
阶段三:生产加固(4-6 周)
- 高可用:向量数据库主从复制,LLM 多 Provider 故障转移
- 降级方案:LLM 不可用时返回检索文档摘要;向量库不可用时降级为 BM25 关键词检索
- 安全防护:输入过滤(Prompt 注入检测)、输出过滤(敏感信息脱敏)、访问控制(RBAC)
-
可观测性:LangFuse 全链路追踪 + Prometheus 指标监控 + 告警
-
阶段四:持续优化(长期)
- 数据飞轮:收集用户反馈 → 标注 Bad Case → 优化分块/Prompt/Rerank
- 成本优化:Semantic Cache、模型分层、上下文压缩
- 能力扩展:多模态支持、Agentic RAG、多轮对话
3️⃣ Key Differences