Appearance
RAG 面试专题
RAG 面试主线:为什么用 RAG → 文档处理 → Embedding → 向量检索 → 检索优化 → 质量评估 → 工程实践。
Q1: 什么是 RAG?详细描述一个完整 RAG 系统的详细工作流程? 「🟢 校招/初级」
考察点:RAG 基本概念和完整工作流。
参考答案:
RAG(Retrieval-Augmented Generation)= 检索增强生成
核心思想:在 LLM 生成回答之前,先从外部知识库中检索相关信息,将检索到的内容作为上下文注入 Prompt,让 LLM 基于检索到的信息生成回答。
完整 RAG 工作流程:
┌─────────────────────────────────────────────────────────────┐
│ RAG 系统完整流程 │
│ │
│ 【离线阶段:知识库构建】 │
│ 原始文档 → 加载 → 切分 → Embedding → 存入向量数据库 │
│ │
│ 【在线阶段:检索生成】 │
│ 用户问题 → Query 改写 → 向量检索 → 重排序 → 组装 Prompt │
│ → LLM 生成 → 后处理 → 返回答案 │
└─────────────────────────────────────────────────────────────┘离线阶段详解:
1. 文档加载(Document Loading)
PDF/Word/HTML/Markdown → 统一为文本
2. 文档切分(Chunking)
长文档 → 切分为语义完整的片段(Chunk)
常用策略:固定长度 + 重叠、按段落、按语义
3. 向量化(Embedding)
每个 Chunk → Embedding 模型 → 向量表示(768/1536 维)
4. 存储(Indexing)
向量 + 原文 + 元数据 → 存入向量数据库在线阶段详解:
1. Query 处理
用户问题 → [可选] Query 改写/扩展 → 向量化
2. 检索(Retrieval)
Query 向量 → 向量数据库 → Top-K 相似 Chunk
3. 重排序(Reranking)
Top-K Chunk → Cross-Encoder 重排序 → Top-N 最相关
4. 上下文组装
System Prompt + 检索到的 Chunk + 用户问题 → 完整 Prompt
5. LLM 生成
完整 Prompt → LLM → 生成回答
6. 后处理
引用标注、格式化、事实校验 → 最终输出python
# RAG 完整流程实现
class RAGSystem:
def __init__(self, llm, embed_model, vector_store, reranker=None):
self.llm = llm
self.embed_model = embed_model
self.vector_store = vector_store
self.reranker = reranker
# ===== 离线:知识库构建 =====
def ingest(self, documents):
"""文档入库"""
chunks = []
for doc in documents:
# 1. 切分
chunks.extend(self._split(doc))
# 2. 向量化
embeddings = [self.embed_model.embed(c.text) for c in chunks]
# 3. 存储
self.vector_store.add(
embeddings=embeddings,
documents=[c.text for c in chunks],
metadatas=[c.metadata for c in chunks]
)
# ===== 在线:检索生成 =====
def query(self, question):
"""用户查询"""
# 1. Query 向量化
query_emb = self.embed_model.embed(question)
# 2. 向量检索
results = self.vector_store.search(query_emb, top_k=10)
# 3. 重排序
if self.reranker:
results = self.reranker.rerank(question, results, top_n=5)
# 4. 组装 Prompt
context = "\n\n".join(r.text for r in results[:5])
prompt = f"""基于以下参考信息回答问题。
参考信息:
{context}
问题:{question}
要求:基于参考信息回答,不要编造。如果参考信息不足,请说明。
"""
# 5. LLM 生成
answer = self.llm.generate(prompt)
return {"answer": answer, "sources": results[:5]}追问延伸:
- RAG 和传统搜索引擎有什么区别?(RAG 用 LLM 生成,搜索引擎只返回文档)
- 为什么不直接把所有文档塞给 LLM?(上下文窗口有限、成本高、噪声大)
Q2: 大模型的 RAG 主要用来解决什么问题? 「🟢 校招/初级」
考察点:RAG 解决的核心痛点。
参考答案:
RAG 主要解决 LLM 的三大问题:
| 问题 | 说明 | RAG 解决方式 |
|---|---|---|
| 知识截止 | LLM 训练数据有截止日期,不知道新信息 | 实时检索最新知识 |
| 幻觉问题 | LLM 会编造看起来合理但错误的信息 | 基于检索到的真实信息回答 |
| 私有知识 | LLM 不知道企业内部数据/私有文档 | 检索私有知识库 |
1. 知识截止问题:
LLM 知识截止到训练时间(如 GPT-4 截止 2023 年)
用户:"2025 年最新的 Agent 框架有哪些?"
LLM 直接回答 → 可能编造或信息过时
RAG:
→ 检索最新文档 → 获得真实最新信息
→ 基于检索结果回答 → 信息准确2. 幻觉问题:
LLM 直接回答 → 可能"一本正经地胡说八道"
RAG:
→ 检索真实文档 → 给 LLM 提供事实依据
→ LLM 基于上下文回答 → 大幅减少幻觉
→ 还可以要求 LLM 标注引用来源 → 可验证3. 私有知识问题:
LLM 不知道企业的 API 文档、业务规则、内部知识
RAG:
→ 向量数据库存储企业私有文档
→ 检索相关文档 → 注入上下文
→ LLM 基于企业知识回答4. 额外解决的问题:
| 问题 | RAG 价值 |
|---|---|
| 成本 | 不需要微调模型,用 RAG 即可注入新知识 |
| 时效性 | 知识库可实时更新,无需重新训练 |
| 可控性 | 知识来源可控,可追溯引用 |
| 多语言 | 检索原文,LLM 翻译/总结 |
| 权限控制 | 按用户权限检索不同文档 |
追问延伸:
- RAG 能完全消除幻觉吗?(不能,但可以大幅减少,还需要事实校验)
- RAG 和给 LLM 提示"不知道就说不知道"有什么区别?(RAG 提供了真实信息源)
Q3: 相比直接微调 LLM,RAG 解决了什么问题?微调和 RAG 各自的优劣势是什么? 「🟡 中级」
考察点:RAG vs 微调的对比选型。
参考答案:
| 维度 | RAG | 微调(Fine-tuning) |
|---|---|---|
| 知识更新 | 实时更新(改文档即可) | 需要重新训练 |
| 成本 | 低(不需要训练) | 高(GPU + 数据标注) |
| 幻觉 | 可减少(基于检索信息) | 改善有限 |
| 知识量 | 理论无限(向量库可扩展) | 受模型容量限制 |
| 可解释性 | 高(可追溯引用来源) | 低(知识内化在参数中) |
| 响应速度 | 多一步检索,略慢 | 直接推理,快 |
| 风格/格式 | 不擅长改变输出风格 | 擅长(可以训练特定风格) |
| 领域适应 | 提供领域知识 | 改变模型行为模式 |
| 数据安全 | 知识库可控权限 | 模型可能泄露训练数据 |
各自擅长什么:
RAG 擅长:
✓ 知识密集型任务(问答、文档分析)
✓ 需要实时更新的知识
✓ 需要引用来源的场景
✓ 多源知识融合
微调擅长:
✓ 风格/格式定制(代码风格、写作风格)
✓ 特定任务优化(分类、抽取、翻译)
✓ 减少示例(few-shot → zero-shot)
✓ 改变模型行为模式选型决策:
需求是什么?
├── 需要新知识/实时信息 → RAG
├── 需要改变输出风格 → 微调
├── 需要特定任务能力 → 微调
├── 两者都需要 → RAG + 微调
└── 成本敏感 → RAG(先做 RAG,效果不够再微调)组合方案(RAG + 微调):
微调:让模型更擅长检索增强问答的格式和风格
RAG:提供实时准确的知识
效果:1+1 > 2
- 微调后的模型更会利用检索到的信息
- RAG 提供知识,微调提供能力追问延伸:
- 什么时候必须用微调?(模型需要新能力而非新知识时,如学新语言)
- RAG + LoRA 微调的组合怎么做先用?(先建 RAG,再在 RAG 数据上做 LoRA 微调)
Q4: RAG 中的文档是怎么存的?粒度是多大?详细说说文档切割(Chunking)策略? 「🟡 中级」
考察点:文档处理和 Chunking 策略。
参考答案:
文档存储方式:
原文 + Embedding 向量 + 元数据 → 存入向量数据库
一条记录的结构:
{
"id": "doc_001_chunk_003",
"text": "这是切分后的文本片段...",
"embedding": [0.12, -0.34, 0.56, ...], // 768/1536维
"metadata": {
"source": "handbook.pdf",
"page": 15,
"section": "第三章 安全规范",
"chunk_index": 3,
"timestamp": "2025-08-25"
}
}Chunk 粒度的选择:
| 粒度 | 大小 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|---|
| 粗粒度 | 1000-2000 字 | 上下文完整 | 检索精度低、Token 多 | 长文档摘要 |
| 中粒度 | 200-500 字 | 平衡精度和上下文 | 需要调参 | 通用 RAG |
| 细粒度 | 50-100 字 | 检索精度高 | 上下文不完整 | 精确问答 |
| 自适应 | 不固定 | 按语义切分 | 实现复杂 | 高质量 RAG |
推荐粒度:200-500 字 + 50-100 字重叠
Chunking 策略:
1. 固定长度切分(Fixed-size):
python
def fixed_size_chunking(text, chunk_size=500, overlap=50):
"""固定长度 + 重叠切分"""
chunks = []
start = 0
while start < len(text):
end = start + chunk_size
chunk = text[start:end]
chunks.append(chunk)
start = end - overlap # 重叠部分
return chunks优点:简单、快速
缺点:可能切断句子/段落
适合:均匀文本(日志、数据)2. 按分隔符切分(Separator-based):
python
def separator_chunking(text, separators=None):
"""按分隔符切分"""
if separators is None:
separators = ["\n\n", "\n", "。", "!", "?", ".", "!", "?"]
chunks = []
# 递归尝试不同分隔符
for sep in separators:
parts = text.split(sep)
if len(parts) > 1:
for part in parts:
if len(part) > 500:
# 太长则继续切
chunks.extend(separator_chunking(part, separators[1:]))
else:
chunks.append(part.strip())
break
return chunks优点:保持语义完整性
缺点:Chunk 大小不均
适合:结构化文档(Markdown、HTML)3. 按语义切分(Semantic Chunking):
python
def semantic_chunking(text, embed_model, threshold=0.5):
"""基于语义相似度切分"""
sentences = split_sentences(text)
# 计算相邻句子的语义相似度
embeddings = [embed_model.embed(s) for s in sentences]
chunks = [sentences[0]]
for i in range(1, len(sentences)):
similarity = cosine_sim(embeddings[i-1], embeddings[i])
if similarity > threshold:
# 语义相似,合并到同一 Chunk
chunks[-1] += sentences[i]
else:
# 语义不连续,开始新 Chunk
chunks.append(sentences[i])
return chunks优点:语义完整性最好
缺点:计算成本高(每句都要 Embedding)
适合:高质量 RAG 系统4. 父子文档切分(Parent-Child):
检索时:用细粒度的子 Chunk(精确匹配)
生成时:返回粗粒度的父 Chunk(上下文完整)
文档结构:
Parent Chunk(1000 字)
├── Child Chunk 1(200 字)
├── Child Chunk 2(200 字)
└── Child Chunk 3(200 字)
检索 → 命中 Child 2
返回 → 整个 Parent Chunk(上下文完整)python
def parent_child_chunking(text, parent_size=1000, child_size=200):
"""父子文档切分"""
parents = fixed_size_chunking(text, parent_size, overlap=0)
result = []
for i, parent in enumerate(parents):
children = fixed_size_chunking(parent, child_size, overlap=50)
result.append({
"parent_id": f"parent_{i}",
"parent_text": parent,
"children": [
{"id": f"parent_{i}_child_{j}", "text": c}
for j, c in enumerate(children)
]
})
return result5. 表格/代码特殊处理:
表格 → 保持完整不切分,或转为 Markdown 表格
代码 → 按函数/类切分,不切断代码块
图片 → 用多模态模型生成描述文本Chunking 参数调优建议:
| 参数 | 推荐值 | 说明 |
|---|---|---|
| chunk_size | 300-500 | 通用场景 |
| overlap | 50-100 | 10-20% 的 chunk_size |
| separator | 按优先级 | \n\n > \n > 。 > . |
| min_chunk_size | 50 | 太短的 Chunk 没有检索价值 |
追问延伸:
- Chunk 大小对检索效果的影响?(太大→噪声多,太小→上下文不足)
- 怎么处理超长文档的切分?(层级切分 + 摘要索引)
Q5: 怎么规避语义被切割掉的问题? 「🟡 中级**
考察点:Chunking 中的语义完整性保障。
参考答案:
语义被切割的常见场景:
问题切分:
"Agent 的记忆模块包括短期记忆和长期记忆,长期记忆存储在" ← 被切断了
"向量数据库中,短期记忆存储在上下文窗口中。" ← 上下文断裂
检索时:
检索到后半句 → 缺少前半句的上下文 → LLM 不知道"长期记忆"指的是什么规避策略:
1. 重叠切分(Overlap):
Chunk 1: "...Agent 的记忆模块包括短期记忆和长期记忆,长期记忆存储在向量数据库中"
Chunk 2: "长期记忆存储在向量数据库中,短期记忆存储在上下文窗口中..."
重叠部分确保边界信息不丢失2. 递归分隔符切分:
python
# 按优先级递归切分,尽量在自然边界处切
separators = [
"\n## ", # Markdown 标题
"\n### ",
"\n\n", # 段落
"\n", # 行
"。", # 句号
";", # 分号
",", # 逗号(最后才在逗号处切)
]3. 上下文增强(Contextual Enrichment):
python
def enrich_chunk(chunk, document_title, section_title, prev_chunk_summary):
"""为每个 Chunk 添加上下文信息"""
return f"""
文档标题:{document_title}
章节标题:{section_title}
前文摘要:{prev_chunk_summary}
正文:{chunk.text}
"""原始 Chunk: "它支持事务和 ACID 特性"
增强后:
"文档标题:数据库选型指南
章节标题:PostgreSQL 介绍
前文摘要:PostgreSQL 是一个开源关系型数据库
正文:它支持事务和 ACID 特性"
→ LLM 知道"它"指的是 PostgreSQL4. 父子文档切分(推荐方案):
检索:子 Chunk(精确匹配)
生成:父 Chunk(上下文完整)
→ 根本不会出现语义被切割的问题5. 后检索拼接(Post-retrieval Concatenation):
python
def post_retrieval_expand(query, retrieved_chunks, vector_store):
"""检索后扩展上下文"""
expanded = []
for chunk in retrieved_chunks:
# 获取前后相邻的 Chunk
prev = vector_store.get_by_index(chunk.metadata["chunk_index"] - 1)
next_c = vector_store.get_by_index(chunk.metadata["chunk_index"] + 1)
# 拼接完整上下文
full_context = f"{prev.text}\n{chunk.text}\n{next_c.text}"
expanded.append(full_context)
return expanded6. 语义切分(Semantic Chunking):
python
# 只在语义断点处切分,不在语义中间切
# 相邻句子语义相似度低于阈值时切分7. 文档结构感知切分:
按文档结构切分,不在结构中间断开:
- Markdown:按标题层级切分
- HTML:按 DOM 节点切分
- PDF:按页/章节切分
- 代码:按函数/类切分追问延伸:
- 重叠切分的重叠比例怎么定?(10-20% 为宜,太小没效果,太大浪费)
- 父子切分的存储成本?(多存一份父 Chunk,但检索用子 Chunk,空间换质量)
Q6: 在 RAG 中 Embedding 是什么?如何选择和评估一个 Embedding 模型? 「🟡 中级」
考察点:Embedding 的原理和模型选型。
参考答案:
Embedding 是什么:
Embedding = 将文本转化为密集向量表示
"今天天气很好" → [0.12, -0.34, 0.56, ..., 0.78] (768/1536维)
语义相近的文本 → 向量距离近
"今天天气很好" → [0.12, -0.34, ...]
"今日天气不错" → [0.11, -0.32, ...] ← 语义相近,向量也近
"股票大涨" → [-0.45, 0.67, ...] ← 语义无关,向量距离远Embedding 在 RAG 中的作用:
文档端:Chunk → Embedding → 存入向量库
查询端:Query → Embedding → 与库中向量做相似度检索 → 返回最相关的 Chunk如何选择 Embedding 模型:
| 维度 | 考量 | 示例 |
|---|---|---|
| 语言支持 | 中文/英文/多语言 | bge-m3 支持多语言 |
| 维度大小 | 768/1024/1536 | 维度高表达能力强但存储大 |
| 模型大小 | 100M-7B | 影响推理速度和部署成本 |
| 领域适配 | 通用/特定领域 | 法律、医疗等领域模型 |
| 最大输入 | 512/2048/8192 tokens | 影响可处理的 Chunk 大小 |
| 开源/商用 | 免费/付费 | OpenAI text-embedding-3 付费 |
主流 Embedding 模型:
| 模型 | 维度 | 语言 | 特点 | 适用场景 |
|---|---|---|---|---|
| text-embedding-3-large | 3072 | 多语言 | OpenAI 最新,效果好 | 商用、高质量 |
| text-embedding-3-small | 1536 | 多语言 | 性价比高 | 通用商用 |
| bge-m3 | 1024 | 多语言 | 开源最强之一 | 私有部署 |
| bge-large-zh | 1024 | 中文 | 中文效果好 | 中文场景 |
| gte-large | 1024 | 多语言 | 阿里开源 | 通用 |
| jina-embeddings-v3 | 1024 | 多语言 | 长文本支持好 | 长文档 RAG |
| e5-mistral-7b | 4096 | 多语言 | 基于 LLM | 高质量要求 |
如何评估 Embedding 模型:
1. 检索指标评估:
python
def evaluate_embedding(embed_model, test_dataset):
"""
test_dataset: [(query, relevant_docs, irrelevant_docs), ...]
"""
results = []
for query, relevant, irrelevant in test_dataset:
query_emb = embed_model.embed(query)
doc_embs = [embed_model.embed(d) for d in relevant + irrelevant]
# 计算相似度排序
scores = [cosine_sim(query_emb, de) for de in doc_embs]
ranked = sorted(zip(relevant + irrelevant, scores), key=lambda x: -x[1])
# 计算 MRR / NDCG / Recall
results.append({
"recall@5": recall_at_k(ranked, relevant, k=5),
"mrr": mrr(ranked, relevant),
"ndcg": ndcg(ranked, relevant)
})
return aggregate(results)2. 评估指标:
| 指标 | 说明 | 期望值 |
|---|---|---|
| Recall@K | 前 K 个结果中相关文档占比 | > 0.8 |
| MRR | 第一个相关文档的排名倒数 | > 0.7 |
| NDCG | 考虑排名位置的归一化增益 | > 0.8 |
| Hit Rate | 至少命中一个相关文档的比例 | > 0.9 |
3. MTEB 排行榜:
MTEB(Massive Text Embedding Benchmark)
→ 评估 Embedding 模型在多个任务上的综合表现
→ 网址:https://huggingface.co/spaces/mteb/leaderboard
→ 按语言/任务筛选,选适合的模型选型建议:
中文场景 → bge-large-zh 或 bge-m3
多语言 → bge-m3 或 text-embedding-3-small
私有部署 → BGE 系列(开源)
快速原型 → OpenAI API(无需部署)
高质量 → text-embedding-3-large 或 e5-mistral-7b追问延伸:
- Embedding 维度越高越好吗?(不一定,维度高存储和检索成本也高)
- 可以自己微调 Embedding 模型吗?(可以,用对比学习在领域数据上微调)
Q7: Embedding 有哪几种算法你了解过吗? 「🔴 高级」
考察点:Embedding 技术的底层算法理解。
参考答案:
Embedding 算法演进:
Word2Vec → BERT → SimCSE → BGE → LLM-based
2013 2018 2021 2023 2024+1. Word2Vec(2013):
方法:CBOW / Skip-gram
原理:通过上下文预测(或被预测)学习词向量
特点:
- 词级别,不考虑上下文
- 静态向量:同一个词在不同语境下向量相同
- "苹果"(水果)和"苹果"(公司)向量一样 → 语义混淆2. BERT(2018):
方法:Transformer Encoder + 双向注意力
原理:预训练 + 微调
特点:
- 句子级别,考虑上下文
- 动态向量:同一个词在不同语境下向量不同
- 通常取 [CLS] token 的输出作为句向量
- 问题:句向量质量不高(各向异性,向量分布不均匀)3. SimCSE(Simple Contrastive Learning, 2021):
方法:对比学习
原理:正例对(相似句子)拉近,负例对(不相似句子)推远
Dropout SimCSE:
同一个句子过两次模型(不同 Dropout)→ 正例对
不同句子 → 负例对
特点:
- 显式优化句向量质量
- 无监督版本不需要标注数据
- 句向量各向异性问题大幅改善4. BGE(BAAI General Embedding, 2023):
方法: RetroMAE 预训练 + 对比学习微调
原理:
- 预训练:重建被 Mask 的 Token
- 微调:指令增强的对比学习(Instruction + Query + Document)
特点:
- 支持指令:不同任务用不同前缀
"为这个句子生成表示以用于检索相关文档:"
- 中英文效果好
- 开源可用5. LLM-based Embedding(2024+):
方法:用 LLM 本身做 Embedding
原理:取 LLM 的隐层输出作为向量
代表:
- E5-Mistral(基于 Mistral-7B)
- LLM2Vec
- NV-Embed
特点:
- 维度高(4096+)
- 效果好但成本高
- 适合离线 Embedding,不适合实时6. 其他算法:
| 算法 | 类型 | 特点 |
|---|---|---|
| GloVe | 词级别 | 基于共现矩阵分解 |
| FastText | 词级别 | 考虑子词(subword) |
| Sentence-BERT | 句子级别 | 双塔模型,效率高 |
| ColBERT | 检索 | 延迟交互(late interaction),效果介于双塔和交叉编码之间 |
双塔 vs 交叉编码:
双塔模型(Bi-Encoder):
Query → Encoder → 向量
Doc → Encoder → 向量
→ 计算相似度
优点:Doc 可离线 Embedding,检索快
缺点:Query 和 Doc 交互不充分
交叉编码(Cross-Encoder):
[Query, Doc] → Encoder → 相似度分数
优点:交互充分,效果好
缺点:不能离线处理,速度慢
用途:Reranking 阶段追问延伸:
- 为什么不用 BERT 直接做 Embedding?(向量各向异性,检索效果差)
- ColBERT 的延迟交互是什么?(保留 Token 级别向量,检索时做细粒度匹配)
Q8: 什么是向量数据库?有没有做过向量数据库的对比选型? 「🟡 中级」
考察点:向量数据库的原理和选型。
参考答案:
向量数据库是什么:
向量数据库 = 专门存储和检索向量的数据库
核心功能:
1. 存储:高维向量(768-4096维)+ 元数据
2. 检索:近似最近邻(ANN)搜索
3. 过滤:元数据过滤 + 向量检索组合
4. 增删改:动态更新向量数据主流向量数据库对比:
| 数据库 | 类型 | 语言 | 特点 | 适用场景 |
|---|---|---|---|---|
| Milvus | 分布式 | C++/Go | 支持百亿级向量,云原生 | 大规模生产 |
| Pinecone | 云服务 | - | 全托管,免运维 | 快速上线 |
| Chroma | 轻量级 | Python | 嵌入式,简单易用 | 原型/小规模 |
| Qdrant | 独立服务 | Rust | 性能好,过滤强 | 中大规模 |
| Weaviate | 全功能 | Go | 内置向量化和检索 | 多模态 |
| pgvector | PostgreSQL 扩展 | SQL | 复用 PG 生态 | 已有 PG 环境 |
| Elasticsearch | 搜索引擎 | Java | 向量 + 全文混合检索 | 已有 ES 环境 |
| FAISS | 库 | C++ | 高性能 ANN 库 | 离线/嵌入 |
选型维度:
| 维度 | 评估点 |
|---|---|
| 数据规模 | 10 万 / 100 万 / 1 亿 / 10 亿 |
| 检索延迟 | < 10ms / < 50ms / < 200ms |
| 可用性 | 单机 / 集群 / 云服务 |
| 运维成本 | 自部署 / 全托管 |
| 元数据过滤 | 支持 / 不支持 / 灵活程度 |
| 混合检索 | 向量 + 关键词 |
| 动态更新 | 是否支持实时增删改 |
| 成本 | 开源 / 付费 |
选型建议:
快速原型 → Chroma(嵌入式,零配置)
中小规模 → Qdrant(性能好,易部署)
大规模生产 → Milvus(百亿级,云原生)
已有 PostgreSQL → pgvector(无需新引入)
已有 ES → Elasticsearch(复用现有设施)
不想运维 → Pinecone(全托管)
混合检索需求 → Weaviate / Elasticsearch追问延伸:
- 向量数据库为什么比传统数据库快?(ANN 算法,牺牲精度换速度)
- 向量检索的精度和速度怎么平衡?(调整 nprobe/ef 参数,精度越高越慢)
Q9: 讲讲你用的向量数据库?数据量级是多大?性能如何?遇到过性能瓶颈吗? 「🔴 高级」
考察点:向量数据库的实际使用经验。
参考答案:
以 Milvus 为例(面试时替换为自己实际使用的):
基本配置:
向量数据库:Milvus 2.4
部署方式:Docker / K8s 集群
数据量:约 500 万条向量
维度:1024(bge-m3)
索引类型:HNSW
检索 Top-K:10性能指标:
| 指标 | 数值 |
|---|---|
| 检索延迟(P99) | < 30ms |
| QPS | 500+ |
| 写入速度 | 5000 条/秒 |
| 存储大小 | 约 20GB |
| 索引构建时间 | 约 30 分钟 |
遇到的性能瓶颈和解决方案:
1. 检索延迟突增:
现象:偶尔检索延迟从 30ms 飙到 500ms
原因:HNSW 索引在数据增长后 ef 参数太小,召回率不够
解决:
- 调大 ef_search 参数:64 → 128
- 延迟回到 50ms,召回率提升到 98%2. 元数据过滤慢:
现象:带过滤条件的检索比纯向量检索慢 10 倍
原因:先向量检索再过滤,大量结果被过滤掉
解决:
- 使用 Milvus 的标量索引加速过滤
- 调整检索数量:top_k=100 → 过滤 → 取 top 10
- 或使用分区策略:按部门/时间分区3. 批量写入 OOM:
现象:大批量写入时内存溢出
原因:一次性插入太多向量,Embedding 计算占用大量内存
解决:
- 分批写入:每批 1000 条
- 使用流式 Embedding 计算
- 增加服务器内存4. 索引构建时间长:
现象:500 万条向量建 HNSW 索引要 30 分钟
解决:
- 使用 IVF 索引(构建快但精度略低)
- 或 HNSW + 增量更新(不重建索引)监控指标:
python
# 关键监控项
metrics = {
"search_latency_p99": "< 50ms",
"search_qps": "> 500",
"index_size": "约 20GB",
"memory_usage": "< 80%",
"disk_usage": "< 70%",
"recall_rate": "> 95%",
"error_rate": "< 0.1%"
}追问延伸:
- HNSW 和 IVF 索引怎么选?(HNSW 精度高但内存大,IVF 内存小但需训练)
- 向量数据库怎么做高可用?(Milvus 集群 + 多副本)
Q10: 你使用 RAG 给大模型一个输入,系统是怎样的工作流程? 「🟡 中级」
考察点:RAG 系统的端到端流程。
参考答案:
用户视角的完整流程:
用户输入:"公司请假流程是什么?"
RAG 系统内部流程:
1. Query 接收
→ 接收用户问题:"公司请假流程是什么?"
2. Query 预处理
→ Query 改写:扩展为多个相关查询
→ "公司请假流程" "请假申请流程" "假期审批流程"
→ 向量化每个查询
3. 多路检索
→ 向量检索:检索 Top 10 相关 Chunk
→ 关键词检索:BM25 检索 Top 10
→ 合并去重:15 条结果
4. 重排序
→ Cross-Encoder 对 15 条结果重排序
→ 取 Top 5 最相关
5. 上下文组装
→ 将 5 条 Chunk 拼接为上下文
→ 添加引用元数据
6. Prompt 构建
System: 你是公司知识库助手,基于以下信息回答。
Context:
[1] 员工请假流程:首先在 OA 系统提交申请...
[2] 请假审批流程:直属领导审批 → HR 审批...
[3] 年假规定:年假需提前 3 天申请...
User: 公司请假流程是什么?
7. LLM 生成
→ LLM 基于上下文生成回答
→ "公司请假流程如下:
1. 在 OA 系统提交请假申请 [1]
2. 直属领导审批 [2]
3. HR 审批 [2]
4. 年假需提前 3 天申请 [3]"
8. 后处理
→ 添加引用标注
→ 格式化输出
→ 返回给用户python
def rag_pipeline(question, components):
# 1. Query 改写
queries = components["query_rewriter"].rewrite(question)
# 2. 多路检索
vec_results = components["vector_store"].batch_search(
[components["embed_model"].embed(q) for q in queries],
top_k=10
)
keyword_results = components["bm25"].search(question, top_k=10)
all_results = deduplicate(vec_results + keyword_results)
# 3. 重排序
reranked = components["reranker"].rerank(question, all_results, top_n=5)
# 4. 组装上下文
context = format_context(reranked)
# 5. 构建 Prompt
prompt = f"""
你是知识库助手。基于以下参考信息回答问题。
如果参考信息不足以回答,请说明。
参考信息:
{context}
问题:{question}
"""
# 6. LLM 生成
answer = components["llm"].generate(prompt)
# 7. 后处理
answer_with_citations = add_citations(answer, reranked)
return {
"answer": answer_with_citations,
"sources": reranked,
"latency": {
"retrieval": "...",
"rerank": "...",
"generation": "...",
}
}追问延伸:
- 怎么减少端到端延迟?(异步检索 + 缓存 + 流式生成)
- 用户问的问题和知识库不相关怎么办?(意图检测 → 不走 RAG → 直接回答或拒答)
Q11: 请你介绍一下向量检索和关键词检索的区别? 「🟡 中级」
考察点:两种检索方式的对比和互补。
参考答案:
| 维度 | 向量检索(Dense) | 关键词检索(Sparse/BM25) |
|---|---|---|
| 原理 | 语义相似度(向量距离) | 词频匹配(TF-IDF) |
| 检索方式 | Embedding → ANN | 倒排索引 |
| 优势 | 理解语义、同义词、近义词 | 精确匹配、专有名词、ID |
| 劣势 | 精确匹配差、计算成本高 | 不理解语义、同义词 |
| 适合 | 自然语言问答、概念查询 | 代码搜索、精确名称查询 |
具体例子:
用户查询:"怎么防止缓存穿透"
向量检索(语义匹配):
✓ "缓存击穿的解决方案" → 语义相似,命中
✓ "布隆过滤器在缓存中的应用" → 语义相关,命中
✗ "防止缓存穿透" → 完全匹配但也命中(向量也能匹配)
关键词检索(词匹配):
✗ "缓存击穿的解决方案" → 没有完全匹配的词
✓ "防止缓存穿透的最佳实践" → 关键词匹配
✓ "缓存穿透 缓存击穿 布隆过滤器" → 包含关键词用户查询:"ERROR_10001"
向量检索:
✗ 可能检索到语义相关但不包含 ERROR_10001 的文档
→ 精确错误码检索效果差
关键词检索:
✓ 精确匹配包含 ERROR_10001 的文档
→ 精确查询效果好混合检索(Hybrid Search):
python
def hybrid_search(query, vector_store, bm25_index, alpha=0.5):
"""向量检索 + 关键词检索 + 融合"""
# 向量检索
query_emb = embed_model.embed(query)
vec_results = vector_store.search(query_emb, top_k=10)
# 关键词检索
keyword_results = bm25_index.search(query, top_k=10)
# 融合排序(Reciprocal Rank Fusion)
fused = rrf_fusion(vec_results, keyword_results, k=60)
return fused[:10]
def rrf_fusion(vec_results, keyword_results, k=60):
"""RRF 融合算法"""
scores = {}
for rank, doc in enumerate(vec_results):
scores[doc.id] = scores.get(doc.id, 0) + 1 / (k + rank + 1)
for rank, doc in enumerate(keyword_results):
scores[doc.id] = scores.get(doc.id, 0) + 1 / (k + rank + 1)
return sorted(scores.items(), key=lambda x: -x[1])结论:
生产 RAG 系统推荐:混合检索
向量检索 → 语义理解
关键词检索 → 精确匹配
RRF 融合 → 两者优势互补追问延伸:
- 什么时候纯向量检索就够了?(文档都是自然语言、没有专有名词/代码)
- RRF 的 k 参数是什么意思?(平滑参数,k 越大排名差异越平滑)
Q12: 如何润色用户的 Query(Query Rewrite)?目的是什么? 「🟡 中级」
考察点:Query 改写的策略和目的。
参考答案:
为什么要 Query Rewrite:
用户输入往往:
1. 太短 → 信息不足,检索效果差
2. 口语化 → 与文档语言风格不匹配
3. 有歧义 → 多种理解,需要消歧
4. 单一查询 → 漏掉相关信息Query Rewrite 策略:
1. Query 扩展(Expansion):
原始 Query: "缓存穿透"
扩展后: "缓存穿透 原因 解决方案 布隆过滤器 缓存击穿"2. Query 改写(Paraphrasing):
python
def query_paraphrase(query, llm):
"""用 LLM 改写为更规范的查询"""
prompt = f"""将以下口语化问题改写为更清晰、规范的检索查询:
原始:{query}
改写:"""
return llm.generate(prompt)
# 示例
# 原始: "那个缓存老出问题咋办"
# 改写: "缓存故障排查和解决方案"3. 多查询生成(Multi-Query):
python
def multi_query(query, llm, n=3):
"""生成多个不同角度的查询"""
prompt = f"""为以下问题生成 {n} 个不同角度的检索查询:
问题:{query}
要求:每个查询从不同角度表述,用于多路检索。
"""
response = llm.generate(prompt)
return parse_queries(response)
# 示例
# 问题: "怎么提高 RAG 准确率"
# 查询1: "RAG 系统准确率优化方法"
# 查询2: "检索增强生成质量提升策略"
# 查询3: "RAG 检索质量和生成质量评估"4. HyDE(Hypothetical Document Embeddings):
python
def hyde(query, llm, embed_model):
"""生成假设性文档,用文档做检索"""
# 1. 让 LLM 假设答案
hypothetical_answer = llm.generate(
f"请简短回答以下问题(用于检索):{query}"
)
# 2. 用假设答案的向量做检索(答案比问题更接近文档)
hyp_emb = embed_model.embed(hypothetical_answer)
results = vector_store.search(hyp_emb, top_k=10)
return results
# 原理:问题向量 → 答案向量 → 答案与文档更相似
# "怎么防止缓存穿透" → 假设答案 "使用布隆过滤器..." → 检索到相关文档5. Query 分解(Decomposition):
复杂问题: "RAG 和微调的区别,什么时候用哪个?"
分解为:
子查询1: "RAG 的优缺点"
子查询2: "微调的优缺点"
子查询3: "RAG 和微调选型标准"
→ 分别检索 → 合并上下文6. 对话上下文改写:
对话历史:
User: 什么是 RAG?
Assistant: RAG 是检索增强生成...
User: 它和微调比有什么优势? ← "它"指代 RAG
改写后: "RAG 相比微调有什么优势?" ← 消除指代python
def contextualize_query(query, chat_history, llm):
"""结合对话历史改写当前查询"""
prompt = f"""根据对话历史,改写当前问题为独立、完整的问题:
对话历史:
{chat_history}
当前问题:{query}
改写后的问题:"""
return llm.generate(prompt)Query Rewrite 的效果:
| 策略 | 检索提升 | 额外成本 | 适用场景 |
|---|---|---|---|
| 多查询 | +15% | N 次 LLM | 复杂查询 |
| HyDE | +10-20% | 1 次 LLM | 短查询 |
| 上下文改写 | 必需 | 1 次 LLM | 多轮对话 |
| Query 扩展 | +5-10% | 1 次 LLM | 短查询 |
追问延伸:
- HyDE 为什么有效?(答案向量比问题向量更接近文档向量)
- Query Rewrite 增加了延迟,怎么平衡?(异步改写 + 缓存 + 按需启用)
Q13: 什么是多路召回?具体怎么做? 「🟡 中级」
考察点:多路召回策略的工程实现。
参考答案:
多路召回 = 从多个渠道检索文档,融合结果
┌→ 向量检索(语义匹配)→ Top 10
Query ──┼→ 关键词检索(BM25) → Top 10
├→ 知识图谱检索 → Top 5
└→ SQL 检索(结构化) → Top 5
↓
融合去重排序 → Top 5为什么要多路召回:
单路召回的问题:
纯向量检索 → 精确匹配差(专有名词、代码、ID)
纯关键词检索 → 语义理解差(同义词、近义词)
多路召回:
向量 → 语义覆盖
关键词 → 精确匹配
知识图谱 → 关系推理
结构化 → 精确数据
→ 综合覆盖,互补不足具体实现:
python
class MultiRouteRetriever:
def __init__(self, vector_store, bm25_index, kg_store, sql_db, embed_model):
self.vector_store = vector_store
self.bm25 = bm25_index
self.kg = kg_store
self.sql = sql_db
self.embed = embed_model
def retrieve(self, query, top_k=10):
# 并行检索
results = []
# 1. 向量检索
query_emb = self.embed.embed(query)
vec_results = self.vector_store.search(query_emb, top_k=top_k)
results.extend([(r, "vector", rank) for rank, r in enumerate(vec_results)])
# 2. 关键词检索
kw_results = self.bm25.search(query, top_k=top_k)
results.extend([(r, "keyword", rank) for rank, r in enumerate(kw_results)])
# 3. 知识图谱检索(可选)
if self.kg:
kg_results = self.kg.query(query, top_k=5)
results.extend([(r, "kg", rank) for rank, r in enumerate(kg_results)])
# 4. 融合排序
fused = self._rrf_fusion(results)
return fused[:top_k]
def _rrf_fusion(self, results, k=60):
"""Reciprocal Rank Fusion"""
scores = {}
docs = {}
for doc, source, rank in results:
doc_id = doc.id
scores[doc_id] = scores.get(doc_id, 0) + 1 / (k + rank + 1)
docs[doc_id] = doc
sorted_ids = sorted(scores.items(), key=lambda x: -x[1])
return [docs[doc_id] for doc_id, _ in sorted_ids]融合算法对比:
| 算法 | 原理 | 优点 | 缺点 |
|---|---|---|---|
| RRF | 排名倒数加权 | 不需要分数归一化 | 忽略分数大小 |
| 加权平均 | 分数加权 | 考虑分数 | 需要分数归一化 |
| Cascade | 逐级过滤 | 可控 | 串行慢 |
| 交集/并集 | 集合操作 | 简单 | 信息丢失 |
多路召回的效果:
单路向量检索 Recall@10: 78%
单路关键词检索 Recall@10: 65%
混合检索 Recall@10: 91% → +13% 提升追问延伸:
- 各路检索结果数量不一致怎么办?(统一 top_k,或按比例分配)
- 多路召回增加了延迟怎么优化?(并行检索 + 超时降级)
Q14: RAG 检索优化策略有哪些? 「🔴 高级」
考察点:RAG 检索的全面优化方法。
参考答案:
检索优化全景图:
┌─────────────────────────────────────┐
│ RAG 检索优化策略 │
│ │
│ 【检索前】 │
│ 1. Query 改写/扩展 │
│ 2. 多查询生成 │
│ 3. HyDE │
│ │
│ 【检索中】 │
│ 4. 多路混合检索 │
│ 5. 元数据过滤 │
│ 6. 分级检索 │
│ │
│ 【检索后】 │
│ 7. 重排序(Reranking) │
│ 8. 上下文压缩 │
│ 9. 去重与多样性 │
│ 10. 引用标注 │
│ │
│ 【系统级】 │
│ 11. 缓存 │
│ 12. 预计算 │
│ 13. 索引优化 │
└─────────────────────────────────────┘1. Query 改写(见 Q12)
2. 多路混合检索(见 Q13)
3. 重排序(Reranking):
python
# 用 Cross-Encoder 重排序
from sentence_transformers import CrossEncoder
reranker = CrossEncoder("BAAI/bge-reranker-large")
def rerank(query, documents, top_n=5):
pairs = [(query, doc.text) for doc in documents]
scores = reranker.predict(pairs)
ranked = sorted(zip(documents, scores), key=lambda x: -x[1])
return [doc for doc, _ in ranked[:top_n]]为什么需要重排序:
向量检索(Bi-Encoder):
快但粗 → Top 10 中可能有不相关的
重排序(Cross-Encoder):
慢但精 → 从 Top 10 中选 Top 5 最相关的
流程:向量检索 Top 100 → 重排序选 Top 54. 上下文压缩:
python
def compress_context(query, documents, llm):
"""压缩检索到的文档,只保留与问题相关的部分"""
compressed = []
for doc in documents:
# 用 LLM 提取与问题相关的内容
relevant = llm.generate(
f"从以下文本中提取与问题相关的信息,去掉无关内容:\n"
f"文本:{doc.text}\n问题:{query}\n相关内容:"
)
if relevant.strip():
compressed.append(relevant)
return compressed5. 去重与多样性:
python
def diversify_results(results, max_per_source=2):
"""限制同一来源的文档数量"""
source_count = {}
diversified = []
for doc in results:
source = doc.metadata.get("source", "unknown")
count = source_count.get(source, 0)
if count < max_per_source:
diversified.append(doc)
source_count[source] = count + 1
return diversified6. 缓存:
python
# 语义缓存:相似查询复用结果
class SemanticCache:
def __init__(self, embed_model, vector_store, threshold=0.95):
self.embed = embed_model
self.cache_store = vector_store # 专用向量库
self.threshold = threshold
def get(self, query):
query_emb = self.embed.embed(query)
results = self.cache_store.search(query_emb, top_k=1)
if results and results[0].score > self.threshold:
return results[0].answer # 缓存命中
return None
def set(self, query, answer):
emb = self.embed.embed(query)
self.cache_store.add(emb, {"query": query, "answer": answer})7. 分级检索:
Level 1:摘要检索(快)→ 粗筛
Level 2:全文检索(慢)→ 精选
流程:
Query → 检索文档摘要 → 选 Top 10 相关文档
→ 对 Top 10 做全文检索 → 选 Top 3优化效果汇总:
| 优化策略 | Recall 提升 | 延迟增加 | 实现难度 |
|---|---|---|---|
| Query 改写 | +10-15% | +200ms | 中 |
| 多路检索 | +10-15% | +100ms(并行) | 中 |
| 重排序 | +5-10% | +300ms | 低 |
| 上下文压缩 | Token -30% | +200ms | 中 |
| 缓存 | 命中时 -90% | 无增加 | 中 |
追问延伸:
- 重排序模型怎么选?(bge-reranker / cohere-rerank / 自训练)
- 怎么平衡优化效果和延迟?(分级优化 + 异步 + 缓存)
Q15: 了解哪些更复杂的 RAG 范式? 「🔴 高级」
考察点:RAG 前沿范式和进阶架构。
参考答案:
1. Self-RAG(自反思 RAG):
LLM 自主决定:是否需要检索 → 检索 → 评估检索质量 → 生成 → 自我批判
流程:
1. 判断:这个问题需要检索吗?
→ 简单事实("1+1=?")→ 不检索,直接回答
→ 知识型问题 → 检索
2. 检索:并行检索多个 Chunk
3. 评估:每个 Chunk 相关吗?
→ 相关 → 使用
→ 不相关 → 丢弃
4. 生成:基于相关 Chunk 生成
5. 批判:回答有依据吗?需要再检索吗?2. Corrective RAG(CRAG):
检索 → 评估检索质量 → 纠正
流程:
1. 检索文档
2. 评估文档相关性(Correct/Incorrect/Ambiguous)
3. 如果 Correct → 直接用
4. 如果 Incorrect → 触发网络搜索补充
5. 如果 Ambiguous → 细化查询后重新检索
6. 知识精炼 → 去噪、提取关键信息
7. 生成回答3. Adaptive RAG(自适应 RAG):
python
class AdaptiveRAG:
"""根据问题复杂度自适应选择策略"""
def query(self, question):
complexity = self._assess_complexity(question)
if complexity == "simple":
# 简单问题:单次检索
return self._simple_rag(question)
elif complexity == "moderate":
# 中等:多路检索 + 重排序
return self._multi_route_rag(question)
else:
# 复杂:迭代检索 + 反思
return self._iterative_rag(question)
def _iterative_rag(self, question, max_rounds=3):
"""迭代检索:多轮检索逐步补充信息"""
context = ""
for round_i in range(max_rounds):
results = self._retrieve(question, context)
context += "\n" + results
# LLM 判断信息是否充分
if self._is_sufficient(question, context):
break
# 不充分 → 生成补充查询
question = self._generate_followup(question, context)
return self._generate(question, context)4. Graph RAG(知识图谱 RAG):
传统 RAG:文档 → 向量 → 检索
Graph RAG:文档 → 知识图谱 → 图检索
流程:
1. 从文档中抽取实体和关系 → 构建知识图谱
2. 用户问题 → 实体识别 → 图遍历
3. 获取相关子图 + 实体描述
4. LLM 基于图谱信息生成
优势:
- 多跳推理(A→B→C 的关系链)
- 全局摘要(不只是局部 Chunk)
- 实体关系明确5. Agentic RAG(Agent 驱动的 RAG):
Agent + RAG 结合:
Agent 自主决定:
- 是否需要检索
- 检索什么
- 检索结果够不够
- 需不需要补充检索
- 如何使用检索结果
特点:
- 自主决策而非固定流程
- 可以多轮检索
- 可以调用多种工具(搜索+RAG+计算)6. Modular RAG(模块化 RAG):
将 RAG 拆分为可组合的模块:
- 检索模块(向量/关键词/图)
- 排序模块
- 压缩模块
- 生成模块
- 评估模块
按需组合,灵活配置各范式对比:
| 范式 | 复杂度 | 效果 | 成本 | 适用场景 |
|---|---|---|---|---|
| 基础 RAG | 低 | 基准 | 低 | 简单问答 |
| Self-RAG | 中 | +10% | 中 | 质量要求高 |
| CRAG | 中 | +15% | 中 | 需要纠错 |
| Adaptive | 中 | +20% | 中 | 问题类型多样 |
| Graph RAG | 高 | +25%(多跳) | 高 | 关系推理 |
| Agentic | 高 | +30% | 最高 | 复杂任务 |
追问延伸:
- Graph RAG 怎么构建知识图谱?(LLM 抽取实体关系 + 图数据库存储)
- Self-RAG 的反思 token 怎么训练?(标注反思数据 + 指令微调)
Q16: 在什么场景下,你会选择使用图数据库来增强传统的向量检索? 「🔴 高级」
考察点:Graph RAG 的适用场景判断。
参考答案:
适合图数据库增强的场景:
1. 多跳推理(Multi-hop Reasoning):
问题:"张三的导师合作过哪些公司?"
传统 RAG:
检索 "张三" → 可能只找到张三的个人信息
→ 无法找到 "导师→合作公司" 的关系链
Graph RAG:
张三 → [导师是] → 李四 → [合作过] → 公司 A, B, C
图遍历直接找到答案2. 关系密集型领域:
| 领域 | 实体 | 关系 | 适合 Graph RAG |
|---|---|---|---|
| 医疗 | 疾病、药物、症状 | 治疗、引起、禁忌 | ✓ |
| 法律 | 法条、案例、当事人 | 引用、适用、判决 | ✓ |
| 金融 | 公司、人、产品 | 投资、控股、合作 | ✓ |
| 研发 | 模块、接口、团队 | 依赖、负责、调用 | ✓ |
3. 全局摘要需求:
传统 RAG:检索局部 Chunk → 缺乏全局视角
问题:"这家公司的主要业务方向有哪些?"
Graph RAG:
构建公司实体图谱 → 所有业务实体 → 全局视图
→ 生成完整的业务方向摘要4. 实体消歧:
问题:"苹果的市值是多少?"
传统 RAG:可能检索到水果"苹果"的文档
Graph RAG:实体 "苹果" → 上下文关联 "科技公司" → 消歧Graph RAG + 向量检索结合:
python
class GraphRAG:
def query(self, question):
# 1. 实体识别
entities = self.llm.extract_entities(question)
# 2. 图检索:获取实体子图
subgraph = self.graph_store.get_subgraph(entities, hops=2)
# 3. 向量检索:补充语义相关信息
vec_results = self.vector_store.search(
self.embed.embed(question), top_k=5
)
# 4. 融合上下文
context = format_graph(subgraph) + "\n" + format_docs(vec_results)
# 5. LLM 生成
return self.llm.generate(f"基于图谱和文档回答:\n{context}\n问题:{question}")不适合 Graph RAG 的场景:
✗ 简单事实问答("今天天气如何")
✗ 文档内容为主(小说、新闻)
✗ 无明确实体关系(通用闲聊)
✗ 数据量小(不值得构建图谱)追问延伸:
- 知识图谱怎么自动构建?(LLM 抽取实体关系 → 存入 Neo4j)
- Graph RAG 的构建成本高吗?(高,需要实体抽取 + 关系建模 + 图存储)
Q17: 如何规避 RAG 系统中大模型的幻觉? 「🟡 中级」
考察点:RAG 中幻觉问题的缓解策略。
参考答案:
RAG 中幻觉的来源:
1. 检索不到相关信息 → LLM 编造答案
2. 检索到不相关信息 → LLM 被误导
3. 检索结果有矛盾 → LLM 选择性采信
4. LLM 自身知识干扰 → 不用检索结果而用自己的知识
5. Prompt 设计不当 → 没有明确约束 LLM 基于上下文回答规避策略:
1. Prompt 约束:
System Prompt:
"你是一个严格基于参考信息回答的助手。
规则:
1. 只能基于提供的参考信息回答
2. 如果参考信息不足以回答,必须说'根据现有信息无法回答'
3. 不要使用你自己的知识补充
4. 每个陈述必须标注引用来源
5. 不要编造任何信息"2. 检索质量保障:
python
def retrieve_with_confidence(query, vector_store, threshold=0.7):
"""只返回高置信度的检索结果"""
results = vector_store.search(embed(query), top_k=10)
# 过滤低相似度结果
high_confidence = [r for r in results if r.score > threshold]
if not high_confidence:
return None # 没有高置信度结果 → 不生成
return high_confidence3. 答案校验:
python
def verify_answer(answer, context, llm):
"""校验答案是否基于上下文"""
verification = llm.generate(f"""
判断以下回答是否完全基于参考信息,有没有编造内容:
参考信息:{context}
回答:{answer}
判断:
1. 回答中的每个事实是否都能在参考信息中找到?
2. 有没有编造的信息?
3. 结果:[PASS/FAIL] 原因:...
""")
return verification4. 引用标注:
要求 LLM 标注每个事实的来源:
回答:"公司请假流程需要 OA 申请 [1],领导审批 [2]"
[1] 来源:员工手册第 15 页
[2] 来源:审批流程文档第 3 节
→ 用户可以验证每个事实5. 置信度评估:
python
def assess_confidence(answer, sources):
"""评估答案的置信度"""
# 因素1:检索结果相似度
avg_similarity = mean(s.score for s in sources)
# 因素2:答案覆盖的来源数
source_coverage = len(set(s.id for s in sources if s.cited))
# 因素3:校验通过率
verification_pass = verify_answer(answer, sources)
confidence = (avg_similarity * 0.4 +
source_coverage * 0.1 * 0.3 +
verification_pass * 0.3)
if confidence < 0.5:
return answer + "\n\n⚠️ 此回答置信度较低,建议人工确认。"
return answer6. 拒答机制:
当检索结果不足时,不生成答案而是拒答:
if len(high_confidence_results) == 0:
return "抱歉,知识库中没有找到与您问题相关的信息。"
→ 宁可不回答,也不编造追问延伸:
- 怎么平衡"严格基于上下文"和"灵活回答"?(分场景:精确问答严格,闲聊灵活)
- 幻觉检测有没有自动化工具?(SelfCheckGPT / 事实核查 API)
Q18: 怎么量化你的 RAG 效果? 「🔴 高级**
考察点:RAG 评估体系和方法。
参考答案:
RAG 评估的两个维度:
RAG = 检索(Retrieval)+ 生成(Generation)
评估也分两部分:
1. 检索质量评估:检索到的文档准不准?
2. 生成质量评估:生成的回答好不好?1. 检索质量评估:
| 指标 | 说明 | 计算 |
|---|---|---|
| Recall@K | 前 K 个结果中相关文档比例 | 相关命中数 / 总相关数 |
| Precision@K | 前 K 个结果中相关文档比例 | 相关命中数 / K |
| MRR | 第一个相关文档的排名倒数 | 1/第一个相关文档排名 |
| NDCG | 考虑排名位置的归一化增益 | DCG / IDCG |
| Hit Rate | 至少命中一个的比例 | 有命中的查询 / 总查询 |
python
def evaluate_retrieval(retriever, test_dataset, k=5):
"""
test_dataset: [(query, relevant_doc_ids), ...]
"""
metrics = {"recall@k": [], "mrr": [], "ndcg": []}
for query, relevant_ids in test_dataset:
retrieved = retriever.search(query, top_k=k)
retrieved_ids = [r.id for r in retrieved]
# Recall@K
hit = len(set(retrieved_ids) & set(relevant_ids))
metrics["recall@k"].append(hit / len(relevant_ids))
# MRR
for rank, rid in enumerate(retrieved_ids, 1):
if rid in relevant_ids:
metrics["mrr"].append(1 / rank)
break
else:
metrics["mrr"].append(0)
return {k: mean(v) for k, v in metrics.items()}2. 生成质量评估:
| 指标 | 说明 |
|---|---|
| Faithfulness | 回答是否忠于检索到的上下文(不编造) |
| Answer Relevance | 回答是否与问题相关 |
| Context Relevance | 检索到的上下文是否与问题相关 |
| Context Recall | 检索是否覆盖了回答所需的全部信息 |
RAGAS 框架:
python
from ragas import evaluate
from ragas.metrics import (
faithfulness,
answer_relevancy,
context_precision,
context_recall
)
# 评估数据集格式
eval_data = {
"question": ["公司请假流程是什么?"],
"answer": ["请假需要OA申请,领导审批..."],
"contexts": [["员工手册第15页...", "审批流程文档..."]],
"ground_truth": ["请假流程:1.OA申请 2.领导审批 3.HR审批"]
}
results = evaluate(
eval_data,
metrics=[faithfulness, answer_relevancy, context_precision, context_recall]
)
# faithfulness: 0.85 (忠于上下文)
# answer_relevancy: 0.92 (回答相关)
# context_precision: 0.80 (检索精确)
# context_recall: 0.75 (检索召回)3. 端到端评估:
| 指标 | 说明 |
|---|---|
| 准确率 | 回答正确的比例 |
| 完整性 | 回答覆盖了所有要点 |
| 引用准确率 | 引用标注是否正确 |
| 拒答率 | 无法回答的比例(合理拒答是好的) |
| 幻觉率 | 编造信息的比例 |
| 延迟 | 端到端响应时间 |
| 成本 | 每次查询的 Token 消耗 |
4. 人工评估 vs 自动评估:
人工评估:
- 黄金标准,但成本高
- 适合小样本验证
自动评估(LLM-as-Judge):
- 用 GPT-4 评估 RAG 输出质量
- 成本低,可大规模
- 适合持续监控
混合:
- 定期人工评估校准自动评估
- 日常用自动评估监控评估流程:
1. 构建评估集(100-500 个标注样本)
2. 离线评估:检索指标 + 生成指标
3. 在线监控:用户反馈(点赞/点踩)
4. A/B 测试:对比不同策略
5. 持续优化:根据评估结果迭代追问延伸:
- 评估集怎么构建?(人工标注 + LLM 辅助生成)
- 怎么做 A/B 测试对比两个 RAG 方案?(同一批评估集,对比指标差异)
Q19: RAG 知识库如何实现动态与持续更新? 「🟡 中级**
考察点:RAG 知识库的运维和更新策略。
参考答案:
知识库更新的场景:
1. 新增文档:新产品文档、新政策
2. 修改文档:更新已有文档内容
3. 删除文档:过期文档废弃
4. 增量索引:大量新文档批量入库更新策略:
1. 全量重建 vs 增量更新:
| 策略 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|
| 全量重建 | 文档大量变更 | 简单、一致性好 | 耗时长、服务中断 |
| 增量更新 | 少量文档变更 | 快、不中断 | 需要管理版本 |
2. 增量更新实现:
python
class KnowledgeBaseUpdater:
def __init__(self, vector_store, embed_model):
self.vector_store = vector_store
self.embed = embed_model
def add_document(self, doc):
"""新增文档"""
chunks = self._chunk(doc)
embeddings = [self.embed.embed(c.text) for c in chunks]
self.vector_store.add(
embeddings=embeddings,
documents=[c.text for c in chunks],
metadatas=[c.metadata for c in chunks]
)
def update_document(self, doc_id, new_content):
"""更新文档(先删后增)"""
# 删除旧文档的所有 Chunk
self.vector_store.delete(filter={"doc_id": doc_id})
# 增加新内容
self.add_document(new_content)
def delete_document(self, doc_id):
"""删除文档"""
self.vector_store.delete(filter={"doc_id": doc_id})3. 文档变更检测:
python
class DocumentWatcher:
"""监控文档变更"""
def __init__(self, source_dir):
self.source_dir = source_dir
self.file_hashes = {} # 记录文件哈希
def check_changes(self):
"""检测新增、修改、删除的文件"""
changes = {"added": [], "modified": [], "deleted": []}
current_files = {}
for file in scan_files(self.source_dir):
hash = md5(file.content)
current_files[file.path] = hash
if file.path not in self.file_hashes:
changes["added"].append(file)
elif self.file_hashes[file.path] != hash:
changes["modified"].append(file)
for path in self.file_hashes:
if path not in current_files:
changes["deleted"].append(path)
self.file_hashes = current_files
return changes4. 自动化更新流水线:
文档变更 → 检测变更 → 增量更新向量库 → 质量验证 → 发布
Pipeline:
1. 定时扫描文档目录(每小时/每天)
2. 检测文件变更(新增/修改/删除)
3. 对变更文档做 Chunking + Embedding
4. 增量更新向量数据库
5. 运行评估集验证质量
6. 验证通过 → 上线
验证失败 → 回滚 + 告警5. 版本管理:
知识库版本:
v1.0 → 初始版本
v1.1 → 新增 10 篇文档
v1.2 → 修改 3 篇文档
v2.0 → 大规模重构
支持回滚:
v1.2 质量下降 → 回滚到 v1.16. 多版本共存:
场景:新旧版本文档同时存在(如不同版本 API 文档)
策略:
- 元数据标注版本号
- 检索时按版本过滤
- 或者:只保留最新版本,旧版本归档追问延伸:
- 更新后 Embedding 需要重新计算吗?(修改的文档需要,未修改的不需要)
- 大规模更新怎么不中断服务?(双写 + 灰度切换)
Q20: 在实际落地中,你觉得 RAG 最难的地方是哪里? 「🔴 高级**
考察点:RAG 工程实践的经验和深度。
参考答案:
RAG 落地的难点排序:
1. 数据质量(最难的):
"Garbage In, Garbage Out"
问题:
- 原始文档格式混乱(PDF 表格、图片、扫描件)
- 文档质量参差不齐(过时信息、错误信息、重复信息)
- 文档切分后语义不完整
- 知识冲突(多个文档说法矛盾)
解决投入:占整个 RAG 项目 60-70% 的工作量
- 数据清洗 → 格式统一 → 结构化标注 → 切分优化 → 质量审核2. 检索效果调优:
问题:
- 召回率不够 → 用户问的问题检索不到相关文档
- 精确度不够 → 检索到大量不相关文档
- 专有名词/代码/ID 检索差
- 多语言/跨领域检索效果差
解决:
- Chunking 策略调优
- Embedding 模型选型和微调
- 混合检索(向量+关键词)
- 重排序模型优化
- 需要大量评估集和迭代3. 评估体系缺失:
问题:
- 没有标注数据 → 不知道效果好不好
- 评估指标不明确 → 优化没有方向
- 评估成本高 → 需要人工标注
解决:
- 构建 100-500 个标注样本的评估集
- 用 RAGAS / LLM-as-Judge 自动评估
- 持续收集用户反馈4. 复杂文档处理:
问题:
- 表格 → 切分后丢失结构
- 图片 → 无法直接向量化
- 公式 → 切分后不可读
- 代码 → 切分后不完整
- 超长文档 → 信息密度不均
解决:
- 多模态 Embedding(表格、图片用专用模型)
- 文档结构感知切分
- 父子文档切分5. 成本与延迟:
问题:
- 检索 + 生成 = 两步,延迟高
- 重排序增加延迟
- 多路检索增加成本
- 大上下文 Token 消耗大
解决:
- 缓存(语义缓存)
- 并行检索
- 上下文压缩
- 模型选型(小模型+大模型组合)6. 权限和安全:
问题:
- 不同用户能看到不同文档
- 检索结果可能泄露敏感信息
- LLM 可能从上下文推断出不该知道的信息
解决:
- 按用户权限过滤检索结果
- 敏感信息脱敏
- 输出审核经验总结:
RAG 项目投入分布:
数据处理 → 60%
检索优化 → 20%
生成优化 → 10%
工程架构 → 10%
RAG 成功的关键:
1. 数据质量 > 模型选择 > 算法优化
2. 评估驱动迭代,不要凭感觉调参
3. 先做基础 RAG,再逐步优化
4. 关注端到端用户体验,不只看检索指标追问延伸:
- 怎么说服业务方投入数据清洗的成本?(用评估数据展示数据质量对效果的影响)
- RAG 项目的 ROI 怎么衡量?(减少人工回答时间 + 提升回答准确率 + 用户满意度)